From 3181a16860fe7d1c28fcc625bf3c622fce5fac66 Mon Sep 17 00:00:00 2001 From: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Wed, 27 May 2026 11:30:58 +0200 Subject: [PATCH 1/3] 5.x.x commit 8c2d7566ba5a36a11d0d1d48c6ff02ea74f446d4 Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Wed May 27 11:26:58 2026 +0200 patch commit 02438ed9083bb2453d57ca1dd4866e884056028a Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Wed May 27 11:24:38 2026 +0200 patch commit 6c4e9c268aea07c0d2c7f3180dea3f8bacdd2c8f Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Wed May 27 11:23:18 2026 +0200 patch commit 1bac1fda0f9d985ee7dad6aeefe3b6234711d3bf Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Fri May 22 16:37:43 2026 +0200 patch commit 268e49acf98a25ca29da186504c1001259ffabd7 Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Fri May 22 10:31:29 2026 +0200 patch commit cf820ad83f3f988f7c190fe771d67834b540f259 Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Fri May 22 10:26:47 2026 +0200 patch commit a8120186ee697e9fadf8c5002d38008b8a80a960 Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Fri May 22 10:24:10 2026 +0200 patch commit e22f39f44ad0b59a0235d73327740fba0f42a5d6 Merge: d1f06e6 75573ea Author: Ludo <54670129+lbr38@users.noreply.github.com> Date: Thu May 21 18:38:36 2026 +0200 Merge pull request #375 from Cloud-Kid/improve-empty-states Improve empty states commit d1f06e6e44c25faa84a6d4aa42bb2cc9cad3f733 Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Thu May 21 18:37:26 2026 +0200 patch commit 358a566ec99e6db474507a05c4d249570a353720 Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Tue May 19 17:43:26 2026 +0200 patch commit 7e195a189a255169d1db41ad07e71e5d8253b160 Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Tue May 19 17:42:18 2026 +0200 patch commit 9b9245fdc9255b29ea1a69fbc48119194945413f Author: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Mon May 18 18:48:29 2026 +0200 5.11.0 commit 1b91d11269c87f697000df11fa2f00d313129277 Merge: de4fe05 ffee6e3 Author: Ludo <54670129+lbr38@users.noreply.github.com> Date: Sat May 16 11:09:31 2026 +0200 Merge pull request #374 from Cloud-Kid/validate-source-repository-templates Validate source repository templates in CI commit 75573ea7a29665db1bedf15e352b03edc4903715 Author: Cloud-Kid <49484832+Cloud-Kid@users.noreply.github.com> Date: Mon May 11 11:22:14 2026 +0200 Improve empty states commit ffee6e3c675d794854a522819a945adf1e315df1 Author: Cloud-Kid <49484832+Cloud-Kid@users.noreply.github.com> Date: Mon May 11 11:22:42 2026 +0200 Validate source repository templates in CI --- CLAUDE.md | 7 + .../vars/repo-storage-chart.vars.inc.php | 44 -- .../vars/hosts/overview.vars.inc.php | 62 +- .../Container/vars/repos/list.vars.inc.php | 61 +- .../Container/vars/tasks/tasks.vars.inc.php | 13 + www/controllers/Layout/Tab/Run.php | 23 +- www/controllers/Layout/Tab/Task.php | 20 + www/controllers/Layout/Tab/Tasks.php | 20 + www/controllers/Layout/Table/Render.php | 4 +- www/controllers/Repo/Package/Sync.php | 4 +- www/controllers/Repo/Task/Create.php | 3 + www/controllers/Repo/Task/Rebuild.php | 6 + www/controllers/Task/Form/Rename.php | 4 +- www/controllers/Task/Task.php | 33 + www/controllers/Utils/Generate/Html/Label.php | 32 +- www/models/Repo/Listing.php | 11 +- www/public/assets/icons/calendar.svg | 40 + www/public/assets/icons/disk.svg | 46 ++ www/public/assets/icons/server.svg | 47 +- www/public/resources/js/classes/Checkbox.js | 14 +- .../resources/js/classes/Host/HostSearch.js | 5 +- www/public/resources/js/classes/Repo.js | 8 +- .../resources/js/events/host/actions.js | 81 ++- www/public/resources/js/events/host/layout.js | 4 +- www/public/resources/js/events/repo/edit.js | 4 +- www/public/resources/js/events/repo/env.js | 28 +- www/public/resources/js/events/repo/list.js | 27 +- .../resources/js/events/task/actions.js | 58 +- www/public/resources/js/functions/task.js | 12 +- www/public/resources/js/task.js | 58 +- www/public/resources/styles/common.css | 42 +- .../resources/styles/components/card.css | 60 ++ .../styles/components/confirmbox.css | 39 +- .../resources/styles/components/label.css | 38 +- .../resources/styles/components/layout.css | 11 +- www/public/resources/styles/main.css | 685 +++++++++++++----- www/public/resources/styles/run.css | 30 +- www/public/resources/styles/stats-hosts.css | 137 ++++ www/update/database/5.x.x.php | 13 + .../containers/header/debug-mode.inc.php | 4 +- .../header/general-error-messages.inc.php | 4 +- .../header/general-log-messages.inc.php | 4 +- .../includes/containers/header/menu.inc.php | 266 +++---- .../containers/header/menu_old.inc.php | 197 +++++ .../containers/header/service-status.inc.php | 4 +- .../includes/containers/host/packages.inc.php | 31 +- .../includes/containers/host/summary.inc.php | 14 +- .../includes/containers/hosts/list.inc.php | 564 +++++++------- .../containers/hosts/overview.inc.php | 39 + .../repos/includes-temp/group.inc.php | 52 ++ .../repos/includes-temp/repo.inc.php | 140 ++++ .../includes/containers/repos/list.inc.php | 183 +++-- .../containers/repos/properties.inc.php | 124 ---- .../includes/containers/tasks/log.inc.php | 106 ++- .../includes/containers/tasks/tasks.inc.php | 47 ++ www/views/includes/footer.inc.php | 12 +- www/views/includes/head.inc.php | 3 + .../panels/header/menu-burger.inc.php | 4 +- www/views/includes/repos-list.inc.php | 427 +---------- www/views/includes/repos-list.inc_old.php | 431 +++++++++++ .../tables/host/available-packages.inc.php | 122 ++-- .../includes/tables/host/history.inc.php | 12 +- .../includes/tables/host/requests.inc.php | 33 +- www/views/includes/tables/tasks/list.inc.php | 655 ++++++++--------- www/views/layout.html.php | 55 +- www/views/templates/tasks/rebuild.inc.php | 19 +- www/views/templates/tasks/update.inc.php | 17 - 67 files changed, 3299 insertions(+), 2104 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 www/controllers/Layout/Chart/vars/repo-storage-chart.vars.inc.php create mode 100644 www/controllers/Layout/Container/vars/tasks/tasks.vars.inc.php create mode 100644 www/controllers/Layout/Tab/Task.php create mode 100644 www/controllers/Layout/Tab/Tasks.php create mode 100644 www/public/assets/icons/calendar.svg create mode 100644 www/public/assets/icons/disk.svg create mode 100644 www/public/resources/styles/components/card.css create mode 100644 www/update/database/5.x.x.php create mode 100644 www/views/includes/containers/header/menu_old.inc.php create mode 100644 www/views/includes/containers/repos/includes-temp/group.inc.php create mode 100644 www/views/includes/containers/repos/includes-temp/repo.inc.php delete mode 100644 www/views/includes/containers/repos/properties.inc.php create mode 100644 www/views/includes/containers/tasks/tasks.inc.php create mode 100644 www/views/includes/repos-list.inc_old.php diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..dfa4cd630 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +## Approach +- Read existing files before writing. Don't re-read unless changed. +- Thorough in reasoning, concise in output. +- Skip files over 100KB unless required. +- No sycophantic openers or closing fluff. +- No emojis or em-dashes. +- Do not guess APIs, versions, flags, commit SHAs, or package names. Verify by reading code or docs before asserting. \ No newline at end of file diff --git a/www/controllers/Layout/Chart/vars/repo-storage-chart.vars.inc.php b/www/controllers/Layout/Chart/vars/repo-storage-chart.vars.inc.php deleted file mode 100644 index 8d08c2b30..000000000 --- a/www/controllers/Layout/Chart/vars/repo-storage-chart.vars.inc.php +++ /dev/null @@ -1,44 +0,0 @@ - 0 && $diskUsedSpacePercent <= 30) { - $datasets[0]['colors'][] = '#15bf7f'; - $datasets[0]['colors'][] = 'rgba(21, 191, 127, 0.40)'; -} -if ($diskUsedSpacePercent > 30 && $diskUsedSpacePercent <= 50) { - $datasets[0]['colors'][] = '#ffb536'; - $datasets[0]['colors'][] = 'rgba(255, 181, 54, 0.40)'; -} -if ($diskUsedSpacePercent > 50 && $diskUsedSpacePercent <= 70) { - $datasets[0]['colors'][] = '#ff7c49'; - $datasets[0]['colors'][] = 'rgba(255, 124, 73, 0.40)'; -} -if ($diskUsedSpacePercent > 70 && $diskUsedSpacePercent <= 100) { - $datasets[0]['colors'][] = '#F32F63'; - $datasets[0]['colors'][] = 'rgba(243, 47, 99, 0.40)'; -} - -$options['innerRadius'] = '65%'; -$options['outerRadius'] = '99%'; -$options['toolbox']['show'] = false; -$options['legend']['show'] = false; -$options['tooltip']['show'] = false; -$options['emphasis']['disabled'] = true; - -unset($diskTotalSpace, $diskFreeSpace, $diskUsedSpace); diff --git a/www/controllers/Layout/Container/vars/hosts/overview.vars.inc.php b/www/controllers/Layout/Container/vars/hosts/overview.vars.inc.php index 6780808f8..c7f5fa5a4 100644 --- a/www/controllers/Layout/Container/vars/hosts/overview.vars.inc.php +++ b/www/controllers/Layout/Container/vars/hosts/overview.vars.inc.php @@ -1,27 +1,61 @@ get(); -/** - * Getting total hosts - */ -$totalHosts = count($hostListingController->get()); +// Getting total hosts +$totalHosts = count($hosts); -/** - * Getting a list of all hosts kernel - */ +// Getting a list of all host kernels and sort them by count desc $kernels = $hostListingController->getKernel(); array_multisort(array_column($kernels, 'Count'), SORT_DESC, $kernels); -/** - * Getting a list of all hosts profiles - */ +// Getting a list of all host profiles and sort them by count desc $profiles = $hostListingController->getProfile(); array_multisort(array_column($profiles, 'Count'), SORT_DESC, $profiles); -/** - * Getting a list of all hosts requiring a reboot - */ +// Getting a list of all hosts requiring a reboot $rebootRequiredList = $hostListingController->getRebootRequired(); $rebootRequiredCount = count($rebootRequiredList); -unset($hostListingController); +// Getting general hosts threshold settings +$hostsSettings = $hostController->getSettings(); + +// Threshold of the maximum number of available update above which the host is considered as 'not up to date' (but not critical) +$packagesCountConsideredOutdated = $hostsSettings['pkgs_count_considered_outdated']; + +// Loop through the list of hosts to determine the number of hosts up to date and not up to date +foreach ($hosts as $host) { + // Open the dedicated database of the host from its ID to be able to retrieve additional information + $hostPackageController = new Package($host['Id']); + + // Retrieve the total number of available packages + $packagesAvailableTotal = count($hostPackageController->getAvailable()); + + // Retrieve the total number of installed packages + $packagesInstalledTotal = count($hostPackageController->getInstalled()); + + /** + * If the total number of available packages retrieved previously is > $packagesCountConsideredOutdated (threshold defined by the user) then we increment $totalOutdated (counts the number of hosts that are not up to date in the chartjs) + * Else it's $totalUptodate that we increment. + */ + if ($packagesAvailableTotal >= $packagesCountConsideredOutdated) { + $totalOutdated++; + } else { + $totalUptodate++; + } +} + +// Calculation of the compliance percent (number of hosts up to date / total number of hosts * 100) +if ($totalHosts > 0) { + $compliancePercent = round(($totalUptodate / $totalHosts) * 100); +} + +unset($hostController, $hostListingController, $hostPackageController, $hosts, $hostsSettings, $packagesCountConsideredOutdated, $packagesAvailableTotal, $packagesInstalledTotal); diff --git a/www/controllers/Layout/Container/vars/repos/list.vars.inc.php b/www/controllers/Layout/Container/vars/repos/list.vars.inc.php index 6c618f2ce..0f06ac089 100644 --- a/www/controllers/Layout/Container/vars/repos/list.vars.inc.php +++ b/www/controllers/Layout/Container/vars/repos/list.vars.inc.php @@ -1,4 +1,6 @@ $group['Name'], + 'repos' => [], + 'count' => 0, + 'show' => $show + ]; + } + + foreach ($myrepoListing->listByGroup($group['Name']) as $repo) { + // Add the whole repo to the repos array + $repos[] = $repo; + + // Add the repoId to the group if not already in the group repos (to avoid duplicate repos in the same group) + if (!in_array($repo['repoId'], $groups[$group['Id']]['repos'])) { + $groups[$group['Id']]['repos'][] = $repo['repoId']; + } + + // Add 1 to the group count only if the repoId is different from the previous one + if ($previousId != $repo['repoId']) { + $groups[$group['Id']]['count']++; + } + + // TODO: If the repository is in the user permissions, then the group become showable for the user + + $previousId = $repo['repoId']; + } + + + +} + unset($groupController, $repoController, $taskController, $diskTotalSpace, $diskFreeSpace, $diskUsedSpace, $data); diff --git a/www/controllers/Layout/Container/vars/tasks/tasks.vars.inc.php b/www/controllers/Layout/Container/vars/tasks/tasks.vars.inc.php new file mode 100644 index 000000000..9c6f3507f --- /dev/null +++ b/www/controllers/Layout/Container/vars/tasks/tasks.vars.inc.php @@ -0,0 +1,13 @@ +get()); + +// Get running tasks +$runningCount = count($taskListingController->getRunning()); + +// Get scheduled tasks +$scheduledCount = count($taskListingController->getScheduled()); + +unset($taskListingController); diff --git a/www/controllers/Layout/Tab/Run.php b/www/controllers/Layout/Tab/Run.php index 94ea57c5d..b96d1f47d 100644 --- a/www/controllers/Layout/Tab/Run.php +++ b/www/controllers/Layout/Tab/Run.php @@ -2,20 +2,27 @@ namespace Controllers\Layout\Tab; -use Controllers\User\Permission\Task as TaskPermission; -use Controllers\Layout\Container\Render; - class Run { public static function render() { - // If user is not allowed to see tasks, redirect to home page - if (!TaskPermission::allowed()) { - header('Location: /'); + /** + * /run now redirects to "/tasks", which is the main page for task management + * If it is followed by an ID (ex: /run/123), then redirects to /task/123 + */ + $currentPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); + + // If the path is exactly "/run", redirect to "/tasks" + if ($currentPath === '/run') { + header('Location: /tasks'); exit; } - Render::render('tasks/log'); - Render::render('tasks/list'); + // If the path starts with "/run/" followed by an ID, redirect to "/task/{ID}" + if (preg_match('#^/run/(\d+)$#', $currentPath, $matches)) { + $taskId = $matches[1]; + header('Location: /task/' . $taskId); + exit; + } } } diff --git a/www/controllers/Layout/Tab/Task.php b/www/controllers/Layout/Tab/Task.php new file mode 100644 index 000000000..a16e499cd --- /dev/null +++ b/www/controllers/Layout/Tab/Task.php @@ -0,0 +1,20 @@ + 1) { - $output .= ''; + $output .= ''; } // First page button (n°1) @@ -92,7 +92,7 @@ public static function paginationBtn($currentPage, $totalPages) // Next button if ($currentPage < $totalPages) { - $output .= ''; + $output .= ''; } echo $output; diff --git a/www/controllers/Repo/Package/Sync.php b/www/controllers/Repo/Package/Sync.php index bad8438bf..e3b8bb086 100644 --- a/www/controllers/Repo/Package/Sync.php +++ b/www/controllers/Repo/Package/Sync.php @@ -58,12 +58,12 @@ private function syncPackage() if ($this->repoSnapshotController->getDateById($this->repoController->getSnapId()) != $this->repoController->getDate()) { if ($this->repoController->getPackageType() == 'rpm') { if ($this->rpmRepoController->existsSnapDate($this->repoController->getName(), $this->repoController->getReleasever(), $this->repoController->getDate())) { - throw new Exception('A snapshot already exists on the ' . Label::black($this->repoController->getDateFormatted())); + throw new Exception('A snapshot already exists on the ' . Label::white($this->repoController->getDateFormatted())); } } if ($this->repoController->getPackageType() == 'deb') { if ($this->debRepoController->existsSnapDate($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection(), $this->repoController->getDate())) { - throw new Exception('A snapshot already exists on the ' . Label::black($this->repoController->getDateFormatted())); + throw new Exception('A snapshot already exists on the ' . Label::white($this->repoController->getDateFormatted())); } } } diff --git a/www/controllers/Repo/Task/Create.php b/www/controllers/Repo/Task/Create.php index cd9f25a87..de1bbef55 100644 --- a/www/controllers/Repo/Task/Create.php +++ b/www/controllers/Repo/Task/Create.php @@ -38,6 +38,9 @@ public function __construct(string $taskId) // Set repo type for the task to be executed $this->type = $this->params['repo-type']; + // Set + // $this->repoController->setAdvancedParams($this->params['advanced-params']); + // Execute the task try { $this->execute(); diff --git a/www/controllers/Repo/Task/Rebuild.php b/www/controllers/Repo/Task/Rebuild.php index c74aa37bf..1303d956c 100644 --- a/www/controllers/Repo/Task/Rebuild.php +++ b/www/controllers/Repo/Task/Rebuild.php @@ -36,6 +36,12 @@ public function __construct(string $taskId) */ public function execute() { + // TODO debug + // while (true) { + // sleep(5); + // } + + /** * Set snapshot metadata rebuild state in database */ diff --git a/www/controllers/Task/Form/Rename.php b/www/controllers/Task/Form/Rename.php index ba5b560df..edcd734a2 100644 --- a/www/controllers/Task/Form/Rename.php +++ b/www/controllers/Task/Form/Rename.php @@ -51,10 +51,10 @@ public function validate(array $formParams): void // Add history if ($repoController->getPackageType() == 'rpm') { - History::set('Running task: renaming repository ' . Label::white($repoController->getName()) . '⸺' . Label::black($repoController->getDateFormatted()) . ' ➡ ' . Label::white($formParams['name'])); + History::set('Running task: renaming repository ' . Label::white($repoController->getName()) . '⸺' . Label::white($repoController->getDateFormatted()) . ' ➡ ' . Label::white($formParams['name'])); } if ($repoController->getPackageType() == 'deb') { - History::set('Running task: renaming repository ' . Label::white($repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection()) . '⸺' . Label::black($repoController->getDateFormatted()) . ' ➡ ' . Label::white($formParams['name'] . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection())); + History::set('Running task: renaming repository ' . Label::white($repoController->getName() . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection()) . '⸺' . Label::white($repoController->getDateFormatted()) . ' ➡ ' . Label::white($formParams['name'] . ' ❯ ' . $repoController->getDist() . ' ❯ ' . $repoController->getSection())); } unset($repoController, $rpmRepoController, $debRepoController); diff --git a/www/controllers/Task/Task.php b/www/controllers/Task/Task.php index a0deb4abc..904d22360 100644 --- a/www/controllers/Task/Task.php +++ b/www/controllers/Task/Task.php @@ -1007,4 +1007,37 @@ public function getLatestStatus(string $snapId) : array { return $this->model->getLatestStatus($snapId); } + + /** + * Generate a human readable literal action from the technical action name + */ + public static function generateLiteralAction(string $action): string + { + if ($action == 'create') { + return 'Create repository'; + } + if ($action == 'update') { + return 'Update repository'; + } + if ($action == 'env') { + return 'Point an environment'; + } + if ($action == 'removeEnv') { + return 'Remove an environment'; + } + if ($action == 'rebuild') { + return 'Rebuild repository metadata'; + } + if ($action == 'rename') { + return 'Rename repository'; + } + if ($action == 'duplicate') { + return 'Duplicate repository'; + } + if ($action == 'delete') { + return 'Delete repository'; + } + + return ucfirst($action); + } } diff --git a/www/controllers/Utils/Generate/Html/Label.php b/www/controllers/Utils/Generate/Html/Label.php index e001fe973..dfe173e2d 100644 --- a/www/controllers/Utils/Generate/Html/Label.php +++ b/www/controllers/Utils/Generate/Html/Label.php @@ -7,7 +7,7 @@ class Label /** * Generate environment tag */ - public static function envtag(string $name, string|null $css = null) : string + public static function envtag(string $name, string|null $css = null, string|null $additionalCssClasses = null, string|null $additionalStyle = null) : string { // Default class and colors $class = 'env'; @@ -26,17 +26,33 @@ public static function envtag(string $name, string|null $css = null) : string } } + // Outlined style: transparent bg, colored border and text if ($background == '#ffffff') { - $border = '1px solid #949494'; + // No color configured: use a subtle gray outline + $color = '#c0d0e2'; + $border = '1.5px solid #c0d0e2'; } else { - $border = '1px solid ' . $background; + // Use the configured color for border and text + $color = $background; + $border = '1.5px solid ' . $background; } + $background = 'transparent'; if ($css == 'fit') { $class = 'env-fit'; } - return '' . $name . ''; + if (!empty($additionalCssClasses)) { + $class .= ' ' . $additionalCssClasses; + } + + $style = 'background-color: ' . $background . '; color: ' . $color . '; border: ' . $border; + + if (!empty($additionalStyle)) { + $style .= '; ' . $additionalStyle; + } + + return '' . $name . ''; } /** @@ -46,12 +62,4 @@ public static function white(string $string): string { return '' . $string . ''; } - - /** - * Generate black label - */ - public static function black(string $string): string - { - return '' . $string . ''; - } } diff --git a/www/models/Repo/Listing.php b/www/models/Repo/Listing.php index 83d7f2e28..d2ce6011a 100644 --- a/www/models/Repo/Listing.php +++ b/www/models/Repo/Listing.php @@ -198,7 +198,16 @@ public function listSnapshots(int $repoId) : array ORDER BY Env ), '' - ) AS Environments + ) AS Environments, + COALESCE( + ( + SELECT GROUP_CONCAT(Id, ',') + FROM repos_env + WHERE Id_snap = repos_snap.Id + ORDER BY Env + ), + '' + ) AS EnvironmentIds FROM repos_snap WHERE repos_snap.Id_repo = :repoId AND repos_snap.Status = 'active' diff --git a/www/public/assets/icons/calendar.svg b/www/public/assets/icons/calendar.svg new file mode 100644 index 000000000..ababc8c46 --- /dev/null +++ b/www/public/assets/icons/calendar.svg @@ -0,0 +1,40 @@ + + + + + + + diff --git a/www/public/assets/icons/disk.svg b/www/public/assets/icons/disk.svg new file mode 100644 index 000000000..65cd5a162 --- /dev/null +++ b/www/public/assets/icons/disk.svg @@ -0,0 +1,46 @@ + + + + + + + + diff --git a/www/public/assets/icons/server.svg b/www/public/assets/icons/server.svg index dacbe0db8..128b042e5 100644 --- a/www/public/assets/icons/server.svg +++ b/www/public/assets/icons/server.svg @@ -1,20 +1,23 @@ + + + id="defs10" /> + inkscape:current-layer="svg6" /> + style="stroke-width:1.19298;fill:#ffffff;fill-opacity:1" /> - + style="stroke-width:1.19298;fill:#ffffff;fill-opacity:1" /> diff --git a/www/public/resources/js/classes/Checkbox.js b/www/public/resources/js/classes/Checkbox.js index e093eefd2..8adbcd1c8 100644 --- a/www/public/resources/js/classes/Checkbox.js +++ b/www/public/resources/js/classes/Checkbox.js @@ -18,12 +18,6 @@ class Checkbox // If the 'Select all' checkbox is checked, check the child checkbox and make it visible, else uncheck the child checkbox and remove any custom visibility so it returns to default checkboxes.each(function () { - // If the checkbox is the latest one, directly simulate a click on it to trigger any event attached to it - if (--count === 0) { - $(this).trigger('click'); - return; - } - if (status) { $(this).prop('checked', true); $(this).css('opacity', '1'); @@ -31,7 +25,15 @@ class Checkbox $(this).prop('checked', false); $(this).css('opacity', ''); } + + // Trigger change event so visual state (e.g. task-selected) is updated + $(this).trigger('change'); }); + + // Trigger select on the last checkbox to update the confirmbox, without toggling its state + if (checkboxes.length) { + this.select(checkboxes.last()); + } } /** diff --git a/www/public/resources/js/classes/Host/HostSearch.js b/www/public/resources/js/classes/Host/HostSearch.js index 92ab17306..af05ba5a4 100644 --- a/www/public/resources/js/classes/Host/HostSearch.js +++ b/www/public/resources/js/classes/Host/HostSearch.js @@ -42,7 +42,7 @@ class HostSearch $('.hosts-group-container').show(); // Hide all host lines, only those corresponding to the search will be re-displayed - $('.host-line').removeClass('flex').hide(); + $('.host-line').hide(); /** * Check if the user has entered a filter in his search, different filters are possible: @@ -176,7 +176,7 @@ class HostSearch if (div) { var txtValue = div.textContent || div.innerText; if (txtValue.toUpperCase().indexOf(search) > -1) { - $(this).addClass('flex').show(); + $(this).show(); } } }); @@ -332,7 +332,6 @@ class HostSearch $('#hosts-search').addClass('hide'); $('.hosts-group-container, .host-line').show(); - $('.host-line').addClass('flex'); $('#hosts').show(); } diff --git a/www/public/resources/js/classes/Repo.js b/www/public/resources/js/classes/Repo.js index 279a93218..280f83edc 100644 --- a/www/public/resources/js/classes/Repo.js +++ b/www/public/resources/js/classes/Repo.js @@ -93,10 +93,12 @@ class Repo { getSize() { // Loop through all repos and get their size - $('#repos-list-container').find('.item-size').each(function () { + // item-size is the legacy class for snap-size + $('#repos-list-container').find('.snap-size, .item-size').each(function () { var repoId = $(this).attr('repo-id'); var snapId = $(this).attr('snap-id'); var path = $(this).attr('repo-relative-path'); + let element = $(this); ajaxRequest( // Controller: @@ -112,9 +114,9 @@ class Repo { // Print error alert: false ).then(function () { - $("#repos-list-container").find('.item-size[repo-id="' + repoId + '"][snap-id="' + snapId + '"]').html(jsonValue.message); + $(element).html(jsonValue.message); }).catch(function () { - $("#repos-list-container").find('.item-size[repo-id="' + repoId + '"][snap-id="' + snapId + '"]').replaceWith(''); + $(element).replaceWith(''); }); }); } diff --git a/www/public/resources/js/events/host/actions.js b/www/public/resources/js/events/host/actions.js index 27a9d2678..42e3b0e3f 100644 --- a/www/public/resources/js/events/host/actions.js +++ b/www/public/resources/js/events/host/actions.js @@ -102,6 +102,37 @@ $(document).on('click','.host-update-packages-btn',function () { mypanel.get('hosts/requests/update-packages', {hostId: id}); }); +/** + * Event: click on a host-line to select it + * Do not trigger if the user clicked on a link () + */ +$(document).on('click', '.host-line', function (e) { + // Ignore clicks on links or the checkbox itself + if ($(e.target).closest('a, input[name="checkbox-host[]"]').length) { + return; + } + + // Find the checkbox inside this container and toggle it + var checkbox = $(this).find('input[name="checkbox-host[]"]'); + + if (checkbox.length) { + checkbox.click(); + } +}); + +/** + * Event: toggle selected state on host-line when checkbox is checked/unchecked + */ +$(document).on('change', 'input[name="checkbox-host[]"]', function () { + var container = $(this).closest('.host-line'); + + if ($(this).is(':checked')) { + container.addClass('host-selected'); + } else { + container.removeClass('host-selected'); + } +}); + /** * Event: when a host checkbox is checked */ @@ -120,14 +151,16 @@ $(document).on('click','input[type="checkbox"].select-group-hosts-checkbox',func // Retrieve checkbox status const status = $(this).attr('status'); - // Retrieve all checkboxes of the group - const hostsCheckboxes = $('input[name="checkbox-host[]"][group="' + group + '"]:visible'); + // Retrieve all checkboxes of the group (filter by visible host-line parent since checkboxes are hidden) + const hostsCheckboxes = $('input[name="checkbox-host[]"][group="' + group + '"]').filter(function () { + return $(this).closest('.host-line').is(':visible'); + }); // If current status is 'selected', then unselect all hosts if (status == 'selected') { hostsCheckboxes.each(function () { if ($(this).is(':checked')) { - $(this).prop('checked', false); + $(this).prop('checked', false).trigger('change'); } }); @@ -139,7 +172,7 @@ $(document).on('click','input[type="checkbox"].select-group-hosts-checkbox',func // Check all checkbox-host[] of the same group hostsCheckboxes.each(function () { if (!$(this).is(':checked')) { - $(this).prop('checked', true); + $(this).prop('checked', true).trigger('change'); } }); @@ -161,9 +194,11 @@ $(document).on('click', '#select-all-hosts', function () { const hostGroupsCheckboxes = $('input[type="checkbox"].select-group-hosts-checkbox:visible'); /** - * Retrieve all hosts checkboxes that are visible + * Retrieve all hosts checkboxes (filter by visible host-line parent) */ - const hostCheckboxes = $('input[type="checkbox"][name="checkbox-host[]"]:visible'); + const hostCheckboxes = $('input[type="checkbox"][name="checkbox-host[]"]').filter(function () { + return $(this).closest('.host-line').is(':visible'); + }); /** * Retrieve select status @@ -182,7 +217,7 @@ $(document).on('click', '#select-all-hosts', function () { hostCheckboxes.each(function () { if (!$(this).is(':checked')) { - $(this).prop('checked', true); + $(this).prop('checked', true).trigger('change'); } }); @@ -206,7 +241,7 @@ $(document).on('click', '#select-all-hosts', function () { hostCheckboxes.each(function () { if ($(this).is(':checked')) { - $(this).prop('checked', false); + $(this).prop('checked', false).trigger('change'); } }); @@ -304,6 +339,33 @@ $(document).on('submit', '#host-update-packages-form', function () { return false; }); +/** + * Event: click on a host-package-item to select it + */ +$(document).on('click', '.host-package-item', function (e) { + // Ignore if clicked on a link or directly on the checkbox + if ($(e.target).closest('a').length || $(e.target).is('.available-package-checkbox')) { + return; + } + + var checkbox = $(this).find('.available-package-checkbox'); + if (checkbox.length) { + checkbox.trigger('click'); + } +}); + +/** + * Event: toggle selected state on host-package-item when checkbox changes + */ +$(document).on('change', '.available-package-checkbox', function () { + var container = $(this).closest('.host-package-item'); + if (this.checked) { + container.addClass('host-package-selected'); + } else { + container.removeClass('host-package-selected'); + } +}); + /** * Event: available package checkbox selection */ @@ -406,6 +468,9 @@ $(document).on('click','input[type="checkbox"].available-package-checkbox',funct } else { myconfirmbox.close(); } + + // Trigger change to update visual state + $(this).trigger('change'); }); /** diff --git a/www/public/resources/js/events/host/layout.js b/www/public/resources/js/events/host/layout.js index 1163515f6..40d70722b 100644 --- a/www/public/resources/js/events/host/layout.js +++ b/www/public/resources/js/events/host/layout.js @@ -90,8 +90,8 @@ $(document).on('mouseenter', '.search-package-tooltip', function (e) { content += '

name=apache2 version=2.4 strict-name=true

'; content += '
'; - content += '

● Use scrict filters to search for exact package name and/or version

'; - content += '

● Search and filters are case-insensitive

'; + content += '

- Use scrict filters to search for exact package name and/or version

'; + content += '

- Search and filters are case-insensitive

'; content += '
'; // Print tooltip diff --git a/www/public/resources/js/events/repo/edit.js b/www/public/resources/js/events/repo/edit.js index b9cbbbf82..200ffbd62 100644 --- a/www/public/resources/js/events/repo/edit.js +++ b/www/public/resources/js/events/repo/edit.js @@ -166,8 +166,8 @@ $(document).on('submit','#edit-form',function () { true ).then(function () { // Uncheck all checkboxes and remove all styles JQuery could have applied - $('.reposList').find('input[name=checkbox-repo]').prop('checked', false); - $('.reposList').find('input[name=checkbox-repo]').removeAttr('style'); + $('#repositories-list').find('input[name=checkbox-repo]').prop('checked', false); + $('#repositories-list').find('input[name=checkbox-repo]').removeAttr('style'); }); return false; diff --git a/www/public/resources/js/events/repo/env.js b/www/public/resources/js/events/repo/env.js index 29b4f1274..6582017c5 100644 --- a/www/public/resources/js/events/repo/env.js +++ b/www/public/resources/js/events/repo/env.js @@ -1,21 +1,35 @@ /** - * Event: select one or multiple snapshot environment(s) to delete + * Event: click on a snap-env-container to toggle the environment checkbox */ -$(document).on('click','.select-env-checkbox',function (e) { +$(document).on('click', '.snap-env-container', function (e) { + // Prevent triggering the parent snap-container click + e.stopPropagation(); + + // Toggle the hidden checkbox + var checkbox = $(this).find('.select-env-checkbox'); + checkbox.prop('checked', !checkbox.prop('checked')).trigger('change'); +}); + +/** + * Event: toggle selected state and confirmbox when env checkbox changes + */ +$(document).on('change', '.select-env-checkbox', function (e) { // Prevent parent to be triggered e.stopPropagation(); - var actions = []; + var container = $(this).closest('.snap-env-container'); - // If the checkbox is checked, make it visible, else remove any custom visibility so it returns to default + // Toggle visual state if ($(this).is(':checked')) { - $(this).css('visibility', 'visible'); + container.addClass('env-selected'); } else { - $(this).css('visibility', ''); + container.removeClass('env-selected'); } + var actions = []; + // Get all checked checkboxes - const checked = $('.reposList').find('input[type="checkbox"].select-env-checkbox:checked'); + const checked = $('#repositories-list').find('input[type="checkbox"].select-env-checkbox:checked'); if (checked.length == 0) { myconfirmbox.close(); diff --git a/www/public/resources/js/events/repo/list.js b/www/public/resources/js/events/repo/list.js index 9f5bd318d..f554b6a6c 100644 --- a/www/public/resources/js/events/repo/list.js +++ b/www/public/resources/js/events/repo/list.js @@ -11,18 +11,18 @@ $(document).on('click','#hide-all-repo-groups',function () { $(this).find('img').attr('src', 'assets/icons/view-off.svg'); // Retrieve all groups and hide them if they are visible - $('.repo-list-group-container').each(function () { - // Retrieve group id - var id = $(this).attr('group-id'); + $('.repos-list-group').each(function () { + var content = $(this).children('.group-content'); - // If the group is visible then hide it, else do nothing - if ($(this).is(":visible")) { - slide('.repo-list-group-container[group-id="' + id + '"]'); + // If the group content is visible then hide it, else do nothing + if (content.is(":visible")) { + content.slideUp(200); } }); // Change all up/down icons to 'down' $('img.hide-repo-group').attr('src', 'assets/icons/view-off.svg'); + $('img.hide-repo-group').attr('state', 'hidden'); } // If actual state is 'hidden' then show all groups @@ -32,18 +32,18 @@ $(document).on('click','#hide-all-repo-groups',function () { $(this).find('img').attr('src', 'assets/icons/view.svg'); // Retrieve all groups and show them if they are hidden - $('.repo-list-group-container').each(function () { - // Retrieve group id - var id = $(this).attr('group-id'); + $('.repos-list-group').each(function () { + var content = $(this).children('.group-content'); - // If the group is hidden then show it, else do nothing - if ($(this).is(":hidden")) { - slide('.repo-list-group-container[group-id="' + id + '"]'); + // If the group content is hidden then show it, else do nothing + if (content.is(":hidden")) { + content.slideDown(200); } }); // Change all up/down icons to 'up' $('img.hide-repo-group').attr('src', 'assets/icons/view.svg'); + $('img.hide-repo-group').attr('state', 'visible'); } }); @@ -51,7 +51,6 @@ $(document).on('click','#hide-all-repo-groups',function () { * Event: show / hide repos group content */ $(document).on('click','.hide-repo-group',function () { - var id = $(this).attr('group-id'); var state = $(this).attr('state'); if (state == 'visible') { @@ -64,5 +63,5 @@ $(document).on('click','.hide-repo-group',function () { $(this).attr('src', 'assets/icons/view.svg'); } - slide('.repo-list-group-container[group-id="' + id + '"]'); + $(this).closest('.repos-list-group').children('.group-content').slideToggle(200); }); diff --git a/www/public/resources/js/events/task/actions.js b/www/public/resources/js/events/task/actions.js index b381dc9ba..4add4ec05 100644 --- a/www/public/resources/js/events/task/actions.js +++ b/www/public/resources/js/events/task/actions.js @@ -26,26 +26,14 @@ $(document).on('click','.show-step-content-btn',function () { }); /** - * Event: show logfile + * Event: show logfile (restore AJAX navigation) */ -$(document).on('click','.show-task-btn',function () { - var taskId = $(this).attr('task-id'); - - // Add a loading spinner to the container - $('#log-refresh-container').html('
'); - - // Change URL without reloading the page - history.pushState(null, null, '/run/' + taskId); +// $(document).on('click','.show-task-btn',function () { +// const taskId = $(this).attr('task-id'); - // Reload task container to print the new task log - mycontainer.reload('tasks/log').then(function () { - // Restart log scroll event listener - scrollEventListener(); - - // Remove loading spinner - $('#log-refresh-container .loading-veil').remove(); - }); -}); +// // Go to task log page /task/{id} +// window.location.href = '/task/' + taskId; +// }); /** * Event: view task process log (debug log) @@ -234,6 +222,27 @@ $(document).on('click','.step-down-btn',function () { $('.task-step-content[task-id="' + taskId + '"][step="' + step + '"]').scrollTop($('.task-step-content[task-id="' + taskId + '"][step="' + step + '"]').scrollTop() + 100); }); +/** + * Event: click on a selectable task item to toggle its hidden checkbox + */ +$(document).on('click', '.task-item-selectable', function (e) { + // Prevent parent to be triggered + e.stopPropagation(); + + // Ignore clicks on action icons and the details button + if ($(e.target).closest('.show-scheduled-task-info-btn, .relaunch-task-btn, .stop-task-btn, .task-checkbox-input').length) { + return; + } + + // Find the hidden checkbox inside this task item and toggle it + var checkbox = $(this).find('.task-checkbox-input'); + + if (checkbox.length) { + // click() toggles the native checked state + fires click and change events + checkbox.click(); + } +}); + /** * Event: show or hide scheduled task informations */ @@ -249,6 +258,19 @@ $(document).on('click','.show-scheduled-task-info-btn',function (e) { $('.scheduled-task-info[task-id="' + taskId + '"]').toggle(); }); +/** + * Event: toggle selected state on task-item when its hidden checkbox is toggled + */ +$(document).on('change', '.task-checkbox-input', function () { + var container = $(this).closest('.task-item'); + + if ($(this).is(':checked')) { + container.addClass('task-selected'); + } else { + container.removeClass('task-selected'); + } +}); + /** * Event: relaunch task */ diff --git a/www/public/resources/js/functions/task.js b/www/public/resources/js/functions/task.js index 03519fe15..0a043ade4 100644 --- a/www/public/resources/js/functions/task.js +++ b/www/public/resources/js/functions/task.js @@ -4,17 +4,17 @@ function autorefresh() { // Ignore refresh if 'legacy' attribute is set - if ($('#log-refresh-container').attr('legacy') == 'true') { + if ($('#task-refresh-container').attr('legacy') == 'true') { return; } // Autorefresh with new steps and content every 2sec setInterval(function () { // Retrieve task Id - var taskId = $('#log-refresh-container').attr('task-id'); + var taskId = $('#task-refresh-container').attr('task-id'); // Ignore refresh if task is not running (wait for 2sec and try again) - if ($('#log-refresh-container').attr('task-status') != 'running') { + if ($('#task-refresh-container').attr('task-status') != 'running') { return; } @@ -67,7 +67,7 @@ function autorefresh() var status = jsonValue.message; // Refresh task status in the DOM, if the task is not running anymore, this will prevent the task from refreshing again - $('#log-refresh-container').attr('task-status', status); + $('#task-refresh-container').attr('task-status', status); // Remove the autoscroll button if the task is done, error or stopped if (status == 'done' || status == 'error' || status == 'stopped') { @@ -87,7 +87,7 @@ function autorefresh() function enableAutoScroll() { // Autoscroll can be enabled only if the task is running - if ($('#log-refresh-container').attr('task-status') == 'running') { + if ($('#task-refresh-container').attr('task-status') == 'running') { // Set autoscroll cookie to true to enable autoscroll mycookie.set('autoscroll', 'true'); @@ -135,7 +135,7 @@ function refreshStepsInDOM(steps) var autoscroll = false; // Retrieve task Id - var taskId = $('#log-refresh-container').attr('task-id'); + var taskId = $('#task-refresh-container').attr('task-id'); // Parse steps JSON steps = JSON.parse(JSON.stringify(steps)); diff --git a/www/public/resources/js/task.js b/www/public/resources/js/task.js index 924d55a12..4cc336f46 100644 --- a/www/public/resources/js/task.js +++ b/www/public/resources/js/task.js @@ -129,6 +129,37 @@ $(document).on('change','select.task-param[param-name="schedule-frequency"]',fun } }).trigger('change'); +/** + * Event: click on a snap-container to select the snapshot + * Do not trigger if the user clicked on a link (
) or an environment tag (.snap-env) + */ +$(document).on('click', '.snap-container', function (e) { + // Ignore clicks on links, environment tags/containers, or the checkbox itself + if ($(e.target).closest('a, .snap-env-container, .snap-env, input[name="checkbox-repo"]').length) { + return; + } + + // Find the checkbox inside this container and toggle it + var checkbox = $(this).find('input[name="checkbox-repo"]'); + + if (checkbox.length) { + checkbox.click(); + } +}); + +/** + * Event: toggle selected state on snap-container when checkbox is checked/unchecked + */ +$(document).on('change', 'input[name="checkbox-repo"]', function () { + var container = $(this).closest('.snap-container'); + + if ($(this).is(':checked')) { + container.addClass('snap-selected'); + } else { + container.removeClass('snap-selected'); + } +}); + /** * Event: when a checkbox is checked/unchecked */ @@ -153,14 +184,15 @@ $(document).on('click',"input[name=checkbox-repo]",function () { /** * Count the number of checked checkboxes */ - var count_checked = $('.reposList').find('input[name=checkbox-repo]:checked').length; + var count_checked = $('#repositories-list').find('input[name=checkbox-repo]:checked').length; /** * If all checkboxes are unchecked then we hide all action buttons */ if (count_checked == 0) { myconfirmbox.close(); - $('.reposList').find('input[name=checkbox-repo]').removeAttr('style'); + $('#repositories-list').find('input[name=checkbox-repo]').removeAttr('style'); + $('#repositories-list').find('.snap-container').removeClass('snap-selected'); $('.repos-list-group[group-id=' + groupId + ']').find('.repos-list-group-select-all-btns').hide(); return; } @@ -296,8 +328,8 @@ $(document).on('click',"input[name=checkbox-repo]",function () { * If there is at least 1 checkbox checked then we display all the other checkboxes * All checked checkboxes are set to opacity = 1 */ - $('.reposList').find('input[name=checkbox-repo]').css("visibility", "visible"); - $('.reposList').find('input[name=checkbox-repo]:checked').css("opacity", "1"); + $('#repositories-list').find('input[name=checkbox-repo]').css("visibility", "visible"); + $('#repositories-list').find('input[name=checkbox-repo]:checked').css("opacity", "1"); }); function executeAction(action) @@ -307,7 +339,7 @@ function executeAction(action) /** * Loop through all checked repos and retrieve their id */ - $('.reposList').find('input[name=checkbox-repo]:checked').each(function () { + $('#repositories-list').find('input[name=checkbox-repo]:checked').each(function () { var obj = {}; /** @@ -355,25 +387,23 @@ function executeAction(action) * Event: Click on 'select all latest snapshots' button */ $(document).on('click',".repos-list-group-select-all-btns",function () { - /** - * Retrieve group Id - */ - var groupId = $(this).attr('group-id'); + // Retrieve group Id + const groupId = $(this).attr('group-id'); /** * Retrieve all repos in the group */ - var reposCheckboxes = $('.repos-list-group[group-id=' + groupId + ']').find('input[name=checkbox-repo]'); + const reposCheckboxes = $('.repos-list-group[group-id=' + groupId + ']').find('input[name=checkbox-repo]'); /** * Retrieve select status */ - var selectStatus = $(this).attr('status'); + const status = $(this).attr('status'); /** * If current status is not 'selected', then select all the latest snaps */ - if (selectStatus != 'selected') { + if (status != 'selected') { /** * Loop through all checkboxes and check the latest snap * The latest snap is the first snap in the list (the first checkbox of each repo) @@ -604,8 +634,8 @@ $(document).on('submit','#task-form',function (e) { mypanel.close(); // Uncheck all checkboxes and remove all styles JQuery could have applied - $('.reposList').find('input[name=checkbox-repo]').prop('checked', false); - $('.reposList').find('input[name=checkbox-repo]').removeAttr('style'); + $('#repositories-list').find('input[name=checkbox-repo]').prop('checked', false); + $('#repositories-list').find('input[name=checkbox-repo]').removeAttr('style'); }); return false; diff --git a/www/public/resources/styles/common.css b/www/public/resources/styles/common.css index a596d5c3d..4b7744b4f 100644 --- a/www/public/resources/styles/common.css +++ b/www/public/resources/styles/common.css @@ -54,7 +54,7 @@ h1, h2, h3, h4 { h3 { width: max-content; font-size: 18px; - margin: 50px 0 40px 0; + margin: 40px 0 40px 0; border-bottom: 1px solid #15bf7f; } @@ -253,7 +253,7 @@ pre.codeblock { .margin-top-0 {margin-top: 0px !important}.margin-top-5 {margin-top: 5px !important}.margin-top-8{margin-top:8px!important}.margin-top-10 {margin-top: 10px !important}.margin-top-15 {margin-top: 15px !important}.margin-top-20 {margin-top: 20px !important}.margin-top-30 {margin-top: 30px !important}.margin-top-40 {margin-top: 40px !important}.margin-top-50{margin-top:50px !important} .margin-bottom-0 {margin-bottom: 0px !important}.margin-bottom-5 {margin-bottom: 5px !important}.margin-bottom-8{margin-bottom:8px!important}.margin-bottom-10 {margin-bottom: 10px !important}.margin-bottom-15 {margin-bottom: 15px !important}.margin-bottom-20 {margin-bottom: 20px !important}.margin-bottom-30 {margin-bottom: 30px !important}.margin-bottom-40 {margin-bottom: 40px !important} .margin-bottom-50{margin-bottom:50px!important} .min-height-100{min-height:100px!important}.min-height-120{min-height:120px!important}.min-height-150{min-height:150px!important}.min-height-200{min-height:200px!important}.min-height-300{min-height:300px!important}.min-height-400{min-height:400px!important}.min-height-500{min-height:500px!important}.min-height-600{min-height:600px!important}.min-height-700{min-height:700px!important}.min-height-800{min-height:800px!important}.min-height-900{min-height:900px!important}.min-height-1000{min-height:1000px!important} -.min-width-100 {min-width: 100px}.min-width-200 {min-width: 200px}.min-width-300 {min-width: 300px}.min-width-400 {min-width: 400px}.min-width-500 {min-width: 500px}.min-width-600 {min-width: 600px}.min-width-700 {min-width: 700px}.min-width-800 {min-width: 800px}.min-width-900 {min-width: 900px}.min-width-1000 {min-width: 1000px} +.min-width-100 {min-width: 100px}.min-width-150{min-width: 150px}.min-width-200 {min-width: 200px}.min-width-300 {min-width: 300px}.min-width-400 {min-width: 400px}.min-width-500 {min-width: 500px}.min-width-600 {min-width: 600px}.min-width-700 {min-width: 700px}.min-width-800 {min-width: 800px}.min-width-900 {min-width: 900px}.min-width-1000 {min-width: 1000px} .min-height-50vh{min-height:50vh}.min-height-90vh{min-height:90vh}.min-height-100vh{min-height:100vh} .min-width-100vw{min-width:100vw} .width-100{width:100%} @@ -299,40 +299,48 @@ code { display: flex; align-items: center; justify-content: center; - min-width: 35px; + min-width: 32px; height: 30px; - border: none; - border-radius: 60px; - color: white; + border: 1px solid #2d5575; + border-radius: 6px; + color: #97a5b4; text-align: center; text-decoration: none; - font-size: 14px; + font-size: 13px; cursor: pointer; - background-color: #15bf7f; - border-radius: 0; + background-color: transparent; + margin: 0 2px; + transition: all 0.15s ease; } [class*=' pagination-btn']:hover,[class^='pagination-btn']:hover { - background-color: #12a16a; + background-color: rgba(34, 56, 79, 0.6); + border-color: #3d7aab; + color: #ffffff; } .pagination-btn-current { - background-color: #12a16a; - border: 1px solid #10885a; + background-color: rgba(21, 191, 127, 0.15); + border-color: #15bf7f; + color: #15bf7f; +} + +.pagination-btn-current:hover { + background-color: rgba(21, 191, 127, 0.25); + border-color: #15bf7f; + color: #15bf7f; } .pagination-btn-first { - border-top-left-radius: 60px; - border-bottom-left-radius: 60px; + border-radius: 6px; } .pagination-btn-last { - border-top-right-radius: 60px; - border-bottom-right-radius: 60px; + border-radius: 6px; } .pagination-btn-previous, .pagination-btn-next { - width: 40px; + width: 35px; } .round-item { diff --git a/www/public/resources/styles/components/card.css b/www/public/resources/styles/components/card.css new file mode 100644 index 000000000..0eb3a1a15 --- /dev/null +++ b/www/public/resources/styles/components/card.css @@ -0,0 +1,60 @@ +.kpi-card { + display: flex; + align-items: center; + column-gap: 12px; + width: auto; + padding: 14px; + background-color: #182b3e; + border: 1px solid #24405c; + border-radius: 20px; + box-shadow: rgb(0 0 0) 0px 10px 13px -12px, rgb(0 0 0 / 15%) 0px 0px 10px 2px; +} + +.kpi-value { + font-family: 'Archivo'; + font-size: 24px; + font-weight: 700; + line-height: 1; + transition: transform 0.2s ease; + transform-origin: left; +} + +.kpi-card:hover .kpi-value { + transform: scale(1.10); +} + +.storage-card { + max-width: 100vw; +} + +.storage-meter { + width: 100%; + height: 9px; + overflow: hidden; + background-color: #0e1e2f; + border: 1px solid #24405c; + border-radius: 20px; +} + +.storage-meter span { + display: block; + height: 100%; + min-width: 3px; + background-color: #15bf7f; + border-radius: 20px; +} + +.kpi-container > .kpi-card, +.kpi-container > .storage-card { + flex: 1 1 calc(50% - 10px); + box-sizing: border-box; +} + +/* Desktop configuration */ +@media (min-width:1025px) { + .kpi-container > .kpi-card, + .kpi-container > .storage-card { + flex: 1 1 0; + min-width: 0; + } +} diff --git a/www/public/resources/styles/components/confirmbox.css b/www/public/resources/styles/components/confirmbox.css index 7c2f16a24..18f4c893b 100644 --- a/www/public/resources/styles/components/confirmbox.css +++ b/www/public/resources/styles/components/confirmbox.css @@ -7,20 +7,17 @@ align-items: flex-start; visibility: hidden; min-height: 20px; - padding: 20px 0px 20px 0px; + padding: 20px 0px; font-size: 16px; position: fixed; - bottom: 0; - right: -1000px; bottom: 50px; + right: -1000px; z-index: 1000; - background-color: #22384F; - box-shadow: 0px 0px 5px 0px rgba(0,0,0,1); + background-color: #182b3e; + border: 1px solid #24405c; + border-radius: 20px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); color: white; - border-radius: 20px 0 0 20px; - border-left: 5px solid #15bf7f; - border-top: 1px solid #d1d1d11f; - border-bottom: 1px solid #d1d1d11f; } .confirm-box-cancel-btn { @@ -30,6 +27,30 @@ cursor: pointer; } +.confirm-box .confirm-box-btn { + padding: 10px 5px; + border-radius: 12px; + border: 1px solid #2e5275; + background-color: rgba(34, 56, 79, 0.6); + box-shadow: none; + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.confirm-box .confirm-box-btn:hover { + background-color: rgba(79, 195, 247, 0.12); + border-color: rgba(79, 195, 247, 0.4); +} + +.confirm-box .confirm-box-btn.btn-auto-red { + border-color: #F32F63; + background-color: rgba(243, 47, 99, 0.1); +} + +.confirm-box .confirm-box-btn.btn-auto-red:hover { + background-color: #F32F63; + border-color: #F32F63; +} + /* Desktop configuration */ @media (min-width:1025px) { .confirm-box { diff --git a/www/public/resources/styles/components/label.css b/www/public/resources/styles/components/label.css index b61b9e1fa..f1aa78429 100644 --- a/www/public/resources/styles/components/label.css +++ b/www/public/resources/styles/components/label.css @@ -3,14 +3,15 @@ font-size: 13px; vertical-align: middle; display: inline-block; - padding: 6px; + padding: 6px 10px; min-width: 12px; min-height: 12px; line-height: 12px !important; text-align: center; - border-radius: 60px; - color: white; - box-shadow: rgb(12 18 20 / 67%) 0px 0px 0px 1px; + border-radius: 20px; + background: transparent; + border: 1.5px solid currentColor; + box-shadow: none; } .label-icon-tr { display: grid; @@ -28,29 +29,30 @@ border: 1px solid #24405c; color: white; text-align: left; - box-shadow: rgb(12 18 20 / 67%) 0px 0px 0px 1px; -} -.label-black { - background-color: rgb(46, 54, 58); - box-shadow: rgb(12 18 20 / 67%) 0px 0px 0px 1px; + box-shadow: none; } -.label-white { - background-color: white; - color: #000000d9; + +.label-white, .label-black { + color: #c0d0e2; + border-color: #c0d0e2; } .label-green { - background-color: #15bf7f; + color: #15bf7f; + border-color: #15bf7f; } .label-blue { - background-color: #5473e8; + color: #5473e8; + border-color: #5473e8; } .label-red { - background-color: #F32F63; + color: #F32F63; + border-color: #F32F63; } .label-yellow { - background-color: #ffb536; + color: #ffb536; + border-color: #ffb536; } .label-tr { - background-color: initial; - border: 1px solid #24405c; + color: #8A99AA; + border-color: #24405c; } diff --git a/www/public/resources/styles/components/layout.css b/www/public/resources/styles/components/layout.css index c761c141d..d7bcf7572 100644 --- a/www/public/resources/styles/components/layout.css +++ b/www/public/resources/styles/components/layout.css @@ -12,12 +12,21 @@ body { } .div-generic-blue { - position: relative; + /* position: relative; */ padding: 15px; margin-bottom: 30px; border-radius: 20px; background-color: #182b3e; box-shadow: rgb(0 0 0) 0px 10px 13px -12px, rgb(0 0 0 / 15%) 0px 0px 10px 2px; + border: 1px solid #1a4a6a; +} + +.div-generic-blue.repo-accent-deb { + border-left: 2px solid #F32F63; +} + +.div-generic-blue.repo-accent-rpm { + border-left: 2px solid #5473e8; } .div-generic-blue h3 { diff --git a/www/public/resources/styles/main.css b/www/public/resources/styles/main.css index 70f7d9264..8aaba6716 100644 --- a/www/public/resources/styles/main.css +++ b/www/public/resources/styles/main.css @@ -10,40 +10,20 @@ header { margin-bottom: 20px; position: relative; } + #menu { - width: 100%; - height: 66px; - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - -webkit-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.75); - -moz-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.75); - box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.75); -} -#menu-inline { display: none; visibility: hidden; + width: 0px; } + #menu-burger { display: flex; align-items: center; justify-content: flex-end; - padding: 0 20px 0 0; + padding: 0 0 20px 0; } -#title { - position: relative; - margin-left: 20px; - margin-top: 0px; - margin-bottom: 0px; - padding: 0px 25px 0px 0px; - color: white; -} -#title span { - font-family: 'ethnocentric'; - font-size: 18px; -} #devel { font-size: 8px !important; position: absolute; @@ -52,26 +32,7 @@ header { background-color: #15bf7f; border-radius: 60px; } -[class^="menu-sub-container"] { - height: 99%; - display: flex; - align-items: center; - margin-left: 25px; - margin-right: 25px; -} -.menu-sub-container a { - opacity: 0.50; -} -.menu-sub-container-underline { - border-bottom: 1px solid #15bf7f; - opacity: 1; -} -.menu-sub-container:hover a { - opacity: 1; -} -.menu-tab-title { - display: none; -} + #header-refresh { position: relative; } @@ -134,14 +95,20 @@ header { align-items: center; } -/* Les articles constituent le container principal des sections section-left et section-right. */ article { - width: auto; + width: 100%; display: flex; flex-direction: column; justify-content: space-between; - margin: auto 20px auto 20px; + align-content: flex-start; + padding: 25px 25px; } + +#virtual-space { + display: none; + visibility: hidden; +} + .section-left, .section-right { width: 100%; vertical-align: top; @@ -175,6 +142,9 @@ article { #repos-list-container { width: 100%; font-size: 14px; + display: flex; + flex-direction: column; + row-gap: 15px; } .repos-list-group-select-all-btns { @@ -388,11 +358,291 @@ article { column-gap: 10px; } -input::placeholder { - color: #ffffff9e; +/* Task list items */ +.task-item { + display: grid; + grid-template-columns: 1fr auto auto; + align-items: center; + column-gap: 20px; + padding: 14px 20px; + background: linear-gradient(135deg, #121f30, #162a3e); + border: 1px solid #1e3a54; + border-left: 3px solid #1e3a54; + border-radius: 10px; + transition: border-color 0.2s ease, background 0.2s ease; +} + +.task-item:hover { + border-color: #2d5575; + background: linear-gradient(135deg, #152538, #1a2f44); +} + +.task-accent-green { + border-left-color: #15bf7f; +} + +.task-accent-red { + border-left-color: #F32F63; +} + +.task-accent-running { + border-left-color: #4fc3f7; +} + +.task-accent-yellow { + border-left-color: #ffb536; +} + +.task-accent-orange { + border-left-color: #e8851e; +} + +/* .task-item-icon { + width: 20px; + height: 20px; + opacity: 0.6; +} */ + +.task-item-date { + font-family: 'archivo'; + font-size: 14px; + font-weight: 500; + color: #b8c8d8; + letter-spacing: 0.3px; +} + +.task-item-schedule { + font-size: 12px; +} + +.task-item-action { + font-size: 13px; +} + +.task-item-repo { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} + +.task-item-repo-name { + font-size: 13px; + font-family: 'archivo'; + font-weight: 600; + color: #c0d0e2; + letter-spacing: 0.2px; +} + +.task-item-status { + display: flex; + align-items: center; + column-gap: 8px; +} + +.task-checkbox-input { + display: none; +} + +.task-item.task-selected { + border-color: #15bf7f; + border-left-color: #15bf7f; + background: linear-gradient(135deg, #0f2a1f, #132f28); +} + +/* Group headers */ +.group-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 20px 14px 5px; +} + +.group-header-left { + display: flex; + align-items: center; + gap: 12px; +} + +.group-header-icon { + width: 22px; + height: 22px; +} + +.group-header-name { + font-family: 'archivo'; + font-size: 18px; + font-weight: 700; + color: #f0f4f8; + letter-spacing: 0.3px; +} + +.group-header-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 24px; + height: 24px; + padding: 0 8px; + font-family: 'archivo'; + font-size: 12px; + font-weight: 700; + color: #4fc3f7; + background-color: rgba(79, 195, 247, 0.12); + border: 1px solid rgba(79, 195, 247, 0.35); + border-radius: 12px; +} + +.group-header-right { + display: flex; + align-items: center; + gap: 10px; +} + +/* Group content wrapper */ +.group-content { + margin-left: 12px; + padding-left: 20px; + border-left: 2px solid #1e3a54; +} + +/* Repo name within a group */ +.group-repo-name { + display: flex; + align-items: center; + gap: 10px; + margin-top: 10px; + margin-bottom: 8px; + padding-left: 0; +} + +.group-repo-name-icon { + width: 18px; + height: 18px; + opacity: 0.7; + filter: invert(70%) sepia(60%) saturate(600%) hue-rotate(160deg) brightness(1.05); +} + +.group-repo-name span { + font-family: 'archivo'; + font-size: 16px; + font-weight: 600; + color: #c0d0e2; + letter-spacing: 0.2px; +} + +/* new ui */ +.snap-container { + width: auto; + display: grid; + /* grid-template-columns: 36px 20px auto 30px 16px auto 16px auto 1fr; */ + /* grid-template-columns: 1fr 1fr; */ + align-items: center; + column-gap: 6px; + row-gap: 20px; + padding: 16px 24px; + background: linear-gradient(135deg, #121f30, #162a3e); + border: 1px solid #1e3a54; + border-radius: 10px; + transition: border-color 0.2s ease, background 0.2s ease; +} + +.snap-container:hover { + border-color: #2d5575; + background: linear-gradient(135deg, #152538, #1a2f44); +} + +.snap-separator { + width: 1px; + height: 24px; + background-color: #2a4562; + justify-self: center; +} + +.snap-checkbox { + display: flex; + align-items: center; + justify-content: center; +} + +.snap-checkbox input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; +} + +.snap-checkbox-input { + display: none; +} + +.snap-container.snap-selected { + border-color: #15bf7f; + background: linear-gradient(135deg, #0f2a1f, #132f28); +} + +.snap-icon { + width: 18px; + height: 18px; +} + +.snap-date { + font-family: 'archivo'; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.3px; +} + +.snap-size { + font-size: 12px; + letter-spacing: 0.8px; +} + +.snap-signed { + font-size: 12px; + letter-spacing: 0.8px; +} + +.snap-envs { + display: flex; + align-items: center; + flex-wrap: wrap; + column-gap: 8px; + row-gap: 20px; +} + +.snap-env { + padding: 5px 14px !important; + font-size: 12px; + font-family: 'archivo'; + font-weight: 600; + letter-spacing: 0.3px; + border-radius: 20px; + background: transparent !important; + border: 1.5px solid currentColor !important; + box-shadow: none; + margin-left: 0; +} + +.snap-env-container { + position: relative; + cursor: pointer; + border-radius: 20px; + transition: box-shadow 0.2s ease; +} + +.snap-env-container .select-env-checkbox { + display: none; +} + +.snap-env-container.env-selected .snap-env { + background-color: rgba(21, 191, 127, 0.15) !important; + border-color: #15bf7f !important; + color: #15bf7f !important; + box-shadow: 0 0 8px rgba(21, 191, 127, 0.3); } -/* Etiquettes d'environnements */ + +/* Env labels */ .env, .last-env, .env-fit, .last-env-fit { font-size: 13px; padding-left: 10px; @@ -403,22 +653,26 @@ input::placeholder { border-radius: 16px; } .env, .env-fit { - background-color: white; - color: #000000d9; + background: transparent; + color: #a0b0c0; + border: 1.5px solid currentColor; + box-shadow: none; } .env-fit, .last-env-fit { text-align: center; display: block; } .env-fit:hover, .last-env-fit:hover { - background-color: #e6e6e6; + opacity: 0.8; } .last-env, .last-env-fit { - color: white; - background-color: #F32F63; + color: #F32F63; + background: transparent; + border: 1.5px solid #F32F63; + box-shadow: none; } .last-env:hover, .last-env-fit:hover { - background-color: #dc0044; + opacity: 0.8; } [class^="label-pkg"] { @@ -442,116 +696,24 @@ input::placeholder { footer { display: flex; - flex-direction: column; + align-items: center; justify-content: space-between; - row-gap: 40px; - width: auto; - height: 300px; - flex-grow: 1; - margin-top: 200px; - padding: 40px 25px 50px 25px; - box-shadow: - 0px 1px 1px 0px rgba(0, 0, 0, 0.5) inset, - 0px 2px 2px 0px rgba(109, 109, 109, 0.2); - background-color:#182b3e; -} - -footer #github img { width: 25px; } - -.circle-div-container div { - display: inline-block; - vertical-align: middle; - margin: 5px 10px 5px 0px; -} - -.circle-div-container-count, .circle-div-container-count-green, .circle-div-container-count-yellow, .circle-div-container-count-red { - position: relative; - border-radius: 50%; - border: 4px solid #5473e8; - text-align: center; - display: inline-block; - padding: 10px; - width: 25px; - height: 25px; - -webkit-box-shadow: 0px 10px 13px -12px #000000, 0px 0px 10px 2px rgb(0 0 0 / 15%); - box-shadow: 0px 10px 13px -12px #000000, 0px 0px 10px 2px rgb(0 0 0 / 15%); -} -.circle-div-container-count span, .circle-div-container-count-green span, .circle-div-container-count-yellow span, .circle-div-container-count-red span { - position: absolute; - top: 50%; - left: 50%; - -ms-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - font-size: 13.5px; -} -.circle-div-container-count-green { border: 4px solid #15bf7f; } -.circle-div-container-count-yellow { border: 4px solid #ffb536; } -.circle-div-container-count-red { border: 4px solid #F32F63; } - -#repo-storage-chart { - min-height: 18px !important; - height: 18px !important; - width: 18px !important; -} - -.donut-chart-container { - width: 120px; - height: 120px; - position: relative; - margin: auto; -} - -.donut-chart { - position: absolute; - z-index: 20; - display: block; - width: 100% !important; - height: 100% !important; -} - -.donut-chart-container .donut-legend { - position: absolute; - text-align: center; - font-size: 13px; - top: 40%; - left: 50%; - -ms-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); -} -.donut-legend-title { - position: absolute; - text-align: center; - font-size: 13px; - top: 33%; - left: 50%; - -ms-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); -} -.donut-legend-content { - width: 100%; - position: absolute; - text-align: center; - font-size: 18px; - top: 60%; - left: 50%; - -ms-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); -} - -.profiles-container { - columns: 1; - column-count: 1; + flex-wrap: wrap; + row-gap: 20px; width: 100%; -} - -.profiles-container > div { - break-inside: avoid; + margin-top: 80px; + padding: 30px 0; } .header-blue, .header-blue-min { background-color: #22384F; } .header-light-red, .header-light-red-min { background-color: #ff3369 !important; } .header-light-blue, .header-light-blue-min { background-color: #2b4d6e !important; } +/* Disable scroll when maintenance mode is active */ +body:has(#maintenance-container) { + overflow: hidden; +} + #maintenance-container { position: fixed; top: 0; @@ -593,30 +755,199 @@ footer #github img { width: 25px; } /* Desktop configuration */ @media (min-width:1500px) { - #menu { - justify-content: flex-start; + #menu-burger { + display: none; } - #menu-inline { - height: 100%; + + #menu + { + display: flex !important; + visibility: visible !important; + flex-direction: column; + align-items: center; + justify-content: space-between; + row-gap: 10px; + width: 60px; + height: 100vh; + background: red; + } + + #menu-fixed { + position: fixed; + top: 0; + left: 0; + height: 100vh; + max-height: 100vh; + width: 60px; display: flex; - visibility: visible; + flex-direction: column; + align-items: center; justify-content: space-between; - flex-grow: 1; + row-gap: 10px; + background-color: #182b3e; + border-right: 1px solid #24405c; + padding: 20px 0px; + box-sizing: border-box; + z-index: 500; } - #menu-burger { - display: none !important; - visibility: hidden !important; + + #menu img { + width: 24px; + height: 24px; + } + + /* Menu items hover & active state */ + .menu-item { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 12px; + transition: background-color 0.2s ease, transform 0.15s ease; + } + + .menu-item > a { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + border-radius: inherit; + } + + .menu-item img { + opacity: 0.55; + transition: opacity 0.2s ease, filter 0.2s ease; + } + + .menu-item:hover { + background-color: rgba(79, 195, 247, 0.08); + transform: scale(1.08); + } + + .menu-item-tasks:hover { + transform: none; + } + + .menu-item:hover img { + opacity: 1; + filter: drop-shadow(0 0 3px rgba(79, 195, 247, 0.4)); + } + + .menu-item-active { + background-color: rgba(21, 191, 127, 0.12); + position: relative; + } + + .menu-item-active::before { + content: ''; + position: absolute; + left: -10px; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 24px; + background-color: #15bf7f; + border-radius: 0 3px 3px 0; + } + + .menu-item-active img { + opacity: 1; + filter: brightness(1.2) drop-shadow(0 0 2px rgba(21, 191, 127, 0.3)); + } + + .menu-item-active:hover { + background-color: rgba(21, 191, 127, 0.16); + } + + /* Running tasks badge on sidebar menu */ + .menu-item-tasks { + position: relative; } - .menu-tab-title { + + .menu-task-badge { + position: absolute; + top: -4px; + right: -4px; + width: 16px; + height: 16px; + line-height: 16px !important; + border-radius: 50%; + background-color: #F32F63; + color: white; + text-align: center; + font-size: 9px; + font-family: monospace; + pointer-events: none; + z-index: 2; + } + + .menu-task-popup { + display: none; + position: absolute; + left: calc(100% + 12px); + top: 50%; + transform: translateY(-50%); + min-width: max-content; + background-color: #182b3e; + border: 1px solid #24405c; + border-radius: 12px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + padding: 6px; + z-index: 400; + } + + .menu-task-popup::before { + content: ''; + position: absolute; + left: -12px; + top: 0; + width: 12px; + height: 100%; + } + + .menu-item-tasks:hover .menu-task-popup { display: block; - font-family: 'Archivo'; - font-weight: 600; - font-size: 16px; + } + + .menu-task-popup-item { + display: flex; + align-items: center; + column-gap: 8px; + padding: 8px 12px; + border-radius: 8px; + white-space: nowrap; + transition: background-color 0.15s ease; + } + + .menu-task-popup-item:hover { + background-color: rgba(79, 195, 247, 0.08); + font-weight: normal; + } + + .menu-task-popup-item span { + font-size: 13px; + color: #97a5b4; + } + + .menu-task-popup-repo { + font-size: 13px; + color: white !important; + background-color: #F32F63; + padding: 2px 8px; + border-radius: 50px; } article { flex-direction: row; flex-wrap: wrap; + padding: 10px 0; + } + + #virtual-space { + display: block; + visibility: visible; } .section-main { @@ -639,4 +970,16 @@ footer #github img { width: 25px; } .repos-list-group-flex-div { grid-template-columns: 350px 55px auto auto auto 55px 1fr 40px; } + + .repo-toolbar { + flex-wrap: nowrap; + } + + .repo-toolbar #repo-search-input { + min-width: 150px; + } + + .snap-envs { + justify-content: flex-end; + } } \ No newline at end of file diff --git a/www/public/resources/styles/run.css b/www/public/resources/styles/run.css index 3fdf5d64f..6bc0a0ace 100644 --- a/www/public/resources/styles/run.css +++ b/www/public/resources/styles/run.css @@ -1,20 +1,24 @@ -#log-refresh-container { +#task-refresh-container { flex: 0 0 100%; position: relative; min-height: 90vh; } -#log-refresh-container h3 { +#task-refresh-container h3 { margin-top: 0 !important; margin-bottom: 0 !important; border: none; } -#log-refresh-container h6 { +#task-refresh-container #task-details h6 { + margin-bottom: 5px; +} + +#task-refresh-container h6 { margin-top: 30px; } -#log-refresh-container pre { /* empecher le contenu des
 de dépasser */
+#task-refresh-container pre { /* empecher le contenu des 
 de dépasser */
     white-space: pre-wrap;       /* css-3 */
     white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
     white-space: -pre-wrap;      /* Opera 4-6 */
@@ -39,14 +43,15 @@
     line-height: 2 !important;
 }
 
-/* Boutons Top et Down pour atteindre le haut ou le bas de page */
 #scroll-btns-container {
-    display: none;
+    display: block;
+    position: fixed;
+    right: 20px;
+    bottom: 20px;
+    z-index: 1000;
 }
