diff --git a/app/js/custom.js b/app/js/custom.js
index 53ea7ae35..eac5e17bd 100644
--- a/app/js/custom.js
+++ b/app/js/custom.js
@@ -468,6 +468,95 @@ $('#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');
+ 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-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 successText = $('
').text(successHtml).text();
+ 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');
+
+ 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(successText);
+ $('#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) {
+ $("#install-plugin-progress").modal('hide');
+ window.location.reload();
+});
+
+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 +596,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);
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);
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/includes/system.php b/includes/system.php
index 31feaf798..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'])) {
@@ -85,53 +86,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 +117,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 = $pluginInstaller->getUserPlugins();
+ $pluginsTable = $pluginInstaller->getHTMLPluginsTable($plugins);
+
echo renderTemplate("system", compact(
"arrLocales",
"status",
@@ -167,11 +140,62 @@ function DisplaySystem(&$extraFooterScripts)
"cputemp",
"cputemp_status",
"cputemp_led",
- "hostapd",
- "hostapd_status",
- "hostapd_led",
"themes",
"selectedTheme",
- "logLimit"
+ "logLimit",
+ "pluginsTable"
));
}
+
+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
+ ];
+}
+
diff --git a/installers/common.sh b/installers/common.sh
index 472058d92..bd78ef006 100755
--- a/installers/common.sh
+++ b/installers/common.sh
@@ -51,6 +51,7 @@ function _install_raspap() {
_download_latest_files
_change_file_ownership
_create_hostapd_scripts
+ _create_plugin_scripts
_create_lighttpd_scripts
_install_lighttpd_configs
_default_configuration
@@ -298,6 +299,19 @@ function _create_hostapd_scripts() {
_install_status 0
}
+# Generate plugin helper scripts
+function _create_plugin_scripts() {
+ _install_log "Creating plugin helper scripts"
+ sudo mkdir $raspap_dir/plugins || _install_status 1 "Unable to create directory '$raspap_dir/plugins'"
+
+ # Copy plugin helper script
+ sudo cp "$webroot_dir/installers/"plugin_helper.sh "$raspap_dir/plugins" || _install_status 1 "Unable to move plugin script"
+ # Change ownership and permissions of plugin script
+ sudo chown -c root:root "$raspap_dir/plugins/"*.sh || _install_status 1 "Unable change owner and/or group"
+ sudo chmod 750 "$raspap_dir/plugins/"*.sh || _install_status 1 "Unable to change file permissions"
+ _install_status 0
+}
+
# Generate lighttpd service control scripts
function _create_lighttpd_scripts() {
_install_log "Creating lighttpd control scripts"
diff --git a/installers/plugin_helper.sh b/installers/plugin_helper.sh
new file mode 100755
index 000000000..2d946e363
--- /dev/null
+++ b/installers/plugin_helper.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+#
+# PluginInstaller helper for RaspAP
+# @author billz
+# license: GNU General Public License v3.0
+
+# Exit on error
+set -o errexit
+
+readonly raspap_user="www-data"
+
+[ $# -lt 1 ] && { echo "Usage: $0
[parameters...]"; exit 1; }
+
+action="$1" # action to perform
+shift 1
+
+case "$action" in
+
+ "sudoers")
+ [ $# -ne 1 ] && { echo "Usage: $0 sudoers "; exit 1; }
+ file="$1"
+ plugin_name=$(basename "$file")
+ dest="/etc/sudoers.d/${plugin_name}"
+
+ mv "$file" "$dest" || { echo "Error: Failed to move $file to $dest."; exit 1; }
+
+ chown root:root "$dest" || { echo "Error: Failed to set ownership for $dest."; exit 1; }
+ chmod 0440 "$dest" || { echo "Error: Failed to set permissions for $dest."; exit 1; }
+
+ echo "OK"
+ ;;
+
+ "packages")
+ [ $# -lt 1 ] && { echo "Usage: $0 packages "; exit 1; }
+
+ echo "Installing APT packages..."
+ for package in "$@"; do
+ echo "Installing package: $package"
+ apt-get install -y "$package" || { echo "Error: Failed to install $package."; exit 1; }
+ done
+ echo "OK"
+ ;;
+
+ "user")
+ [ $# -lt 2 ] && { echo "Usage: $0 user ."; exit 1; }
+
+ username=$1
+ password=$2
+
+ if id "$username" &>/dev/null; then # user already exists
+ echo "OK"
+ exit 0
+ fi
+ # create the user without shell access
+ useradd -r -s /bin/false "$username"
+
+ # set password non-interactively
+ echo "$username:$password" | chpasswd
+
+ echo "OK"
+ ;;
+
+ "config")
+ [ $# -lt 2 ] && { echo "Usage: $0 config
@@ -105,3 +107,83 @@
+
+