js: add ogStorage to prevent localStorage key collission

Define ogStorage class to manage the localStorage operations.
The new keys are constructed with the following structure:
"group-context-id"
Where group is either "show" for the collapsed items in the
sidebar, or "check" for the selected checkboxes of the sidebar.

Add sotrage versioning to delete obsolete localStorage when a
new design for the storage is included in ogCP.
master
Alejandro Sirgo Rica 2024-09-16 17:29:14 +02:00
parent 7296372e9c
commit f03077edb7
5 changed files with 77 additions and 44 deletions

View File

@ -3,6 +3,61 @@ 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);
@ -54,13 +109,6 @@ function showSelectedClientsOnEvents() {
});
}
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');
@ -89,27 +137,10 @@ function setParentStatus(checkboxes) {
});
}
function deleteInvalidStorageItems(items, context) {
const existingIds = items.map(function() {
return context + this.id;
}).get();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (!key.startsWith(context)) {
continue;
}
if (!existingIds.includes(key)) {
localStorage.removeItem(key);
}
}
}
function configureCommandCheckboxes(context) {
const checkboxes = $('input:checkbox[form="scopesForm"]');
deleteInvalidStorageItems(checkboxes, context);
ogStorage.deleteInvalidStorage(checkboxes, StorageGroup.CHECK, context);
// Ensure the form fields are sent
$('#scopesForm').on('submit', function() {
@ -151,7 +182,7 @@ function configureCommandCheckboxes(context) {
checkboxes.each(function() {
showSelectedClient(this);
storeCheckboxStatus(this, context);
ogStorage.storeCheckboxStatus(this, context);
});
});
}
@ -160,33 +191,35 @@ function keepSelectedClients(context) {
const checkboxes = $('#sidebar input:checkbox')
checkboxes.on('change', function (event) {
storeCheckboxStatus(this, context);
ogStorage.storeCheckboxStatus(this, context);
});
deleteInvalidStorageItems(checkboxes, context);
ogStorage.deleteInvalidStorage(checkboxes, StorageGroup.CHECK, context);
checkboxes.each(function () {
if (localStorage.getItem(context + this.id) == 'check') {
if (ogStorage.hasKey(StorageGroup.CHECK, context, this.id)) {
this.checked = true;
$(this).trigger('show-client');
}
});
}
function keepTreeState(selector) {
const tree = $(selector + ' .collapse');
function keepTreeState(selector, context) {
const tree_items = $(selector + ' .collapse');
tree.on('hidden.bs.collapse', function (event) {
ogStorage.deleteInvalidStorage(tree_items, StorageGroup.SHOW, context);
tree_items.on('hidden.bs.collapse', function (event) {
event.stopPropagation();
localStorage.removeItem(this.id);
ogStorage.remove(StorageGroup.SHOW, context, this.id)
});
tree.on('shown.bs.collapse', function (event) {
tree_items.on('shown.bs.collapse', function (event) {
event.stopPropagation();
localStorage.setItem(this.id, 'show');
ogStorage.store(StorageGroup.SHOW, context, this.id, "")
});
tree.each(function () {
if (localStorage.getItem(this.id) == 'show') {
tree_items.each(function () {
if (ogStorage.hasKey(StorageGroup.SHOW, context, this.id)) {
$(this).collapse('show');
} else {
$(this).siblings('a').addClass('collapsed');
@ -324,14 +357,14 @@ function checkFolderParent(context) {
folder.on('change', function() {
const folder_parent = $('#' + $.escapeSelector(this.dataset.parentInput));
folder_parent.prop('checked', this.checked);
storeCheckboxStatus(folder_parent.get(0), context);
ogStorage.storeCheckboxStatus(folder_parent.get(0), context);
});
}
function limitCheckboxes(context) {
const checkboxes = $('#sidebar input:checkbox');
deleteInvalidStorageItems(checkboxes, context);
ogStorage.deleteInvalidStorage(checkboxes, StorageGroup.CHECK, context);
checkboxes.on('change', function () {
const currentCheckbox = $(this);
@ -353,7 +386,7 @@ function limitCheckboxes(context) {
checkCheckbox('scope-server');
checkboxes.each(function() {
storeCheckboxStatus(this, context);
ogStorage.storeCheckboxStatus(this, context);
showSelectedClient(this);
});
});

View File

@ -111,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=22"></script>
<script src="{{ url_for('static', filename='js/ogcp.js') }}?v=24"></script>
<script>
// error messages

View File

@ -14,7 +14,7 @@
// in the scope
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
keepTreeState('#servers');
keepTreeState('#servers', 'images');
keepSelectedClients('images');
checkOnChange('image-server');
}

View File

@ -10,7 +10,7 @@
if (document.readyState === 'complete') {
showSelectedClientsOnEvents();
updateScopeState();
keepTreeState('#scopes');
keepTreeState('#scopes', 'scopes');
let context = {{ selection_mode | tojson | safe }};
{% if selection_mode == 'commands' %}
configureCommandCheckboxes(context);

View File

@ -43,7 +43,7 @@
// in the scope
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
keepTreeState('#repos-list')
keepTreeState('#repos-list', 'repos');
keepSelectedClients('repos');
checkOnChange('repos-server');
}