Compare commits

..

No commits in common. "master" and "v1.1.3-31" have entirely different histories.

64 changed files with 1045 additions and 1402 deletions

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020-2024 Soleta Networks <info@soleta.eu>
# Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020-2024 Soleta Networks <info@soleta.eu>
# Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the
@ -73,8 +73,7 @@ class SetupForm(FlaskForm):
ips = HiddenField()
disk = SelectField(label='Disk', validate_choice=False)
disk_type = SelectField(label=_l('Type'),
choices=[('EMPTY', 'EMPTY'),
('MSDOS', 'MBR'),
choices=[('MSDOS', 'MBR'),
('GPT', 'GPT')])
partitions = FieldList(FormField(PartitionForm),
min_entries=1,
@ -173,11 +172,6 @@ class RunScriptForm(FlaskForm):
arguments = StringField(label=_l('Arguments'))
submit = SubmitField(label=_l('Submit'))
class RunCmdForm(FlaskForm):
ips = HiddenField()
command = StringField(label=_l('Command'))
submit = SubmitField(label=_l('Submit'))
class ImportClientsForm(FlaskForm):
server = HiddenField()
room = SelectField(label=_l('Room'))
@ -192,15 +186,9 @@ class BootModeForm(FlaskForm):
class OgliveForm(FlaskForm):
ips = HiddenField()
server = HiddenField()
oglive = SelectField(label=_l('ogLive'))
ok = SubmitField(label=_l('Submit'))
class SetRepoForm(FlaskForm):
ips = HiddenField()
repo = SelectField(label=_l('Repository'))
ok = SubmitField(label=_l('Submit'))
class ImageCreateForm(FlaskForm):
ip = HiddenField()
os = SelectField(label=_l('Partition'), choices=[])

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020-2024 Soleta Networks <info@soleta.eu>
# Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020-2024 Soleta Networks <info@soleta.eu>
# Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020-2024 Soleta Networks <info@soleta.eu>
# Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the
@ -6,22 +6,10 @@
# (at your option) any later version.
from ogcp import app
from flask import session, flash
from flask_babel import _
import requests
import json
class ServerError(Exception):
pass
def flash_once(message, category='message'):
if '_flashes' not in session:
session['_flashes'] = []
if (category, message) not in session['_flashes']:
flash(message, category)
class OGServer:
def __init__(self, name, ip, port, api_token):
self.name = name
@ -34,57 +22,26 @@ class OGServer:
self.URL = f'http://{self.ip}:{self.port}'
self.HEADERS = {'Authorization' : self.api_token}
def _log_http_status_code(self, res):
if res.status_code == 400:
err_msg = _('Invalid payload')
elif res.status_code == 404:
err_msg = _('Object not found')
elif res.status_code == 405:
err_msg = _('Method not allowed')
elif res.status_code == 409:
err_msg = _('Object already exists')
elif res.status_code == 423:
err_msg = _('Object in use')
elif res.status_code == 501:
err_msg = _('Cannot connect to database')
elif res.status_code == 507:
err_msg = _('Disk full')
else:
err_msg = _(f'Received status code {res.status_code}')
flash_once(err_msg, category='error')
def _request(self, method, path, payload, expected_status):
try:
res = requests.request(
method,
f'{self.URL}{path}',
headers=self.HEADERS,
json=payload,
)
if res.status_code not in expected_status:
self._log_http_status_code(res)
raise ServerError
return res
except requests.exceptions.ConnectionError:
flash_once(_('Cannot connect to ogserver'), category='error')
except requests.exceptions.Timeout:
flash_once(_('Request to ogserver timed out'), category='error')
except requests.exceptions.TooManyRedirects:
flash_once(_('Too many redirects occurred while contacting ogserver'), category='error')
except requests.exceptions.RequestException as e:
flash_once(_('An error occurred while contacting ogserver: %(error)s', error=str(e)), category='error')
raise ServerError
def get(self, path, payload=None):
return self._request('GET', path, payload, expected_status={200})
try:
r = requests.get(f'{self.URL}{path}',
headers=self.HEADERS,
json=payload)
except requests.exceptions.ConnectionError:
return None
return r
def post(self, path, payload):
return self._request('POST', path, payload, expected_status={200, 202})
r = requests.post(f'{self.URL}{path}',
headers=self.HEADERS,
json=payload)
return r
def delete(self, path, payload):
return self._request('DELETE', path, payload, expected_status={200})
r = requests.delete(f'{self.URL}{path}',
headers=self.HEADERS,
json=payload)
return r
@property
def id(self):

View File

@ -3,61 +3,6 @@ const macs = new Map();
const Interval = 1000;
let updateTimeoutId = null;
const StorageGroup = Object.freeze({
SHOW: 'show',
CHECK: 'check',
});
class ogStorage {
static STORAGE_VERSION = Object.freeze(1);
static store(group, context, elemId, value) {
const key = `${group}-${context}-${elemId}`;
localStorage.setItem(key, value);
}
static remove(group, context, elemId) {
const key = `${group}-${context}-${elemId}`;
localStorage.removeItem(key);
}
static hasKey(group, context, elemId) {
const key = `${group}-${context}-${elemId}`;
return localStorage.getItem(key) !== null;
}
static deleteInvalidStorage(items, group, context) {
if (localStorage.getItem('storageVersion') < ogStorage.STORAGE_VERSION) {
localStorage.clear();
localStorage.setItem('storageVersion', ogStorage.STORAGE_VERSION);
return
}
const prefix = `${group}-${context}`;
const existingKeys = items.map(function() {
return `${group}-${context}-${this.id}`;
}).get();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (!key.startsWith(prefix)) {
continue;
}
if (!existingKeys.includes(key)) {
localStorage.removeItem(key);
}
}
}
static storeCheckboxStatus(checkbox, context) {
if (checkbox.checked)
ogStorage.store(StorageGroup.CHECK, context, checkbox.id, "");
else
ogStorage.remove(StorageGroup.CHECK, context, checkbox.id);
}
}
async function show_client_mac(pill_id) {
const pill = $('#' +pill_id);
@ -102,13 +47,20 @@ function showSelectedClient(client_checkbox) {
}
function showSelectedClientsOnEvents() {
const checkboxes = $('#sidebar input:checkbox');
const checkboxes = $('input:checkbox[form|="scopesForm"]');
checkboxes.on('change show-client', function () {
showSelectedClient(this);
});
}
function storeCheckboxStatus(checkbox, context) {
if (checkbox.checked)
localStorage.setItem(context + checkbox.id, "check");
else
localStorage.removeItem(context + checkbox.id);
}
function findParentCheckboxes(element) {
const $element = $(element);
const parents = $element.parentsUntil('#scopes').not('ul');
@ -140,8 +92,6 @@ function setParentStatus(checkboxes) {
function configureCommandCheckboxes(context) {
const checkboxes = $('input:checkbox[form="scopesForm"]');
ogStorage.deleteInvalidStorage(checkboxes, StorageGroup.CHECK, context);
// Ensure the form fields are sent
$('#scopesForm').on('submit', function() {
checkboxes.each(function() {
@ -161,7 +111,7 @@ function configureCommandCheckboxes(context) {
checkboxes.on('change', function () {
const checked = this.checked;
const childrenCheckboxes = $('input[type="checkbox"][form="scopesForm"]', this.parentNode);
const childrenCheckboxes = $('input:checkbox[form|="scopesForm"]', this.parentNode);
// Uncheck all other checkboxes outside of the actual center branch
if (checked) {
@ -182,44 +132,79 @@ function configureCommandCheckboxes(context) {
checkboxes.each(function() {
showSelectedClient(this);
ogStorage.storeCheckboxStatus(this, context);
storeCheckboxStatus(this, context);
});
});
}
function keepSelectedClients(context) {
const checkboxes = $('#sidebar input:checkbox')
const checkboxes = $('input:checkbox[form|="scopesForm"]')
checkboxes.on('change', function (event) {
ogStorage.storeCheckboxStatus(this, context);
storeCheckboxStatus(this, context);
});
ogStorage.deleteInvalidStorage(checkboxes, StorageGroup.CHECK, context);
checkboxes.each(function () {
if (ogStorage.hasKey(StorageGroup.CHECK, context, this.id)) {
if (localStorage.getItem(context + this.id) == 'check') {
this.checked = true;
$(this).trigger('show-client');
}
});
}
function keepTreeState(selector, context) {
const tree_items = $(selector + ' .collapse');
ogStorage.deleteInvalidStorage(tree_items, StorageGroup.SHOW, context);
tree_items.on('hidden.bs.collapse', function (event) {
function keepImagesTreeState() {
const images_tree = $('#servers .collapse')
images_tree.on('hidden.bs.collapse', function (event) {
event.stopPropagation();
ogStorage.remove(StorageGroup.SHOW, context, this.id)
localStorage.removeItem(this.id);
});
tree_items.on('shown.bs.collapse', function (event) {
images_tree.on('shown.bs.collapse', function (event) {
event.stopPropagation();
ogStorage.store(StorageGroup.SHOW, context, this.id, "")
localStorage.setItem(this.id, 'show');
});
tree_items.each(function () {
if (ogStorage.hasKey(StorageGroup.SHOW, context, this.id)) {
images_tree.each(function () {
if (localStorage.getItem(this.id) == 'show') {
$(this).collapse('show');
} else {
$(this).siblings('a').addClass('collapsed');
}
});
}
function keepReposTreeState() {
const repos_tree = $('#repos-list .collapse')
repos_tree.on('hidden.bs.collapse', function (event) {
event.stopPropagation();
localStorage.removeItem(this.id);
});
repos_tree.on('shown.bs.collapse', function (event) {
event.stopPropagation();
localStorage.setItem(this.id, 'show');
});
repos_tree.each(function () {
if (localStorage.getItem(this.id) == 'show') {
$(this).collapse('show');
}
});
}
function keepScopesTreeState() {
const scopes_tree = $('#scopes .collapse')
scopes_tree.on('hidden.bs.collapse', function (event) {
event.stopPropagation();
localStorage.removeItem(this.id);
});
scopes_tree.on('shown.bs.collapse', function (event) {
event.stopPropagation();
localStorage.setItem(this.id, 'show');
});
scopes_tree.each(function () {
if (localStorage.getItem(this.id) == 'show') {
$(this).collapse('show');
} else {
$(this).siblings('a').addClass('collapsed');
@ -352,19 +337,56 @@ function updateScopes(scopes) {
return hasLiveChildren;
}
function unfoldAll() {
$('#scopes .collapse').collapse('show');
}
function checkImageServer() {
const images = $('input:checkbox[form|="imagesForm"][name!="image-server"]')
images.on('change', function() {
const selectedServer = $('#' + $.escapeSelector(this.dataset.server));
const serversSelector = 'input:checkbox[name|="image-server"]';
const nonSelectedServers = $(serversSelector).not(selectedServer);
selectedServer.prop('checked', true);
nonSelectedServers.each(function() {
$(this).prop('checked', false);
const checkboxes = $('input:checkbox[data-server|="' + this.id + '"]');
checkboxes.prop('checked', false);
});
});
}
function checkRepoServer() {
const repos = $('input:checkbox[form|="reposForm"][name!="repos-server"]')
repos.on('change', function() {
const selectedServer = $('#' + $.escapeSelector(this.dataset.server));
const serversSelector = 'input:checkbox[name|="repos-server"]';
const nonSelectedServers = $(serversSelector).not(selectedServer);
selectedServer.prop('checked', true);
nonSelectedServers.each(function() {
$(this).prop('checked', false);
const checkboxes = $('input:checkbox[data-server|="' + this.id + '"]');
checkboxes.prop('checked', false);
});
});
}
function checkFolderParent(context) {
const folder = $('#sidebar input:checkbox[name="folder"]')
const folder = $('input:checkbox[form|="scopesForm"][name="folder"]')
folder.on('change', function() {
const folder_parent = $('#' + $.escapeSelector(this.dataset.parentInput));
folder_parent.prop('checked', this.checked);
ogStorage.storeCheckboxStatus(folder_parent.get(0), context);
storeCheckboxStatus(folder_parent.get(0), context);
});
}
function limitCheckboxes(context) {
const checkboxes = $('#sidebar input:checkbox');
ogStorage.deleteInvalidStorage(checkboxes, StorageGroup.CHECK, context);
const checkboxes = $('input:checkbox[form|="scopesForm"]');
checkboxes.on('change', function () {
const currentCheckbox = $(this);
@ -383,18 +405,18 @@ function limitCheckboxes(context) {
}
});
checkCheckbox('scope-server');
checkScopeServer();
checkboxes.each(function() {
ogStorage.storeCheckboxStatus(this, context);
storeCheckboxStatus(this, context);
showSelectedClient(this);
});
});
}
function checkCheckbox(inputName) {
const checkboxes = $('#sidebar input:checkbox[name="' + inputName + '"]');
checkboxes.each(function() {
function checkScopeServer() {
const servers = $('input:checkbox[form|="scopesForm"][name="scope-server"]');
servers.each(function() {
const checkbox = this;
const checkboxChildren = $('input:checkbox', this.parentNode).not(this);
if (checkboxChildren.length == 0) return;
@ -403,15 +425,3 @@ function checkCheckbox(inputName) {
checkbox.checked = checkedChildren.length > 0;
});
}
function checkOnChange(inputName) {
const checkboxes = $('#sidebar input:checkbox')
checkboxes.on('change', function (event) {
checkCheckbox(inputName);
});
checkboxes.each(function () {
checkCheckbox(inputName)
});
}

View File

@ -14,7 +14,7 @@
action=url_for('action_center_add'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -14,7 +14,7 @@
action=url_for('action_room_add'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -12,7 +12,6 @@
{{ wtf.quick_form(form,
action=url_for('server_add_post'),
method='post',
button_map={'submit_btn':'primary'},
extra_classes="m-5") }}
button_map={'submit_btn':'primary'}) }}
{% endblock %}

View File

@ -13,6 +13,6 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -13,6 +13,6 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -14,6 +14,6 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -38,16 +38,10 @@
</div>
{% set show_part_images = True %}
{% set show_free_size = True %}
{% set readonly_disk_inspector = True %}
{% include 'disk_inspector.html' %}
<br>
{% include 'cache_inspector.html' %}
<br>
{% include 'efi_inspector.html' %}
{% endblock %}

View File

@ -20,6 +20,6 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -1,89 +0,0 @@
{% extends 'scopes.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% import "macros.html" as macros %}
{% set btn_back = true %}
{% block nav_client %} active {% endblock %}
{% block nav_client_search %} active {% endblock %}
{% block content %}
<h2 class="mx-5 subhead-heading">
{{ _('Search clients') }}
</h2>
<div class="mx-5 my-3">
<label for="name-filter">{{ _('Name') }}</label>
<input type="text" id="name-filter" class="form-control mb-2">
<label for="ip-filter">{{ _('IP Address') }}</label>
<input type="text" id="ip-filter" class="form-control mb-2">
<button id="search-button" class="btn btn-primary">{{ _('Search') }}</button>
</div>
<div id="clients-container" class="mx-5 mt-3"></div>
<script>
let clients = {{ clients|tojson|safe }};
function renderClients(data) {
const container = document.getElementById('clients-container');
container.innerHTML = '';
let currentPath = null;
let ul = null;
data.forEach(client => {
if (client.tree_path !== currentPath) {
currentPath = client.tree_path;
const pathElement = document.createElement('p');
pathElement.innerHTML = `<strong>${currentPath}</strong>:`;
container.appendChild(pathElement);
ul = document.createElement('ul');
container.appendChild(ul);
}
const li = document.createElement('li');
li.textContent = `${client.name} (IP: ${client.ip.join(', ')})`;
ul.appendChild(li);
});
}
function filterClients() {
const nameFilter = document.getElementById('name-filter').value.toLowerCase();
const ipFilter = document.getElementById('ip-filter').value;
// If both filters are empty, don't display any clients
if (!nameFilter && !ipFilter) {
document.getElementById('clients-container').innerHTML = '';
return;
}
const filtered = clients.filter(client => {
const matchesName = nameFilter ? client.name.toLowerCase().includes(nameFilter) : true;
const matchesIP = ipFilter ? client.ip.some(ip => ip.includes(ipFilter)) : true;
return matchesName && matchesIP;
});
renderClients(filtered);
}
filterClients();
document.getElementById('search-button').addEventListener('click', filterClients);
// Search on Enter key press
document.querySelectorAll('#name-filter, #ip-filter').forEach(input => {
input.addEventListener('keydown', event => {
if (event.key === 'Enter') {
event.preventDefault();
filterClients();
}
});
});
</script>
{% endblock %}

View File

@ -45,7 +45,7 @@
action=url_for('action_center_delete'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -21,7 +21,7 @@
action=url_for('action_client_delete'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -17,7 +17,7 @@
action=url_for('action_image_delete'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -1,7 +1,5 @@
{% extends 'repos.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block nav_repos %} active{% endblock %}
@ -11,7 +9,7 @@
<h2 class="mx-5 subhead-heading">{{_('Delete repo')}}</h2>
<form class="form m-5" method="POST">
<form class="form mx-5" method="POST">
{{ form.hidden_tag() }}
{{ form.server() }}

View File

@ -48,7 +48,7 @@
action=url_for('action_room_delete'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -16,7 +16,7 @@
action=url_for('server_delete_post'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -13,7 +13,7 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -45,6 +45,6 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'danger'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -13,7 +13,7 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -16,7 +16,7 @@
action=url_for('action_hardware'),
method='post',
button_map={'refresh': 'primary'},
extra_classes='m-5')}}
extra_classes='m-2')}}
<table class="table table-striped">
<thead class="thead-dark">

View File

@ -1,14 +1,13 @@
{% extends 'images.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block content %}
<h2 class="mx-5 subhead-heading">{{_('Update image')}} {{ form.name.data }}</h2>
<form class="form m-5" method="POST" action="{{ url_for('action_image_config') }}">
<form class="form mx-5" method="POST" action="{{ url_for('action_image_config') }}">
{{ form.hidden_tag() }}
{{ form.image_id() }}

View File

@ -10,15 +10,14 @@
<h2 class="mx-5 subhead-heading">{{_('Create a partition image')}}</h2>
{{ macros.cmd_selected_clients(selected_clients) }}
{% set partition_field_id = 'os' %}
{% include 'partition_warning.html' %}
<h2 class="mx-5">
{{ _('Selected client') }}: {{ form.ip.data }}
</h1>
{{ wtf.quick_form(form,
action=url_for('action_image_create'),
method='post',
button_map={'create': 'primary'},
extra_classes='m-5') }}
extra_classes='mx-5') }}
{% endblock %}

View File

@ -1,14 +1,13 @@
{% extends 'images.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block content %}
<h2 class="mx-5 subhead-heading">{{_('Image details')}}</h2>
<div class="container m-5">
<div class="container mx-5">
<form class="form" method="POST">
{{ form.hidden_tag() }}

View File

@ -17,9 +17,6 @@
{{ macros.cmd_selected_clients(selected_clients) }}
{% set partition_field_id = 'partition' %}
{% include 'partition_warning.html' %}
{{ wtf.quick_form(form,
action=url_for('action_image_restore'),
method='post',

View File

@ -13,9 +13,6 @@
{{ macros.cmd_selected_clients(selected_clients) }}
{% set partition_field_id = 'os' %}
{% include 'partition_warning.html' %}
<form class="form mx-5" method="POST" action="{{ url_for('action_image_update') }}">
{{ form.hidden_tag() }}

View File

@ -14,6 +14,6 @@
action=url_for('action_clients_import_post'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -10,30 +10,6 @@
<h2 class="mx-5 subhead-heading">{{_('Client log')}}</h2>
<style>
/* Prevent overflow */
pre {
max-width: 100%;
overflow: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
<div class="container-fluid d-flex flex-column" style="height: 90vh;">
<div class="border p-3 overflow-auto flex-grow-1" id="logContainer">
<pre>{{ log }}</pre>
</div>
</div>
<script>
function scrollToBottom() {
const logContainer = document.getElementById('logContainer');
logContainer.scrollTo({ top: logContainer.scrollHeight, behavior: 'instant' });
}
document.addEventListener("DOMContentLoaded", scrollToBottom);
</script>
<pre>{{ log }}</pre>
{% endblock %}

View File

@ -1,7 +1,6 @@
{% extends 'images.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block content %}

View File

@ -1,21 +0,0 @@
{% extends 'lives.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block content %}
<h2 class="mx-5 subhead-heading">
{{ _('Set default ogLive') }}
</h2>
<p class="mx-5">{{ _('Default live: %(default_live)s', default_live=default_live) }}</p>
{{ wtf.quick_form(form,
action=url_for('action_live_default'),
method='post',
button_map={'ok': 'primary'},
extra_classes="m-5") }}
{% endblock %}

View File

@ -1,46 +0,0 @@
{% extends 'commands.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% import "macros.html" as macros %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block content %}
<h2 class="mx-5 subhead-heading">
{{ _('Partition scheme mismatch') }}
</h2>
{{ macros.cmd_selected_clients(selected_clients) }}
</br>
<div class="container mx-5">
<b>{{ _('Cannot proceed with this command, selected clients have non-uniform or valid partition scheme') }}</b>
</div>
<table class="table table-bordered table-hover">
<thead class="text-center">
<tr>
<th style="min-width: 15em;">{{ _('Partitions') }}</th>
<th>{{ _('Clients') }}</th>
</tr>
</thead>
<tbody>
{% for idx in range(part_data | length) %}
<tr>
<td>
{% for disk_id, part_id, part_type, fs_type, part_size in part_data.get_partition_setup(idx) %}
<div>Part {{ part_id }} | {{ fs_type }} | {{ (part_size / 1024) | int}} MiB</div>
{% else %}
{{ _('Empty') }}
{% endfor %}
</td>
<td>
{% for ip in part_data.get_clients(idx) %}<div class="card d-inline-block" style="padding: 5px; margin: 3px;">{{ ip }}</div>{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -21,7 +21,7 @@
action=url_for('action_poweroff'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -21,7 +21,7 @@
action=url_for('action_reboot'),
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -1,7 +1,5 @@
{% extends 'repos.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block nav_repos %} active{% endblock %}
@ -11,7 +9,7 @@
<h2 class="mx-5 subhead-heading">{{_('Repo details')}}</h2>
<form class="form m-5" method="POST">
<form class="form mx-5" method="POST">
{{ form.hidden_tag() }}
{{ form.server() }}

View File

@ -1,69 +0,0 @@
{% extends 'commands.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% import "macros.html" as macros %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block nav_setup %} active{% endblock %}
{% block content %}
{% set ip_list = form.ips.data.split(' ') %}
{% set ip_count = ip_list | length %}
<h2 class="mx-5 subhead-heading">
{{ _('Changing repository of %(ip_count)d computer(s)', ip_count=ip_count) }}
</h2>
{{ macros.cmd_selected_clients(selected_clients) }}
{% if repos_set|length > 1 %}
<div class="mx-5 form-group">
<p>Selected clients have different ogLive</p>
<table class="table table-hover">
<thead class="thead-light">
<tr>
<th>Repository</th>
<th>Clients</th>
</tr>
</thead>
<tbody class="text-left">
{% for repo, clients in repos_set.items() %}
<tr>
<th>{{repo}}</th>
<td>
{% for ip in clients %}<div class="card d-inline-block" style="padding: 5px; margin: 3px;">{{ ip }}</div>{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{{ wtf.quick_form(form,
action=url_for('action_repo_set'),
method='post',
button_map={'ok': 'primary'},
extra_classes="m-5") }}
<!-- jQuery -->
<script src="{{ url_for('static', filename='AdminLTE/plugins/jquery/jquery.min.js') }}"></script>
<script>
var reposSet = {{ repos_set|tojson|safe }};
// Update pill data
$('.badge-pill').each(function(index) {
for (const repo in reposSet) {
for (const clientName of reposSet[repo]) {
if ($(this).html().includes(clientName)) {
$(this).html($(this).html() + '<br>' + repo);
break;
}
}
}
});
</script>
{% endblock %}

View File

@ -1,7 +1,5 @@
{% extends 'repos.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block nav_repos %} active{% endblock %}

View File

@ -1,7 +1,5 @@
{% extends 'repos.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% set sidebar_state = 'disabled' %}
{% set btn_back = true %}
{% block nav_repos %} active{% endblock %}

View File

@ -13,6 +13,6 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -13,7 +13,7 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -20,6 +20,6 @@
{{ wtf.quick_form(form,
method='post',
button_map={'submit': 'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -9,7 +9,7 @@
<h2 class="mx-5 subhead-heading">{{_('Update server')}}</h2>
<form class="form m-5" method="POST">
<form class="form mx-5" method="POST">
{{ form.hidden_tag() }}
{{ form.server_addr() }}

View File

@ -16,15 +16,13 @@
{{ macros.cmd_selected_clients(selected_clients) }}
{% if os_groups|length > 0 %}
<p class="mx-5">
<p>
{% if os_groups|length > 1 %}
The selected clients have different installed OS:
{% endif %}
</p>
<form class="form-inline m-5" method="POST" id="sessionForm">
<form class="form-inline" method="POST" id="sessionForm">
<table class="table table-hover">
<thead class="thead-light">
<tr>
@ -53,10 +51,4 @@ The selected clients have different installed OS:
</button>
</form>
{% else %}
<div class="card text-center p-3">
<b>{{ _('No bootable OS') }}</b>
</div>
{% endif %}
{% endblock %}

View File

@ -14,6 +14,6 @@
action=url_for('action_software'),
method='post',
button_map={'view': 'primary', 'update': 'primary'},
extra_classes="m-5")}}
extra_classes="mx-5")}}
{% endblock %}

View File

@ -13,6 +13,6 @@
action=url_for('user_delete_post'),
method='post',
button_map={'submit_btn':'primary'},
extra_classes="m-5") }}
extra_classes="mx-5") }}
{% endblock %}

View File

@ -43,9 +43,6 @@
<li class="nav-item {% block nav_servers %}{% endblock %}">
<a class="nav-link" href="{{ url_for('manage_servers') }}">{{ _('Servers') }}</a>
</li>
<li class="nav-item {% block nav_lives %}{% endblock %}">
<a class="nav-link" href="{{ url_for('manage_lives') }}">{{ _('Lives') }}</a>
</li>
<li class="nav-item {% block nav_users %}{% endblock %}">
<a class="nav-link" href="{{ url_for('users') }}">{{ _('Users') }}</a>
</li>
@ -114,7 +111,7 @@
<!-- ChartJS -->
<script src="{{ url_for('static', filename='AdminLTE/plugins/chart.js/Chart.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/ogcp.js') }}?v=25"></script>
<script src="{{ url_for('static', filename='js/ogcp.js') }}?v=16"></script>
<script>
// error messages

View File

@ -27,7 +27,7 @@
</ul>
<ul class="list-group list-group-horizontal">
<li class="list-group-item w-50">
{{ _('Cache size') }}
{{ _('Disk size') }}
</li>
<li class="list-group-item w-50">
{{ _('used') }} (%)
@ -100,9 +100,9 @@
}
function updateChart(ip) {
var totalCache = toGiB(storageData[ip].used + storageData[ip].free, 3);
var totalCache = toGiB(storageData[ip].total, 3);
var usedCache = toGiB(storageData[ip].used, 3);
var freeCache = toGiB(storageData[ip].free, 3);
var freeCache = toGiB(storageData[ip].total - storageData[ip].used, 3)
cacheChart.data.datasets[0].data = [
usedCache,
@ -134,7 +134,9 @@
$('.badge-pill').each(function(index) {
for (var ip in storageData) {
if ($(this).html().includes(ip)) {
var freeCache = toGiB(storageData[ip].free, 1)
var totalCache = storageData[ip].total;
var usedCache = storageData[ip].used;
var freeCache = toGiB(totalCache - usedCache, 1)
$(this).html($(this).html() + '<br>free: ' + freeCache + ' GiB');
break;
}

View File

@ -8,8 +8,6 @@
<div class="container mx-5">
{% include 'client_status_leyend.html' %}
{% for server_id, server_data in servers_data.items() %}
<div class="accordion card" id="shellAccordion">
<div class="card-header" id="heading_1">
@ -24,10 +22,8 @@
<table class="table table-hover">
<thead class="thead-light">
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('IP') }}</th>
<th>{{ _('Link speed') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Details') }}</th>
</tr>
</thead>
@ -35,10 +31,9 @@
<tbody data-target="cache-fieldset" id="cacheTable" class="text-left">
{% for client_data in server_data.clients %}
<tr data-toggle="fieldset-entry">
<td>{{ client_data.name }}</td>
<td>{{ client_data.addr }}</td>
<td>
{% if client_data.speed is not none and client_data.speed > 0 %}
{% if client_data.speed is not none %}
{% if client_data.speed >= 1000 %}
{{ (client_data.speed / 1000) | int }} Gb/s
{% else %}
@ -48,31 +43,6 @@
{{ _('Not available') }}
{% endif %}
</td>
<td>
<i class="nav-icon fa-circle
{% if client_data.state == 'OPG' and client_data.last_cmd.result == 'failure' %}
fas text-warning fa-times-circle
{% elif client_data.state == 'OPG' %}
fas text-warning
{% elif client_data.state == 'LNX' %}
fas text-linux
{% elif client_data.state == 'LNX' %}
fas fa-user-circle text-linux
{% elif client_data.state == 'WIN' %}
fas text-windows
{% elif client_data.state == 'WIN' %}
fas fa-user-circle text-windows
{% elif client_data.state == 'BSY' %}
fas text-danger
{% elif client_data.state == 'VDI' %}
fas text-success
{% elif client_data.state == 'WOL_SENT' %}
fas text-wol
{% else %}
far
{% endif %}
"></i>
</td>
<td><a href="{{ url_for('action_client_info', client_ip = client_data.addr) }}">{{ _('View details') }}</a></td>
</tr>
{% endfor %}

View File

@ -1,15 +0,0 @@
<div class="card">
<div class="card-body">
<ul id="clients-color-legend" class="d-flex flex-wrap justify-content-center nav ogcp-nav nav-pills">
<li class="nav-item"><i class="nav-icon far fa-circle"></i> {{_('Shutdown')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-wol"></i> {{_('WoL sent')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-warning"></i> ogLive </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-danger"></i> {{_('Busy')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-linux"></i> Linux </li>
<li class="nav-item"><i class="nav-icon fas fa-user-circle text-linux"></i> {{_('Linux session')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-windows"></i> Windows </li>
<li class="nav-item"><i class="nav-icon fas fa-user-circle text-windows"></i> {{_('Windows session')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-success"></i> VDI </li>
</ul>
</div>
</div>

View File

@ -40,8 +40,6 @@
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<input class="btn btn-light dropdown-item{% block nav_setup_set_bootmode %}{% endblock %}" type="submit" value="{{ _('Set boot mode') }}"
form="scopesForm" formaction="{{ url_for('action_mode') }}" formmethod="get">
<input class="btn btn-light dropdown-item{% block nav_setup_set_repo %}{% endblock %}" type="submit" value="{{ _('Set repository') }}"
form="scopesForm" formaction="{{ url_for('action_repo_set') }}" formmethod="get">
<input class="btn btn-light dropdown-item{% block nav_setup_set_oglive %}{% endblock %}" type="submit" value="{{ _('Set ogLive') }}"
form="scopesForm" formaction="{{ url_for('action_oglive') }}" formmethod="get">
<input class="btn btn-light dropdown-item{% block nav_setup_setup %}{% endblock %}" type="submit" value="{{ _('Partition & Format') }}"
@ -97,13 +95,11 @@
<div class="dropdown btn">
<button class="btn btn-secondary btn-light dropdown-toggle{% block nav_script %}{% endblock %}" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false">
{{ _('Run') }}
{{ _('Script') }}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<input class="btn btn-light dropdown-item{% block nav_run_script %}{% endblock %}" type="submit" value="{{ _('Script') }}"
<input class="btn btn-light dropdown-item{% block nav_run_script %}{% endblock %}" type="submit" value="{{ _('Run') }}"
form="scopesForm" formaction="{{ url_for('action_run_script') }}" formmethod="get">
<input class="btn btn-light dropdown-item{% block nav_run_cmd %}{% endblock %}" type="submit" value="{{ _('Command') }}"
form="scopesForm" formaction="{{ url_for('action_run_cmd') }}" formmethod="get">
<input class="btn btn-light dropdown-item{% block nav_display_output %}{% endblock %}" type="submit" value="{{ _('Display output') }}"
form="scopesForm" formaction="{{ url_for('action_script_display_output') }}" formmethod="get">
</div>

View File

@ -93,7 +93,7 @@
<div class="row">
<!-- disk stats -->
<div class="col-6">
<div class="col-{{ colsize }}">
<div class="card text-center">
<div class="card-header">
{{ _('Disk stats') }}
@ -129,7 +129,7 @@
</div>
<!-- Memory stats -->
<div class="col-6">
<div class="col-{{ colsize }}">
<div class="card text-center">
<div class="card-header">
{{ _('Memory') }}
@ -165,7 +165,7 @@
</div>
<!-- Swap stats -->
<div class="col-6">
<div class="col-{{ colsize }}">
<div class="card text-center">
<div class="card-header">
{{ _('Swap') }}
@ -205,7 +205,7 @@
</div>
<!-- latest images -->
<div class="col-6">
<div class="col-{{ colsize }}">
<div class="card text-center">
<div class="card-header">
{{ _('Latest images') }}
@ -224,7 +224,7 @@
</div>
<!-- ogLives -->
<div class="col-6">
<div class="col-{{ colsize }}">
<div class="card text-center">
<div class="card-header">
{{ _('ogLive images') }}

View File

@ -1,6 +1,6 @@
{% if selected_disk is defined and setup_data is defined %}
<form class="form-inline m-5" method="POST" id="setupForm">
<form class="form-inline mx-5" method="POST" id="setupForm">
{{ disk_form.hidden_tag() }}
{{ disk_form.ips() }}
@ -37,9 +37,6 @@
<th>{{ _('Type') }}</th>
<th>{{ _('Filesystem') }}</th>
<th>{{ _('Size') }} (MiB)</th>
{% if show_free_size is defined %}
<th>{{ _('Free') }} (MiB)</th>
{% endif %}
{% if show_part_images is defined %}
<th>{{ _('Image') }}</th>
{% endif %}
@ -67,13 +64,12 @@
{% endif %}
</td>
<td>
{% if not readonly_disk_inspector is defined %}
{% if readonly_disk_inspector is defined %}
{{ partition.size(class_="form-control", oninput="handleEdit(this)", readonly="readonly") }}
{% else %}
{{ partition.size(class_="form-control", oninput="handleEdit(this)") }}
{% endif %}
</td>
{% if show_free_size is defined %}
<td></td>
{% endif %}
{% if show_part_images is defined %}
<td></td>
{% endif %}
@ -180,12 +176,7 @@
let freeSpace = diskSize;
let partNum = 1;
$('#partitionsTable tr').each(function() {
{% if readonly_disk_inspector is defined %}
let partitionSize = parseInt($(this).find('td').eq(3).text().trim());
{% else %}
let partitionSize = parseInt($(this).find('td').eq(3).find('input').val().trim());
{% endif %}
if (isNaN(partitionSize)) {
partitionSize = 0;
}
@ -300,23 +291,12 @@
let row = partitionsTable.find('tr').eq(i - 1);
var idx = 0;
row.find('td').eq(idx++).text(p.partition);
row.find('td').eq(idx++).find('select').val(p.code);
row.find('td').eq(idx++).find('select').val(p.filesystem);
{% if readonly_disk_inspector is defined %}
row.find('td').eq(idx++).text(Math.floor(p.size / 1024));
{% else %}
row.find('td').eq(idx++).find('input').val(Math.floor(p.size / 1024));
{% endif %}
{% if show_free_size is defined %}
row.find('td').eq(idx++).text(Math.floor(p.free_size / (1024 * 1024)));
{% endif %}
row.find('td').eq(0).text(p.partition);
row.find('td').eq(1).find('select').val(p.code);
row.find('td').eq(2).find('select').val(p.filesystem);
row.find('td').eq(3).find('input').val(Math.floor(p.size / 1024));
{% if show_part_images is defined %}
row.find('td').eq(idx++).text(p.image);
row.find('td').eq(4).text(p.image);
{% endif %}
}

View File

@ -1,55 +0,0 @@
{% if efi_data is defined %}
{% if efi_data['entries']|length > 0 %}
<div class="form-group mx-5">
<label class="control-label">{{ _('Boot entries') }}</label>
<table class="table table-bordered">
<thead class="thead-light">
<tr>
<th>{{ _('Order') }}</th>
<th>{{ _('Active') }}</th>
<th>{{ _('Name') }}</th>
<th>{{ _('Description') }}</th>
</tr>
</thead>
<tbody>
{% for entry_data in efi_data['entries'] %}
<tr>
<td>
<p>
{% if entry_data['order'] is defined %}
{{ entry_data['order'] }}
{% else %}
-
{% endif %}
</p>
</td>
<td>
<p>
{% if entry_data['active'] == 1 %}
{{ _('yes') }}
{% else %}
{{ _('no') }}
{% endif %}
</p>
</td>
<td>
<p>{{ entry_data['name'] }}</p>
</td>
<td>
<p>{{ entry_data['description'] }}</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card text-center p-3">
<b>{{ _('No EFI contents') }}</b>
</div>
{% endif %}
{% endif %}

View File

@ -14,9 +14,8 @@
// in the scope
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
keepTreeState('#servers', 'images');
keepSelectedClients('images');
checkOnChange('image-server');
keepImagesTreeState();
checkImageServer();
}
});
</script>
@ -29,7 +28,6 @@
{% set parent_id = "repos-" ~ loop.index0 %}
<li class="nav-item">
<input class="form-check-input" type="checkbox" form="imagesForm"
{% if sidebar_state %}style="filter: grayscale(100%);" onclick="return false;"{% endif %}
id="{{ server_str }}" value="{{ server_str }}"
onclick="return false;" name="image-server" hidden/>
<a class="nav-link" data-toggle="collapse" data-target="#repos-{{ loop.index0 }}">
@ -45,11 +43,9 @@
{% for image in repo_data["images"] %}
<li id="{{ image["name"] }}_{{ image["id"] }}" class="nav-item">
<input class="form-check-input" type="checkbox" form="imagesForm"
{% if sidebar_state %}style="filter: grayscale(100%);" onclick="return false;"{% endif %}
data-server="{{ server_str }}" value="{{ image["id"] }}"
{% if image.get("selected", False) %}checked{% endif %}
name="{{ image["name"] }}_{{ image["id"] }}"
id="image{{ image["id"] }}"/>
name="{{ image["name"] }}_{{ image["id"] }}" />
{{ image["name"] }}
</li>
{% endfor %}
@ -72,7 +68,7 @@
form="imagesForm" formaction="{{ url_for('action_image_delete') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('IMAGE', 'UPDATE') %}
<input class="btn btn-light" type="submit" value="{{ _('Edit image') }}"
<input class="btn btn-light" type="submit" value="{{ _('Update image') }}"
form="imagesForm" formaction="{{ url_for('action_image_config') }}" formmethod="get">
{% endif %}
{% endif %}

View File

@ -1,57 +0,0 @@
{% extends 'base.html' %}
{% block nav_lives %}active{% endblock %}
{% block container %}
<form id="livesForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
{{ super() }}
</form>
<script>
// Launch the javascript on document ready, so all the global functions exists
// in the scope
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
keepTreeState('#servers', 'lives');
}
});
</script>
{% endblock %}
{% block sidebar %}
<ul id="servers" class="nav ogcp-nav flex-column nav-pills">
{% for lives_data in oglive_list %}
<li class="nav-item">
{% set server_ip_port = lives_data["server"].ip ~ ":" ~ lives_data["server"].port %}
<input id="{{ server_ip_port }}" class="form-check-input" type="checkbox" form="livesForm"
{% if sidebar_state %}style="filter: grayscale(100%);" onclick="return false;"{% endif %}
value="{{ server_ip_port }}" name="server"
{% if loop.index == 1 %}checked{% endif %}></input>
<a class="nav-link" data-toggle="collapse" href="#server{{loop.index}}">
{{ lives_data["server"]["name"] }}
</a>
<ul class="nav flex-column collapse" id="server{{loop.index}}">
{% for oglive in lives_data["json"]["oglive"] %}
<li class="nav-item">
<a>{{ oglive["directory"] }}</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
{% endblock %}
{% block commands %}
{% if current_user.is_authenticated %}
<input class="btn btn-light {% block nav_live_default %}{% endblock %}" type="submit" value="{{ _('Set default') }}"
form="livesForm" formaction="{{ url_for('action_live_default') }}" formmethod="get">
{% if btn_back %}
<button class="btn btn-danger ml-3" type="button" id="backButton" onclick="history.back()">
{{ _("Back") }}
</button>
{% endif %}
{% endif %}
{% endblock %}

View File

@ -10,7 +10,7 @@
if (document.readyState === 'complete') {
showSelectedClientsOnEvents();
updateScopeState();
keepTreeState('#scopes', 'scopes');
keepScopesTreeState();
let context = {{ selection_mode | tojson | safe }};
{% if selection_mode == 'commands' %}
configureCommandCheckboxes(context);
@ -92,10 +92,22 @@
{% endmacro %}
{% macro selected_clients() -%}
<h3 class="mx-5 subhead-heading">{{_('Selected clients')}}</h3>
{% include 'client_status_leyend.html' %}
<h2 class="mx-5 subhead-heading">{{_('Selected clients')}}</h2>
<div class="card">
<div class="card-body">
<ul id="clients-color-legend" class="d-flex flex-wrap justify-content-center nav ogcp-nav nav-pills">
<li class="nav-item"><i class="nav-icon far fa-circle"></i> {{_('Shutdown')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-wol"></i> {{_('WoL sent')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-warning"></i> ogLive </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-danger"></i> {{_('Busy')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-linux"></i> Linux </li>
<li class="nav-item"><i class="nav-icon fas fa-user-circle text-linux"></i> {{_('Linux session')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-windows"></i> Windows </li>
<li class="nav-item"><i class="nav-icon fas fa-user-circle text-windows"></i> {{_('Windows session')}} </li>
<li class="nav-item"><i class="nav-icon fas fa-circle text-success"></i> VDI </li>
</ul>
</div>
</div>
<div id="selected-clients" class="d-flex flex-wrap justify-content-center"></div>
{% endmacro %}

View File

@ -1,30 +0,0 @@
<div class="mx-5" id="partition-warning" style="display:none; color:red; font-weight: bold;">
{{ _('Warning: You have selected a partition from a disk other than 1. This will be considered for storage only.') }}
</div>
<script>
function checkPartition() {
var partitionSelect = document.getElementById('{{ partition_field_id }}');
var selectedValue = partitionSelect.value;
var warningDiv = document.getElementById('partition-warning');
// Extract the disk_id
var diskId = parseInt(selectedValue.split(' ')[0]);
if (diskId !== 1) {
warningDiv.style.display = 'block';
} else {
warningDiv.style.display = 'none';
}
}
document.addEventListener("DOMContentLoaded", function () {
var partitionSelect = document.getElementById('{{ partition_field_id }}');
if (partitionSelect) {
partitionSelect.addEventListener('change', checkPartition);
checkPartition();
} else {
console.error("Element with ID '{{ partition_field_id }}' not found.");
}
});
</script>

View File

@ -18,7 +18,6 @@
{% set repos_list = repos["json"]["repositories"] %}
<li class="nav-item">
<input id="{{ server_ip_port }}"class="form-check-input" type="checkbox" form="reposForm"
{% if sidebar_state %}style="filter: grayscale(100%);" onclick="return false;"{% endif %}
value="{{ server_ip_port }}" name="repos-server" />
<a class="nav-link {% if not repos_list %}disabled{% endif %}" href="#server{{loop.index}}"
{% if repos_list %}data-toggle="collapse"{% endif %}>
@ -28,8 +27,6 @@
{% for r in repos_list %}
<li class="nav-item">
<input class="form-check-input" type="checkbox" form="reposForm"
{% if sidebar_state %}style="filter: grayscale(100%);" onclick="return false;"{% endif %}
id="repo{{ r["id"] }}"
data-server="{{server_ip_port}}"
value="{{ r["id"] }}"
name="{{ r["name"]~_~r["id"] }}" />
@ -45,9 +42,8 @@
// in the scope
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
keepTreeState('#repos-list', 'repos');
keepSelectedClients('repos');
checkOnChange('repos-server');
keepReposTreeState()
checkRepoServer()
}
});
</script>
@ -66,7 +62,7 @@
form="reposForm" formaction="{{ url_for('action_repo_delete') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('REPOSITORY', 'UPDATE') %}
<input class="btn btn-light {% block nav_repo_update %}{% endblock %}" type="submit" value="{{ _('Edit repo') }}"
<input class="btn btn-light {% block nav_repo_update %}{% endblock %}" type="submit" value="{{ _('Update repo') }}"
form="reposForm" formaction="{{ url_for('action_repo_update') }}" formmethod="get">
{% endif %}
{% endif %}

View File

@ -28,7 +28,7 @@
<input class="btn btn-light dropdown-item {% block nav_client_add %}{% endblock %}" type="submit" value="{{ _('Add client') }}"
form="scopesForm" formaction="{{ url_for('action_client_add') }}" formmethod="get">
{% endif %}
<input class="btn btn-light dropdown-item {% block nav_client_update %}{% endblock %}" type="submit" value="{{ _('Edit client') }}"
<input class="btn btn-light dropdown-item {% block nav_client_update %}{% endblock %}" type="submit" value="{{ _('Update client') }}"
form="scopesForm" formaction="{{ url_for('action_client_update') }}" formmethod="get">
{% if current_user.get_permission('CLIENT', 'UPDATE') %}
<input class="btn btn-light dropdown-item {% block nav_client_move %}{% endblock %}" type="submit" value="{{ _('Move client') }}"
@ -42,8 +42,6 @@
<input class="btn btn-light dropdown-item {% block nav_client_delete %}{% endblock %}" type="submit" value="{{ _('Delete client') }}"
form="scopesForm" formaction="{{ url_for('action_client_delete') }}" formmethod="get">
{% endif %}
<input class="btn btn-light dropdown-item {% block nav_client_search %}{% endblock %}" type="submit" value="{{ _('Search client') }}"
form="scopesForm" formaction="{{ url_for('action_client_search') }}" formmethod="get">
</div>
</div>
{% endif %}
@ -58,7 +56,7 @@
form="scopesForm" formaction="{{ url_for('action_room_add') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('ROOM', 'UPDATE') %}
<input class="btn btn-light dropdown-item {% block nav_room_update %}{% endblock %}" type="submit" value="{{ _('Edit room') }}"
<input class="btn btn-light dropdown-item {% block nav_room_update %}{% endblock %}" type="submit" value="{{ _('Update room') }}"
form="scopesForm" formaction="{{ url_for('action_room_update') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('ROOM', 'DELETE') %}
@ -80,7 +78,7 @@
form="scopesForm" formaction="{{ url_for('action_center_add') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('CENTER', 'UPDATE') %}
<input class="btn btn-light dropdown-item {% block nav_center_update %}{% endblock %}" type="submit" value="{{ _('Edit center') }}"
<input class="btn btn-light dropdown-item {% block nav_center_update %}{% endblock %}" type="submit" value="{{ _('Update center') }}"
form="scopesForm" formaction="{{ url_for('action_center_update') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('CENTER', 'DELETE') %}
@ -103,7 +101,7 @@
form="scopesForm" formaction="{{ url_for('action_folder_add') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('FOLDER', 'UPDATE') %}
<input class="btn btn-light dropdown-item {% block nav_folder_update %}{% endblock %}" type="submit" value="{{ _('Edit folder') }}"
<input class="btn btn-light dropdown-item {% block nav_folder_update %}{% endblock %}" type="submit" value="{{ _('Update folder') }}"
form="scopesForm" formaction="{{ url_for('action_folder_update') }}" formmethod="get">
{% endif %}
{% if current_user.get_permission('FOLDER', 'DELETE') %}

View File

@ -27,7 +27,7 @@
{% block commands %}
{% if current_user.is_authenticated %}
<input class="btn btn-light {% block nav_server_update %}{% endblock %}" type="submit" value="{{ _('Edit server') }}"
<input class="btn btn-light {% block nav_server_update %}{% endblock %}" type="submit" value="{{ _('Update server') }}"
form="serversForm" formaction="{{ url_for('server_update_get') }}" formmethod="get">
{% if btn_back %}
<button class="btn btn-danger ml-3" type="button" id="backButton" onclick="history.back()">

File diff suppressed because it is too large Load Diff