-/* Les boutons scroll sont fixes à l'intérieur du container */
+/* Les boutons scroll sont affichés en colonne */
 #scroll-btns {
-    position: sticky;
-    top: 570px;
     display: flex;
     flex-direction: column;
     row-gap: 5px;
@@ -169,15 +174,10 @@
 
 /* Desktop configuration */
 @media (min-width:1500px) {
-    #log-refresh-container {
+    #task-refresh-container {
         flex: 0 0 94%;
     }
 
-    #scroll-btns-container {
-        display: block;
-        order: 2;
-    }
-
     .task-step-content {
         grid-template-columns: 96% 4%;
     }
diff --git a/www/public/resources/styles/stats-hosts.css b/www/public/resources/styles/stats-hosts.css
index 3e60cb60a..cffb719b9 100644
--- a/www/public/resources/styles/stats-hosts.css
+++ b/www/public/resources/styles/stats-hosts.css
@@ -69,6 +69,92 @@
     break-inside: avoid;
 }
 
+/* Host group header (aligned with repos group-header style) */
+.hosts-group-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 14px 20px 14px 5px;
+}
+
+.hosts-group-header-left {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+}
+
+.hosts-group-header-name {
+    font-family: 'archivo';
+    font-size: 18px;
+    font-weight: 700;
+    color: #f0f4f8;
+    letter-spacing: 0.3px;
+}
+
+.hosts-group-header-count {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 24px;
+    height: 24px;
+    padding: 0 8px;
+    font-family: 'archivo';
+    font-size: 12px;
+    font-weight: 700;
+    color: #4fc3f7;
+    background-color: rgba(79, 195, 247, 0.12);
+    border: 1px solid rgba(79, 195, 247, 0.35);
+    border-radius: 12px;
+}
+
+/* Host group content (aligned with repos group-content style) */
+.hosts-group-content {
+    margin-left: 12px;
+    padding-left: 20px;
+    border-left: 2px solid #1e3a54;
+}
+
+/* Host line item (aligned with task-item / snap-container style) */
+.host-line {
+    display: grid;
+    grid-template-columns: 1fr;
+    align-items: center;
+    column-gap: 20px;
+    padding: 14px 20px;
+    background: linear-gradient(135deg, #121f30, #162a3e);
+    border: 1px solid #1e3a54;
+    border-left: 3px solid #1e3a54;
+    border-radius: 10px;
+    cursor: pointer;
+    transition: border-color 0.2s ease, background 0.2s ease;
+}
+
+.host-line:hover {
+    border-color: #2d5575;
+    background: linear-gradient(135deg, #152538, #1a2f44);
+}
+
+.host-line.host-selected {
+    border-color: #15bf7f;
+    background: linear-gradient(135deg, #0f2a1f, #132f28);
+}
+
+.host-line .js-host-checkbox {
+    display: none;
+}
+
+.host-accent-green {
+    border-left-color: #15bf7f;
+}
+
+.host-accent-red {
+    border-left-color: #F32F63;
+}
+
+.host-accent-yellow {
+    border-left-color: #ffb536;
+}
+
 .searchInput-container {
     width: 100%;
     margin-bottom: 50px;
@@ -255,4 +341,55 @@
         column-gap: 10px;
         row-gap: 15px;
     }
+}
+
+/**
+ *  Host detail page items (packages, events, requests)
+ */
+.host-package-item,
+.host-event-item,
+.host-request-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 18px;
+    background: linear-gradient(135deg, #121f30, #162a3e);
+    border: 1px solid #1e3a54;
+    border-left: 3px solid #1e3a54;
+    border-radius: 10px;
+    transition: border-color 0.2s ease, background 0.2s ease;
+}
+
+.host-package-item:hover,
+.host-event-item:hover,
+.host-request-item:hover {
+    border-color: #2d5575;
+    background: linear-gradient(135deg, #152538, #1a2f44);
+}
+
+/* Selectable packages */
+.host-package-item.pointer {
+    cursor: pointer;
+}
+
+.host-package-item .available-package-checkbox {
+    display: none;
+}
+
+.host-package-item.host-package-selected {
+    border-color: #15bf7f;
+    background: linear-gradient(135deg, #0f2a1f, #132f28);
+}
+
+/* Request status accents */
+.host-request-item.request-accent-green {
+    border-left-color: #15bf7f;
+}
+
+.host-request-item.request-accent-red {
+    border-left-color: #F32F63;
+}
+
+.host-request-item.request-accent-yellow {
+    border-left-color: #ffb536;
 }
\ No newline at end of file
diff --git a/www/update/database/5.x.x.php b/www/update/database/5.x.x.php
new file mode 100644
index 000000000..8c4deccf5
--- /dev/null
+++ b/www/update/database/5.x.x.php
@@ -0,0 +1,13 @@
+db->columnExist('repos_snap', 'Pkg_translation')) {
+    try {
+        $this->db->exec("ALTER TABLE repos_snap DROP COLUMN Pkg_translation");
+    } catch (Exception $e) {
+        throw new Exception('could not delete Pkg_translation column from repos_snap table');
+    }
+}
diff --git a/www/views/includes/containers/header/debug-mode.inc.php b/www/views/includes/containers/header/debug-mode.inc.php
index 24e976643..4a442d8f6 100644
--- a/www/views/includes/containers/header/debug-mode.inc.php
+++ b/www/views/includes/containers/header/debug-mode.inc.php
@@ -1,4 +1,4 @@
-
+
@@ -31,4 +31,4 @@
-
+ diff --git a/www/views/includes/containers/header/general-error-messages.inc.php b/www/views/includes/containers/header/general-error-messages.inc.php index 907892fe9..0fb028118 100644 --- a/www/views/includes/containers/header/general-error-messages.inc.php +++ b/www/views/includes/containers/header/general-error-messages.inc.php @@ -1,4 +1,4 @@ -
+
-
+ diff --git a/www/views/includes/containers/header/general-log-messages.inc.php b/www/views/includes/containers/header/general-log-messages.inc.php index acac98b91..87525d4a9 100644 --- a/www/views/includes/containers/header/general-log-messages.inc.php +++ b/www/views/includes/containers/header/general-log-messages.inc.php @@ -1,4 +1,4 @@ -
+
-
\ No newline at end of file + \ No newline at end of file diff --git a/www/views/includes/containers/header/menu.inc.php b/www/views/includes/containers/header/menu.inc.php index 7d45ec77d..8c4b953ce 100644 --- a/www/views/includes/containers/header/menu.inc.php +++ b/www/views/includes/containers/header/menu.inc.php @@ -1,197 +1,105 @@ - -
-
\ No newline at end of file + + + diff --git a/www/views/includes/containers/header/menu_old.inc.php b/www/views/includes/containers/header/menu_old.inc.php new file mode 100644 index 000000000..a7ca2dbfa --- /dev/null +++ b/www/views/includes/containers/header/menu_old.inc.php @@ -0,0 +1,197 @@ + + +
+ +
\ No newline at end of file diff --git a/www/views/includes/containers/header/service-status.inc.php b/www/views/includes/containers/header/service-status.inc.php index 02d9594fe..cb1ed3e01 100644 --- a/www/views/includes/containers/header/service-status.inc.php +++ b/www/views/includes/containers/header/service-status.inc.php @@ -1,4 +1,4 @@ -
+
@@ -10,4 +10,4 @@ -
\ No newline at end of file + \ No newline at end of file diff --git a/www/views/includes/containers/host/packages.inc.php b/www/views/includes/containers/host/packages.inc.php index 44ea50c1e..9674a4904 100644 --- a/www/views/includes/containers/host/packages.inc.php +++ b/www/views/includes/containers/host/packages.inc.php @@ -48,28 +48,25 @@ -
+
-
-
+
+
-
-
-

- ' . $item['Name'] . ' (uninstalled)'; - } else { - echo $item['Name']; - } ?> -

-

+
+

+ ' . $item['Name'] . ' (uninstalled)'; + } else { + echo $item['Name']; + } ?> +

+

+

-
+
-
- - Reset -
+

Reset

-
- - Delete -
+

Delete

@@ -66,7 +60,7 @@ $tooltip = 'No network information available'; } - echo '' . $interface . ''; + echo '' . $interface . ''; } } else { echo '

Unknown

'; diff --git a/www/views/includes/containers/hosts/list.inc.php b/www/views/includes/containers/hosts/list.inc.php index ebf0a77cf..f2ab47d88 100644 --- a/www/views/includes/containers/hosts/list.inc.php +++ b/www/views/includes/containers/hosts/list.inc.php @@ -115,7 +115,7 @@ } ?> '> -
+
-
+
+
+ + + +
+
-

-

+
-
-
- - - -
+
+
will contain all the host informations in order to be able to search on it (input 'search a host') ?> -
-
-
- - - - - -
+ // Here the
will contain all the host informations in order to be able to search on it (input 'search a host') + $hostAccent = 'host-accent-green'; + if ($agentStatus != 'running') { + $hostAccent = 'host-accent-red'; + } ?> + +
+
+ +
+
+ -
- -
-
-

- - - -

- -

-
+

+ + + +

+

+
+
+ +
+
+

+

-
- - + -
+ if (!$compactView) : ?> +
+ +

+

+
-
-
-
IP
-

-
- -
-
TYPE
-

-
+
+
+
OS
+

+
+ +
+
TYPE
+

+
-
-
AGENT VERSION
-

-
+
+
KERNEL
+

+
-
-
REBOOT REQUIRED
-

- '; - echo 'Yes'; - } else { - echo 'No'; - } ?> -

-
+
+
ARCH
+

+
-
-
OS
-
-

- -
-
+
+
PROFILE
+

+
-
-
OS VERSION
-

-
+
+
ENVIRONMENT
+

+ +

+
-
-
KERNEL
-

-
+
+
AGENT VERSION
+

+
-
-
ARCH
-

-
+
+
REBOOT REQUIRED
+

+ '; + echo 'Yes'; + } else { + echo 'No'; + } ?> +

+
-
-
PROFILE
-

-
+
+
INSTALLED
+

+ +

+
-
-
ENVIRONMENT
-

- -

-
+
+
AVAILABLE
+

+ = $packagesCountConsideredCritical) { + echo '' . $packagesAvailableTotal . ''; + } elseif ($packagesAvailableTotal >= $packagesCountConsideredOutdated) { + echo '' . $packagesAvailableTotal . ''; + } else { + echo '' . $packagesAvailableTotal . ''; + } ?> +

+
+
+ +
+ -
INSTALLED
-

- -

-
+ // Request status + if ($lastPendingRequest['Status'] == 'new') { + $requestStatus = 'Pending'; + $requestStatusIcon = 'pending.svg'; + } + if ($lastPendingRequest['Status'] == 'sent') { + $requestStatus = 'Sent'; + $requestStatusIcon = 'pending.svg'; + } + if ($lastPendingRequest['Status'] == 'running') { + $requestStatus = 'Running'; + $requestStatusIcon = 'loading.svg'; + } + if ($lastPendingRequest['Status'] == 'canceled') { + $requestStatus = 'Canceled'; + $requestStatusIcon = 'warning-red.svg'; + } + if ($lastPendingRequest['Status'] == 'failed') { + $requestStatus = 'Failed'; + $requestStatusIcon = 'error.svg'; + } + if ($lastPendingRequest['Status'] == 'completed') { + $requestStatus = 'Completed'; + $requestStatusIcon = 'check.svg'; + } -
-
AVAILABLE
-

- = $packagesCountConsideredCritical) { - echo '' . $packagesAvailableTotal . ''; - } elseif ($packagesAvailableTotal >= $packagesCountConsideredOutdated) { - echo '' . $packagesAvailableTotal . ''; - } else { - echo '' . $packagesAvailableTotal . ''; - } ?> -

