В данной системе бэкапирования к сожалению нет удобного визуального контроля за тем какие файлы и в какой момент были записаны на ленточное хранилище.
И я решил сам сделать такой функционал т.к. смотреть через bconsole не очень удобно из-за привязки записанных файлов к заданию job
Я изучил структуру базы данных и зависимости таблиц между собой, что бы понять какие данные мне нужно включить в отчетность и что бы ничего лишнего не попало туда.
Кратко описываю что мне нужно извлечь из базы
Есть таблицы job, file, media
в таблице job есть столбец name, из него мы считываем только содержимое с названием BackupOnTape,
так же есть столбец jobid и он идентичен в таблице file
Теперь в таблице file сопоставив jobid мы должны вытащить из столбца name который в таблице file название
В таблице media есть столбец poolid он понадобится для понимания какой используется пул
Но мне из таблицы media более важно знать что в столбце volumename и выводить эту информацию Еще в таблице media есть вот такой столбец mediatype, мне важно что бы в финальном отчете он тоже фигурировал, но при условии что в mediatype будет исключительно содержимое с названием LTO
И из таблицы job нужны два столбца starttime и endtime, которые отвечают за начало и конец записи на ленту.
Таким образом резюмируем
Мне нужно сформировать отчет в котором будет:
- volumename,poolid,mediatype из таблицы media
- name из таблицы file
- name,jobid,starttime,endtime из таблицы job
У меня получился вот такой SQL запрос
SELECT
m.volumename AS volumename,
f.name AS file_name,
j.name AS job_name,
j.jobid AS jobid,
m.mediatype AS mediatype,
j.starttime AS starttime,
j.endtime AS endtime,
m.poolid AS poolid
FROM
public.job j
JOIN
public.file f ON j.jobid = f.jobid
JOIN
public.media m ON m.poolid IS NOT NULL
WHERE
j.name = 'BackupOnTape' AND
m.mediatype = 'LTO' AND
m.poolid = 2;
Для проверки можно сохранить в файл вывод запроса
COPY (
SELECT
m.volumename AS volumename,
f.name AS file_name,
j.name AS job_name,
j.jobid AS jobid,
m.mediatype AS mediatype,
j.starttime AS starttime,
j.endtime AS endtime,
m.poolid AS poolid
FROM
public.job j
JOIN
public.file f ON j.jobid = f.jobid
JOIN
public.media m ON m.poolid IS NOT NULL
WHERE
j.name = 'BackupOnTape' AND
m.mediatype = 'LTO' AND
m.poolid = 2
) TO '/ВАШ ПУТЬ/report.txt' WITH (FORMAT csv, HEADER);
Но это все сырые данные и так мы только убедились что все работает, теперь нужно создать веб интерфейс и прикрутить кнопку в основной веб интерфейс bareos
Создание веб интерфейса
Создаем такую структуру
/var/www/html/
└── reports_bareos
├── reports_bareos.py
├── static
│ └── style.css
└── templates
├── index.html
└── report.html
Содержимое файла index.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Форма для отчета</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>Форма для отчета</h1>
<form action="/report" method="get">
<label for="volumename">Номер ленты (Пример 000005):</label>
<input type="text" id="volumename" name="volumename">
<label for="start_date">Дата начала:</label>
<input type="date" id="start_date" name="start_date">
<label for="end_date">Дата окончания:</label>
<input type="date" id="end_date" name="end_date">
<input type="submit" value="Сформировать отчет">
</form>
</body>
</html>
Содержимое файла report.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Отчет</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>Отчет</h1>
<table border="1">
<tr>
<th>Volume Name</th>
<th>File Name</th>
<th>Job Name</th>
<th>Job ID</th>
<th>Media Type</th>
<th>Start Time</th>
<th>End Time</th>
<th>Pool ID</th>
</tr>
{% for row in reports %}
<tr>
<td>{{ row[0] }}</td>
<td>{{ row[1] }}</td>
<td>{{ row[2] }}</td>
<td>{{ row[3] }}</td>
<td>{{ row[4] }}</td>
<td>{{ row[5] }}</td>
<td>{{ row[6] }}</td>
<td>{{ row[7] }}</td>
</tr>
{% endfor %}
</table>
<br>
<a href="/">Вернуться назад</a>
</body>
</html>
Содержимое файла style.css
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
color: #333;
}
form {
max-width: 600px;
margin: auto;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 8px;
color: #555;
}
input[type="text"],
input[type="date"] {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
}
input[type="submit"] {
background-color: #5cb85c;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
input[type="submit"]:hover {
background-color: #4cae4c;
}
/* Стили для таблицы */
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
table, th, td {
border: 1px solid #ccc;
}
th {
background-color: #5cb85c;
color: white;
padding: 10px;
}
td {
padding: 10px;
text-align: center;
}
/* Стили для ссылки */
a {
display: block;
text-align: center;
margin-top: 20px;
color: #5cb85c;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
Создаем логику работы (backend)
Логику будем описывать с помощью Python и фреймворка Flask
Содержимое файла reports_bareos.py
from flask import Flask, request, render_template
import psycopg2
app = Flask(__name__)
def get_db_connection():
conn = psycopg2.connect(dbname='ВАША БАЗА', user='ВАШ ПОЛЬЗОВАТЕЛЬ', password='ВАШ ПАРОЛЬ', host='localhost')
return conn
@app.route('/')
def index():
return render_template('index.html')
@app.route('/report', methods=['GET'])
def report():
volumename = request.args.get('volumename')
start_date = request.args.get('start_date')
end_date = request.args.get('end_date')
conn = get_db_connection()
cur = conn.cursor()
query = '''
SELECT
m.volumename AS volumename,
f.name AS file_name,
j.name AS job_name,
j.jobid AS jobid,
m.mediatype AS mediatype,
j.starttime AS starttime,
j.endtime AS endtime,
m.poolid AS poolid
FROM
public.job j
JOIN
public.file f ON j.jobid = f.jobid
JOIN
public.media m ON m.poolid IS NOT NULL
WHERE
j.name = 'BackupOnTape' AND
m.mediatype = 'LTO' AND
m.poolid = 2
'''
if volumename:
query += ' AND m.volumename = %s'
cur.execute(query, (volumename,))
else:
cur.execute(query)
reports = cur.fetchall()
cur.close()
conn.close()
# Фильтруем строки, у которых file_name пустая
reports = [row for row in reports if row[1]] # row[1] - это file_name
return render_template('report.html', reports=reports)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Тестируем
Откройте порт 5000 в вашем firewall если это необходимо
Далее нужно вручную запустить приложение командой
python /var/www/html/reports_bareos.py
Теперь идем в браузер
http://ВАШ IP:5000
Значит все идет как нужно. Но запускать таким образом приложение не удобно, нужно сделать его отдельным демоном в системе
Создаем демона для reports_bareos.py
Открываем
nano /etc/systemd/system/reports_bareos.service
Содержимое будет примерно такое
[Unit]
Description=Flask Application for Reports Bareos
After=network.target
[Service]
User=bareos
Group=bareos
WorkingDirectory=/var/www/html/reports_bareos
ExecStart=/var/lib/bareos/.local/bin/gunicorn -w 4 -b 0.0.0.0:5000 reports_bareos:app
Restart=always
RestartSec=10
Environment="FLASK_ENV=development"
Environment="PATH=/var/lib/bareos/.local/bin:$PATH"
StandardOutput=append:/var/log/reports_bareos.log
StandardError=append:/var/log/reports_bareos.log
[Install]
WantedBy=multi-user.target
Запускаем демона и проверяем его
systemctl daemon-reload
systemctl enable reports_bareos.service
systemctl start reports_bareos.service
systemctl status reports_bareos.service
Теперь идем в браузер
http://ВАШ IP:5000
Прикручиваем кнопку отчетов в bareos-webui
Тут уже все индивидуально и куда вам будет удобнее, туда и прикручивайте, я покажу свой пример как сделал я
Внешний вид интерфейса вот такой по умолчанию
Я решил в пункте Analytics сделать кнопку отчетности
Открываем файл
nano /usr/share/bareos-webui/module/Analytics/view/analytics/analytics/index.phtml
Ищем в нем где упоминается Stored Data как видно на скрине выше это такой раздел
И туда отдельным пунктом добавляем кнопку перехода в наш созданный интерфейс
<li><a href="http://10.0.5.210:5000" class="nav-link">Отчетность</a></li>
На этом все, мы проделали отличную работу по созданию интерфейса для отчетности.
Его можно расширять если вам необходимы еще какие-то данные из базы визуализировать