From 07ec56227d43d18a600e3fcb6fb18c45201cc608 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 11:45:15 -0800 Subject: [PATCH 01/26] Minor: mode change --- includes/footer.php | 0 includes/restapi.php | 0 templates/system/plugins.php | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) mode change 100644 => 100755 includes/footer.php mode change 100644 => 100755 includes/restapi.php create mode 100644 templates/system/plugins.php diff --git a/includes/footer.php b/includes/footer.php old mode 100644 new mode 100755 diff --git a/includes/restapi.php b/includes/restapi.php old mode 100644 new mode 100755 diff --git a/templates/system/plugins.php b/templates/system/plugins.php new file mode 100644 index 000000000..beef0bfa2 --- /dev/null +++ b/templates/system/plugins.php @@ -0,0 +1,34 @@ + +
+

+ + + +
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+ + + +
+ From 36b02851582234413a608196f240fb557ccc42c7 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:33:46 -0800 Subject: [PATCH 02/26] Define RASPI_PLUGINS_URL --- config/config.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/config.php b/config/config.php index 2950632db..afd386a20 100755 --- a/config/config.php +++ b/config/config.php @@ -38,6 +38,9 @@ // Constant for the GitHub API latest release endpoint define('RASPI_API_ENDPOINT', 'https://api.github.com/repos/RaspAP/raspap-webgui/releases/latest'); +// Constant for the GitHub plugin submodules URL +define("RASPI_PLUGINS_URL", "https://raw.githubusercontent.com/RaspAP/plugins"); + // Constant for the 5GHz wireless regulatory domain define("RASPI_5GHZ_CHANNEL_MIN", 100); define("RASPI_5GHZ_CHANNEL_MAX", 192); From 66e35c564c38df2840c541e871df19c02da55218 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:34:05 -0800 Subject: [PATCH 03/26] Initial commit --- templates/system/plugins.php | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/templates/system/plugins.php b/templates/system/plugins.php index beef0bfa2..2595198d9 100644 --- a/templates/system/plugins.php +++ b/templates/system/plugins.php @@ -7,28 +7,15 @@
-
- +
+ Install to download and activate a plugin from the list. Uninstall removes an existing plugin."); ?>
+
-
-
- -
- -
-
-
- -
From 117370efcff60ccd3eeba7ce20a19e17db17a4ea Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:34:58 -0800 Subject: [PATCH 04/26] Add plugins tab, render system/plugins template --- templates/system.php | 2 ++ templates/system/tools.php | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/system.php b/templates/system.php index 29950e39e..4c17bcf2f 100755 --- a/templates/system.php +++ b/templates/system.php @@ -18,6 +18,7 @@ +
@@ -26,6 +27,7 @@ +
diff --git a/templates/system/tools.php b/templates/system/tools.php index a30601db6..32eb19ca5 100644 --- a/templates/system/tools.php +++ b/templates/system/tools.php @@ -1,4 +1,4 @@ - +

@@ -36,4 +36,3 @@
- From 89c4f16e45a315118f973e058bc40c7df2e7866e Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:42:53 -0800 Subject: [PATCH 05/26] Get plugin submodules, fetch manifest details + format output --- includes/system.php | 236 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 192 insertions(+), 44 deletions(-) diff --git a/includes/system.php b/includes/system.php index 31feaf798..868082d5c 100755 --- a/includes/system.php +++ b/includes/system.php @@ -85,53 +85,22 @@ function DisplaySystem(&$extraFooterScripts) $kernel = $system->kernelVersion(); $systime = $system->systime(); $revision = $system->rpiRevision(); - - // mem used + + // memory use $memused = $system->usedMemory(); - $memused_status = "primary"; - if ($memused > 90) { - $memused_status = "danger"; - $memused_led = "service-status-down"; - } elseif ($memused > 75) { - $memused_status = "warning"; - $memused_led = "service-status-warn"; - } elseif ($memused > 0) { - $memused_status = "success"; - $memused_led = "service-status-up"; - } + $memStatus = getMemStatus($memused); + $memused_status = $memStatus['status']; + $memused_led = $memStatus['led']; // cpu load $cpuload = $system->systemLoadPercentage(); - if ($cpuload > 90) { - $cpuload_status = "danger"; - } elseif ($cpuload > 75) { - $cpuload_status = "warning"; - } elseif ($cpuload >= 0) { - $cpuload_status = "success"; - } + $cpuload_status = getCPULoadStatus($cpuload); // cpu temp $cputemp = $system->systemTemperature(); - if ($cputemp > 70) { - $cputemp_status = "danger"; - $cputemp_led = "service-status-down"; - } elseif ($cputemp > 50) { - $cputemp_status = "warning"; - $cputemp_led = "service-status-warn"; - } else { - $cputemp_status = "success"; - $cputemp_led = "service-status-up"; - } - - // hostapd status - $hostapd = $system->hostapdStatus(); - if ($hostapd[0] == 1) { - $hostapd_status = "active"; - $hostapd_led = "service-status-up"; - } else { - $hostapd_status = "inactive"; - $hostapd_led = "service-status-down"; - } + $cpuStatus = getCPUTempStatus($cputemp); + $cputemp_status = $cpuStatus['status']; + $cputemp_led = $cpuStatus['led']; // theme options $themes = [ @@ -147,6 +116,9 @@ function DisplaySystem(&$extraFooterScripts) $extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false); $logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT; + $plugins = getUserPlugins(); + $pluginsTable = getHTMLPluginsTable($plugins); + echo renderTemplate("system", compact( "arrLocales", "status", @@ -167,11 +139,187 @@ function DisplaySystem(&$extraFooterScripts) "cputemp", "cputemp_status", "cputemp_led", - "hostapd", - "hostapd_status", - "hostapd_led", "themes", "selectedTheme", - "logLimit" + "logLimit", + "pluginsTable" )); } + +/** + * Returns user plugin details from associated manifest.json files + * + * @return array $plugins + */ +function getUserPlugins() +{ + try { + $submodules = getSubmodules(RASPI_PLUGINS_URL); + $plugins = []; + foreach ($submodules as $submodule) { + $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; + $manifest = getPluginManifest($manifestUrl); + + if ($manifest) { + $plugins[] = [ + 'version' => $manifest['version'] ?? 'unknown', + 'name' => $manifest['name'] ?? 'unknown', + 'description' => $manifest['description'] ?? 'No description provided', + 'plugin_uri' => $manifest['plugin_uri'] ?? $submodule['url'], + 'fa-icon' => $manifest['icon'] ?? 'fas fa-plug', + ]; + } + } + return $plugins; + } catch (Exception $e) { + echo "An error occured: " .$e->getMessage(); + } +} + +/** + * Returns git submodules for the specified repository + * + * @param string $repoURL + * @return array $submodules + */ +function getSubmodules(string $repoUrl): array +{ + $gitmodulesUrl = $repoUrl . '/refs/heads/master/.gitmodules'; + $gitmodulesContent = file_get_contents($gitmodulesUrl); + + if ($gitmodulesContent === false) { + throw new Exception('Unable to fetch .gitmodules file from the repository'); + } + + $submodules = []; + $lines = explode("\n", $gitmodulesContent); + $currentSubmodule = []; + + foreach ($lines as $line) { + $line = trim($line); + + if (strpos($line, '[submodule "') === 0) { + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + $currentSubmodule = []; + } elseif (strpos($line, 'path = ') === 0) { + $currentSubmodule['path'] = substr($line, strlen('path = ')); + } elseif (strpos($line, 'url = ') === 0) { + $currentSubmodule['url'] = substr($line, strlen('url = ')); + } + } + + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + + return $submodules; +} + +/** + * Returns a plugin's associated manifest in JSON format + * + * @param string $url + * @return array $json + */ +function getPluginManifest(string $url): ?array +{ + $options = [ + 'http' => [ + 'method' => 'GET', + 'follow_location' => 1, + ], + ]; + + $context = stream_context_create($options); + $content= file_get_contents($url, false, $context); + + if ($content === false) { + return null; + } + $json = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + return null; + } + return $json; +} + +/** + * Returns a list of available plugins formatted as an HTML table + * + * @param array $plugins + * @return string $html + */ +function getHTMLPluginsTable(array $plugins): string +{ + $html = ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + foreach ($plugins as $plugin) { + $name = ''. htmlspecialchars($plugin['name']). ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
NameVersionDescription
' . $name . '' . htmlspecialchars($plugin['version']) . '' . htmlspecialchars($plugin['description']) . '
'; + return $html; +} + +function getMemStatus($memused): array +{ + $memused_status = "primary"; + $memused_led = ""; + + if ($memused > 90) { + $memused_status = "danger"; + $memused_led = "service-status-down"; + } elseif ($memused > 75) { + $memused_status = "warning"; + $memused_led = "service-status-warn"; + } elseif ($memused > 0) { + $memused_status = "success"; + $memused_led = "service-status-up"; + } + + return [ + 'status' => $memused_status, + 'led' => $memused_led + ]; +} + +function getCPULoadStatus($cpuload): string +{ + if ($cpuload > 90) { + $status = "danger"; + } elseif ($cpuload > 75) { + $status = "warning"; + } elseif ($cpuload >= 0) { + $status = "success"; + } + return $status; +} + +function getCPUTempStatus($cputemp): array +{ + if ($cputemp > 70) { + $cputemp_status = "danger"; + $cputemp_led = "service-status-down"; + } elseif ($cputemp > 50) { + $cputemp_status = "warning"; + $cputemp_led = "service-status-warn"; + } else { + $cputemp_status = "success"; + $cputemp_led = "service-status-up"; + } + return [ + 'status' => $cputemp_status, + 'led' => $cputemp_led + ]; +} + From 247b35b254fe32c329bb20407aaff58a15a1611f Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 18:52:09 -0800 Subject: [PATCH 06/26] Initial commit --- src/RaspAP/Plugins/PluginInstaller.php | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/RaspAP/Plugins/PluginInstaller.php diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php new file mode 100644 index 000000000..74a90d70f --- /dev/null +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -0,0 +1,52 @@ + + * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace RaspAP\Plugins; + +class PluginInstaller +{ + private static $instance = null; + + public function __construct() + { + $this->pluginPath = 'plugins'; + } + + // Returns a single instance of PluginInstaller + public static function getInstance(): PluginInstaller + { + if (self::$instance === null) { + self::$instance = new PluginInstaller(); + } + return self::$instance; + } + + public function getPlugins(): array + { + $plugins = []; + if (file_exists($this->pluginPath)) { + $directories = scandir($this->pluginPath); + + foreach ($directories as $directory) { + $pluginClass = "RaspAP\\Plugins\\$directory\\$directory"; + $pluginFile = $this->pluginPath . "/$directory/$directory.php"; + + if (file_exists($pluginFile) && class_exists($pluginClass)) { + $plugins[] = $pluginClass; + } + } + } + return $plugins; + } + +} + From 9b087f88a75be9e24d41fed73573eb110073bf60 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 18:53:35 -0800 Subject: [PATCH 07/26] Call PluginInstaller::getInstance(), set install option --- includes/system.php | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/includes/system.php b/includes/system.php index 868082d5c..09467cf8a 100755 --- a/includes/system.php +++ b/includes/system.php @@ -153,6 +153,9 @@ function DisplaySystem(&$extraFooterScripts) */ function getUserPlugins() { + $pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance(); + $installedPlugins = $pluginInstaller->getPlugins(); + try { $submodules = getSubmodules(RASPI_PLUGINS_URL); $plugins = []; @@ -161,12 +164,24 @@ function getUserPlugins() $manifest = getPluginManifest($manifestUrl); if ($manifest) { + $namespace = $manifest['namespace'] ?? ''; + $installed = false; + + foreach ($installedPlugins as $plugin) { + if (str_contains($plugin, $namespace)) { + $installed = true; + break; + } + } + $plugins[] = [ 'version' => $manifest['version'] ?? 'unknown', 'name' => $manifest['name'] ?? 'unknown', 'description' => $manifest['description'] ?? 'No description provided', 'plugin_uri' => $manifest['plugin_uri'] ?? $submodule['url'], + 'namespace' => $namespace, 'fa-icon' => $manifest['icon'] ?? 'fas fa-plug', + 'installed' => $installed ]; } } @@ -218,7 +233,8 @@ function getSubmodules(string $repoUrl): array } /** - * Returns a plugin's associated manifest in JSON format + * Decodes a plugin's associated manifest JSON. + * Returns an array of key-value pairs * * @param string $url * @return array $json @@ -233,7 +249,7 @@ function getPluginManifest(string $url): ?array ]; $context = stream_context_create($options); - $content= file_get_contents($url, false, $context); + $content = file_get_contents($url, false, $context); if ($content === false) { return null; @@ -259,13 +275,26 @@ function getHTMLPluginsTable(array $plugins): string $html .= 'Name'; $html .= 'Version'; $html .= 'Description'; + $html .= ''; $html .= ''; foreach ($plugins as $plugin) { - $name = ''. htmlspecialchars($plugin['name']). ''; - $html .= '' . $name . ''; - $html .= '' . htmlspecialchars($plugin['version']) . ''; - $html .= '' . htmlspecialchars($plugin['description']) . ''; + $installed = $plugin['installed']; + if ($installed === true ) { + $status = 'Installed'; + } else { + $status = ''; + } + $name = '' + . htmlspecialchars($plugin['name']). ''; + $html .= '' .$name. ''; + $html .= '' .htmlspecialchars($plugin['version']). ''; + $html .= '' .htmlspecialchars($plugin['description']). ''; + $html .= '' .$status. ''; } $html .= ''; return $html; From ee38614334676c3abe3ead428e3a412f1833ad97 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 10:39:46 -0800 Subject: [PATCH 08/26] Initial commit --- ajax/plugins/get_manifest.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100755 ajax/plugins/get_manifest.php diff --git a/ajax/plugins/get_manifest.php b/ajax/plugins/get_manifest.php new file mode 100755 index 000000000..70b18c9f6 --- /dev/null +++ b/ajax/plugins/get_manifest.php @@ -0,0 +1,32 @@ +getPluginManifest($manifestUrl); + if ($manifest) { + echo json_encode($manifest); + } else { + http_response_code(404); + echo json_encode(['error' => 'Plugin manifest not found']); + } + } catch (Exception $e) { + http_response_code(500); + echo json_encode(['error' => 'An unexpected error occurred']); + } +} else { + http_response_code(400); + echo json_encode(['error' => 'Plugin URI is required']); + exit; +} + From 6785cc1104ed658be06befcea7326e03613f27f6 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 10:40:41 -0800 Subject: [PATCH 09/26] Move getPluginManifest() to PluginInstaller class --- includes/system.php | 36 +++----------------------- src/RaspAP/Plugins/PluginInstaller.php | 34 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/includes/system.php b/includes/system.php index 09467cf8a..b0f7e21dd 100755 --- a/includes/system.php +++ b/includes/system.php @@ -161,7 +161,7 @@ function getUserPlugins() $plugins = []; foreach ($submodules as $submodule) { $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; - $manifest = getPluginManifest($manifestUrl); + $manifest = $pluginInstaller->getPluginManifest($manifestUrl); if ($manifest) { $namespace = $manifest['namespace'] ?? ''; @@ -232,36 +232,6 @@ function getSubmodules(string $repoUrl): array return $submodules; } -/** - * Decodes a plugin's associated manifest JSON. - * Returns an array of key-value pairs - * - * @param string $url - * @return array $json - */ -function getPluginManifest(string $url): ?array -{ - $options = [ - 'http' => [ - 'method' => 'GET', - 'follow_location' => 1, - ], - ]; - - $context = stream_context_create($options); - $content = file_get_contents($url, false, $context); - - if ($content === false) { - return null; - } - $json = json_decode($content, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - return null; - } - return $json; -} - /** * Returns a list of available plugins formatted as an HTML table * @@ -284,8 +254,8 @@ function getHTMLPluginsTable(array $plugins): string $status = 'Installed'; } else { $status = ''; + name="install-plugin" data-bs-toggle="modal" data-bs-target="#install-user-plugin" + data-record-id="'.htmlspecialchars($plugin['plugin_uri']).'" />' . _("Install now") .''; } $name = 'profile)` : '') : 'Unknown' + ); + $('#plugin-license').text(manifestData.license || 'Unknown'); + $('#plugin-locale').text(manifestData.default_locale || 'Unknown'); + $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); + $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); + $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); + $('#plugin-user-name').html(manifestData.user_nonprivileged.name || {}); + console.log(manifestData); + } +}); + +function formatProperty(prop) { + if (Array.isArray(prop)) { + if (typeof prop[0] === 'object') { + return prop.map(item => { + return Object.entries(item) + .map(([key, value]) => `${key}: ${value}`) + .join('
'); + }).join('

'); + } + return prop.map(line => `${line}
`).join(''); + } + if (typeof prop === 'object') { + return Object.entries(prop) + .map(([key, value]) => `${key}: ${value}`) + .join('
'); + } + return prop || 'None'; +} + $(document).ready(function(){ $("#PanelManual").hide(); $('.ip_address').mask('0ZZ.0ZZ.0ZZ.0ZZ', { @@ -507,7 +549,6 @@ $('#wg-upload,#wg-manual').on('click', function (e) { } }); -// Add the following code if you want the name of the file appear on select $(".custom-file-input").on("change", function() { var fileName = $(this).val().split("\\").pop(); $(this).siblings(".custom-file-label").addClass("selected").html(fileName); From c3968ba42e62f14ab45c5850091cfd8c66d622a5 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 22:40:55 -0800 Subject: [PATCH 11/26] Encapsulate plugin related functions in PluginInstaller class --- includes/system.php | 129 +------------------------ src/RaspAP/Plugins/PluginInstaller.php | 123 ++++++++++++++++++++++- 2 files changed, 124 insertions(+), 128 deletions(-) diff --git a/includes/system.php b/includes/system.php index b0f7e21dd..6ef6b74ef 100755 --- a/includes/system.php +++ b/includes/system.php @@ -9,6 +9,7 @@ function DisplaySystem(&$extraFooterScripts) { $status = new \RaspAP\Messages\StatusMessage; + $pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance(); if (isset($_POST['SaveLanguage'])) { if (isset($_POST['locale'])) { @@ -116,8 +117,8 @@ function DisplaySystem(&$extraFooterScripts) $extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false); $logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT; - $plugins = getUserPlugins(); - $pluginsTable = getHTMLPluginsTable($plugins); + $plugins = $pluginInstaller->getUserPlugins(); + $pluginsTable = $pluginInstaller->getHTMLPluginsTable($plugins); echo renderTemplate("system", compact( "arrLocales", @@ -146,130 +147,6 @@ function DisplaySystem(&$extraFooterScripts) )); } -/** - * Returns user plugin details from associated manifest.json files - * - * @return array $plugins - */ -function getUserPlugins() -{ - $pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance(); - $installedPlugins = $pluginInstaller->getPlugins(); - - try { - $submodules = getSubmodules(RASPI_PLUGINS_URL); - $plugins = []; - foreach ($submodules as $submodule) { - $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; - $manifest = $pluginInstaller->getPluginManifest($manifestUrl); - - if ($manifest) { - $namespace = $manifest['namespace'] ?? ''; - $installed = false; - - foreach ($installedPlugins as $plugin) { - if (str_contains($plugin, $namespace)) { - $installed = true; - break; - } - } - - $plugins[] = [ - 'version' => $manifest['version'] ?? 'unknown', - 'name' => $manifest['name'] ?? 'unknown', - 'description' => $manifest['description'] ?? 'No description provided', - 'plugin_uri' => $manifest['plugin_uri'] ?? $submodule['url'], - 'namespace' => $namespace, - 'fa-icon' => $manifest['icon'] ?? 'fas fa-plug', - 'installed' => $installed - ]; - } - } - return $plugins; - } catch (Exception $e) { - echo "An error occured: " .$e->getMessage(); - } -} - -/** - * Returns git submodules for the specified repository - * - * @param string $repoURL - * @return array $submodules - */ -function getSubmodules(string $repoUrl): array -{ - $gitmodulesUrl = $repoUrl . '/refs/heads/master/.gitmodules'; - $gitmodulesContent = file_get_contents($gitmodulesUrl); - - if ($gitmodulesContent === false) { - throw new Exception('Unable to fetch .gitmodules file from the repository'); - } - - $submodules = []; - $lines = explode("\n", $gitmodulesContent); - $currentSubmodule = []; - - foreach ($lines as $line) { - $line = trim($line); - - if (strpos($line, '[submodule "') === 0) { - if (!empty($currentSubmodule)) { - $submodules[] = $currentSubmodule; - } - $currentSubmodule = []; - } elseif (strpos($line, 'path = ') === 0) { - $currentSubmodule['path'] = substr($line, strlen('path = ')); - } elseif (strpos($line, 'url = ') === 0) { - $currentSubmodule['url'] = substr($line, strlen('url = ')); - } - } - - if (!empty($currentSubmodule)) { - $submodules[] = $currentSubmodule; - } - - return $submodules; -} - -/** - * Returns a list of available plugins formatted as an HTML table - * - * @param array $plugins - * @return string $html - */ -function getHTMLPluginsTable(array $plugins): string -{ - $html = ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - - foreach ($plugins as $plugin) { - $installed = $plugin['installed']; - if ($installed === true ) { - $status = 'Installed'; - } else { - $status = ''; - } - $name = '' - . htmlspecialchars($plugin['name']). ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - } - $html .= '
NameVersionDescription
' .$name. '' .htmlspecialchars($plugin['version']). '' .htmlspecialchars($plugin['description']). '' .$status. '
'; - return $html; -} - function getMemStatus($memused): array { $memused_status = "primary"; diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 20188b987..f6a0f96fd 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -31,8 +31,45 @@ public static function getInstance(): PluginInstaller } /** - * Decodes a plugin's associated manifest JSON. - * Returns an array of key-value pairs + * Returns user plugin details from associated manifest.json files + * + * @return array $plugins + */ + public function getUserPlugins() + { + $installedPlugins = $this->getPlugins(); + + try { + $submodules = $this->getSubmodules(RASPI_PLUGINS_URL); + $plugins = []; + foreach ($submodules as $submodule) { + $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; + $manifest = $this->getPluginManifest($manifestUrl); + + if ($manifest) { + $installed = false; + + foreach ($installedPlugins as $plugin) { + if (str_contains($plugin, $plugins['manifest']['namespace'])) { + $installed = true; + break; + } + } + + $plugins[] = [ + 'manifest' => $manifest, + 'installed' => $installed + ]; + } + } + return $plugins; + } catch (Exception $e) { + echo "An error occured: " .$e->getMessage(); + } + } + + /** + * Retrieves a plugin's associated manifest JSON * * @param string $url * @return array $json @@ -60,6 +97,88 @@ public function getPluginManifest(string $url): ?array return $json; } + /** + * Returns git submodules for the specified repository + * + * @param string $repoURL + * @return array $submodules + */ + public function getSubmodules(string $repoUrl): array + { + $gitmodulesUrl = $repoUrl . '/refs/heads/master/.gitmodules'; + $gitmodulesContent = file_get_contents($gitmodulesUrl); + + if ($gitmodulesContent === false) { + throw new Exception('Unable to fetch .gitmodules file from the repository'); + } + + $submodules = []; + $lines = explode("\n", $gitmodulesContent); + $currentSubmodule = []; + + foreach ($lines as $line) { + $line = trim($line); + + if (strpos($line, '[submodule "') === 0) { + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + $currentSubmodule = []; + } elseif (strpos($line, 'path = ') === 0) { + $currentSubmodule['path'] = substr($line, strlen('path = ')); + } elseif (strpos($line, 'url = ') === 0) { + $currentSubmodule['url'] = substr($line, strlen('url = ')); + } + } + + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + + return $submodules; + } + + /** + * Returns a list of available plugins formatted as an HTML table + * + * @param array $plugins + * @return string $html + */ + public function getHTMLPluginsTable(array $plugins): string + { + $html = ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + foreach ($plugins as $plugin) { + + $manifest = htmlspecialchars(json_encode($plugin['manifest']), ENT_QUOTES, 'UTF-8'); + $installed = $plugin['installed']; + if ($installed === true ) { + $status = 'Installed'; + } else { + $button = ''; + } + $name = '' + . htmlspecialchars($plugin['manifest']['name']). ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
NameVersionDescription
' .$name. '' .htmlspecialchars($plugin['manifest']['version']). '' .htmlspecialchars($plugin['manifest']['description']). '' .$button. '
'; + return $html; + } + + /** Returns an array of installed plugins in pluginPath * * @return array $plugins From 2cb66660c5d810aac843eb079515e12afa921373 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 22:42:34 -0800 Subject: [PATCH 12/26] Create modal install-user-plugin dialog, update template text --- ajax/plugins/get_manifest.php | 32 ------------------- templates/system.php | 59 +++++++++++++++++++++++++++++++++++ templates/system/plugins.php | 6 ++-- 3 files changed, 61 insertions(+), 36 deletions(-) delete mode 100755 ajax/plugins/get_manifest.php diff --git a/ajax/plugins/get_manifest.php b/ajax/plugins/get_manifest.php deleted file mode 100755 index 70b18c9f6..000000000 --- a/ajax/plugins/get_manifest.php +++ /dev/null @@ -1,32 +0,0 @@ -getPluginManifest($manifestUrl); - if ($manifest) { - echo json_encode($manifest); - } else { - http_response_code(404); - echo json_encode(['error' => 'Plugin manifest not found']); - } - } catch (Exception $e) { - http_response_code(500); - echo json_encode(['error' => 'An unexpected error occurred']); - } -} else { - http_response_code(400); - echo json_encode(['error' => 'Plugin URI is required']); - exit; -} - diff --git a/templates/system.php b/templates/system.php index 4c17bcf2f..dcc4679d4 100755 --- a/templates/system.php +++ b/templates/system.php @@ -107,3 +107,62 @@ + + + diff --git a/templates/system/plugins.php b/templates/system/plugins.php index 2595198d9..71677d19d 100644 --- a/templates/system/plugins.php +++ b/templates/system/plugins.php @@ -3,19 +3,17 @@

-
-
- Install to download and activate a plugin from the list. Uninstall removes an existing plugin."); ?> +
+ Details for more information and to install a plugin. Uninstall removes an existing plugin."); ?>
-
From e5987a6b591a8b0e537642719233672632372b14 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 22 Dec 2024 09:27:40 -0800 Subject: [PATCH 13/26] Update w/ link to plugin_uri --- app/js/custom.js | 5 ++++- templates/system.php | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/js/custom.js b/app/js/custom.js index 354f9cd66..1b73de317 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -473,6 +473,10 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { var manifestData = button.data('plugin-manifest'); if (manifestData) { + $('#plugin-uri').html(manifestData.plugin_uri + ? `${manifestData.plugin_uri}` + : 'Unknown' + ); $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`); $('#plugin-name').text(manifestData.name || 'Unknown'); $('#plugin-version').text(manifestData.version || 'Unknown'); @@ -487,7 +491,6 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); $('#plugin-user-name').html(manifestData.user_nonprivileged.name || {}); - console.log(manifestData); } }); diff --git a/templates/system.php b/templates/system.php index dcc4679d4..bee431d81 100755 --- a/templates/system.php +++ b/templates/system.php @@ -112,7 +112,7 @@ From 92ba7df9c615c658ed970d190620401e82282386 Mon Sep 17 00:00:00 2001 From: billz Date: Wed, 25 Dec 2024 19:03:41 -0800 Subject: [PATCH 22/26] Create plugin install event handler --- app/js/custom.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/js/custom.js b/app/js/custom.js index 1b73de317..30f397b51 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -490,10 +490,42 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); - $('#plugin-user-name').html(manifestData.user_nonprivileged.name || {}); + $('#plugin-user-name').html(manifestData.user_nonprivileged.name || 'None'); } }); +$('#js-install-plugin-confirm').on('click', function (e) { + var progressText = $('#js-install-plugin-confirm').attr('data-message'); + var successHtml = $('#plugin-install-message').attr('data-message'); + var closeHtml = $('#js-system-reset-cancel').attr('data-message'); + var pluginUri = $('#plugin-uri a').attr('href'); + var pluginVersion = $('#plugin-version').text(); + var csrfToken = $('meta[name=csrf_token]').attr('content'); + + $("#install-user-plugin").modal('hide'); + $("#install-plugin-progress").modal('show'); + + $.post('ajax/plugins/do_plugin_install.php?',{'plugin_uri': pluginUri, 'plugin_version': pluginVersion, 'csrf_token': csrfToken},function(data){ + setTimeout(function(){ + response = JSON.parse(data); + if(response === true) { + $('#plugin-install-message').text(successHtml); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary').addClass('fas fa-check'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } else { + $('#plugin-install-message').text('An error occurred installing the plugin.'); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } + },300); + }); +}); + +$('#js-install-plugin-ok').on('click', function (e) { + $("#install-plugin-progress").modal('hide'); + window.location.reload(); +}); + function formatProperty(prop) { if (Array.isArray(prop)) { if (typeof prop[0] === 'object') { From ad3669522474cd9fc2370d5d682c280ed8cb48b1 Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 08:04:16 -0800 Subject: [PATCH 23/26] Set/read plugin-installed flag --- app/js/custom.js | 83 +++++++++++++++----------- installers/plugin_helper.sh | 2 +- src/RaspAP/Plugins/PluginInstaller.php | 2 +- templates/system.php | 2 +- 4 files changed, 50 insertions(+), 39 deletions(-) diff --git a/app/js/custom.js b/app/js/custom.js index 30f397b51..d68c94f14 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -471,54 +471,65 @@ $('#js-sys-reboot, #js-sys-shutdown').on('click', function (e) { $('#install-user-plugin').on('shown.bs.modal', function (e) { var button = $(e.relatedTarget); var manifestData = button.data('plugin-manifest'); - - if (manifestData) { - $('#plugin-uri').html(manifestData.plugin_uri - ? `${manifestData.plugin_uri}` - : 'Unknown' + var installed = button.data('plugin-installed'); + + if (manifestData) { + $('#plugin-uri').html(manifestData.plugin_uri + ? `${manifestData.plugin_uri}` + : 'Unknown' + ); + $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`); + $('#plugin-name').text(manifestData.name || 'Unknown'); + $('#plugin-version').text(manifestData.version || 'Unknown'); + $('#plugin-description').text(manifestData.description || 'No description provided'); + $('#plugin-author').html(manifestData.author + ? manifestData.author + (manifestData.author_uri + ? ` (profile)` : '') : 'Unknown' ); - $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`); - $('#plugin-name').text(manifestData.name || 'Unknown'); - $('#plugin-version').text(manifestData.version || 'Unknown'); - $('#plugin-description').text(manifestData.description || 'No description provided'); - $('#plugin-author').html(manifestData.author - ? manifestData.author + (manifestData.author_uri - ? ` (profile)` : '') : 'Unknown' - ); - $('#plugin-license').text(manifestData.license || 'Unknown'); - $('#plugin-locale').text(manifestData.default_locale || 'Unknown'); - $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); - $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); - $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); - $('#plugin-user-name').html(manifestData.user_nonprivileged.name || 'None'); - } + $('#plugin-license').text(manifestData.license || 'Unknown'); + $('#plugin-locale').text(manifestData.default_locale || 'Unknown'); + $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); + $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); + $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); + $('#plugin-user-name').html(manifestData.user_nonprivileged.name || 'None'); + } + if (installed) { + $('#js-install-plugin-confirm').html('OK'); + } else { + $('#js-install-plugin-confirm').html('Install now'); + } }); $('#js-install-plugin-confirm').on('click', function (e) { var progressText = $('#js-install-plugin-confirm').attr('data-message'); var successHtml = $('#plugin-install-message').attr('data-message'); - var closeHtml = $('#js-system-reset-cancel').attr('data-message'); var pluginUri = $('#plugin-uri a').attr('href'); var pluginVersion = $('#plugin-version').text(); var csrfToken = $('meta[name=csrf_token]').attr('content'); $("#install-user-plugin").modal('hide'); - $("#install-plugin-progress").modal('show'); - $.post('ajax/plugins/do_plugin_install.php?',{'plugin_uri': pluginUri, 'plugin_version': pluginVersion, 'csrf_token': csrfToken},function(data){ - setTimeout(function(){ - response = JSON.parse(data); - if(response === true) { - $('#plugin-install-message').text(successHtml); - $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary').addClass('fas fa-check'); - $('#js-install-plugin-ok').removeAttr("disabled"); - } else { - $('#plugin-install-message').text('An error occurred installing the plugin.'); - $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); - $('#js-install-plugin-ok').removeAttr("disabled"); - } - },300); - }); + if ($('#js-install-plugin-confirm').text() === 'Install now') { + $("#install-plugin-progress").modal('show'); + + $.post('ajax/plugins/do_plugin_install.php?',{'plugin_uri': pluginUri, + 'plugin_version': pluginVersion, 'csrf_token': csrfToken},function(data){ + setTimeout(function(){ + response = JSON.parse(data); + if (response === true) { + $('#plugin-install-message').contents().first().replaceWith(successHtml); + $('#plugin-install-message').find('i') + .removeClass('fas fa-cog fa-spin link-secondary') + .addClass('fas fa-check'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } else { + $('#plugin-install-message').contents().first().replaceWith('An error occurred installing the plugin.'); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } + },200); + }); + } }); $('#js-install-plugin-ok').on('click', function (e) { diff --git a/installers/plugin_helper.sh b/installers/plugin_helper.sh index 08c424a39..2d946e363 100755 --- a/installers/plugin_helper.sh +++ b/installers/plugin_helper.sh @@ -1,7 +1,7 @@ #!/bin/bash # # PluginInstaller helper for RaspAP -# # @author billz +# @author billz # license: GNU General Public License v3.0 # Exit on error diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 3157e3fee..b5ba43af0 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -428,7 +428,7 @@ public function getHTMLPluginsTable(array $plugins): string if ($installed === true ) { $button = ''; + data-plugin-manifest="' .$manifest. '" data-plugin-installed="' .$installed. '"> ' . _("Installed") .''; } else { $button = ' - + From 361a2f75311cfa1c9b9de0cbf942a1c43ba89cf2 Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 09:17:12 -0800 Subject: [PATCH 24/26] Use text() instead of replaceWith() --- app/js/custom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/custom.js b/app/js/custom.js index d68c94f14..396313e6f 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -517,7 +517,7 @@ $('#js-install-plugin-confirm').on('click', function (e) { setTimeout(function(){ response = JSON.parse(data); if (response === true) { - $('#plugin-install-message').contents().first().replaceWith(successHtml); + $('#plugin-install-message').contents().first().text(successHtml); $('#plugin-install-message').find('i') .removeClass('fas fa-cog fa-spin link-secondary') .addClass('fas fa-check'); From 3c61954971ba4c9f55a1191c738940bd9a4d558d Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 09:17:33 -0800 Subject: [PATCH 25/26] Minor: update label --- templates/system/plugins.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/system/plugins.php b/templates/system/plugins.php index 71677d19d..194e161e0 100644 --- a/templates/system/plugins.php +++ b/templates/system/plugins.php @@ -9,7 +9,7 @@
- Details for more information and to install a plugin. Uninstall removes an existing plugin."); ?> + Details for more information and to install a plugin."); ?>
From 9bb2075b77f1141455c66362a6a514f0f17817b8 Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 09:41:52 -0800 Subject: [PATCH 26/26] Add strings to en_US locale, update template --- app/js/custom.js | 3 +- locale/en_US/LC_MESSAGES/messages.mo | Bin 42621 -> 43927 bytes locale/en_US/LC_MESSAGES/messages.po | 63 +++++++++++++++++++++++++++ templates/system.php | 2 +- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/app/js/custom.js b/app/js/custom.js index 396313e6f..eac5e17bd 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -503,6 +503,7 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { $('#js-install-plugin-confirm').on('click', function (e) { var progressText = $('#js-install-plugin-confirm').attr('data-message'); var successHtml = $('#plugin-install-message').attr('data-message'); + var successText = $('
').text(successHtml).text(); var pluginUri = $('#plugin-uri a').attr('href'); var pluginVersion = $('#plugin-version').text(); var csrfToken = $('meta[name=csrf_token]').attr('content'); @@ -517,7 +518,7 @@ $('#js-install-plugin-confirm').on('click', function (e) { setTimeout(function(){ response = JSON.parse(data); if (response === true) { - $('#plugin-install-message').contents().first().text(successHtml); + $('#plugin-install-message').contents().first().replaceWith(successText); $('#plugin-install-message').find('i') .removeClass('fas fa-cog fa-spin link-secondary') .addClass('fas fa-check'); diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo index fd8a0a6552a77a33ce2529228bd94e52e68d9042..23d44ab78cdf1a7d985ab1637c4cea170dd46994 100644 GIT binary patch delta 12251 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 NsJq|){~q&w{tu+gcD?`r diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po index 95fa1281e..b6b6e88b4 100644 --- a/locale/en_US/LC_MESSAGES/messages.po +++ b/locale/en_US/LC_MESSAGES/messages.po @@ -920,6 +920,69 @@ msgstr "Changing log limit size to %s KB" msgid "Information provided by raspap.sysinfo" msgstr "Information provided by raspap.sysinfo" +msgid "The following user plugins are available to extend RaspAP's functionality." +msgstr "The following user plugins are available to extend RaspAP's functionality." + +msgid "Choose Details for more information and to install a plugin." +msgstr "Choose Details for more information and to install a plugin." + +msgid "Plugins" +msgstr "Plugins" + +msgid "Plugin details" +msgstr "Plugin details" + +msgid "Name" +msgstr "Name" + +msgid "Version" +msgstr "Version" + +msgid "Description" +msgstr "Description" + +msgid "Plugin source" +msgstr "Plugin source" + +msgid "Author" +msgstr "Author" + +msgid "License" +msgstr "License" + +msgid "Language locale" +msgstr "Language locale" + +msgid "Configuration files" +msgstr "Configuration files" + +msgid "Dependencies" +msgstr "Dependencies" + +msgid "Permissions" +msgstr "Permissions" + +msgid "Non-privileged users" +msgstr "Non-privileged users" + +msgid "Install now" +msgstr "Install now" + +msgid "Installing plugin" +msgstr "Installing plugin" + +msgid "Plugin installation in progress..." +msgstr "Plugin installation in progress..." + +msgid "Plugin install completed." +msgstr "Plugin install completed." + +msgid "Details" +msgstr "Details" + +msgid "Installed" +msgstr "Installed" + #: includes/data_usage.php msgid "Data usage" msgstr "Data usage" diff --git a/templates/system.php b/templates/system.php index cd4dbaf1a..1891bda0e 100755 --- a/templates/system.php +++ b/templates/system.php @@ -164,7 +164,7 @@