Commit 915a4f3f authored by Oleg Borisenko's avatar Oleg Borisenko
Browse files

webui in progress

parent ad3d3d98
......@@ -20,7 +20,8 @@ retry.attempts = 3
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
debugtoolbar.hosts = 0.0.0.0/0
debugtoolbar.extra_panels = performance session
[pshell]
setup = tapebackup.pshell.setup
......
def includeme(config):
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.add_route('status', '/status')
config.add_route('library_scan', '/library_scan')
config.add_route('add_backup_target', '/add_backup_target')
config.add_route('scan_backup_target', '/scan_backup_target/{unique_label}')
......
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.cell {
min-height: 75px;
flex-grow: 1;
flex-basis: 100%;
}
.row {
display: flex;
justify-content: flex-start;
align-items: stretch;
flex-wrap: nowrap;
padding: 10px;
}
.navbar-burger-line {
padding: 1px;
background-color: white;
margin: 5px 0;
}
.navbar-burger {
margin: 10px 0;
width: 45px;
padding: 5px 10px;
display: none;
float: right;
cursor: pointer;
}
.navbar-menu-link {
margin: 0;
color: inherit;
text-decoration: none;
display: inline-block;
padding: 10px 15px;
}
.navbar-menu {
padding: 10px 0;
display: block;
float: right;
margin: 0;
}
.navbar-container::after {
content: "";
clear: both;
display: block;
}
.navbar-container {
max-width: 950px;
margin: 0 auto;
width: 95%;
}
.navbar {
background-color: #222;
color: #ddd;
min-height: 50px;
width: 100%;
}
.navbar-items-c {
display: inline-block;
float: right;
}
.paragraph {
padding: 10px;
}
table {
width: 100%;
border-collapse: collapse;
font-family: Helvetica, serif;
}
table td {
padding: 2px 5px;
border: 1px solid black;
}
.robot {
border: 1px solid black;
padding: 20px 5px;
}
.gray {
background-color: lightgray;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.heading {
padding: 10px;
font-size: x-large;
text-align: center;
}
.paragraph {
padding: 10px;
}
.library {
font-size: x-small;
}
@media (max-width: 768px) {
.row {
flex-wrap: wrap;
}
.navbar-menu-link {
display: block;
}
.navbar-menu {
width: 100%;
}
.navbar-items-c {
display: none;
width: 100%;
}
.navbar-burger {
display: block;
}
}
\ No newline at end of file
{% extends "layout.jinja2" %}
{% extends "home.jinja2" %}
{% block content %}
<div class="content">
......
<!DOCTYPE html>
<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Tapebackup management UI">
<link rel="shortcut icon" href="{{request.static_url('tapebackup:static/pyramid-16x16.png')}}">
<title>Tapebackup UI</title>
<!-- Bootstrap core CSS
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tapebackup:static/css/style.css')}}" rel="stylesheet">
</head>
<body>
<div data-gjs="navbar" class="navbar">
<div class="navbar-container" id="itd409">
<div id="iwiimc" class="navbar-burger">
<div class="navbar-burger-line"></div>
<div class="navbar-burger-line"></div>
<div class="navbar-burger-line"></div>
</div>
<div data-gjs="navbar-items" class="navbar-items-c">
<nav data-gjs="navbar-menu" class="navbar-menu">
<a href="/" class="navbar-menu-link" id="help">Описание</a>
<a href="/status" class="navbar-menu-link" id="status">Статус
системы</a>
<a href="/" class="navbar-menu-link" id="targets">Цели бэкапа и восстановления</a>
<a href="/"
class="navbar-menu-link"
id="tapes">Обзор
кассет</a>
<a href="/" class="navbar-menu-link" id="files">Поиск файлов</a>
</nav>
</div>
</div>
</div>
{% block content %}{% endblock %}
</body>
</html>
{% extends "base.jinja2" %}
{% block content %}
<div class="row">
<div class="cell">
<section class="bdg-sect" id="ignhy">
<h1 class="heading"><i>Надеюсь, вы здесь из любопытства, а не из-за катастрофы</i>
</h1>
<h2 class="heading">Список допустимых действий в системе:
</h2>
<div id="iqd7w" class="paragraph">
<br class="Apple-interchange-newline"/>Раздел
<b>&quot;Статус системы&quot;
</b> показывает состояние системы копирования и ленточной библиотеки. Переход на эту вкладку может занимать больше минуты; это нормально, поскольку перед показом страницы происходит сканирование библиотеки и актуализация инвентаря библиотеки.
</div>
<div id="il5oq" class="paragraph">
<div>Раздел
<b>&quot;Цели бэкапа и восстановления&quot;
</b> позволяет узнать, какие папки подлежат резервному копированию и их тип. Также можно добавить новые локальные папки для дальнейшего резервного копирования (система подразумевает, что цели копирования заранее смонтированы на сервер и вписаны в /etc/fstab для автоматического монтирования). Цели резервного копирования характеризуются путем к папке и типом (wgs, vcf, db, files).
</div>Цели восстановления характеризуются только путем; восстановление можно производить из любого типа целей резервного копирования.
<div>Также одна из возможностей, предлагаемых в данном разделе -- восстановление цели резервного копирования на одну или несколько целей восстановления (в случае, если места на одной цели восстановления не хватает для полного восстановления, требуется использовать восстановление индивидуальных файлов/образцов -- см. раздел &quot;Поиск файлов&quot;).
</div>
</div>
<div id="iwun1" class="paragraph">Раздел
<b>&quot;Обзор кассет&quot;
</b> отображает список всех известных системе кассет с их статусом и местоположением. Также в этом разделе можно производить изъятие кассет из библиотеки (массовое и одиночное) в различных вариациях. Переход на эту вкладку может занимать больше минуты, т.к подразумевается сканирование библиотеки. Действия (перемещения и рескан) в этой вкладке также могут занимать несколько минут из-за особенностей ленточной библиотеки; запаситесь терпением.
</div>
<div id="i511i" class="paragraph">Раздел
<b>&quot;Поиск файлов&quot;
</b> предлагает с одной стороны оглавление файлов, известных системе, и их соответствие кассетам. С другой стороны, в этом разделе можно найти, какие файлы соответствуют заданным лабораторным номерам и восстановить произвольный набор файлов.
</div>
</section>
</div>
</div>
<script>var items = document.querySelectorAll('#iwiimc');
for (var i = 0, len = items.length; i < len; i++) {
(function(){
var e,t=0,n=function(){
var e,t=document.createElement("void"),n={
transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};
for(e in n)if(void 0!==t.style[e])return n[e]}
(),r=function(e){
var t=window.getComputedStyle(e),n=t.display,r=(t.position,t.visibility,t.height,parseInt(t["max-height"]));
if("none"!==n&&"0"!==r)return e.offsetHeight;
e.style.height="auto",e.style.display="block",e.style.position="absolute",e.style.visibility="hidden";
var i=e.offsetHeight;
return e.style.height="",e.style.display="",e.style.position="",e.style.visibility="",i}
,i=function(e){
t=1;
var n=r(e),i=e.style;
i.display="block",i.transition="max-height 0.25s ease-in-out",i.overflowY="hidden",""==i["max-height"]&&(i["max-height"]=0),0==parseInt(i["max-height"])?(i["max-height"]="0",setTimeout(function(){
i["max-height"]=n+"px"}
,10)):i["max-height"]="0"}
,a=function(r){
if(r.preventDefault(),!t){
var a=this.closest("[data-gjs=navbar]"),o=a.querySelector("[data-gjs=navbar-items]");
i(o),e||(o.addEventListener(n,function(){
t=0;
var e=o.style;
0==parseInt(e["max-height"])&&(e.display="",e["max-height"]="")}
),e=1)}
};
"gjs-collapse"in this||this.addEventListener("click",a),this["gjs-collapse"]=1
}
.bind(items[i]))();
}
</script>
{% endblock content %}
\ No newline at end of file
<!DOCTYPE html>
<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="{{request.static_url('tapebackup:static/pyramid-16x16.png')}}">
<title>Cookiecutter Starter project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tapebackup:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
<script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
<img class="logo img-responsive" src="{{request.static_url('tapebackup:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
{% block content %}
<p>No content</p>
{% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
<div class="row">
<div class="copyright">
Copyright &copy; Pylons Project
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
{% extends "layout.jinja2" %}
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
<p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
{% extends "base.jinja2" %}
{% block content %}
<div class="row">
<div class="cell" id="ixzf5m">
<div id="il4534">Статус системы копирования</div>
<div id="i9hs3h">Процесс системы резервирования: запущен</div>
<div id="iy3wqs">Процесс системы восстановления: остановлен</div>
<div id="ic3w0b">Данных в целях бэкапа: 
<div>файлов - </div>
<div>суммарный объем -</div>
</div>
<div id="irto82">Данных скопировано на кассеты:
<div>файлов - </div>
<div>суммарная емкость - </div>
<div>лабораторных номеров - </div>
</div>
<div id="i2q7h9">Кассет:
<div>записано всего - </div>
<div>вне ленточной библиотеки -</div>
<div>внутри ленточной библиотеки - </div>
<div>готовых к изъятию и перевозке - </div>
</div>
</div>
<div class="cell" id="icz0xd">
<div id="iifin7">Статус ленточной библиотеки</div>
<table id="HPE MSL3040" class="library">
<thead>
<tr>
<td id="empty_cell_left_upper">
</td>
<td id="drive"><p class="paragraph" id="izj3r">Имя
<br/>12CHAR
<br/>Status
</p></td>
<td id="empty_cell_right_upper">
</td>
</tr>
</thead>
<tbody id="i4ska">
<tr>
<td><p class="paragraph" id="ismwm">
l01
<br/>12CHAR
<br/>Status
</p></td>
<td rowspan="20" id="ip0wy">
<div class="robot"><p class="paragraph">
robot
<br/>12CHAR
<br/>Status
</p></div>
</td>
<td><p class="paragraph">
r01
<br/>12CHAR
<br/>Status
</p></td>
</tr>
<tr>
<td><p class="paragraph" id="i6lmi">
l02
</p></td>
<td><p class="paragraph">
r02
</p></td>
</tr>
<tr>
<td><p class="paragraph" id="ie564">
l03
</p></td>
<td><p class="paragraph">
r03
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l04
</p></td>
<td><p class="paragraph" id="ijngj">
r04
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l05
</p></td>
<td id="icfni"><p class="paragraph">
r05
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l06
</p></td>
<td><p class="paragraph">
r06
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l07
</p></td>
<td id="im9mw"><p class="paragraph">
r07
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l08
</p></td>
<td><p class="paragraph">
r08
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l09
</p></td>
<td><p class="paragraph">
r09
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l10
</p></td>
<td><p class="paragraph">
r10
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l11
</p></td>
<td><p class="paragraph">
r11
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l12
</p></td>
<td><p class="paragraph">
r12
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l13
</p></td>
<td><p class="paragraph">
r13
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l14
</p></td>
<td><p class="paragraph">
r14
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l15
</p></td>
<td><p class="paragraph">
r15
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l16
</p></td>
<td class="gray"><p class="paragraph">
r16
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l17
</p></td>
<td class="gray"><p class="paragraph">
r17
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l18
</p></td>
<td class="gray"><p class="paragraph">
r18
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l19
</p></td>
<td class="gray"><p class="paragraph">
r19
</p></td>
</tr>
<tr>
<td><p class="paragraph">
l20
</p></td>
<td id="i7c971" class="gray"><p class="paragraph">
r20
</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<script>var items = document.querySelectorAll('#iwiimc');
for (var i = 0, len = items.length; i < len; i++) {
(function () {
var e, t = 0, n = function () {
var e, t = document.createElement("void"), n = {
transition: "transitionend",
OTransition: "oTransitionEnd",
MozTransition: "transitionend",
WebkitTransition: "webkitTransitionEnd"
};
for (e in n) if (void 0 !== t.style[e]) return n[e]
}(), r = function (e) {
var t = window.getComputedStyle(e), n = t.display,
r = (t.position, t.visibility, t.height, parseInt(t["max-height"]));
if ("none" !== n && "0" !== r) return e.offsetHeight;
e.style.height = "auto", e.style.display = "block", e.style.position = "absolute", e.style.visibility = "hidden";
var i = e.offsetHeight;
return e.style.height = "", e.style.display = "", e.style.position = "", e.style.visibility = "", i
}, i = function (e) {
t = 1;
var n = r(e), i = e.style;
i.display = "block", i.transition = "max-height 0.25s ease-in-out", i.overflowY = "hidden", "" == i["max-height"] && (i["max-height"] = 0), 0 == parseInt(i["max-height"]) ? (i["max-height"] = "0", setTimeout(function () {
i["max-height"] = n + "px"
}, 10)) : i["max-height"] = "0"
}, a = function (r) {
if (r.preventDefault(), !t) {
var a = this.closest("[data-gjs=navbar]"), o = a.querySelector("[data-gjs=navbar-items]");
i(o), e || (o.addEventListener(n, function () {
t = 0;
var e = o.style;
0 == parseInt(e["max-height"]) && (e.display = "", e["max-height"] = "")
}), e = 1)
}
};
"gjs-collapse" in this || this.addEventListener("click", a), this["gjs-collapse"] = 1
}.bind(items[i]))();
}</script>
{% endblock content %}
\ No newline at end of file
......@@ -245,22 +245,28 @@ class TapeManager:
return
def scan_tapes(self):
log.debug("Rescanning tapes in the library")
known_tapes = self.dbsession.query(models.Tape).all()
log.debug("Backup system knows about %d tapes", len(known_tapes))
for present_tape in self.magazine + self.mailslot + self.drives:
if present_tape['is_data_tape']: