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/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('rename', json_decode($item['repos'], true));
+} catch (JsonException $e) {
+    throw new Exception('Error while retrieving the form content: ' . $e->getMessage());
+}
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/Deb.php b/www/controllers/Repo/Deb.php
index f16885657..5534e98ab 100644
--- a/www/controllers/Repo/Deb.php
+++ b/www/controllers/Repo/Deb.php
@@ -20,14 +20,6 @@ public function getIdByNameDistComponent(string $name, string $distribution, str
         return $this->model->getIdByNameDistComponent($name, $distribution, $component);
     }
 
-    /**
-     *  Return repository environment description
-     */
-    public function getDescriptionByName(string $name, string $dist, string $component, string $env) : string|null
-    {
-        return $this->model->getDescriptionByName($name, $dist, $component, $env);
-    }
-
     /**
      *  Return environment Id from repo name
      */
diff --git a/www/controllers/Repo/Environment.php b/www/controllers/Repo/Environment.php
index f99a3f937..db6f0077c 100644
--- a/www/controllers/Repo/Environment.php
+++ b/www/controllers/Repo/Environment.php
@@ -38,19 +38,6 @@ public function remove(int $id) : void
         $this->model->remove($id);
     }
 
-    /**
-     *  Update environment description
-     */
-    public function updateDescription(int $id, string $description) : void
-    {
-        // Description should not contain single quotes or backslashes
-        if (str_contains($description, "'") || str_contains($description, "\\") || str_contains($description, '')) {
-            throw new Exception('Description contains invalid characters');
-        }
-
-        $this->model->updateDescription($id, Validate::string($description));
-    }
-
     /**
      *  Return true if the repository environment Id exists
      */
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/Repo.php b/www/controllers/Repo/Repo.php
index eb34138d1..3856a1d39 100644
--- a/www/controllers/Repo/Repo.php
+++ b/www/controllers/Repo/Repo.php
@@ -533,4 +533,17 @@ public function updateSource(int $repoId, string $source): void
     {
         $this->model->updateSource($repoId, $source);
     }
+
+    /**
+     *  Update description
+     */
+    public function updateDescription(int $id, string $description) : void
+    {
+        // Description should not contain single quotes or backslashes
+        if (str_contains($description, "'") || str_contains($description, "\\") || str_contains($description, '')) {
+            throw new Exception('Description contains invalid characters');
+        }
+
+        $this->model->updateDescription($id, Validate::string($description));
+    }
 }
diff --git a/www/controllers/Repo/Rpm.php b/www/controllers/Repo/Rpm.php
index 264c8ad41..f15c34232 100644
--- a/www/controllers/Repo/Rpm.php
+++ b/www/controllers/Repo/Rpm.php
@@ -20,14 +20,6 @@ public function getIdByNameReleasever(string $name, string $releaseVersion) : in
         return $this->model->getIdByNameReleasever($name, $releaseVersion);
     }
 
-    /**
-     *  Return repository environment description
-     */
-    public function getDescriptionByName(string $name, string $releaseVersion, string $env) : string|null
-    {
-        return $this->model->getDescriptionByName($name, $releaseVersion, $env);
-    }
-
     /**
      *  Return environment Id from repo name
      */
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/Env.php b/www/controllers/Repo/Task/Env.php
index 4d9634645..8f507e6a1 100644
--- a/www/controllers/Repo/Task/Env.php
+++ b/www/controllers/Repo/Task/Env.php
@@ -67,27 +67,6 @@ public function execute()
                 }
             }
 
-            /**
-             *  If the user did not specify any description then we get the one currently in place on the environment of the same name (if the environment exists and if it has a description)
-             */
-            if (empty($this->repoController->getDescription())) {
-                if ($this->repoController->getPackageType() == 'rpm') {
-                    $actualDescription = $this->rpmRepoController->getDescriptionByName($this->repoController->getName(), $this->repoController->getReleasever(), $env);
-                }
-                if ($this->repoController->getPackageType() == 'deb') {
-                    $actualDescription = $this->debRepoController->getDescriptionByName($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection(), $env);
-                }
-
-                /**
-                 *  If the description is empty then the description will remain empty
-                 */
-                if (!empty($actualDescription)) {
-                    $this->repoController->setDescription(htmlspecialchars_decode($actualDescription));
-                } else {
-                    $this->repoController->setDescription('');
-                }
-            }
-
             $this->taskLogSubStepController->completed();
             $this->taskLogSubStepController->new('create-symlink-' . $env, 'CREATING SYMLINK');
 
diff --git a/www/controllers/Repo/Task/Finalize.php b/www/controllers/Repo/Task/Finalize.php
index aad2e24f8..a21c648b3 100644
--- a/www/controllers/Repo/Task/Finalize.php
+++ b/www/controllers/Repo/Task/Finalize.php
@@ -122,23 +122,6 @@ protected function finalize()
                 $this->taskLogSubStepController->new('adding-env', 'ADDING ENVIRONMENT');
 
                 foreach ($this->repoController->getEnv() as $env) {
-                    // If the user has not specified any description, then we retrieve the one currently in place on the environment of the same name (if the environment exists and if it has a description)
-                    if (empty($this->repoController->getDescription())) {
-                        if ($this->repoController->getPackageType() == 'rpm') {
-                            $actualDescription = $this->rpmRepoController->getDescriptionByName($this->repoController->getName(), $this->repoController->getReleasever(), $env);
-                        }
-                        if ($this->repoController->getPackageType() == 'deb') {
-                            $actualDescription = $this->debRepoController->getDescriptionByName($this->repoController->getName(), $this->repoController->getDist(), $this->repoController->getSection(), $env);
-                        }
-
-                        // If the retrieved description is empty then the description will remain empty
-                        if (!empty($actualDescription)) {
-                            $this->repoController->setDescription(htmlspecialchars_decode($actualDescription));
-                        } else {
-                            $this->repoController->setDescription('');
-                        }
-                    }
-
                     // Retrieve the Id of the environment currently in place (if there is one)
                     if ($this->repoController->getPackageType() == 'rpm') {
                         $actualEnvIds = $this->rpmRepoController->getEnvIdFromRepoName($this->repoController->getName(), $this->repoController->getReleasever(), $env);
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/Form.php b/www/controllers/Task/Form/Form.php
index fab2243f5..58b7b82f8 100644
--- a/www/controllers/Task/Form/Form.php
+++ b/www/controllers/Task/Form/Form.php
@@ -3,6 +3,7 @@
 namespace Controllers\Task\Form;
 
 use Exception;
+use Controllers\User\User;
 use Controllers\Repo\Repo;
 use Controllers\Utils\Validate;
 use Controllers\Repo\Environment;
@@ -18,7 +19,7 @@ class Form
      */
     public function get(string $action, array $repos) : string
     {
-        $userController = new \Controllers\User\User();
+        $userController = new User();
         $usersEmail = $userController->getEmails();
 
         $content = '
'; @@ -27,31 +28,44 @@ public function get(string $action, array $repos) : string $repoController = new Repo(); $repoEnvController = new Environment(); $scheduledTaskController = new ScheduledTask(); - $repoId = Validate::int($repo['repo-id']); - $snapId = Validate::int($repo['snap-id']); + $repoId = null; + $snapId = null; $envId = null; + $scheduledTasksCount = 0; - // If an environment points to the snapshot (snapId), retrieve the envId from the repo array - if (!empty($repo['envId'])) { - $envId = Validate::int($repo['env-id']); + if (empty($repo['repo-id'])) { + throw new Exception('Repository Id is required'); } - // Check that the Ids are numeric - if (!is_numeric($repoId)) { - throw new Exception("Repository Id is invalid"); + if (!is_numeric($repo['repo-id'])) { + throw new Exception('Repository Id is invalid'); } - if (!is_numeric($snapId)) { - throw new Exception("Snapshot Id is invalid"); + + $repoId = Validate::int($repo['repo-id']); + + // If a snapshot Id is provided + if (!empty($repo['snap-id'])) { + if (!is_numeric($repo['snap-id'])) { + throw new Exception('Snapshot Id is invalid'); + } + + $snapId = Validate::int($repo['snap-id']); } - if (!empty($envId) and !is_numeric($envId)) { - throw new Exception("Environment Id is invalid"); + + // If an environment points to the snapshot (snapId), retrieve the envId from the repo array + if (!empty($repo['env-id'])) { + if (!is_numeric($repo['env-id'])) { + throw new Exception('Environment Id is invalid'); + } + + $envId = Validate::int($repo['env-id']); } // Check that the Ids exist in the database if (!$repoController->existsId($repoId)) { throw new Exception("Repository Id does not exist"); } - if (!$repoController->existsSnapId($snapId)) { + if (!empty($snapId) and !$repoController->existsSnapId($snapId)) { throw new Exception("Snapshot Id does not exist"); } if (!empty($envId) and !$repoEnvController->exists($envId)) { @@ -64,9 +78,11 @@ public function get(string $action, array $repos) : string // Retrieve the package type of the repo $packageType = $repoController->getPackageType(); - // Get scheduled tasks on this snapshot (if any) and count them - $scheduledTasks = $scheduledTaskController->getBySnapId($snapId); - $scheduledTasksCount = count($scheduledTasks); + // Get scheduled tasks on the snapshot (if any) and count them + if (!empty($snapId)) { + $scheduledTasks = $scheduledTaskController->getBySnapId($snapId); + $scheduledTasksCount = count($scheduledTasks); + } // Build the form from a template ob_start(); 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/controllers/ajax/repo/edit.php b/www/controllers/ajax/repo/edit.php index 99d3d4ec9..75ceff862 100644 --- a/www/controllers/ajax/repo/edit.php +++ b/www/controllers/ajax/repo/edit.php @@ -1,4 +1,19 @@ updateDescription($_POST['repoId'], $_POST['description']); + } catch (Exception $e) { + response(HTTP_BAD_REQUEST, $e->getMessage()); + } + + response(HTTP_OK, 'Description has been saved'); +} + /** * Validate form and edit repositories */ diff --git a/www/controllers/ajax/repo/environment.php b/www/controllers/ajax/repo/environment.php deleted file mode 100644 index adf4405f4..000000000 --- a/www/controllers/ajax/repo/environment.php +++ /dev/null @@ -1,18 +0,0 @@ -updateDescription($_POST['envId'], $_POST['description']); - } catch (Exception $e) { - response(HTTP_BAD_REQUEST, $e->getMessage()); - } - - response(HTTP_OK, "Description has been saved"); -} - -response(HTTP_BAD_REQUEST, 'Invalid action'); diff --git a/www/models/Connection.php b/www/models/Connection.php index 9781a6233..e20e389b3 100644 --- a/www/models/Connection.php +++ b/www/models/Connection.php @@ -261,7 +261,8 @@ private function generateMainTables(): void Dist VARCHAR(255), Section VARCHAR(255), Source VARCHAR(255) NOT NULL, - Package_type VARCHAR(10) NOT NULL)"); + Package_type VARCHAR(10) NOT NULL, + Description VARCHAR(255))"); /** * Create indexes @@ -296,18 +297,17 @@ private function generateMainTables(): void $this->exec("CREATE TABLE IF NOT EXISTS repos_env ( Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Env VARCHAR(255), - Description VARCHAR(255), Id_snap INTEGER NOT NULL)"); /** * Create indexes */ - $this->exec("CREATE INDEX IF NOT EXISTS repos_env_index ON repos_env (Env, Description, Id_snap)"); + $this->exec("CREATE INDEX IF NOT EXISTS idx_repos_env ON repos_env (Env, Id_snap)"); /** * Create indexes */ - $this->exec("CREATE INDEX IF NOT EXISTS repos_env_id_snap_index ON repos_env (Id_snap)"); + $this->exec("CREATE INDEX IF NOT EXISTS idx_repos_env_id_snap ON repos_env (Id_snap)"); /** * env table diff --git a/www/models/Repo/Deb.php b/www/models/Repo/Deb.php index fa0891cf0..64c699dde 100644 --- a/www/models/Repo/Deb.php +++ b/www/models/Repo/Deb.php @@ -36,40 +36,6 @@ public function getIdByNameDistComponent(string $name, string $distribution, str return $id; } - /** - * Return repository environment description - */ - public function getDescriptionByName(string $name, string $dist, string $component, string $env) : string|null - { - $description = null; - - try { - $stmt = $this->db->prepare("SELECT repos_env.Description FROM repos_env - INNER JOIN repos_snap - ON repos_snap.Id = repos_env.Id_snap - INNER JOIN repos - ON repos.Id = repos_snap.Id_repo - WHERE repos.Name = :name - AND repos.Dist = :dist - AND repos.Section = :component - AND repos_env.Env = :env - AND repos_snap.Status = 'active'"); - $stmt->bindValue(':name', $name); - $stmt->bindValue(':dist', $dist); - $stmt->bindValue(':component', $component); - $stmt->bindValue(':env', $env); - $result = $stmt->execute(); - } catch (Exception $e) { - DbLog::error($e); - } - - while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $description = $row['Description']; - } - - return $description; - } - /** * Return environment Id from repo name */ diff --git a/www/models/Repo/Environment.php b/www/models/Repo/Environment.php index 284d322b4..b1a31b25c 100644 --- a/www/models/Repo/Environment.php +++ b/www/models/Repo/Environment.php @@ -67,21 +67,6 @@ public function remove(int $id) : void } } - /** - * Update environment description - */ - public function updateDescription(int $id, string $description) : void - { - try { - $stmt = $this->db->prepare("UPDATE repos_env SET Description = :description WHERE Id = :envId"); - $stmt->bindValue(':description', $description); - $stmt->bindValue(':envId', $id); - $stmt->execute(); - } catch (Exception $e) { - DbLog::error($e); - } - } - /** * Return true if the repository environment Id exists */ diff --git a/www/models/Repo/Listing.php b/www/models/Repo/Listing.php index 83d7f2e28..c824d50ed 100644 --- a/www/models/Repo/Listing.php +++ b/www/models/Repo/Listing.php @@ -30,13 +30,13 @@ public function list() : array repos.Releasever, repos.Source, repos.Package_type, + repos.Description, repos_env.Env, repos_snap.Date, repos_snap.Time, repos_snap.Signed, repos_snap.Arch, - repos_snap.Type, - repos_env.Description + repos_snap.Type FROM repos LEFT JOIN repos_snap ON repos.Id = repos_snap.Id_repo @@ -75,6 +75,7 @@ public function listByGroup(string $groupName) : array repos.Releasever, repos.Source, repos.Package_type, + repos.Description, repos_env.Env, repos_snap.Date, repos_snap.Time, @@ -82,8 +83,7 @@ public function listByGroup(string $groupName) : array repos_snap.Arch, repos_snap.Type, repos_snap.Reconstruct, - repos_snap.Status, - repos_env.Description + repos_snap.Status FROM repos LEFT JOIN repos_snap ON repos.Id = repos_snap.Id_repo @@ -102,6 +102,7 @@ public function listByGroup(string $groupName) : array repos.Releasever, repos.Source, repos.Package_type, + repos.Description, repos_env.Env, repos_snap.Date, repos_snap.Time, @@ -109,8 +110,7 @@ public function listByGroup(string $groupName) : array repos_snap.Arch, repos_snap.Type, repos_snap.Reconstruct, - repos_snap.Status, - repos_env.Description + repos_snap.Status FROM repos LEFT JOIN repos_snap ON repos.Id = repos_snap.Id_repo @@ -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/models/Repo/Repo.php b/www/models/Repo/Repo.php index 5cf8a11a8..47d90b573 100644 --- a/www/models/Repo/Repo.php +++ b/www/models/Repo/Repo.php @@ -52,6 +52,7 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null repos.Section, repos.Source, repos.Package_type, + repos.Description, repos_snap.Id AS snapId, repos_snap.Date, repos_snap.Time, @@ -64,7 +65,6 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null repos_snap.Id_repo, repos_env.Id AS envId, repos_env.Env, - repos_env.Description, repos_env.Id_snap FROM repos INNER JOIN repos_snap @@ -148,6 +148,7 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null repos.Section, repos.Source, repos.Package_type, + repos.Description, repos_snap.Id AS snapId, repos_snap.Date, repos_snap.Time, @@ -160,7 +161,6 @@ public function getAllById(string|null $repoId, string|null $snapId, string|null repos_snap.Id_repo, repos_env.Id AS envId, repos_env.Env, - repos_env.Description, repos_env.Id_snap FROM repos INNER JOIN repos_snap @@ -511,4 +511,19 @@ public function updateSource(int $repoId, string $source): void unset($stmt); } + + /** + * Update description + */ + public function updateDescription(int $id, string $description): void + { + try { + $stmt = $this->db->prepare("UPDATE repos SET Description = :description WHERE Id = :id"); + $stmt->bindValue(':description', $description); + $stmt->bindValue(':id', $id); + $stmt->execute(); + } catch (Exception $e) { + DbLog::error($e); + } + } } diff --git a/www/models/Repo/Rpm.php b/www/models/Repo/Rpm.php index 29755488a..3e918939d 100644 --- a/www/models/Repo/Rpm.php +++ b/www/models/Repo/Rpm.php @@ -35,38 +35,6 @@ public function getIdByNameReleasever(string $name, string $releaseVersion) : in return $id; } - /** - * Return repository environment description - */ - public function getDescriptionByName(string $name, string $releaseVersion, string $env) : string|null - { - $description = null; - - try { - $stmt = $this->db->prepare("SELECT repos_env.Description FROM repos_env - INNER JOIN repos_snap - ON repos_snap.Id = repos_env.Id_snap - INNER JOIN repos - ON repos.Id = repos_snap.Id_repo - WHERE repos.Name = :name - AND repos.Releasever = :releaseVersion - AND repos_env.Env = :env - AND repos_snap.Status = 'active'"); - $stmt->bindValue(':name', $name); - $stmt->bindValue(':releaseVersion', $releaseVersion); - $stmt->bindValue(':env', $env); - $result = $stmt->execute(); - } catch (Exception $e) { - DbLog::error($e); - } - - while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $description = $row['Description']; - } - - return $description; - } - /** * Return environment Id from repo name */ 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/Environment.js b/www/public/resources/js/classes/Environment.js deleted file mode 100644 index ae2ba40ba..000000000 --- a/www/public/resources/js/classes/Environment.js +++ /dev/null @@ -1,26 +0,0 @@ -class Environment -{ - /** - * Upodate environment description - * @param {*} id - * @param {*} description - */ - updateDescription(id, description) - { - ajaxRequest( - // Controller: - 'repo/environment', - // Action: - 'update-description', - // Data: - { - envId: id, - description: description - }, - // Print success alert: - true, - // Print error alert: - true - ); - } -} 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..770ccde64 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(''); }); }); } @@ -156,6 +158,30 @@ class Repo { }); } + /** + * Update repository description + * @param {*} id + * @param {*} description + */ + updateDescription(id, description) + { + ajaxRequest( + // Controller: + 'repo/edit', + // Action: + 'description', + // Data: + { + repoId: id, + description: description + }, + // Print success alert: + true, + // Print error alert: + true + ); + } + /** * Print packages tree */ 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..370093f28 100644 --- a/www/public/resources/js/events/repo/edit.js +++ b/www/public/resources/js/events/repo/edit.js @@ -1,75 +1,44 @@ /** - * 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); + + // 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: JSON.stringify([{ + 'repo-id': $(this).attr('repo-id') + }]) + }); +}); /** * Event: submit repository edit form @@ -166,39 +135,93 @@ $(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; }); /** - * Event: add placeholder to description input on mouse enter - * This is to prevent Firefox from always displaying the placeholder + * Event: click on description or edit icon to edit it */ -$(document).on('mouseenter','input[type="text"].repo-description-input',function () { - $(this).attr('placeholder', '🖉 add a description'); -}); +$(document).on('click','p.repo-description-input',function () { + const $container = $(this).closest('.repo-description-container'); + const p = $container.find('p.repo-description-input'); -/** - * Event: remove placeholder on mouse leave - * This is to prevent Firefox from always displaying the placeholder - */ -$(document).on('mouseleave','input[type="text"].repo-description-input',function () { - $(this).attr('placeholder', ''); + // If already in edit mode, do nothing + if (p.find('input').length > 0) { + return; + } + + const currentDescription = p.text().trim(); + const repoId = p.attr('repo-id'); + const envId = p.attr('env-id'); + + // Remove empty class (hide placeholder) + p.removeClass('repo-description-empty'); + + // Create input field + const input = $('') + .attr('repo-id', repoId) + .attr('env-id', envId) + .attr('data-original', currentDescription) + .val(currentDescription); + + // Replace

content with input + p.html(input); + input.focus(); }); /** * Event: edit repository description when pressing 'Enter' key */ -$(document).on('keypress','input[type="text"].repo-description-input',function (e) { +$(document).on('keypress','.repo-description-input-edit',function (e) { e.stopPropagation(); const keycode = (e.keyCode ? e.keyCode : e.which); if (keycode == '13') { - myenvironment.updateDescription($(this).attr('env-id'), $(this).val()); + const input = $(this); + const p = input.closest('p.repo-description-input'); + const newDescription = input.val().trim(); + + // Mark as saved to prevent blur from reverting + input.data('saved', true); + + // Save description + myrepo.updateDescription(input.attr('repo-id'), newDescription); + + // Revert to

with new value + p.text(newDescription); + + // If description is now empty, re-add empty class + if (!newDescription) { + p.addClass('repo-description-empty'); + } + } +}); + +/** + * Event: revert description input on blur (click outside) + */ +$(document).on('blur','.repo-description-input-edit',function () { + const input = $(this); + + // If already saved via Enter, do nothing + if (input.data('saved')) { + return; + } + + const p = input.closest('p.repo-description-input'); + const originalDescription = input.attr('data-original'); + + // Revert to

with original value + p.text(originalDescription); + + // If description is empty, re-add empty class + if (!originalDescription) { + p.addClass('repo-description-empty'); } }); diff --git a/www/public/resources/js/events/repo/env.js b/www/public/resources/js/events/repo/env.js index 29b4f1274..53371ea68 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) { - // Prevent parent to be triggered +$(document).on('click', '.snap-env-container', function (e) { + // Prevent triggering the parent snap-container click e.stopPropagation(); - var actions = []; + // 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(e); + + 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/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/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.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/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..40ec136c0 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; } @@ -88,6 +88,19 @@ h6 { word-break: break-all; } +.repo-description-container { + display: flex; + align-items: center; + column-gap: 8px; + margin-top: 5px; +} + +.repo-description-container:hover .repo-description-empty:empty::before { + content: 'Add a description...'; + opacity: 0.5; + cursor: pointer; +} + .empty-state { display: flex; flex-direction: column; @@ -253,7 +266,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 +312,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..6994a68d7 --- /dev/null +++ b/www/public/resources/styles/components/card.css @@ -0,0 +1,87 @@ +.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); +} + +.kpi-status-running { + background: linear-gradient(135deg, #2a0f2e 0%, #4a1942 50%, #2a0f2e 100%); + border-color: #c44daa; + animation: pulse-running 2s ease-in-out infinite; +} + +.kpi-status-done { + background: linear-gradient(135deg, #0f3d2e 0%, #1a5c40 50%, #0f3d2e 100%); + border-color: #1abc6b; +} + +.kpi-status-error, +.kpi-status-stopped { + background: linear-gradient(135deg, #3d1420 0%, #5c1a2a 50%, #3d1420 100%); + border-color: #c0392b; +} + +.kpi-status-scheduled { + background: linear-gradient(135deg, #2d2a0f 0%, #4a4410 50%, #2d2a0f 100%); + border-color: #b8a825; +} + +@keyframes pulse-running { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.85; } +} + +.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..e0f7fab38 100644 --- a/www/public/resources/styles/components/label.css +++ b/www/public/resources/styles/components/label.css @@ -3,14 +3,14 @@ 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; + box-shadow: none; } .label-icon-tr { display: grid; @@ -28,29 +28,35 @@ 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; + border: 1.5px solid #c0d0e2; } .label-green { - background-color: #15bf7f; + color: #15bf7f; + border-color: #15bf7f; + border: 1.5px solid #15bf7f; } .label-blue { - background-color: #5473e8; + color: #5473e8; + border-color: #5473e8; + border: 1.5px solid #5473e8; } .label-red { - background-color: #F32F63; + color: #F32F63; + border-color: #F32F63; + border: 1.5px solid #F32F63; } .label-yellow { - background-color: #ffb536; + color: #ffb536; + border-color: #ffb536; + border: 1.5px solid #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..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;
@@ -69,6 +62,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;
@@ -89,7 +168,7 @@
 .hosts-charts-container {
     display: flex;
     flex-direction: column;
-    justify-content: space-between;
+    gap: 20px;
     margin: 0 auto 20px auto;
 }
 
@@ -245,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 {
@@ -255,4 +339,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..b8f1d82f6
--- /dev/null
+++ b/www/update/database/5.x.x.php
@@ -0,0 +1,40 @@
+db->exec("DROP INDEX IF EXISTS repos_env_index");
+    $this->db->exec("DROP INDEX IF EXISTS repos_env_id_snap_index");
+} catch (Exception $e) {
+    throw new Exception('could not delete old indexes from database: ' . $e->getMessage());
+}
+
+
+// Delete Pkg_translation column from repos_snap database
+if ($this->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');
+    }
+}
+
+// Add Description column to repos table
+if (!$this->db->columnExist('repos', 'Description')) {
+    try {
+        $this->db->exec("ALTER TABLE repos ADD COLUMN Description VARCHAR(255)");
+    } catch (Exception $e) {
+        throw new Exception('could not add Description column to repos table');
+    }
+}
+
+// Delete Description column from repos_env table
+if ($this->db->columnExist('repos_env', 'Description')) {
+    try {
+        $this->db->exec("ALTER TABLE repos_env DROP COLUMN Description");
+    } catch (Exception $e) {
+        throw new Exception('could not delete Description column from repos_env 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..fb6aeb45e 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..a1469e470 100644 --- a/www/views/includes/containers/hosts/overview.inc.php +++ b/www/views/includes/containers/hosts/overview.inc.php @@ -6,15 +6,63 @@ if ($totalHosts >= 1) : ?>

OVERVIEW

+
+
+ +
+

+

Hosts

+
+
+ +
+ +
+

+

Need update

+
+
+ +
+ +
+

+

Up to date

+
+
+ +
+ +
+

%

+

Hosts compliance

+
+
+
+
-
-
HOSTS ()
+
+
OPERATING SYSTEMS
-
- +
+
+ +
+ +
+
-
+
+
ARCHITECTURES
+ +
+
+ +
+ +
+
@@ -54,30 +102,6 @@
-
-
OPERATING SYSTEMS
- -
-
- -
- -
-
-
- -
-
ARCHITECTURES
- -
-
- -
- -
-
-
-
ENVIRONMENTS
@@ -152,7 +176,6 @@ \ 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..3210e11fe 100644 --- a/www/views/includes/containers/tasks/log.inc.php +++ b/www/views/includes/containers/tasks/log.inc.php @@ -1,39 +1,86 @@ -
-

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 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..49e05ac76 100644 --- a/www/views/includes/footer.inc.php +++ b/www/views/includes/footer.inc.php @@ -1,5 +1,5 @@ @@ -102,7 +100,6 @@ */ if (__ACTUAL_URI__[1] == '') { $jsClasses = [ - 'Environment', 'Repo' ]; @@ -162,7 +159,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/forms/tasks/rename.inc.php b/www/views/includes/forms/tasks/rename.inc.php index 331af4830..65a5b4bc3 100644 --- a/www/views/includes/forms/tasks/rename.inc.php +++ b/www/views/includes/forms/tasks/rename.inc.php @@ -17,16 +17,8 @@

The new name of the repository.

- - - "> + @@ -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/panels/repos/install.inc.php b/www/views/includes/panels/repos/install.inc.php index c3b1d4084..64c7fd2c6 100644 --- a/www/views/includes/panels/repos/install.inc.php +++ b/www/views/includes/panels/repos/install.inc.php @@ -11,16 +11,12 @@
You have selected both deb and rpm repositories. Make sure to install them separately.

'; } - /** - * Print the GPG key installation commands (only for deb packages) - */ + // Print the GPG key installation commands (only for deb packages) if (in_array('deb', $packagesTypes)) : ?>
INSTALL THE GPG KEY

For Debian based systems. Copy and paste the following commands in the shell of the target host.

diff --git a/www/views/includes/panels/repos/rename.inc.php b/www/views/includes/panels/repos/rename.inc.php new file mode 100644 index 000000000..bea24150b --- /dev/null +++ b/www/views/includes/panels/repos/rename.inc.php @@ -0,0 +1,9 @@ + + + + + $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 #

- '; - } ?> -
-
-
-
-