-
-
- -
- = 1 and $failedCount >= 1) { + $requestStatus = 'Partial success'; + $requestStatusIcon = 'warning.svg'; } - // If there was packages to update, retrieve the number of packages updated - if ($responseJson['update']['status'] == 'done' or $responseJson['update']['status'] == 'failed') { - $successCount = $responseJson['update']['success']['count']; - $failedCount = $responseJson['update']['failed']['count']; - - // If the update was successful - if ($responseJson['update']['status'] == 'done') { - $requestStatus = 'Successful'; - $requestStatusIcon = 'check.svg'; - } - - // If the update failed - if ($responseJson['update']['status'] == 'failed') { - $requestStatus = 'Failed with errors'; - $requestStatusIcon = 'error.svg'; - } - - // If there was packages updated AND packages failed - if ($successCount >= 1 and $failedCount >= 1) { - $requestStatus = 'Partial success'; - $requestStatusIcon = 'warning.svg'; - } - - // If there was no packages updated AND packages failed - if ($successCount == 0 and $failedCount >= 1) { - $requestStatus = 'Failed'; - $requestStatusIcon = 'error.svg'; - } - - // Build a short info message - $responseDetails = $successCount . ' package(s) updated, ' . $failedCount . ' failed'; - - // Retrieve the list of packages updated - // $successPackages = $responseJson['update']['success']['packages']; - - // Retrieve the list of packages failed - // $failedPackages = $responseJson['update']['failed']['packages']; + // If there was no packages updated AND packages failed + if ($successCount == 0 and $failedCount >= 1) { + $requestStatus = 'Failed'; + $requestStatusIcon = 'error.svg'; } + + // Build a short info message + $responseDetails = $successCount . ' package(s) updated, ' . $failedCount . ' failed'; + + // Retrieve the list of packages updated + // $successPackages = $responseJson['update']['success']['packages']; + + // Retrieve the list of packages failed + // $failedPackages = $responseJson['update']['failed']['packages']; } } + } - // Only print the request title if it was executed less than 1h ago - if (strtotime($lastPendingRequest['Date'] . ' ' . $lastPendingRequest['Time']) >= strtotime(date('Y-m-d H:i:s') . ' - 1 hour')) : ?> -
LAST REQUEST
-
+ // Only print the request title if it was executed less than 1h ago + if (strtotime($lastPendingRequest['Date'] . ' ' . $lastPendingRequest['Time']) >= strtotime(date('Y-m-d H:i:s') . ' - 1 hour')) : ?> +
LAST REQUEST
+
+ '; + } else { + echo ' '; + } + } ?> +

'; - } else { - echo ' '; - } + echo $requestTitleShort; + + if (!empty($requestInfo)) { + echo ' - ' . $requestInfo; + } + + if (!empty($responseDetails)) { + echo ' - ' . $responseDetails; } ?> -

- -

-
- +
+ -
- - -
-
+ endif; + endif ?> +
+ -
- -
+
+ +
+
diff --git a/www/views/includes/containers/hosts/overview.inc.php b/www/views/includes/containers/hosts/overview.inc.php index 7bb65c0e3..e452d65ca 100644 --- a/www/views/includes/containers/hosts/overview.inc.php +++ b/www/views/includes/containers/hosts/overview.inc.php @@ -6,6 +6,45 @@ if ($totalHosts >= 1) : ?>

OVERVIEW

+
+
+ +
+

+

Hosts

+
+
+ +
+ +
+

+

Need update

+
+
+ +
+ +
+

+

Up to date

+
+
+ +
+ +
+

%

+

Hosts compliance

+
+
+
+ + + + + +
HOSTS ()
diff --git a/www/views/includes/containers/repos/includes-temp/group.inc.php b/www/views/includes/containers/repos/includes-temp/group.inc.php new file mode 100644 index 000000000..168c054cb --- /dev/null +++ b/www/views/includes/containers/repos/includes-temp/group.inc.php @@ -0,0 +1,52 @@ +
+
+
+ + + +
+ +
+ +
+
+ +
+
+

Select latest snapshots

+
+ + '; + } + echo '
'; + // echo ''; + echo '' . $repo['Name'] . ''; + echo '
'; + echo '
'; + } + + include(ROOT . '/views/includes/containers/repos/includes-temp/repo.inc.php'); + + $previousName = $repo['Name']; + } ?> +
+
diff --git a/www/views/includes/containers/repos/includes-temp/repo.inc.php b/www/views/includes/containers/repos/includes-temp/repo.inc.php new file mode 100644 index 000000000..86f4f57ac --- /dev/null +++ b/www/views/includes/containers/repos/includes-temp/repo.inc.php @@ -0,0 +1,140 @@ + + +
+
+
+

+ + + + + Release version + + +

+ +

The repository description

+
+ +
+
+ '; + echo 'local'; + } elseif ($repo['Type'] == 'mirror') { + echo ''; + echo 'mirror'; + } ?> +
+ +
+ '; + echo 'deb'; + } elseif ($repo['Package_type'] == 'rpm') { + echo ''; + echo 'rpm'; + } ?> +
+
+
+ +
+ listSnapshots($repo['repoId']) as $snapshot) : + // Generate repo relative path + if ($repo['Package_type'] == 'rpm') { + $repoRelativePath = 'rpm/' .$repo['Name'] . '/' . $repo['Releasever'] . '/' . $snapshot['Date']; + } + + if ($repo['Package_type'] == 'deb') { + $repoRelativePath = 'deb/' . $repo['Name'] . '/' . $repo['Dist'] . '/' . $repo['Section'] . '/' . $snapshot['Date']; + } + + // Check if a task is running on the snapshot + $taskRunning = $repoSnapshotController->taskRunning($snapshot['Id']); ?> + +
+
+ + + + + + + + +
+ +
+
+ + Calc. +
+ +
+ + + Signed + + + Unsigned + +
+ +
+ + + +
+
+
+ +
+ $env) { + $envId = $envIds[$index] ?? ''; ?> +
+ + +
+ +
+
+ + +
+

#deb

+

#apache

+

#tag3

+

#tag4

+

#tag5

+
+
+
diff --git a/www/views/includes/containers/repos/list.inc.php b/www/views/includes/containers/repos/list.inc.php index fcb81eeaf..59910cdf0 100644 --- a/www/views/includes/containers/repos/list.inc.php +++ b/www/views/includes/containers/repos/list.inc.php @@ -2,113 +2,110 @@ use \Controllers\User\Permission\Repo as RepoPermission; ?>
-
+

REPOSITORIES

-
-
-
- -

+
+
+ +
+

+

+
-

- -
-
-
-
- -
+
+ +
+

%

+

Used storage

+
+
-
-
+
+
+
STORAGE
+

/ free

+
-

% used storage

-
+
+
+
+ + + +
+

+

Last scheduled task

+
+
- -

- - -
- + + +
-
+ if (RepoPermission::allowedAction('edit-groups')) : ?> +
+ + Groups +
+ - -
- - Groups -
- +
+ + Source repositories +
+ -
- - Source repositories -
- +
+ + Create a new repository +
+ - if (RepoPermission::allowedAction('create')) : ?> -
- - Create a new repository +
+
- -
-
- - - - -
-
-
@@ -120,10 +117,4 @@ echo '

Nothing to show here!

'; } ?>
- -
\ No newline at end of file diff --git a/www/views/includes/containers/repos/properties.inc.php b/www/views/includes/containers/repos/properties.inc.php deleted file mode 100644 index f9de977aa..000000000 --- a/www/views/includes/containers/repos/properties.inc.php +++ /dev/null @@ -1,124 +0,0 @@ -
-

PROPERTIES

- -
-
-
-
- - - -
-
- - - -
-
-
- - -
- - -
- -
- - -
LAST SCHEDULED TASK
-
- -
- - - -
-
NEXT SCHEDULED TASKS
-
- -
- -
-
- - 0) { - echo $scheduledTask['left']['days'] . 'd'; - } else { - echo $scheduledTask['left']['time']; - } ?> - -
- - -
- -
-
- -
- - - -
\ No newline at end of file diff --git a/www/views/includes/containers/tasks/log.inc.php b/www/views/includes/containers/tasks/log.inc.php index 77e1301a5..cfa3fc3af 100644 --- a/www/views/includes/containers/tasks/log.inc.php +++ b/www/views/includes/containers/tasks/log.inc.php @@ -1,39 +1,85 @@ -
-

LOG

+ -
-
> - -
+
+

TASK #

-
-
+
> +
+
-
+ if ($taskInfo['Status'] == 'done') { + $status = 'Success'; // Override status text for better readability + $icon = 'check'; + } elseif ($taskInfo['Status'] == 'error' or $taskInfo['Status'] == 'stopped') { + $icon = 'warning-red'; + } elseif ($taskInfo['Status'] == 'scheduled') { + $icon = 'time'; + } elseif ($taskInfo['Status'] == 'running') { + $icon = 'loading'; + } ?> + + +
+
+

-
- -
- -
- -
- + if ((DEVEL or \Controllers\App\DebugMode::enabled()) and file_exists(MAIN_LOGS_DIR . '/repomanager-task-' . $taskId . '-log.process')) { + echo ''; + } ?>
- +

Status

+
+
+ +
+ +
+

+

Action

+
+ +
+ +
+

format('d-m-Y') ?>

+

Date

+
+
+
+ + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/www/views/includes/containers/tasks/tasks.inc.php b/www/views/includes/containers/tasks/tasks.inc.php new file mode 100644 index 000000000..f32175807 --- /dev/null +++ b/www/views/includes/containers/tasks/tasks.inc.php @@ -0,0 +1,47 @@ + + +
+

TASKS

+ +
+
+ +
+

+

Total tasks

+
+
+ +
+ +
+

+

Running tasks

+
+
+ +
+ +
+

+

Scheduled tasks

+
+
+
+ +
+ +
+
diff --git a/www/views/includes/footer.inc.php b/www/views/includes/footer.inc.php index 696ccb9ec..8a71d369c 100644 --- a/www/views/includes/footer.inc.php +++ b/www/views/includes/footer.inc.php @@ -1,5 +1,5 @@ @@ -162,7 +160,7 @@ 'settings' ]; } -if (__ACTUAL_URI__[1] == 'run') { +if (in_array(__ACTUAL_URI__[1], ['tasks', 'task', 'run'])) { $jsFiles = [ 'functions/task', 'events/task/actions', diff --git a/www/views/includes/head.inc.php b/www/views/includes/head.inc.php index 3d797a083..25eee3a27 100644 --- a/www/views/includes/head.inc.php +++ b/www/views/includes/head.inc.php @@ -8,6 +8,7 @@ + @@ -32,6 +33,8 @@ */ $additionalCss = [ "run" => "run.css", + "tasks" => "run.css", + "task" => "run.css", "browse" => "browse.css", "stat" => "stats-hosts.css", "stats" => "stats-hosts.css", diff --git a/www/views/includes/panels/header/menu-burger.inc.php b/www/views/includes/panels/header/menu-burger.inc.php index ac2d87f61..013ff6142 100644 --- a/www/views/includes/panels/header/menu-burger.inc.php +++ b/www/views/includes/panels/header/menu-burger.inc.php @@ -8,11 +8,11 @@
-
TASKS
+
TASKS
- +
HOSTS
diff --git a/www/views/includes/repos-list.inc.php b/www/views/includes/repos-list.inc.php index 03267d3c1..bc0d00ffd 100644 --- a/www/views/includes/repos-list.inc.php +++ b/www/views/includes/repos-list.inc.php @@ -1,426 +1,13 @@ $group) { + if (!$group['show']) { + continue; } - /** - * Getting repos list of the group - */ - $reposList = $myrepoListing->listByGroup($group['Name']); - - /** - * Count repositories - * To have the exact number of repos, count by their repoId (to avoid duplicate repos) - */ - $reposCount = count(array_unique(array_column($reposList, 'repoId'))); - - /** - * Generate count message - */ - if ($reposCount < 2) { - $countMessage = $reposCount . ' repository'; - } else { - $countMessage = $reposCount . ' repositories'; - } ?> - -
-
-
-

-

-
- -
- -
-

Select latest snapshots

-
- - '; - continue; - } ?> - - -
- -
- format('d-m-Y'); - $time = $repo['Time']; - $type = $repo['Type']; - $signed = $repo['Signed']; - $arch = $repo['Arch']; - $env = $repo['Env']; - $description = $repo['Description']; - $repoId = $repo['repoId']; - $snapId = $repo['snapId']; - $envId = $repo['envId']; - - // Conditional variables to print or not some informations - $printRepoName = true; - $printRepoDist = true; - $printRepoSection = true; - $printReleaseVersion = true; - $printEmptyLine = false; - $printDoubleEmptyLine = false; - - if ($name == $previousName) { - $printRepoName = false; - } - - if ($packageType != $previousPackageType) { - $printRepoName = true; - $envCounter = 1; - } - - if ($packageType == 'rpm') { - $snapshotPath = REPOS_DIR . '/rpm/' . $name . '/' . $releaseVersion . '/' . $date; - - if ($name == $previousName and $snapId != $previousSnapId) { - $printEmptyLine = true; - $envCounter = 1; - } - if ($name == $previousName and $releaseVersion === $previousReleaseVersion) { - $printReleaseVersion = false; - } - if ($name == $previousName and $releaseVersion !== $previousReleaseVersion) { - $printDoubleEmptyLine = true; - $envCounter = 1; - } - - // Reset previous dist and section values to avoid some display bugs with deb repos having the same name as rpm repos - $previousDist = ''; - $previousSection = ''; - } - - if ($packageType == 'deb') { - $snapshotPath = REPOS_DIR . '/deb/' . $name . '/' . $dist . '/' . $section . '/' . $date; - - if ($name == $previousName and $dist == $previousDist and $section == $previousSection) { - $printRepoName = false; - $printRepoDist = false; - $printRepoSection = false; - } - if ($name == $previousName and $previousDist != $dist) { - $printDoubleEmptyLine = true; - $envCounter = 1; - } - if ($name == $previousName and $previousDist == $dist and $section != $previousSection) { - $printDoubleEmptyLine = true; - $envCounter = 1; - } - if ($name == $previousName and $dist == $previousDist and $section == $previousSection and $snapId != $previousSnapId) { - $printEmptyLine = true; - $envCounter = 1; - } - if ($previousPackageType == 'deb' and $packageType == 'deb' and $name == $previousName) { - $printRepoName = false; - } - - // Reset previous release version value to avoid some display bugs with rpm repos having the same name as deb repos - $previousReleaseVersion = ''; - } - - // If the current env is the third to be print for the current repo, then print an empty line before to let a space between the previous env - if ($envCounter >= 3) { - $printEmptyLine = true; - } - - // Check if a task is running on the snapshot - $taskRunning = $repoSnapshotController->taskRunning($snapId); - - // Print double empty line - if ($printDoubleEmptyLine) { - echo '
'; - } ?> - -
- -
- - -
- - -
-

-

-

-
-
- -
-

-

-

-
- '; - echo '

Release version ' . $releaseVersion . '

'; - echo ''; - } else { - echo '

Release version ' . $releaseVersion . '

'; - } - } - } ?> -
- -
- '; - } - - /** - * Print a failed icon if repo snapshot rebuild has failed - */ - if ($rebuild == 'failed') { - echo ''; - } - } - - /** - * Print a warning icon if snapshot directory does not exist on the server - * Only print it if there is no task running on the snapshot because some tasks like rename can temporarily remove the snapshot directory - */ - if (!$taskRunning) { - if ($packageType == 'rpm') { - if (!is_dir($snapshotPath)) { - echo ''; - } - } - if ($packageType == 'deb') { - if (!is_dir($snapshotPath)) { - echo ''; - } - } - } - } - - /** - * Checkbox are printed for all users - * Admins can execute all actions - * Regular users can execute actions only if they have the permission to do so (but they can at least 'Install' the repository) - */ - // Print checkbox only if the snapshot is different from the previous one and there is no operation running on the snapshot - if ($snapId != $previousSnapId) : - if ($taskRunning) : ?> - - - env-name="" repo-type="" group-id="" title="Select and execute an action."> - -
- - - -
- -
- - "> - - - - - -
- -
- - '; - } - if ($type == "local") { - echo ''; - } ?> - - - - '; - } - if ($signed == "false") { - echo ''; - } ?> - - - Calc. -
- -
- - '; - } else { - echo '
'; - } - if (!empty($env)) { - echo ''; - } - echo '
'; ?> - -
"> - -
- -
- '; - } - } - - if ($packageType == 'deb') { - if (!is_link(REPOS_DIR . '/deb/' . $name . '/' . $dist . '/' . $section . '/' . $env)) { - echo ''; - } - } - } - - // If the user is an admin or is a regular user with the 'removeEnv' permission - if (RepoPermission::allowedAction('removeEnv')) { ?> - - -
- - '; - if (!empty($env)) { - echo ''; - } - echo '
'; ?> - -
-
- - -
- -
-
- \ No newline at end of file diff --git a/www/views/includes/tables/host/available-packages.inc.php b/www/views/includes/tables/host/available-packages.inc.php index ccfff84a6..301b5af88 100644 --- a/www/views/includes/tables/host/available-packages.inc.php +++ b/www/views/includes/tables/host/available-packages.inc.php @@ -25,79 +25,81 @@
- +
+ -
-
- -
+
+
+ -
-
-

-

+
+

+ +

+ +
- -

- -
+
+

-
-
- + } + } - - title="" /> - + // If there is no package update already running, display the checkbox + if ($packageUpdateRunning == false) { ?> + title="" /> +
+ + +
+
- - -
- -
- + + +
diff --git a/www/views/includes/tables/host/history.inc.php b/www/views/includes/tables/host/history.inc.php index 3a1451e33..247b75c0a 100644 --- a/www/views/includes/tables/host/history.inc.php +++ b/www/views/includes/tables/host/history.inc.php @@ -1,13 +1,16 @@
+
+ $packageState) : ?> -
-
+
+
+

format('d-m-Y') ?>

-
+
$packages) : if (empty($packages)) { @@ -60,6 +63,7 @@ endforeach; unset($date, $packageState, $state, $packages, $title, $icon, $count); ?> +
diff --git a/www/views/includes/tables/host/requests.inc.php b/www/views/includes/tables/host/requests.inc.php index 68e612c6b..752e4bfc8 100644 --- a/www/views/includes/tables/host/requests.inc.php +++ b/www/views/includes/tables/host/requests.inc.php @@ -7,8 +7,11 @@ endif; if (!empty($reloadableTableContent)) : + ?> +
+
-
+
'; } } ?> -
-
-

format('d-m-Y') ?>

-

+

+

format('d-m-Y') ?>

+

+
-
-

- -

+

+

+

diff --git a/www/views/includes/tables/tasks/list.inc.php b/www/views/includes/tables/tasks/list.inc.php index 199a8c18e..00845e2f6 100644 --- a/www/views/includes/tables/tasks/list.inc.php +++ b/www/views/includes/tables/tasks/list.inc.php @@ -1,343 +1,306 @@ + +
-
-
+
+
-
- -
+
- - -
-
-
- - - -
- -
- ' . DateTime::createFromFormat('Y-m-d', $item['Date'])->format('d-m-Y') . ' ' . $item['Time'] . ''; - endif; +
+ Error decoding task #' . $item['Id'] . ' parameters: ' . $e->getMessage() . '

'; + continue; + } + + // Determine status accent color + if ($item['Status'] == 'done') { + $taskAccent = 'task-accent-green'; + } elseif ($item['Status'] == 'error' or $item['Status'] == 'stopped') { + $taskAccent = 'task-accent-red'; + } elseif ($item['Status'] == 'running') { + $taskAccent = 'task-accent-running'; + } elseif ($item['Status'] == 'queued') { + $taskAccent = 'task-accent-yellow'; + } elseif ($item['Status'] == 'scheduled') { + $taskAccent = 'task-accent-orange'; + } + + // Determine action title and icon + $icon = 'plus'; + $actionTitle = Task::generateLiteralAction($taskRawParams['action']); + + if ($taskRawParams['action'] == 'create') { + $icon = 'plus'; + if (!isset($taskRawParams['repo-type'])) { + $actionTitle = 'New repository'; + } else { + $actionTitle = $taskRawParams['repo-type'] == 'local' ? 'New local repository' : 'New mirror repository'; + } + } + if ($taskRawParams['action'] == 'update') { + $icon = 'update'; + } + if ($taskRawParams['action'] == 'rebuild') { + $icon = 'update'; + } + if ($taskRawParams['action'] == 'rename') { + $icon = 'edit'; + } + if ($taskRawParams['action'] == 'env') { + $icon = 'link'; + } + if ($taskRawParams['action'] == 'duplicate') { + $icon = 'duplicate'; + } + if ($taskRawParams['action'] == 'delete') { + $icon = 'delete'; + } + if ($taskRawParams['action'] == 'removeEnv') { + $icon = 'delete'; + } + if ($item['Status'] == 'running') { + $icon = 'loading'; + } + + // Determine click behavior class + $actionBtn = in_array($item['Status'], ['scheduled', 'queued', 'disabled']) ? 'task-item-selectable' : ''; ?> + + > +
+
+ + + - /** - * If task is scheduled - */ - if ($item['Type'] == 'scheduled') : - if (!empty($item['Date']) and !empty($item['Time'])) { - $class = 'margin-top-5'; - } else { - $class = ''; - } ?> + - +
'; - echo DateTime::createFromFormat('Y-m-d', $taskRawParams['schedule']['schedule-date'])->format('d-m-Y') . ' ' . $taskRawParams['schedule']['schedule-time'] . ':00'; - } - - /** - * Case it is a recurring scheduled task - */ - if ($taskRawParams['schedule']['schedule-type'] == 'recurring') { - if ($taskRawParams['schedule']['schedule-frequency'] == 'hourly') { - echo 'Hourly scheduled task'; - } + // Date and time for immediate tasks + if (!empty($item['Date']) and !empty($item['Time'])) : ?> + format('d-m-Y') ?> + + + + format('d-m-Y') . ' ' . $taskRawParams['schedule']['schedule-time'] . ':00'; + } + if ($taskRawParams['schedule']['schedule-type'] == 'recurring') { + if ($taskRawParams['schedule']['schedule-frequency'] == 'hourly') echo 'Hourly'; + if ($taskRawParams['schedule']['schedule-frequency'] == 'daily') echo 'Daily'; + if ($taskRawParams['schedule']['schedule-frequency'] == 'weekly') echo 'Weekly'; + if ($taskRawParams['schedule']['schedule-frequency'] == 'monthly') echo 'Monthly'; + if ($taskRawParams['schedule']['schedule-frequency'] == 'cron') echo 'Cron'; + } ?> + + + Disabled + + + - if ($taskRawParams['schedule']['schedule-frequency'] == 'daily') { - echo 'Daily scheduled task'; - } + +
+
- if ($taskRawParams['schedule']['schedule-frequency'] == 'weekly') { - echo 'Weekly scheduled task'; - } +
+ getRepo($item['Id']); ?> - if ($taskRawParams['schedule']['schedule-frequency'] == 'monthly') { - echo 'Monthly scheduled task'; + +
- if ($taskRawParams['schedule']['schedule-frequency'] == 'cron') { - echo 'Cron scheduled task'; - } - } ?> - +
- - - - -
-
- -
- - getRepo($item['Id']); ?> - - - '; } - } - } ?> -
-
- '; - } - - // Print relaunch button if task has failed - if (($item['Status'] == 'error' or $item['Status'] == 'stopped') and !empty($item['Id'])) { - if (IS_ADMIN or in_array('relaunch', USER_PERMISSIONS['tasks']['allowed-actions'])) { - echo ''; - } - } + if (($item['Status'] == 'error' or $item['Status'] == 'stopped') and !empty($item['Id'])) { + if (IS_ADMIN or in_array('relaunch', USER_PERMISSIONS['tasks']['allowed-actions'])) { + echo ''; + } + } - if ($item['Status'] == 'queued') { - echo ''; - } + if ($item['Status'] == 'queued') { + echo ''; + } - if ($item['Status'] == 'running') { - if (IS_ADMIN or in_array('stop', USER_PERMISSIONS['tasks']['allowed-actions'])) { - echo ''; - } - echo ''; - } + if ($item['Status'] == 'running') { + if (IS_ADMIN or in_array('stop', USER_PERMISSIONS['tasks']['allowed-actions'])) { + echo ''; + } + } ?> +
+
+ - if ($item['Status'] == 'done') { - echo ''; - } + +
+
+
+
SCHEDULE TYPE
+ +

Single execution

+ +

Recurring execution

+ +
- if ($item['Status'] == 'error') { - echo ''; - } +
+ +
SCHEDULE DATE
+

format('d-m-Y') . ' ' . $taskRawParams['schedule']['schedule-time'] . ':00' ?>

+ +
SCHEDULE FREQUENCY
+

+ +

+ +
+
- if ($item['Status'] == 'stopped') { - echo ''; - } + +
DUPLICATE TO
+

+ '; - } - } ?> -
-
+ if ($taskRawParams['action'] == 'rename') : ?> +
RENAME TO
+

+ - -
-
-
-
SCHEDULE TYPE
+
-

Single execution

+ if (!empty($taskRawParams['arch'])) : ?> +
+
ARCHITECTURE
+
+ ' . Label::white($architecture) . '

'; + } ?> +
+
-

Recurring execution

+ if (!empty($taskRawParams['env'])) : ?> +
+
ENVIRONMENT
+
+ ' . Label::envtag($env) . '

'; + } ?> +
+
-
+
-
SCHEDULE DATE
-

format('d-m-Y') . ' ' . $taskRawParams['schedule']['schedule-time'] . ':00' ?>

+ if (!empty($taskRawParams['gpg-check'])) : ?> +
+
CHECK GPG SIGNATURES
+
+ '; + echo 'Enabled'; + } else { + echo ''; + echo 'Disabled'; + } ?> +
+
-
SCHEDULE FREQUENCY
-

- - -

+ if (!empty($taskRawParams['gpg-sign'])) : ?> +
+
SIGN WITH GPG
+
+ '; + echo 'Enabled'; + } else { + echo ''; + echo 'Disabled'; + } ?> +
+
-
- - -
DUPLICATE TO
-

- -
RENAME TO
-

- - -
- -
-
ARCHITECTURE
-
- ' . $architecture . '
'; - } ?> -
-
- +
-
ENVIRONMENT
-
- ' . \Controllers\Utils\Generate\Html\Label::envtag($env) . '
'; - } ?> -
-
- -
- -
- -
-
CHECK GPG SIGNATURES
+
NOTIFY ON TASK ERROR
'; echo 'Enabled'; } else { @@ -346,15 +309,12 @@ } ?>
-
-
SIGN WITH GPG
+
NOTIFY ON TASK SUCCESS
'; echo 'Enabled'; } else { @@ -363,80 +323,49 @@ } ?>
- -
- -
-
-
NOTIFY ON TASK ERROR
-
- '; - echo 'Enabled'; - } else { - echo ''; - echo 'Disabled'; - } ?> -
-
-
NOTIFY ON TASK SUCCESS
-
- '; - echo 'Enabled'; - } else { - echo ''; - echo 'Disabled'; - } ?> +
+
+
SEND A REMINDER
+

+ '; + } else { + echo $reminder . ' days before
'; + } + } + } ?> +

-
-
-
-
-
SEND A REMINDER
-

- '; - } else { - echo $reminder . ' days before
'; +

+
CONTACT
+

+ '; } - } - } ?> -

-
- -
-
CONTACT
-

- '; - } - } ?> -

+ } ?> +

+
-
- + +
- +


diff --git a/www/views/layout.html.php b/www/views/layout.html.php index 6471302aa..6b5240eef 100644 --- a/www/views/layout.html.php +++ b/www/views/layout.html.php @@ -3,28 +3,37 @@ -
- - - -
- -
- - - -
+
+ + + + +
+ + + + + + + +
+ + +
+
+ + + + + + \ No newline at end of file diff --git a/www/views/templates/tasks/rebuild.inc.php b/www/views/templates/tasks/rebuild.inc.php index 133809ba8..64c2ff1f1 100644 --- a/www/views/templates/tasks/rebuild.inc.php +++ b/www/views/templates/tasks/rebuild.inc.php @@ -1,21 +1,4 @@ -
-
-

REBUILD REPOSITORY METADATA

- -
-

format('d-m-Y') . ' ' . $taskInfo['Time'] ?>

-
-

Task #

- '; - } ?> -
-
-
-
- -
+
REPOSITORY
diff --git a/www/views/templates/tasks/update.inc.php b/www/views/templates/tasks/update.inc.php index 851d9f8e6..82046cdd4 100644 --- a/www/views/templates/tasks/update.inc.php +++ b/www/views/templates/tasks/update.inc.php @@ -1,20 +1,3 @@ -
-
-

UPDATE REPOSITORY

- -
-

format('d-m-Y') . ' ' . $taskInfo['Time'] ?>

-
-

Task #

- '; - } ?> -
-
-
-
-
From 7288c2385c205e71c995386231b89e6b92094566 Mon Sep 17 00:00:00 2001 From: Ludovic <54670129+lbr38@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:18:51 +0200 Subject: [PATCH 2/3] patch --- .../Layout/Panel/vars/repos/edit.vars.inc.php | 2 +- .../Panel/vars/repos/install.vars.inc.php | 48 ++++---- .../Panel/vars/repos/rename.vars.inc.php | 23 ++++ www/public/resources/js/events/repo/edit.js | 110 +++++++----------- www/public/resources/js/events/repo/env.js | 2 +- .../resources/js/events/repo/install.js | 41 ++++--- www/public/resources/js/functions.js | 19 +-- .../resources/styles/components/label.css | 6 +- www/public/resources/styles/stats-hosts.css | 16 ++- .../includes/containers/hosts/list.inc.php | 8 +- .../containers/hosts/overview.inc.php | 50 +++----- .../repos/includes-temp/group.inc.php | 21 ++-- .../includes/panels/repos/install.inc.php | 8 +- .../includes/panels/repos/rename.inc.php | 9 ++ 14 files changed, 174 insertions(+), 189 deletions(-) create mode 100644 www/controllers/Layout/Panel/vars/repos/rename.vars.inc.php create mode 100644 www/views/includes/panels/repos/rename.inc.php diff --git a/www/controllers/Layout/Panel/vars/repos/edit.vars.inc.php b/www/controllers/Layout/Panel/vars/repos/edit.vars.inc.php index a429abbd0..b09ef5662 100644 --- a/www/controllers/Layout/Panel/vars/repos/edit.vars.inc.php +++ b/www/controllers/Layout/Panel/vars/repos/edit.vars.inc.php @@ -14,7 +14,7 @@ * Check that action and repos params have been sent */ if (empty($item['repos'])) { - throw new Exception('Task repositories required'); + throw new Exception('Repositories required'); } $slidePanelTitle = 'EDIT REPOSITORY & SNAPSHOT PROPERTIES'; diff --git a/www/controllers/Layout/Panel/vars/repos/install.vars.inc.php b/www/controllers/Layout/Panel/vars/repos/install.vars.inc.php index 767fb96f4..0f7f94515 100644 --- a/www/controllers/Layout/Panel/vars/repos/install.vars.inc.php +++ b/www/controllers/Layout/Panel/vars/repos/install.vars.inc.php @@ -1,33 +1,28 @@ existsId($repo['repo-id'])) { throw new Exception('Repository Id #' . $repo['repo-id'] . ' does not exist'); } @@ -45,14 +38,10 @@ throw new Exception('Snapshot Id #' . $repo['snap-id'] . ' does not exist'); } - /** - * Retrieve all repo data from the Ids - */ + // Retrieve all repo data from the Ids $repoController->getAllById($repo['repo-id'], $repo['snap-id']); - /** - * Retrieve the package type of the repo - */ + // Retrieve the package type of the repo $packageType = $repoController->getPackageType(); $packagesTypes[] = $packageType; ?> @@ -67,20 +56,23 @@ echo '

' . $repoController->getName() . ' ❯ ' . $repoController->getReleasever() . '

⸺'; } ?> -

getDateFormatted() ?>

+

getDateFormatted() ?>

-
- + +
-

+        

+        Alternative syntax (deb822):
+        

         
-        

+        

         get(json_decode($item['repos'], true));
+} catch (JsonException $e) {
+    throw new Exception('Error while decoding repositories: ' . $e->getMessage());
+}
diff --git a/www/public/resources/js/events/repo/edit.js b/www/public/resources/js/events/repo/edit.js
index 200ffbd62..346d1ffbd 100644
--- a/www/public/resources/js/events/repo/edit.js
+++ b/www/public/resources/js/events/repo/edit.js
@@ -1,75 +1,45 @@
 /**
- *  Event: click on edit repository button
- *  TODO: wait for repos list refactoring before implementing the repo-edit-btn
+ *  Event: click on rename repository button
  */
-// $(document).on('click','.repo-edit-btn',function (e) {
-//     e.preventDefault();
-
-//     const repoId = $(this).attr('repo-id');
-
-//     /**
-//      *  The buttons that will be displayed in the confirm box
-//      */
-//     var buttons = [];
-
-//     /**
-//      *  The list of allowed actions the user can execute on the selected repositories
-//      *  By default: all, unless the user has specific permissions
-//      *  Those permissions are later verified by the server so even if the user tries to execute an action he is not allowed to, it will not work
-//      */
-//     var allowedActions = ['update', 'duplicate', 'env', 'rebuild', 'rename', 'edit', 'install', 'delete'];
-
-//     /**
-//      *  Get permissions from cookie
-//      */
-//     if (mycookie.exists('user_permissions')) {
-//         var userPermissions = JSON.parse(mycookie.get('user_permissions'));
-
-//         // Reset allowed actions array
-//         var allowedActions = [];
-
-//         // Loop through all permissions and check if the user has the permission to execute the action
-//         if (userPermissions.repositories && userPermissions.repositories['allowed-actions']) {
-//             var allowedActions = userPermissions.repositories['allowed-actions'];
-//         }
-//     }
-
-//     /**
-//      *  Define confirm box buttons depending on the allowed actions
-//      */
-//     if (allowedActions.includes('rename')) {
-//         buttons.push(
-//             {
-//                 'text': 'Rename',
-//                 'color': 'blue-alt',
-//                 'callback': function () {
-//                     executeAction('rename');
-//                 }
-//             }
-//         );
-//     }
-
-//     if (allowedActions.includes('edit')) {
-//         buttons.push(
-//             {
-//                 'text': 'Edit',
-//                 'color': 'blue-alt',
-//                 'callback': function () {
-//                     executeAction('edit')
-//                 }
-//             }
-//         );
-//     }
-
-//     myconfirmbox.print(
-//         {
-//             'title': 'Edit repository',
-//             'message': '',
-//             'id': 'repo-edit-confirm-box',
-//             'buttons': buttons
-//         }
-//     );
-// });
+$(document).on('click','.repo-rename-btn',function (e) {
+    e.preventDefault(e);
+
+    const repoId = $(this).attr('repo-id');
+
+    // The buttons that will be displayed in the confirm box
+    var buttons = [];
+
+    /**
+     *  The list of allowed actions the user can execute on the selected repositories
+     *  By default: all, unless the user has specific permissions
+     *  Those permissions are later verified by the server so even if the user tries to execute an action he is not allowed to, it will not work
+     */
+    var allowedActions = ['update', 'duplicate', 'env', 'rebuild', 'rename', 'edit', 'install', 'delete'];
+
+    // Get permissions from cookie
+    if (mycookie.exists('user_permissions')) {
+        var userPermissions = JSON.parse(mycookie.get('user_permissions'));
+
+        // Reset allowed actions array
+        var allowedActions = [];
+
+        // Loop through all permissions and check if the user has the permission to execute the action
+        if (userPermissions.repositories && userPermissions.repositories['allowed-actions']) {
+            var allowedActions = userPermissions.repositories['allowed-actions'];
+        }
+    }
+
+    if (!allowedActions.includes('rename')) {
+        myalert.print('You do not have permission to rename repositories', 'error');
+        return;
+    }
+
+    // Get panel
+    mypanel.get('repos/rename', {
+        repos: [repoId],
+    });
+
+});
 
 /**
  *  Event: submit repository edit form
diff --git a/www/public/resources/js/events/repo/env.js b/www/public/resources/js/events/repo/env.js
index 6582017c5..53371ea68 100644
--- a/www/public/resources/js/events/repo/env.js
+++ b/www/public/resources/js/events/repo/env.js
@@ -15,7 +15,7 @@ $(document).on('click', '.snap-env-container', function (e) {
  */
 $(document).on('change', '.select-env-checkbox', function (e) {
     // Prevent parent to be triggered
-    e.stopPropagation();
+    e.stopPropagation(e);
 
     var container = $(this).closest('.snap-env-container');
 
diff --git a/www/public/resources/js/events/repo/install.js b/www/public/resources/js/events/repo/install.js
index 9e30bf627..404d698b3 100644
--- a/www/public/resources/js/events/repo/install.js
+++ b/www/public/resources/js/events/repo/install.js
@@ -1,25 +1,19 @@
 /**
  *  Event: print repository installation commands on environment change
  */
-$(document).on('change','#repo-install-select-env',function () {
-    event.preventDefault();
+$(document).on('change','#repo-install-select-env',function (e) {
+    event.preventDefault(e);
 
-    /**
-     *  Get environment name
-     */
-    var env = $(this).val();
+    // Get environment name
+    const env = $(this).val();
 
-    /**
-     *  If no environment is selected, hide the install commands
-     */
+    // If no environment is selected, hide the install commands
     if (env == '') {
         $('div#repository-install-commands-container').hide();
         return;
     }
 
-    /**
-     *  For each repository, print the installation commands
-     */
+    // For each repository, print the installation commands
     $('pre.repository-install-commands').each(function () {
         var url = $(this).attr('url');
         var hostname = $(this).attr('hostname');
@@ -36,6 +30,17 @@ $(document).on('change','#repo-install-select-env',function () {
             html += 'EOF';
         }
 
+        // deb822 syntax
+        if (packageType == 'deb-alt') {
+            html  = 'cat << EOF > /etc/apt/sources.list.d/' + prefix + name + '-' + dist + '-' + component + '.list\n';
+            html += 'Types: deb\n';
+            html += 'URIs: ' + url + '/deb/' + name + '/' + dist + '/' + component + '/' + env + '\n';
+            html += 'Suites: ' + dist + '\n';
+            html += 'Components: ' + component + '\n';
+            html += 'Enabled: yes\n';
+            html += 'EOF';
+        }
+
         if (packageType == 'rpm') {
             html  = 'cat << EOF > /etc/yum.repos.d/' + prefix + name + '.repo\n'
             html += '['+ prefix + name + '_' + env + ']\n'
@@ -47,19 +52,13 @@ $(document).on('change','#repo-install-select-env',function () {
             html += 'EOF';
         }
 
-        /**
-         *  Print the environment next to the repositories
-         */
+        // Print the environment next to the repositories
         printEnv(env, '.repository-install-env');
 
-        /**
-         *  Append the installation commands
-         */
+        // Append the installation commands
         $(this).html(html);
 
-        /**
-         *  Print the installation commands
-         */
+        // Print the installation commands
         $('div#repository-install-commands-container').show();
     });
 
diff --git a/www/public/resources/js/functions.js b/www/public/resources/js/functions.js
index 3a88a68b5..0f0eb6b1d 100644
--- a/www/public/resources/js/functions.js
+++ b/www/public/resources/js/functions.js
@@ -209,19 +209,24 @@ function empty(value)
  */
 function printEnv(env, selector)
 {
-    // Default colors
-    var background = '#ffffff';
-    var color = '#000000';
+    // Default: outlined style with subtle gray
+    var color = '#c0d0e2';
+    var border = '1.5px solid #c0d0e2';
+    var background = 'transparent';
 
     // Check if the environment color is set in localStorage
     if (localStorage.getItem('env/' + env) !== null) {
-        definition = JSON.parse(localStorage.getItem('env/' + env));
-        color = definition.color;
-        background = definition.background;
+        var definition = JSON.parse(localStorage.getItem('env/' + env));
+
+        if (definition.background && definition.background !== '#ffffff') {
+            // Use configured color for border and text (outlined style)
+            color = definition.background;
+            border = '1.5px solid ' + definition.background;
+        }
     }
 
     // Generate html
-    var html = '⸺' + env + '';
+    var html = '⸺' + env + '';
 
     // Print environment
     $(selector).html(html);
diff --git a/www/public/resources/styles/components/label.css b/www/public/resources/styles/components/label.css
index f1aa78429..e0f7fab38 100644
--- a/www/public/resources/styles/components/label.css
+++ b/www/public/resources/styles/components/label.css
@@ -10,7 +10,6 @@
     text-align: center;
     border-radius: 20px;
     background: transparent;
-    border: 1.5px solid currentColor;
     box-shadow: none;
 }
 .label-icon-tr {
@@ -35,22 +34,27 @@
 .label-white, .label-black {
     color: #c0d0e2;
     border-color: #c0d0e2;
+    border: 1.5px solid #c0d0e2;
 }
 .label-green {
     color: #15bf7f;
     border-color: #15bf7f;
+    border: 1.5px solid #15bf7f;
 }
 .label-blue {
     color: #5473e8;
     border-color: #5473e8;
+    border: 1.5px solid #5473e8;
 }
 .label-red {
     color: #F32F63;
     border-color: #F32F63;
+    border: 1.5px solid #F32F63;
 }
 .label-yellow {
     color: #ffb536;
     border-color: #ffb536;
+    border: 1.5px solid #ffb536;
 }
 .label-tr {
     color: #8A99AA;
diff --git a/www/public/resources/styles/stats-hosts.css b/www/public/resources/styles/stats-hosts.css
index cffb719b9..99a6624bb 100644
--- a/www/public/resources/styles/stats-hosts.css
+++ b/www/public/resources/styles/stats-hosts.css
@@ -20,13 +20,6 @@
     column-gap: 15px;
 }
 
-.host-available-packages-label {
-    min-width: 20px;
-    text-align: center;
-    border-radius: 60px;
-    padding: 0 1px;
-}
-
 .host-additionnal-info {
     display: none;
     flex-direction: column;
@@ -175,7 +168,7 @@
 .hosts-charts-container {
     display: flex;
     flex-direction: column;
-    justify-content: space-between;
+    gap: 20px;
     margin: 0 auto 20px auto;
 }
 
@@ -331,7 +324,12 @@
 
     .hosts-charts-container {
         display: grid;
-        grid-template-columns: repeat(3, 32.5%);
+        grid-template-columns: repeat(3, 1fr);
+        gap: 20px;
+    }
+
+    .hosts-chart-wide {
+        grid-column: span 2;
     }
 
     .event-packages-details {
diff --git a/www/views/includes/containers/hosts/list.inc.php b/www/views/includes/containers/hosts/list.inc.php
index f2ab47d88..fb6aeb45e 100644
--- a/www/views/includes/containers/hosts/list.inc.php
+++ b/www/views/includes/containers/hosts/list.inc.php
@@ -269,17 +269,17 @@
                                                                 
                                                                     
-

+

0) { $class = ''; if ($packagesAvailableTotal >= $packagesCountConsideredCritical) { - $class = 'bkg-red'; + $class = 'label-red'; } elseif ($packagesAvailableTotal >= $packagesCountConsideredOutdated) { - $class = 'bkg-yellow'; + $class = 'label-yellow'; } - echo '

' . $packagesAvailableTotal . '

'; + echo '

' . $packagesAvailableTotal . '

'; } ?>
diff --git a/www/views/includes/containers/hosts/overview.inc.php b/www/views/includes/containers/hosts/overview.inc.php index e452d65ca..a1469e470 100644 --- a/www/views/includes/containers/hosts/overview.inc.php +++ b/www/views/includes/containers/hosts/overview.inc.php @@ -40,20 +40,29 @@
+
+
+
OPERATING SYSTEMS
+
+
+ +
+
+
+
+
+
ARCHITECTURES
+
+
+ +
-
-
-
HOSTS ()
- -
- +
- -
@@ -93,30 +102,6 @@
-
-
OPERATING SYSTEMS
- -
-
- -
- -
-
-
- -
-
ARCHITECTURES
- -
-
- -
- -
-
-
-
ENVIRONMENTS
@@ -191,7 +176,6 @@