Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Oleg Borisenko
tapebackup
Commits
2ef5e1dc
Commit
2ef5e1dc
authored
Jun 08, 2021
by
Oleg Borisenko
Browse files
making views for backups UI
parent
915a4f3f
Changes
11
Hide whitespace changes
Inline
Side-by-side
tapebackup/routes.py
View file @
2ef5e1dc
...
...
@@ -2,10 +2,13 @@ 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
(
'targets'
,
'/targets'
)
config
.
add_route
(
'tapes'
,
'/tapes'
)
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}'
)
config
.
add_route
(
'identify_tape'
,
'/identify_tape'
)
config
.
add_route
(
'list_tapes'
,
'/list_tapes'
)
config
.
add_route
(
'use_tape_for_backup'
,
'/use_tape_for_backup'
)
config
.
add_route
(
'use_tape_for_restore'
,
'/use_tape_for_restore'
)
config
.
add_route
(
'list_backup_targets'
,
'/list_backup_targets'
)
...
...
tapebackup/static/css/style.css
View file @
2ef5e1dc
...
...
@@ -120,6 +120,10 @@ body {
font-size
:
x-small
;
}
.backup_targets
{
font-size
:
x-small
;
}
@media
(
max-width
:
768px
)
{
.row
{
flex-wrap
:
wrap
;
...
...
@@ -141,4 +145,49 @@ body {
.navbar-burger
{
display
:
block
;
}
}
.form
{
border-radius
:
3px
;
padding
:
10px
15px
;
background-color
:
rgba
(
0
,
0
,
0
,
0.2
);
}
.input
{
width
:
100%
;
margin-bottom
:
15px
;
padding
:
7px
10px
;
border-radius
:
2px
;
color
:
#fff
;
background-color
:
#554c57
;
border
:
none
;
}
.select
{
width
:
100%
;
margin-bottom
:
15px
;
padding
:
7px
10px
;
border-radius
:
2px
;
color
:
#fff
;
background-color
:
#554c57
;
border
:
none
;
height
:
30px
;
}
.label
{
width
:
100%
;
display
:
block
;
}
.button
{
width
:
100%
;
margin
:
15px
0
;
background-color
:
#785580
;
border
:
none
;
color
:
#fff
;
border-radius
:
2px
;
padding
:
7px
10px
;
font-size
:
1em
;
cursor
:
pointer
;
}
*
{
box-sizing
:
border-box
;
}
\ No newline at end of file
tapebackup/templates/base.jinja2
View file @
2ef5e1dc
...
...
@@ -5,7 +5,6 @@
<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>
...
...
@@ -30,12 +29,12 @@
<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=
"/"
<a
href=
"/
targets
"
class=
"navbar-menu-link"
id=
"targets"
>
Цели бэкапа и восстановления
</a>
<a
href=
"/
tapes
"
class=
"navbar-menu-link"
id=
"tapes"
>
Обзор
кассет
</a>
<a
href=
"/"
class=
"navbar-menu-link"
id=
"files"
>
Поиск файлов
</a>
<a
href=
"/
files
"
class=
"navbar-menu-link"
id=
"files"
>
Поиск файлов
</a>
</nav>
</div>
</div>
...
...
@@ -44,4 +43,42 @@
{% block content %}{% endblock %}
</body>
<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>
</html>
tapebackup/templates/home.jinja2
View file @
2ef5e1dc
...
...
@@ -2,9 +2,9 @@
{% block content %}
<div class="row">
<div class="cell">
<div class="cell"
style="flex-basis: 70%"
>
<section class="bdg-sect" id="ignhy">
<h1 class="heading"><i>Надеюсь, вы здесь из любопытства, а не из-за катастрофы</i>
<h1 class="heading">
Описание.
<i>Надеюсь, вы здесь из любопытства, а не из-за катастрофы</i>
</h1>
<h2 class="heading">Список допустимых действий в системе:
</h2>
...
...
@@ -31,39 +31,38 @@
</div>
</section>
</div>
<div class="cell" style="flex-basis: 30%">
<section class="bdg-sect">
<h2>Краткая статистика</h2>
<div class="cell">
<p class="paragraph">Процесс системы резервирования: {% if copy_status['backup_is_running'] %} запущен{% else %} не запущен{% endif %}</p>
<p class="paragraph">Процесс системы восстановления: {% if copy_status['restore_is_running'] %} запущен{% else %} не запущен{% endif %}</p>
{% if copy_status['backup_is_running'] and copy_status['restore_is_running'] %}
<p class="paragraph">ВНИМАНИЕ: запускать систему резервирования и восстановления одновременно ОЧЕНЬ ОПАСНО</p>
{% endif %}
<p class="paragraph">Данных в целях бэкапа:</p>
<ul>
<li>файлов - {{ copy_status['total_files'] }}</li>
<li>суммарный объем - {{ copy_status['total_gb'] }} Гбайт</li>
<li>из них wgs - {{ copy_status['total_wgs_gb'] }} Гбайт</li>
</ul>
<p class="paragraph">Данных скопировано на кассеты:</p>
<ul>
<li>файлов - {{ copy_status['copied_files'] }}</li>
<li>суммарная емкость - {{ copy_status['copied_gb'] }} Гбайт</li>
<li>из них wgs - {{ copy_status['copied_wgs_gb'] }} Гбайт</li>
<li>лабораторных номеров - TODO</li>
</ul>
<p class="paragraph">Кассет:</p>
<ul>
<li>записано всего - </li>
<li>вне ленточной библиотеки -</li>
<li>внутри ленточной библиотеки - </li>
<li>готовых к изъятию и перевозке - </li>
</ul>
</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
tapebackup/templates/status.jinja2
View file @
2ef5e1dc
{% extends "base.jinja2" %}
{% block content %}
<section class="bdg-sect" id="ignhy">
<h1 class="heading">Статус системы
</h1>
</section>
<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 class="cell">
<p class="paragraph">Процесс системы резервирования: {% if copy_status['backup_is_running'] %} запущен{% else %} не запущен{% endif %}</p>
<p class="paragraph">Процесс системы восстановления: {% if copy_status['restore_is_running'] %} запущен{% else %} не запущен{% endif %}</p>
{% if copy_status['backup_is_running'] and copy_status['restore_is_running'] %}
<p class="paragraph">ВНИМАНИЕ: запускать систему резервирования и восстановления одновременно ОЧЕНЬ ОПАСНО</p>
{% endif %}
<p class="paragraph">Данных в целях бэкапа:</p>
<ul>
<li>файлов - {{ copy_status['total_files'] }}</li>
<li>суммарный объем - {{ copy_status['total_gb'] }} Гбайт</li>
<li>из них wgs - {{ copy_status['total_wgs_gb'] }} Гбайт</li>
</ul>
<p class="paragraph">Данных скопировано на кассеты:</p>
<ul>
<li>файлов - {{ copy_status['copied_files'] }}</li>
<li>суммарная емкость - {{ copy_status['copied_gb'] }} Гбайт</li>
<li>из них wgs - {{ copy_status['copied_wgs_gb'] }} Гбайт</li>
<li>лабораторных номеров - TODO</li>
</ul>
<p class="paragraph">Кассет:</p>
<ul>
<li>записано всего - </li>
<li>вне ленточной библиотеки -</li>
<li>внутри ленточной библиотеки - </li>
<li>готовых к изъятию и перевозке - </li>
</ul>
</div>
<div class="cell" id="icz0xd">
<div id="iifin7">Статус ленточной библиотеки</div>
...
...
@@ -213,41 +224,4 @@
</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
tapebackup/templates/tapes.jinja2
0 → 100644
View file @
2ef5e1dc
{% extends "base.jinja2" %}
{% block content %}
<div class="row" id="ir3o5">
<div id="icz0xd" class="cell">
<div id="igwru"><h1>Зарегистрированные в системе цели бэкапа и восстановления</h1></div>
</div>
</div>
<div class="row">
<div class="cell">
<table class="tapes">
<th>Уникальная метка</th>
<th>Состояние</th>
<th>Когда была в библиотеке</th>
<th>Где была в библиотеке (слот)</th>
<th>Местонахождение</th>
{% for tape in known_tapes %}
<tr>
<td>{{ tape['label'] }}</td>
<td>{{ tape['state'] }}</td>
<td>{{ tape['last_seen'] }}</td>
<td>{{ tape['last_seen_slot'] }}</td>
<td>{{ tape['location'] }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}
\ No newline at end of file
tapebackup/templates/targets.jinja2
0 → 100644
View file @
2ef5e1dc
{% extends "base.jinja2" %}
{% block content %}
<div class="row" id="ir3o5">
<div id="icz0xd" class="cell">
<div id="igwru"><h1>Зарегистрированные в системе цели бэкапа и восстановления</h1></div>
</div>
</div>
<div class="row" id="i1ejb">
<div class="cell" id="i0qb4">
<div id="iso3k">Цели бэкапа
</div>
<form class="form" id="backup_unique_label" action="/add_backup_target" method="post">
<div class="form-group">
<label class="label" id="ihwp5">Уникальная метка (только буквы, цифры и подчеркивания)</label>
<input placeholder="Вбейте уникальный идентификатор цели бэкапа" class="input" id="i3f4c"/>
</div>
<div class="form-group" id="backup_local_fullpath">
<label class="label" id="ipsnl">Полный путь</label>
<input type="email" placeholder="Вбейте валидный и существующий локальный путь в ФС сервера backups" class="input"/>
</div>
<div class="form-group" id="backup_period_selector">
<label class="label" id="icpe8">Период сканирования</label>
<select class="select" id="idlsu"><option value="" id="itae1">- Выберите период -</option>
<option value="1">Раз в сутки</option>
<option value="2">Раз в двое суток</option>
<option value="7">Раз в неделю</option>
<option value="30">Раз в 30 дней</option>
</select>
</div>
<div class="form-group" id="i7o81">
<label class="label" id="i8ylf">Тип сканируемой цели (лишние файлы относительно типа игнорируются)</label>
<select class="select" id="i040c">
<option value="" id="i3twi">- Выберите тип -</option>
<option value="wgs">Полногеномные данные</option>
<option value="vcf">Данные VCF</option>
<option value="db">Данные баз данных</option>
<option value="files">Произвольные данные</option>
</select>
</div>
<div class="form-group" id="i0jbz">
<button type="submit" class="button" id="ir3p2">Зарегистрировать цель бэкапа</button>
</div>
</form>
<div class="row" id="icuox">
<div class="cell" id="itde6">
<table class="backup_targets">
<th>Уникальная метка</th>
<th>Тип</th>
<th>Период сканирования</th>
<th>Последнее сканирование</th>
<th>Полный путь</th>
<th>Число файлов</th>
<th>Размер (Гбайт)</th>
<th>Включена?</th>
{% for target in backup_targets %}
<tr>
<td>{{ target['unique_label'] }}</td>
<td>{{ target['kind'] }}</td>
<td>{{ target['rescan_interval'] }}</td>
<td>{{ target['last_scan_time'] }}</td>
<td>{{ target['fullpath'] }}</td>
<td>{{ target['files_count'] }}</td>
<td>{{ target['files_size_on_target_gb'] }}</td>
<td>{{ target['enabled'] }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
<div class="cell" id="iwxir">
<div id="iqvor">Цели восстановления
</div>
<form class="form" id="i5rah">
<div class="form-group">
<label class="label" id="isrn5">Уникальная метка (только буквы, цифры и подчеркивания)</label>
<input placeholder="Type here your name" class="input"/>
</div>
<div class="form-group">
<label class="label" id="iodya">Полный путь</label>
<input type="email" placeholder="Type here your email" class="input"/>
</div>
<div class="form-group">
<button type="submit" class="button">Send</button>
</div>
</form>
<div class="row">
<div class="cell">
</div>
</div>
</div>
</div>
{% endblock %}
\ No newline at end of file
tapebackup/views/backup_target_crud.py
View file @
2ef5e1dc
...
...
@@ -5,6 +5,8 @@ from pyramid.view import view_config
from
pyramid.response
import
Response
from
pyramid.httpexceptions
import
HTTPException
from
sqlalchemy.exc
import
SQLAlchemyError
from
sqlalchemy.types
import
BIGINT
from
sqlalchemy.sql
import
func
,
and_
from
..
import
models
from
..
import
utils
...
...
@@ -31,8 +33,18 @@ def add_backup_target(request):
@
view_config
(
route_name
=
'list_backup_targets'
,
renderer
=
'json'
,
request_method
=
'GET'
)
def
list_backup_targets
(
request
):
try
:
targets
=
[
x
.
to_dict
()
for
x
in
request
.
dbsession
.
query
(
models
.
backuptarget
.
BackupTarget
).
all
()]
return
targets
targets
=
[
x
for
x
in
request
.
dbsession
.
query
(
models
.
backuptarget
.
BackupTarget
).
all
()]
target_to_return
=
[]
for
target
in
targets
:
files_on_target
=
request
.
dbsession
.
query
(
models
.
file_to_backup
.
FileToBackup
).
filter
(
target
.
unique_label
==
models
.
file_to_backup
.
FileToBackup
.
target_unique_label
).
count
()
target_to_ret
=
target
.
to_dict
()
target_to_ret
[
'files_count'
]
=
files_on_target
files_size_on_target
=
request
.
dbsession
.
query
(
func
.
sum
(
models
.
FileToBackup
.
fsize
).
cast
(
BIGINT
)).
filter
(
target
.
unique_label
==
models
.
file_to_backup
.
FileToBackup
.
target_unique_label
).
scalar
()
target_to_ret
[
'files_size_on_target_gb'
]
=
round
(
files_size_on_target
/
(
1024
**
3
),
2
)
target_to_return
.
append
(
target_to_ret
)
return
target_to_return
except
SQLAlchemyError
as
e
:
return
Response
(
json_body
=
{
"error"
:
e
.
_message
()},
content_type
=
'application/json'
,
status
=
500
)
except
HTTPException
as
e
:
...
...
tapebackup/views/copy_status.py
View file @
2ef5e1dc
...
...
@@ -3,7 +3,7 @@ from pyramid.view import view_config
from
pyramid.response
import
Response
from
pyramid.httpexceptions
import
HTTPException
from
sqlalchemy.exc
import
SQLAlchemyError
from
sqlalchemy.sql
import
func
from
sqlalchemy.sql
import
func
,
and_
from
sqlalchemy.types
import
BIGINT
...
...
@@ -14,26 +14,48 @@ from .. import models
def
copy_status
(
request
):
try
:
backup_is_running
=
False
restore_is_running
=
False
for
proc
in
psutil
.
process_iter
():
try
:
# Check if process name contains the given name string.
if
'python'
in
proc
.
name
():
if
any
(
'backup_daemon.py'
in
x
for
x
in
proc
.
cmdline
()):
backup_is_running
=
True
if
any
(
'restore_daemon.py'
in
x
for
x
in
proc
.
cmdline
()):
restore_is_running
=
True
except
(
psutil
.
NoSuchProcess
,
psutil
.
AccessDenied
,
psutil
.
ZombieProcess
):
pass
total
=
request
.
dbsession
.
query
(
func
.
sum
(
models
.
FileToBackup
.
fsize
).
cast
(
BIGINT
)).
scalar
()
copied
=
request
.
dbsession
.
query
(
func
.
sum
(
models
.
FileToBackup
.
fsize
).
cast
(
BIGINT
)).
filter
(
models
.
FileToBackup
.
tape_label
!=
None
).
scalar
()
total_files
=
request
.
dbsession
.
query
(
models
.
FileToBackup
).
filter
(
models
.
FileToBackup
.
is_file
==
True
).
count
()
total_wgs
=
request
.
dbsession
.
query
(
func
.
sum
(
models
.
FileToBackup
.
fsize
).
cast
(
BIGINT
)).
filter
(
and_
(
models
.
FileToBackup
.
is_file
==
True
,
models
.
FileToBackup
.
kind
==
models
.
DataKind
.
wgs
)).
scalar
()
copied
=
request
.
dbsession
.
query
(
func
.
sum
(
models
.
FileToBackup
.
fsize
).
cast
(
BIGINT
)).
filter
(
and_
(
models
.
FileToBackup
.
is_file
==
True
,
models
.
FileToBackup
.
tape_label
!=
None
)).
scalar
()
copied_files
=
request
.
dbsession
.
query
(
models
.
FileToBackup
).
filter
(
and_
(
models
.
FileToBackup
.
is_file
==
True
,
models
.
FileToBackup
.
tape_label
!=
None
)).
count
()
copied_wgs
=
request
.
dbsession
.
query
(
func
.
sum
(
models
.
FileToBackup
.
fsize
).
cast
(
BIGINT
)).
filter
(
and_
(
models
.
FileToBackup
.
is_file
==
True
,
models
.
FileToBackup
.
kind
==
models
.
DataKind
.
wgs
,
models
.
FileToBackup
.
tape_label
!=
None
)).
scalar
()
except
SQLAlchemyError
as
e
:
return
Response
(
json_body
=
{
"error"
:
e
.
_message
()},
content_type
=
'application/json'
,
status
=
500
)
except
HTTPException
as
e
:
return
Response
(
json_body
=
{
"error"
:
e
.
detail
},
content_type
=
'application/json'
,
status
=
e
.
status
)
return
{
"backup_is_running"
:
backup_is_running
,
"restore_is_running"
:
restore_is_running
,
"total"
:
total
,
"total_gb"
:
round
(
total
/
(
1024
**
3
),
2
),
"total_files"
:
total_files
,
"total_wgs"
:
total_wgs
,
"total_wgs_gb"
:
round
(
total_wgs
/
(
1024
**
3
),
2
),
"copied"
:
copied
,
"copied_gb"
:
round
(
copied
/
(
1024
**
3
),
2
)}
"copied_gb"
:
round
(
copied
/
(
1024
**
3
),
2
),
"copied_wgs"
:
copied_wgs
,
"copied_wgs_gb"
:
round
(
copied_wgs
/
(
1024
**
3
),
2
),
"copied_files"
:
copied_files
}
# all batches list by time with status
@
view_config
(
route_name
=
'batches'
,
renderer
=
'json'
,
request_method
=
'GET'
)
...
...
tapebackup/views/default.py
View file @
2ef5e1dc
from
pyramid.view
import
view_config
from
pyramid.response
import
Response
from
pyramid.request
import
Request
from
pyramid.httpexceptions
import
HTTPException
from
..
import
utils
@
view_config
(
route_name
=
'home'
,
renderer
=
'tapebackup:templates/home.jinja2'
)
def
home_route
(
request
):
return
{}
try
:
subreq
=
Request
.
blank
(
'/copy_status'
)
copy_status
=
request
.
invoke_subrequest
(
subreq
,
use_tweens
=
True
).
json
except
HTTPException
as
e
:
return
Response
(
json_body
=
{
"error"
:
e
.
detail
},
content_type
=
'application/json'
,
status
=
e
.
status
)
return
{
"copy_status"
:
copy_status
}
# this view is supposed to return current system status: library info and current state
@
view_config
(
route_name
=
'status'
,
renderer
=
'tapebackup:templates/status.jinja2'
)
def
status_route
(
request
):
try
:
subreq
=
Request
.
blank
(
'/copy_status'
)
copy_status
=
request
.
invoke_subrequest
(
subreq
,
use_tweens
=
True
).
json
manager
=
utils
.
tapemanager
.
TapeManager
(
request
.
dbsession
)
library
=
manager
.
to_dict
()
except
HTTPException
as
e
:
return
Response
(
json_body
=
{
"error"
:
e
.
detail
},
content_type
=
'application/json'
,
status
=
e
.
status
)
return
library
\ No newline at end of file
return
{
"copy_status"
:
copy_status
,
"library"
:
library
}
@
view_config
(
route_name
=
'targets'
,
renderer
=
'tapebackup:templates/targets.jinja2'
)
def
targets_route
(
request
):
try
:
subreq
=
Request
.
blank
(
'/list_backup_targets'
)
backup_targets
=
request
.
invoke_subrequest
(
subreq
,
use_tweens
=
True
).
json
except
HTTPException
as
e
:
return
Response
(
json_body
=
{
"error"
:
e
.
detail
},
content_type
=
'application/json'
,
status
=
e
.
status
)
return
{
"backup_targets"
:
backup_targets
}
@
view_config
(
route_name
=
'tapes'
,
renderer
=
'tapebackup:templates/tapes.jinja2'
)
def
tapes_route
(
request
):
try
:
subreq
=
Request
.
blank
(
'/list_tapes'
)
tapes
=
request
.
invoke_subrequest
(
subreq
,
use_tweens
=
True
).
json
except
HTTPException
as
e
:
return
Response
(
json_body
=
{
"error"
:
e
.
detail
},
content_type
=
'application/json'
,
status
=
e
.
status
)
return
{
"known_tapes"
:
tapes
}
\ No newline at end of file
tapebackup/views/tapes_status.py
View file @
2ef5e1dc
from
pyramid.view
import
view_config
from
pyramid.response
import
Response
from
pyramid.request
import
Request
from
pyramid.httpexceptions
import
HTTPException
,
HTTPConflict
,
HTTPNotImplemented
from
sqlalchemy.exc
import
SQLAlchemyError
from
..
import
models
# by status
def
list_known_tapes
():
return
\ No newline at end of file
@
view_config
(
route_name
=
'list_tapes'
,
renderer
=
'json'
)
def
list_tapes
(
request
):
try
: