From 431565fa5a738dc5705e0c821cc20c4153cc4633 Mon Sep 17 00:00:00 2001 From: Austin Best Date: Wed, 21 Aug 2024 23:36:00 -0400 Subject: [PATCH] WIP... Closes #43 Implement sqlite3 to replace settings and servers json files Add nightly branch Refactor notifications so multiples can be sent per platform Refactor the api functions some Add a trait/interface function loader Add some defines for common platforms Fix the dialogClose function Fix backups not being removed Some rewording --- .github/workflows/docker-publish.yml | 4 +- Dockerfile | 5 + README.md | 10 +- root/app/www/public/ajax/commands.php | 30 +- root/app/www/public/ajax/compose.php | 6 +- root/app/www/public/ajax/containers.php | 466 ++++++++++-------- root/app/www/public/ajax/login.php | 5 + root/app/www/public/ajax/logs.php | 16 +- root/app/www/public/ajax/notification.php | 387 +++++++++------ root/app/www/public/ajax/orphans.php | 37 +- root/app/www/public/ajax/overview.php | 10 +- root/app/www/public/ajax/settings.php | 156 +++--- root/app/www/public/ajax/shared.php | 8 +- root/app/www/public/ajax/tasks.php | 34 +- root/app/www/public/api/index.php | 294 +---------- root/app/www/public/classes/Database.php | 96 ++++ root/app/www/public/classes/Docker.php | 63 +-- root/app/www/public/classes/Maintenance.php | 26 +- root/app/www/public/classes/Notifications.php | 128 +++-- .../www/public/classes/interfaces/Docker.php | 50 ++ .../classes/interfaces/Notifications.php | 15 + .../traits/Database/ContainerGroupLink.php | 70 +++ .../traits/Database/ContainerGroups.php | 80 +++ .../traits/Database/ContainerSettings.php | 111 +++++ .../traits/Database/NotificationLink.php | 81 +++ .../traits/Database/NotificationPlatform.php | 30 ++ .../traits/Database/NotificationTrigger.php | 61 +++ .../classes/traits/Database/Servers.php | 56 +++ .../classes/traits/Database/Settings.php | 69 +++ .../www/public/classes/traits/Docker/API.php | 7 +- .../classes/traits/Docker/Container.php | 18 +- .../classes/traits/Maintenance/maint.php | 6 +- .../traits/Notifications/Templates.php | 64 +++ .../traits/Notifications/notifiarr.php | 4 +- root/app/www/public/crons/health.php | 40 +- root/app/www/public/crons/housekeeper.php | 46 +- root/app/www/public/crons/prune.php | 17 +- root/app/www/public/crons/pulls.php | 79 +-- root/app/www/public/crons/sse.php | 2 +- root/app/www/public/crons/state.php | 107 ++-- root/app/www/public/crons/stats.php | 6 +- root/app/www/public/functions/api.php | 439 ++++++++++++++++- root/app/www/public/functions/common.php | 66 ++- root/app/www/public/functions/containers.php | 163 +++--- root/app/www/public/functions/curl.php | 38 +- root/app/www/public/functions/docker.php | 40 +- root/app/www/public/functions/dockerAPI.php | 2 +- root/app/www/public/functions/files.php | 29 -- .../www/public/functions/helpers/array.php | 5 + .../www/public/functions/helpers/strings.php | 14 +- .../www/public/functions/notifications.php | 47 -- root/app/www/public/includes/constants.php | 24 +- root/app/www/public/includes/footer.php | 2 +- root/app/www/public/includes/header.php | 56 +-- root/app/www/public/index.php | 14 +- root/app/www/public/js/common.js | 49 +- root/app/www/public/js/containers.js | 16 +- root/app/www/public/js/notification.js | 122 ++++- root/app/www/public/js/settings.js | 2 +- root/app/www/public/loader.php | 96 ++-- .../public/migrations/001_initial_setup.php | 274 ++++++++++ .../public/migrations/002_remote_timeout.php | 22 + root/app/www/public/sse.php | 7 +- root/app/www/public/startup.php | 16 +- root/etc/php82/conf.d/dockwatch.ini | 2 +- 65 files changed, 2890 insertions(+), 1455 deletions(-) create mode 100644 root/app/www/public/classes/Database.php create mode 100644 root/app/www/public/classes/interfaces/Docker.php create mode 100644 root/app/www/public/classes/interfaces/Notifications.php create mode 100644 root/app/www/public/classes/traits/Database/ContainerGroupLink.php create mode 100644 root/app/www/public/classes/traits/Database/ContainerGroups.php create mode 100644 root/app/www/public/classes/traits/Database/ContainerSettings.php create mode 100644 root/app/www/public/classes/traits/Database/NotificationLink.php create mode 100644 root/app/www/public/classes/traits/Database/NotificationPlatform.php create mode 100644 root/app/www/public/classes/traits/Database/NotificationTrigger.php create mode 100644 root/app/www/public/classes/traits/Database/Servers.php create mode 100644 root/app/www/public/classes/traits/Database/Settings.php create mode 100644 root/app/www/public/classes/traits/Notifications/Templates.php delete mode 100644 root/app/www/public/functions/notifications.php create mode 100644 root/app/www/public/migrations/001_initial_setup.php create mode 100644 root/app/www/public/migrations/002_remote_timeout.php diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 4b42365..ce38013 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -4,11 +4,11 @@ name: Docker on: push: - branches: [ "main", "develop" ] + branches: [ "main", "develop", "nightly" ] # Publish semver tags as releases. tags: [ 'v*.*.*' ] pull_request: - branches: [ "main", "develop" ] + branches: [ "main", "develop", "nightly" ] env: # Use docker.io for Docker Hub if empty diff --git a/Dockerfile b/Dockerfile index 5ea2348..3f1befc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,11 @@ RUN \ # install sockets RUN apk add --no-cache \ php82-sockets + +# install sqlite3 +RUN apk add --no-cache \ + sqlite \ + php82-sqlite3 # add regctl for container digest checks ARG TARGETARCH diff --git a/README.md b/README.md index 9cfd62d..b31be72 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,12 @@ ## Purpose Simple UI driven way to manage updates & notifications for Docker containers. -No database required. All settings are stored locally in a volume mount. +No database container required, all settings are stored locally with sqlite3 + +## Branches +- `:main` Stable releases after testing on develop +- `:develop` Usually pretty stable +- `:nightly` Use at your own risk, suggest joining Discord to keep up to date with what is going on. Might not be able to hop back to develop or main!! ## Notification triggers @@ -21,12 +26,13 @@ No database required. All settings are stored locally in a volume mount. ## Notification platforms - Notifiarr +- Telegram (*coming soon*) ## Update options - Ignore -- Auto update - Check for updates +- Auto update ## Additional features diff --git a/root/app/www/public/ajax/commands.php b/root/app/www/public/ajax/commands.php index b5c9d4a..4686ae2 100644 --- a/root/app/www/public/ajax/commands.php +++ b/root/app/www/public/ajax/commands.php @@ -19,10 +19,10 @@
@@ -42,10 +42,10 @@ $serverData) { + foreach ($serversTable as $serverId => $serverData) { ?> - + $serverData) { - if (in_array($serverIndex, $servers)) { - $serverOverride = $serverData; - $apiResponse = apiRequest($_POST['command'], ['name' => $_POST['container'], 'params' => $_POST['parameters']]); + foreach ($serversTable as $serverId => $serverData) { + if (in_array($serverId, $servers)) { + apiSetActiveServer($serverData['id'], $serversTable); - if ($apiResponse['code'] == 200) { - $apiResponse = $apiResponse['response']['docker']; - } else { - $apiResponse = $apiResponse['code'] .': '. $apiResponse['error']; - } + $apiResponse = apiRequest($_POST['command'], ['name' => $_POST['container'], 'params' => $_POST['parameters']]); + $apiResponse = $apiResponse['code'] == 200 ? $apiResponse['result'] : $apiResponse['code'] . ': ' . $apiResponse['error']; ?> -

+

@@ -176,4 +176,4 @@ } else { echo $up; } -} \ No newline at end of file +} diff --git a/root/app/www/public/ajax/containers.php b/root/app/www/public/ajax/containers.php index ad97f51..1a10080 100644 --- a/root/app/www/public/ajax/containers.php +++ b/root/app/www/public/ajax/containers.php @@ -10,10 +10,13 @@ require 'shared.php'; if ($_POST['m'] == 'init') { - $dependencyFile = $docker->setContainerDependencies($processList); - $pulls = is_array($pullsFile) ? $pullsFile : json_decode($pullsFile, true); + $containersTable = apiRequest('database-getContainers')['result']; + $containerGroupsTable = apiRequest('database-getContainerGroups')['result']; + $containerLinksTable = apiRequest('database-getContainerGroupLinks')['result']; + $dependencyFile = $docker->setContainerDependencies($processList); + $pulls = is_array($pullsFile) ? $pullsFile : json_decode($pullsFile, true); + $pullsNotice = empty($pullsFile) ? true : false; array_sort_by_key($processList, 'Names'); - $pullsNotice = empty($pullsFile) ? true : false; ?>
@@ -26,7 +29,7 @@
- Real time updates: 60)' : 'disabled') ?> + Real time updates: 60)' : 'disabled' ?>
@@ -47,31 +50,36 @@ $containerGroup) { - $groupCPU = $groupMemory = 0; + $groupContainerHashes = []; + if ($containerLinksTable) { + foreach ($containerGroupsTable as $containerGroup) { + $groupHash = $containerGroup['hash']; + $groupContainers = apiRequest('database-getGroupLinkContainersFromGroupId', ['group' => $containerGroup['id']])['result']; + $groupCPU = $groupMemory = 0; foreach ($processList as $process) { $nameHash = md5($process['Names']); - if (in_array($nameHash, $containerGroup['containers'])) { - $memUsage = floatval(str_replace('%', '', $process['stats']['MemPerc'])); - $groupMemory += $memUsage; - - $cpuUsage = floatval(str_replace('%', '', $process['stats']['CPUPerc'])); - if (intval($settingsFile['global']['cpuAmount']) > 0) { - $cpuUsage = number_format(($cpuUsage / intval($settingsFile['global']['cpuAmount'])), 2); + foreach ($groupContainers as $groupContainer) { + if ($nameHash == $groupContainer['hash']) { + $memUsage = floatval(str_replace('%', '', $process['stats']['MemPerc'])); + $groupMemory += $memUsage; + + $cpuUsage = floatval(str_replace('%', '', $process['stats']['CPUPerc'])); + if (intval($settingsTable['cpuAmount']) > 0) { + $cpuUsage = number_format(($cpuUsage / intval($settingsTable['cpuAmount'])), 2); + } + $groupCPU += $cpuUsage; } - $groupCPU += $cpuUsage; } } ?> - + @@ -83,11 +91,12 @@ $_POST['container']]); + $apiResult = apiRequest('docker-logs', ['name' => $_POST['container']]); logger(UI_LOG, 'dockerLogs:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - echo $apiResult['response']['docker']; + echo $apiResult['result']; } if ($_POST['m'] == 'massApplyContainerTrigger') { logger(UI_LOG, 'massApplyContainerTrigger ->'); - $dependencyFile = getServerFile('dependency'); - if ($dependencyFile['code'] != 200) { - $apiError = $dependencyFile['file']; - } - $dependencyFile = $dependencyFile['file']; - + $dependencyFile = apiRequest('file-dependency')['result']; $container = $docker->findContainer(['hash' => $_POST['hash'], 'data' => $stateFile]); $image = $docker->isIO($container['inspect'][0]['Config']['Image']); $currentImageID = $container['ID']; @@ -190,8 +187,8 @@ logger(UI_LOG, 'skipping ' . $container['Names'].' start request'); $result = 'Skipped ' . $container['Names'] . '
'; } else { - $apiResult = apiRequest('dockerStartContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStartContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-startContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-startContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); $result = 'Started ' . $container['Names'] . '
'; } break; @@ -200,10 +197,10 @@ logger(UI_LOG, 'skipping ' . $container['Names'].' restart request'); $result = 'Skipped ' . $container['Names'] . '
'; } else { - $apiResult = apiRequest('dockerStopContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - $apiResult = apiRequest('dockerStartContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStartContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-stopContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-stopContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-startContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-startContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); $result = 'Restarted ' . $container['Names'] . '
'; $dependencies = $dependencyFile[$container['Names']]['containers']; } @@ -213,8 +210,8 @@ logger(UI_LOG, 'skipping ' . $container['Names'].' stop request'); $result = 'Skipped ' . $container['Names'] . '
'; } else { - $apiResult = apiRequest('dockerStopContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-stopContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-stopContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); $result = 'Stopped ' . $container['Names'] . '
'; $dependencies = $dependencyFile[$container['Names']]['containers']; } @@ -222,13 +219,13 @@ case '4': //-- PULL $regctlDigest = trim(regctlCheck($image)); - $pull = apiRequest('dockerPullContainer', [], ['name' => $image]); - logger(UI_LOG, 'dockerPullContainer:' . json_encode($pull, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-pullContainer', [], ['name' => $image]); + logger(UI_LOG, 'docker-pullContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); - $inspectImage = apiRequest('dockerInspect', ['name' => $image, 'useCache' => false, 'format' => true]); - $inspectImage = json_decode($inspectImage['response']['docker'], true); - list($cr, $imageDigest) = explode('@', $inspectImage[0]['RepoDigests'][0]); - logger(UI_LOG, 'dockerInspect:' . json_encode($inspectImage, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-inspect', ['name' => $image, 'useCache' => false, 'format' => true]); + $apiRequest = json_decode($apiRequest['result'], true); + list($cr, $imageDigest) = explode('@', $apiRequest[0]['RepoDigests'][0]); + logger(UI_LOG, 'dockerInspect:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); $pullsFile[md5($container['Names'])] = [ 'checked' => time(), @@ -237,14 +234,14 @@ 'imageDigest' => $imageDigest ]; - setServerFile('pull', $pullsFile); + apiRequest('file-pull', [], ['contents' => $pullsFile]); $result = 'Pulled ' . $container['Names'] . '
'; break; case '5': //-- GERNERATE RUN - $autoRun = apiRequest('dockerAutoRun', ['name' => $container['Names']]); - logger(UI_LOG, 'dockerAutoRun:' . json_encode($autoRun, JSON_UNESCAPED_SLASHES)); - $autoRun = $autoRun['response']['docker']; - $result = '
' . $autoRun . '
'; + $apiRequest = apiRequest('docker-autoRun', ['name' => $container['Names']]); + logger(UI_LOG, 'docker-autoRun:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $autoRun = $apiRequest['result']; + $result = '
' . $apiRequest . '
'; break; case '6': //-- GENERATE COMPOSE $containerList = ''; @@ -255,10 +252,10 @@ $containerList .= $thisContainer['Names'] . ' '; } - $autoCompose = apiRequest('dockerAutoCompose', ['name' => trim($containerList)]); - logger(UI_LOG, 'dockerAutoCompose:' . json_encode($autoCompose, JSON_UNESCAPED_SLASHES)); - $autoCompose = $autoCompose['response']['docker']; - $result = '
' . $autoCompose . '
'; + $apiRequest = apiRequest('docker-autoCompose', ['name' => trim($containerList)]); + logger(UI_LOG, 'docker-autoCompose:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $apiRequest = $apiRequest['result']; + $result = '
' . $apiRequest . '
'; break; case '7': //-- CHECK FOR UPDATES AND APPLY THEM if (skipContainerActions($image, $skipContainerActions)) { @@ -268,9 +265,9 @@ $image = $container['inspect'][0]['Config']['Image']; logger(UI_LOG, 'image:' . $image); - $apiResponse = apiRequest('dockerInspect', ['name' => $container['Names'], 'useCache' => false, 'format' => true]); - logger(UI_LOG, 'dockerInspect:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $inspectImage = $apiResponse['response']['docker']; + $apiResponse = apiRequest('docker-inspect', ['name' => $container['Names'], 'useCache' => false, 'format' => true]); + logger(UI_LOG, 'docker-inspect:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $inspectImage = $apiResponse['result']; if ($inspectImage) { $inspect = json_decode($inspectImage, true); @@ -283,27 +280,27 @@ } } - $apiResponse = apiRequest('dockerPullContainer', [], ['name' => $image]); - logger(UI_LOG, 'dockerPullContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-pullContainer', [], ['name' => $image]); + logger(UI_LOG, 'docker-pullContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); - $apiResponse = apiRequest('dockerStopContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStopContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-stopContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-stopContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); - $apiResponse = apiRequest('dockerRemoveContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerRemoveContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-removeContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-removeContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); - $apiResponse = apiRequest('dockerCreateContainer', [], ['inspect' => $inspectImage]); - logger(UI_LOG, 'dockerCreateContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $update = $apiResponse['response']['docker']; + $apiRequest = apiRequest('docker-createContainer', [], ['inspect' => $inspectImage]); + logger(UI_LOG, 'docker-createContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $update = $apiRequest['result']; $updateResult = 'failed'; if (strlen($update['Id']) == 64) { // REMOVE THE IMAGE AFTER UPDATE - $removeImage = apiRequest('removeImage', ['image' => $currentImageID]); - logger(UI_LOG, 'removeImage:' . json_encode($removeImage, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-removeImage', [], ['image' => $currentImageID]); + logger(UI_LOG, 'docker-removeImage:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); - $inspectImage = apiRequest('dockerInspect', ['name' => $image, 'useCache' => false, 'format' => true]); - $inspectImage = json_decode($inspectImage['response']['docker'], true); + $inspectImage = apiRequest('docker-inspect', ['name' => $image, 'useCache' => false, 'format' => true]); + $inspectImage = json_decode($inspectImage['result'], true); list($cr, $imageDigest) = explode('@', $inspectImage[0]['RepoDigests'][0]); if ($inspectImage) { @@ -323,11 +320,11 @@ 'imageDigest' => $imageDigest ]; - setServerFile('pull', $pullsFile); + apiRequest('file-pull', [], ['contents' => $pullsFile]); if (str_contains($container['State'], 'running')) { - $apiResponse = apiRequest('dockerStartContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStartContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-startContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-startContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); } else { logger(UI_LOG, 'container was not running, not starting it'); } @@ -350,26 +347,26 @@ logger(UI_LOG, 'skipping ' . $container['Names'].' remove request'); $result = 'Skipped ' . $container['Names'] . '
'; } else { - $apiResult = apiRequest('dockerStopContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - $apiResult = apiRequest('dockerRemoveContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerRemoveContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docke-stopContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'dockerStopContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-removeContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-removeContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); $result = 'Removed ' . $container['Names'] . '
'; } break; case '10': //-- GENERATE API CREATE - $apiResult = apiRequest('dockerContainerCreateAPI', ['name' => $container['Names']]); - logger(UI_LOG, 'dockerContainerCreateAPI:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - $apiResult = json_decode($apiResult['response']['docker'], true); + $apiRequest = apiRequest('dockerAPI-createContainer', ['name' => $container['Names']]); + logger(UI_LOG, 'dockerAPI-createContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $apiRequest = json_decode($apiRequest['result'], true); $result = $container['Names'] . '
'; - $result .= 'Endpoint: ' . $apiResult['endpoint'] . '
'; - $result .= '
' . json_encode($apiResult['payload'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . '
'; + $result .= 'Endpoint: ' . $apiRequest['endpoint'] . '
'; + $result .= '
' . json_encode($apiRequest['payload'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . '
'; break; case '11': //-- CHECK FOR UPDATES - $apiResponse = apiRequest('dockerInspect', ['name' => $image, 'useCache' => false]); - logger(UI_LOG, 'dockerInspect:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $inspectImage = json_decode($apiResponse['response']['docker'], true); + $apiResponse = apiRequest('docker-inspect', ['name' => $image, 'useCache' => false]); + logger(UI_LOG, 'docker-inspect:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $inspectImage = json_decode($apiResponse['result'], true); foreach ($inspectImage[0]['Config']['Labels'] as $label => $val) { if (str_contains($label, 'image.version')) { @@ -409,7 +406,7 @@ 'imageDigest' => $imageDigest ]; - setServerFile('pull', $pullsFile); + apiRequest('file-pull', [], ['contents' => $pullsFile]); } break; case '12': //-- RE-CREATE @@ -420,26 +417,26 @@ $image = $container['inspect'][0]['Config']['Image']; logger(UI_LOG, 'image:' . $image); - $apiResponse = apiRequest('dockerInspect', ['name' => $container['Names'], 'useCache' => false, 'format' => true]); - logger(UI_LOG, 'dockerInspect:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $inspectImage = $apiResponse['response']['docker']; + $apiRequest = apiRequest('docker-inspect', ['name' => $container['Names'], 'useCache' => false, 'format' => true]); + logger(UI_LOG, 'docker-inspect:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $inspectImage = $apiRequest['result']; - $apiResult = apiRequest('dockerStopContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-stopContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-stopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - $apiResult = apiRequest('dockerRemoveContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerRemoveContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiResult = apiRequest('docker-removeContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-removeContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - $apiResponse = apiRequest('dockerCreateContainer', [], ['inspect' => $inspectImage]); - logger(UI_LOG, 'dockerCreateContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $update = $apiResponse['response']['docker']; + $apiRequest = apiRequest('docker-createContainer', [], ['inspect' => $inspectImage]); + logger(UI_LOG, 'docker-createContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $update = $apiRequest['result']; $createResult = 'failed'; if (strlen($update['Id']) == 64) { $createResult = 'complete'; - $apiResponse = apiRequest('dockerStartContainer', [], ['name' => $container['Names']]); - logger(UI_LOG, 'dockerStartContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-startContainer', [], ['name' => $container['Names']]); + logger(UI_LOG, 'docker-startContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); $dependencies = $dependencyFile[$container['Names']]['containers']; if ($dependencies) { @@ -453,10 +450,10 @@ } $getExpandedProcessList = getExpandedProcessList(true, true, true); - $processList = $getExpandedProcessList['processList']; + $processList = $getExpandedProcessList['processList']; - $return = renderContainerRow($_POST['hash'], 'json'); - $return['result'] = $result; + $return = renderContainerRow($_POST['hash'], 'json'); + $return['result'] = $result; $return['dependencies'] = $dependencies; logger(UI_LOG, 'massApplyContainerTrigger <-'); echo json_encode($return); @@ -465,16 +462,16 @@ if ($_POST['m'] == 'controlContainer') { $container = $docker->findContainer(['hash' => $_POST['hash'], 'data' => $stateFile]); - if ($_POST['action'] == 'stop' || $_POST['action'] == 'restart') { - apiRequest('dockerStopContainer', [], ['name' => $container['Names']]); + if (str_equals_any($_POST['action'], ['stop', 'restart'])) { + apiRequest('docker-stopContainer', [], ['name' => $container['Names']]); } - if ($_POST['action'] == 'start' || $_POST['action'] == 'restart') { - apiRequest('dockerStartContainer', [], ['name' => $container['Names']]); + if (str_equals_any($_POST['action'], ['start', 'restart'])) { + apiRequest('docker-startContainer', [], ['name' => $container['Names']]); } $return = renderContainerRow($_POST['hash'], 'json'); - if ($_POST['action'] == 'start' || $_POST['action'] == 'restart') { + if (str_equals_any($_POST['action'], ['start', 'restart'])) { $return['length'] = 'Up 1 second'; } @@ -482,8 +479,8 @@ } if ($_POST['m'] == 'updateContainerRows') { - $processList = apiRequest('dockerProcessList', ['format' => true]); - $processList = json_decode($processList['response']['docker'], true); + $processList = apiRequest('docker-processList', ['format' => true]); + $processList = json_decode($processList['result'], true); $update = []; foreach ($processList as $process) { @@ -495,10 +492,14 @@ } if ($_POST['m'] == 'openContainerGroups') { - $processList = apiRequest('dockerProcessList', ['format' => true]); - $processList = json_decode($processList['response']['docker'], true); + $processList = apiRequest('docker-processList', ['format' => true]); + $processList = json_decode($processList['result'], true); array_sort_by_key($processList, 'Names'); + $containersTable = apiRequest('database-getContainers')['result']; + $containerGroupTable = apiRequest('database-getContainerGroups')['result']; + $containerGroupLinksTable = apiRequest('database-getContainerGroupLinks')['result']; + ?>
@@ -507,11 +508,11 @@
$nameHash])['result']; + $inGroup = ''; + + if ($containerGroupTable) { + foreach ($containerGroupTable as $containerGroup) { + $containersInGroup = apiRequest('database-getGroupLinkContainersFromGroupId', ['group' => $containerGroup['id']])['result']; + + foreach ($containersInGroup as $containerInGroup) { + if ($containerInGroup['hash'] == $nameHash) { + $inGroup = $containerGroup['name']; + break; + } } } } ?> - + - + true]); - $processList = json_decode($processList['response']['docker'], true); + $processList = apiRequest('docker-processList', ['format' => true]); + $processList = json_decode($processList['result'], true); array_sort_by_key($processList, 'Names'); + $containersTable = apiRequest('database-getContainers')['result']; + $containerGroupTable = apiRequest('database-getContainerGroups')['result']; + $containerGroupLinksTable = apiRequest('database-getContainerGroupLinks')['result']; + foreach ($processList as $process) { $nameHash = md5($process['Names']); + $container = apiRequest('database-getContainerFromHash', ['hash' => $nameHash])['result']; $inGroup = ''; $inThisGroup = false; - if ($settingsFile['containerGroups']) { - foreach ($settingsFile['containerGroups'] as $groupHash => $groupContainers) { - if (in_array($nameHash, $groupContainers['containers'])) { - $inGroup = $groupContainers['name']; - if ($groupHash == $_POST['groupHash']) { - $inThisGroup = true; + if ($containerGroupTable) { + foreach ($containerGroupTable as $containerGroup) { + $containersInGroup = apiRequest('database-getGroupLinkContainersFromGroupId', ['group' => $containerGroup['id']])['result']; + + foreach ($containersInGroup as $containerInGroup) { + if ($containerInGroup['hash'] == $nameHash) { + $inGroup = $containerGroup['name']; + + if ($containerGroup['id'] == $_POST['groupId']) { + $inGroup = '' . $containerGroup['name'] . ''; + $inThisGroup = true; + } + + break; } } } } + ?> - + - + $groupId]); } else { - if ($_POST['selection'] == '1' && is_array($settingsFile['containerGroups'])) { - foreach ($settingsFile['containerGroups'] as $groupDetails) { - if (strtolower($groupDetails['name']) == strtolower($groupName)) { + if (!$groupId) { + foreach ($containerGroupTable as $containerGroup) { + if (str_compare('nocase', $containerGroup['name'], $groupName)) { $error = 'A group with that name already exists'; break; } } + + if (!$error) { + $groupId = apiRequest('database-addContainerGroup', [], ['name' => $groupName])['result']; + + if (!$groupId) { + $error = 'Error creating the new \'' . $groupName . '\' group: ' . $database->error(); + } + } + } else { + foreach ($containerGroupTable as $containerGroup) { + if ($containerGroup['id'] == $groupId) { + if ($containerGroup['name'] != $groupName) { + apiRequest('database-updateContainerGroup', [], ['id' => $groupId, 'name' => $groupName]); + } + break; + } + } } if (!$error) { - $containers = []; - foreach ($_POST as $key => $val) { if (!str_contains($key, 'groupContainer')) { continue; } - list($junk, $containerHash) = explode('-', $key); - $containers[] = $containerHash; - } + list($junk, $containerId) = explode('-', $key); - $settingsFile['containerGroups'][$groupHash] = ['name' => $groupName, 'containers' => $containers]; - } - } + $linkExists = false; + foreach ($containerGroupLinksTable as $groupLink) { + if ($groupLink['group_id'] != $groupId) { + continue; + } + + if ($groupLink['container_id'] == $containerId) { + $linkExists = true; + break; + } + } - if (!$error) { - setServerFile('settings', $settingsFile); + if ($linkExists) { + if (!$val) { + apiRequest('database-removeContainerGroupLink', [], ['groupId' => $groupId, 'containerId' => $containerId]); + } + } else { + if ($val) { + apiRequest('database-addContainerGroupLink', [], ['groupId' => $groupId, 'containerId' => $containerId]); + } + } + } + } } echo $error; } if ($_POST['m'] == 'updateOptions') { - $processList = apiRequest('dockerProcessList', ['format' => true]); - $processList = json_decode($processList['response']['docker'], true); + $containersTable = apiRequest('database-getContainers')['result']; + $processList = apiRequest('docker-processList', ['format' => true]); + $processList = json_decode($processList['result'], true); array_sort_by_key($processList, 'Names'); ?> @@ -648,25 +704,24 @@ $nameHash])['result']; ?> - +

- Containers: + Containers:
   
Group
') ?>' ?> Not assigned' ?>
' : '') : '') ?>' : '') : '' ?> Not assigned' ?>
- - - +
- - -
-
- - - -
-
- - - -
+
+
+
+ + + +
+
+ + +
-
$val) { - preg_match('/container-update-([^-\n]+)|container-frequency-([^-\n]+)/', $key, $matches, PREG_OFFSET_CAPTURE); - if (!$matches) { + if (!str_contains($key, 'container-frequency-')) { continue; } - - $hash = $matches[1][0]; - if (!$hash || $hash == "all") { + + $hash = str_replace('container-frequency-', '', $key); + if (!$hash || $hash == 'all') { continue; } - list($minute, $hour, $dom, $month, $dow) = explode(' ', $_POST['container-frequency-' . $hash]); - $frequency = $minute . ' ' . $hour . ' ' . $dom . ' ' . $month . ' ' . $dow; + list($minute, $hour, $dom, $month, $dow) = explode(' ', $key); + $frequency = $minute . ' ' . $hour . ' ' . $dom . ' ' . $month . ' ' . $dow; + $updates = intval($_POST['container-update-' . $hash]); try { $cron = Cron\CronExpression::factory($frequency); @@ -722,22 +774,18 @@ $frequency = DEFAULT_CRON; } - $newSettings[$hash]['updates'] = $val; - $newSettings[$hash]['frequency'] = $frequency; - $newSettings[$hash]['restartUnhealthy'] = $settingsFile['containers'][$hash]['restartUnhealthy']; - $newSettings[$hash]['disableNotifications'] = $settingsFile['containers'][$hash]['disableNotifications']; - $newSettings[$hash]['shutdownDelay'] = $settingsFile['containers'][$hash]['shutdownDelay']; - $newSettings[$hash]['shutdownDelaySeconds'] = $settingsFile['containers'][$hash]['shutdownDelaySeconds']; + //-- ONLY UPDATE WHAT HAS CHANGED + $container = apiRequest('database-getContainerFromHash', ['hash' => $hash])['result']; + if ($container['updates'] != $updates || $container['frequency'] != $frequency) { + apiRequest('database-updateContainer', [], ['hash' => $hash, 'updates' => $updates, 'frequency' => $database->prepare($frequency)]); + } } - - $settingsFile['containers'] = $newSettings; - setServerFile('settings', $settingsFile); } if ($_POST['m'] == 'openEditContainer') { $container = $docker->findContainer(['hash' => $_POST['hash'], 'data' => $stateFile]); - $inspectImage = apiRequest('dockerInspect', ['name' => $container['Image'], 'useCache' => false, 'format' => true]); - $inspectImage = json_decode($inspectImage['response']['docker'], true); + $inspectImage = apiRequest('docker-inspect', ['name' => $container['Image'], 'useCache' => false, 'format' => true]); + $inspectImage = json_decode($inspectImag['result'], true); $inspectImage = $inspectImage[0]; ?> @@ -773,7 +821,7 @@ Web UI - This will create a dockwatch label, should be a valid URL (Ex: http://dockwatch or http://10.1.0.1:9999) + This will create a dockwatch label, should be a valid URL (Ex: http://dockwatch or http://10.1.0.1:) @@ -878,6 +926,8 @@ } if ($_POST['m'] == 'updateContainerOption') { - $settingsFile['containers'][$_POST['hash']][$_POST['option']] = $_POST['setting']; - $saveSettings = setServerFile('settings', $settingsFile); + $containersTable = apiRequest('database-getContainers')['result']; + $container = apiRequest('database-getContainerGroupFromHash', ['hash' => $_POST['hash']])['result']; + + apiRequest('database-updateContainer', [], ['hash' => $_POST['hash'], $_POST['option'] => $database->prepare($_POST['setting'])]); } diff --git a/root/app/www/public/ajax/login.php b/root/app/www/public/ajax/login.php index 700a380..a4aaaa6 100644 --- a/root/app/www/public/ajax/login.php +++ b/root/app/www/public/ajax/login.php @@ -9,6 +9,11 @@ require 'shared.php'; +if ($_POST['m'] == 'resetSession') { + session_unset(); + session_destroy(); +} + if ($_POST['m'] == 'login') { logger(SYSTEM_LOG, 'login ->'); diff --git a/root/app/www/public/ajax/logs.php b/root/app/www/public/ajax/logs.php index d9bbdd8..f722edd 100644 --- a/root/app/www/public/ajax/logs.php +++ b/root/app/www/public/ajax/logs.php @@ -108,10 +108,10 @@ if ($_POST['m'] == 'viewLog') { logger(SYSTEM_LOG, 'View log: ' . $_POST['name']); - $apiResponse = apiRequest('viewLog', ['name' => $_POST['name']]); + $apiRequest = apiRequest('server-log', ['name' => $_POST['name']]); - if ($apiResponse['code'] == 200) { - $result = json_decode($apiResponse['response']['result'], true); + if ($apiRequest['code'] == 200) { + $result = json_decode($apiRequest['result'], true); } else { $error = 'Failed to get log from server ' . ACTIVE_SERVER_NAME; } @@ -122,13 +122,13 @@ if ($_POST['m'] == 'purgeLogs') { logger(SYSTEM_LOG, 'Purge logs: ' . $_POST['group']); - $apiResponse = apiRequest('purgeLogs', [], ['group' => $_POST['group']]); - logger(UI_LOG, 'purgeLogs:' . json_encode($apiResponse)); + $apiRequest = apiRequest('server-purgeLogs', [], ['group' => $_POST['group']]); + logger(UI_LOG, 'purgeLogs:' . json_encode($apiRequest)); } if ($_POST['m'] == 'deleteLog') { logger(SYSTEM_LOG, 'Delete log: ' . $_POST['log']); - $apiResponse = apiRequest('deleteLog', [], ['log' => $_POST['log']]); - logger(UI_LOG, 'deleteLog:' . json_encode($apiResponse)); -} \ No newline at end of file + $apiRequest = apiRequest('server-deleteLog', [], ['log' => $_POST['log']]); + logger(UI_LOG, 'deleteLog:' . json_encode($apiRequest)); +} diff --git a/root/app/www/public/ajax/notification.php b/root/app/www/public/ajax/notification.php index 642e1e9..3204b03 100644 --- a/root/app/www/public/ajax/notification.php +++ b/root/app/www/public/ajax/notification.php @@ -9,179 +9,286 @@ require 'shared.php'; -$triggers = [ - [ - 'name' => 'updated', - 'label' => 'Updated', - 'desc' => 'Send a notification when a container has had an update applied', - 'event' => 'updates' - ],[ - 'name' => 'updates', - 'label' => 'Updates', - 'desc' => 'Send a notification when a container has an update available', - 'event' => 'updates' - ],[ - 'name' => 'stateChange', - 'label' => 'State change', - 'desc' => 'Send a notification when a container has a state change (running -> down)', - 'event' => 'state' - ],[ - 'name' => 'added', - 'label' => 'Added', - 'desc' => 'Send a notification when a container is added', - 'event' => 'state' - ],[ - 'name' => 'removed', - 'label' => 'Removed', - 'desc' => 'Send a notification when a container is removed', - 'event' => 'state' - ],[ - 'name' => 'prune', - 'label' => 'Prune', - 'desc' => 'Send a notification when an image or volume is pruned', - 'event' => 'prune' - ],[ - 'name' => 'cpuHigh', - 'label' => 'CPU usage', - 'desc' => 'Send a notification when container CPU usage exceeds threshold (set in Settings)', - 'event' => 'usage' - ],[ - 'name' => 'memHigh', - 'label' => 'Memory usage', - 'desc' => 'Send a notification when container memory usage exceeds threshold (set in Settings)', - 'event' => 'usage' - ],[ - 'name' => 'health', - 'label' => 'Health change', - 'desc' => 'Send a notification when container becomes unhealthy', - 'event' => 'health' - ] - ]; - if ($_POST['m'] == 'init') { - $notificationPlatforms = $notifications->getPlatforms(); + $notificationPlatformTable = apiRequest('database-getNotificationPlatforms')['result']; + $notificationTriggersTable = apiRequest('database-getNotificationTriggers')['result']; + $notificationLinkTable = apiRequest('database-getNotificationLinks')['result']; + ?> -
-
-

Triggers

-
- - - - - - - - - - - - - - - - - - - - +
+ Platforms +
+ ' : 'Coming soon!'; + + ?> +
+
+
+

+
+
+
+ +
+
+ +
+
+ Configured senders +
+ +
+
+ Notifications have not been setup yet, click the plus icon above to set them up. +
+
+ + -
-
NotificationDescriptionWebhook EventPlatform
class="form-check-input notification-check" id="notifications-name-"> - -
+
+
+
+

+ + + +

+
+ You have not configured any triggers for this notificationgetNotificationTriggerNameFromId($triggerId, $notificationTriggersTable); + $enabledTriggers[] = $trigger; + } + + echo '
Enabled: ' . implode(', ', $enabledTriggers) . '
'; + } + ?> +
+
+
+
+ +
-
-

Platforms

- $platform) { ?> -
-
- - + + getNotificationPlatformNameFromId($_POST['platformId'], $notificationPlatformTable); + $linkRow = $notificationLinkTable[$_POST['linkId']]; + $existingTriggers = $existingParameters = []; + + if ($linkRow) { + $existingTriggers = $linkRow['trigger_ids'] ? json_decode($linkRow['trigger_ids'], true) : []; + $existingParameters = $linkRow['platform_parameters'] ? json_decode($linkRow['platform_parameters'], true) : []; + $existingName = $linkRow['name']; + } + + ?> +
+

+
+
+ + + + + + + + + + - - - + + + + - - - + + +
TriggerDescriptionEvent
NameSettingDescription type="checkbox" class="form-check-input notification-trigger" id="notificationTrigger-">
+ + + + + + + + + + + + + $platformParameterData) { + ?> - - - + + - - -
Setting
+ Name Required
+ The name of this notification sender +
*' : '') ?>Required' : '') ?>
+ type="text" id="notificationPlatformParameter-" class="form-control" value=""> +
+ + + +
+
+ + + + + +
- -
-
-
$val) { - if (!str_contains($key, '-name-')) { - continue; +if ($_POST['m'] == 'addNotification') { + if (!$_POST['platformId']) { + $error = 'Missing required platform id'; + } + + if (!$error) { + $notificationPlatformTable = apiRequest('database-getNotificationPlatforms')['result']; + $notificationTriggersTable = apiRequest('database-getNotificationTriggers')['result']; + $notificationLinkTable = apiRequest('database-getNotificationLinks')['result']; + $platformParameters = json_decode($notificationPlatformTable[$_POST['platformId']]['parameters'], true); + $platformName = $notifications->getNotificationPlatformNameFromId($_POST['platformId'], $notificationPlatformTable); + + //-- CHECK FOR REQUIRED FIELDS + foreach ($platformParameters as $platformParameterField => $platformParameterData) { + if ($platformParameterData['required'] && !$_POST['notificationPlatformParameter-' . $platformParameterField]) { + $error = 'Missing required platform field: ' . $platformParameterData['label']; + break; + } } - $type = str_replace('notifications-name-', '', $key); - $newSettings[$type] = [ - 'active' => trim($val), - 'platform' => $_POST['notifications-platform-' . $type] - ]; - } - $settingsFile['notifications']['triggers'] = $newSettings; + if (!$error) { + $triggerIds = $platformParameters = []; + $senderName = $platformName; - //-- PLATFORM SETTINGS - $newSettings = []; - foreach ($_POST as $key => $val) { - $strip = str_replace('notifications-platform-', '', $key); - list($platformId, $platformField) = explode('-', $strip); + foreach ($_POST as $key => $val) { + if (str_contains($key, 'notificationTrigger-') && $val) { + $triggerIds[] = str_replace('notificationTrigger-', '', $key); + } - if (!is_numeric($platformId)) { - continue; + if (str_contains($key, 'notificationPlatformParameter-')) { + $field = str_replace('notificationPlatformParameter-', '', $key); + + if ($field != 'name') { + $platformParameters[$field] = $val; + } else { + $senderName = $val; + } + } + } + + apiRequest('database-addNotificationLink', ['platformId' => $_POST['platformId']], ['triggerIds' => $triggerIds, 'platformParameters' => $platformParameters, 'senderName' => $senderName]); } + } - $newSettings[$platformId][$platformField] = trim($val); + echo json_encode(['error' => $error]); +} + +if ($_POST['m'] == 'saveNotification') { + if (!$_POST['platformId']) { + $error = 'Missing required platform id'; + } + if (!$_POST['linkId']) { + $error = 'Missing required link id'; } - $settingsFile['notifications']['platforms'] = $newSettings; - $saveSettings = setServerFile('settings', $settingsFile); + if (!$error) { + $notificationPlatformTable = apiRequest('database-getNotificationPlatforms')['result']; + $notificationTriggersTable = apiRequest('database-getNotificationTriggers')['result']; + $notificationLinkTable = apiRequest('database-getNotificationLinks')['result']; + $platformParameters = json_decode($notificationPlatformTable[$_POST['platformId']]['parameters'], true); + $platformName = $notifications->getNotificationPlatformNameFromId($_POST['platformId'], $notificationPlatformTable); + + //-- CHECK FOR REQUIRED FIELDS + foreach ($platformParameters as $platformParameterField => $platformParameterData) { + if ($platformParameterData['required'] && !$_POST['notificationPlatformParameter-' . $platformParameterField]) { + $error = 'Missing required platform field: ' . $platformParameterData['label']; + break; + } + } + + if (!$error) { + $triggerIds = $platformParameters = []; + $senderName = $platformName; - if ($saveSettings['code'] != 200) { - $error = 'Error saving notification settings on server ' . ACTIVE_SERVER_NAME; + foreach ($_POST as $key => $val) { + if (str_contains($key, 'notificationTrigger-') && $val) { + $triggerIds[] = str_replace('notificationTrigger-', '', $key); + } + + if (str_contains($key, 'notificationPlatformParameter-')) { + $field = str_replace('notificationPlatformParameter-', '', $key); + + if ($field != 'name') { + $platformParameters[$field] = $val; + } else { + $senderName = $val; + } + } + } + + apiRequest('database-updateNotificationLink', [], ['linkId' => $_POST['linkId'], 'triggerIds' => $triggerIds, 'platformParameters' => $platformParameters, 'senderName' => $senderName]); + } } - echo json_encode(['error' => $error, 'server' => ACTIVE_SERVER_NAME]); + echo json_encode(['error' => $error]); +} + +if ($_POST['m'] == 'deleteNotification') { + apiRequest('database-deleteNotificationLink', [], ['linkId' => $_POST['linkId']]); } if ($_POST['m'] == 'testNotify') { - $apiResponse = apiRequest('testNotify', [], ['platform' => $_POST['platform']]); + $apiResponse = apiRequest('notify-test', [], ['linkId' => $_POST['linkId']]); if ($apiResponse['code'] == 200) { $result = 'Test notification sent on server ' . ACTIVE_SERVER_NAME; } else { - $error = 'Failed to send test notification on server ' . ACTIVE_SERVER_NAME . '. ' . $apiResponse['response']['result']; + $error = 'Failed to send test notification on server ' . ACTIVE_SERVER_NAME . '. ' . $apiResponse['result']; } echo json_encode(['error' => $error, 'result' => $result]); diff --git a/root/app/www/public/ajax/orphans.php b/root/app/www/public/ajax/orphans.php index 23017bd..7ce389c 100644 --- a/root/app/www/public/ajax/orphans.php +++ b/root/app/www/public/ajax/orphans.php @@ -10,12 +10,12 @@ require 'shared.php'; if ($_POST['m'] == 'init') { - $images = apiRequest('dockerGetOrphanContainers'); - $images = json_decode($images['response']['docker'], true); - $volumes = apiRequest('dockerGetOrphanVolumes'); - $volumes = json_decode($volumes['response']['docker'], true); - $networks = apiRequest('dockerGetOrphanNetworks'); - $networks = json_decode($networks['response']['docker'], true); + $images = apiRequest('docker-getOrphanContainers'); + $images = json_decode($images['result'], true); + $volumes = apiRequest('docker-getOrphanVolumes'); + $volumes = json_decode($volumes['result'], true); + $networks = apiRequest('docker-getOrphanNetworks'); + $networks = json_decode($networks['result'], true); ?>
@@ -126,24 +126,21 @@ switch ($_POST['action']) { case 'remove': if ($_POST['type'] == 'image') { - $remove = apiRequest('dockerRemoveImage', [], ['image' => $_POST['orphan']]); - $remove = $remove['response']['docker']; - if (stripos($remove, 'error') !== false || stripos($remove, 'help') !== false) { - echo $remove; + $apiRequest = apiRequest('docker-removeImage', [], ['image' => $_POST['orphan']])['result']; + if (stripos($apiRequest, 'error') !== false || stripos($apiRequest, 'help') !== false) { + echo $apiRequest; } } - if ($_POST['type'] == 'volume') { - $remove = apiRequest('dockerRemoveVolume', [], ['name' => $_POST['orphan']]); - $remove = $remove['response']['docker']; - if (stripos($remove, 'error') !== false || stripos($remove, 'help') !== false) { - echo $remove; + if ($_POST['type'] == 'network') { + $apiRequest = apiRequest('docker-removeNetwork', [], ['id' => $_POST['orphan']])['result']; + if (stripos($apiRequest, 'error') !== false || stripos($apiRequest, 'help') !== false) { + echo $apiRequest; } } - if ($_POST['type'] == 'network') { - $remove = apiRequest('dockerRemoveNetwork', [], ['id' => $_POST['orphan']]); - $remove = $remove['response']['docker']; - if (stripos($remove, 'error') !== false || stripos($remove, 'help') !== false) { - echo $remove; + if ($_POST['type'] == 'volume') { + $apiRequest = apiRequest('docker-removeVolume', [], ['name' => $_POST['orphan']])['result']; + if (stripos($apiRequest, 'error') !== false || stripos($apiRequest, 'help') !== false) { + echo $apiRequest; } } break; diff --git a/root/app/www/public/ajax/overview.php b/root/app/www/public/ajax/overview.php index 70c3208..23e910f 100644 --- a/root/app/www/public/ajax/overview.php +++ b/root/app/www/public/ajax/overview.php @@ -83,8 +83,8 @@ $network += bytesFromString($netUsed); } - if (intval($settingsFile['global']['cpuAmount']) > 0) { - $cpuActual = number_format(($cpu / intval($settingsFile['global']['cpuAmount'])), 2); + if (intval($settingsTable['cpuAmount']) > 0) { + $cpuActual = number_format(($cpu / intval($settingsTable['cpuAmount'])), 2); } ?> @@ -98,7 +98,7 @@
Running:
Stopped:
- Total: + Total:
@@ -122,7 +122,7 @@
Up to date:
Outdated:
- Unchecked: + Unchecked:
@@ -197,4 +197,4 @@ + + Migration + + The database migration this server is on + Server name - + The name of this server, also passed in the notification payload Maintenance IP - + This IP is used to do updates/restarts for Dockwatch. It will create another container dockwatch-maintenance with this IP and after it has updated/restarted Dockwatch it will be removed. This is only required if you do static IP assignment for your containers. Maintenance port - + This port is used to do updates/restarts for Dockwatch. It will create another container dockwatch-maintenance with this port and after it has updated/restarted Dockwatch it will be removed. -

Login Failures

+

Login failures

@@ -81,7 +85,7 @@
-

Server list

+

servers

@@ -93,30 +97,30 @@ - + - - - + + + 1) { - foreach ($serversFile as $serverIndex => $serverSettings) { - if ($serverIndex == 0) { + if (count($serversTable) > 1) { + foreach ($serversTable as $serverSettings) { + if ($serverSettings['id'] == APP_SERVER_ID) { continue; } ?> - - - + + + + + + + +
Sorry, remote management of server access is not allowed. Go to the server to make those changes.Sorry, remote management of the server list is not allowed. Go to the server to make those changes.
Timeout length + + How long to wait for a remote server to respond, keep in mind 60-90 seconds will throw apache/nginx/cloudflare timeouts
-

New Containers

+

New containers

@@ -148,18 +159,18 @@
Updates1 - + What settings to use for new containers that are added
-

Auto Prune

+

Auto prune

@@ -173,21 +184,21 @@ @@ -198,7 +209,7 @@ '. $x .''; + $option .= ''; } echo $option; ?> @@ -223,21 +234,21 @@ @@ -258,7 +269,7 @@ @@ -279,28 +290,28 @@ @@ -322,25 +333,25 @@ - + - + @@ -358,8 +369,8 @@ } if ($_POST['m'] == 'saveGlobalSettings') { - $currentSettings = getServerFile('settings'); - $newSettings = []; + $activeServer = apiGetActiveServer(); + $newSettings = []; foreach ($_POST as $key => $val) { if ($key == 'm' || str_contains($key, 'serverList')) { @@ -378,7 +389,7 @@ } //-- ENVIRONMENT SWITCHING - if ($currentSettings['global']['environment'] != $_POST['environment']) { + if ($settingsTable['environment'] != $_POST['environment']) { if ($_POST['environment'] == 0) { //-- USE INTERNAL linkWebroot('internal'); } else { //-- USE EXTERNAL @@ -386,18 +397,15 @@ } } - $settingsFile['global'] = $newSettings; - $saveSettings = setServerFile('settings', $settingsFile); - - if ($saveSettings['code'] != 200) { - $error = 'Error saving global settings on server ' . ACTIVE_SERVER_NAME; - } + $settingsTable = apiRequest('database-setSettings', [], ['newSettings' => $newSettings])['result']; //-- ONLY MAKE SERVER CHANGES ON LOCAL - if ($_SESSION['serverIndex'] == 0) { + if ($activeServer['id'] == APP_SERVER_ID) { + $serverList = []; + //-- ADD SERVER TO LIST if ($_POST['serverList-name-new'] && $_POST['serverList-url-new'] && $_POST['serverList-apikey-new']) { - $serversFile[] = ['name' => $_POST['serverList-name-new'], 'url' => rtrim($_POST['serverList-url-new'], '/'), 'apikey' => $_POST['serverList-apikey-new']]; + $serverList[] = ['name' => $_POST['serverList-name-new'], 'url' => rtrim($_POST['serverList-url-new'], '/'), 'apikey' => $_POST['serverList-apikey-new']]; } //-- UPDATE SERVER LIST @@ -406,14 +414,14 @@ continue; } - list($name, $field, $index) = explode('-', $key); + list($name, $field, $instanceId) = explode('-', $key); - if (!is_numeric($index)) { + if (!is_numeric($instanceId)) { continue; } - if ($_POST['serverList-name-' . $index] && $_POST['serverList-url-' . $index] && $_POST['serverList-apikey-' . $index]) { - $serversFile[$index] = ['name' => $_POST['serverList-name-' . $index], 'url' => rtrim($_POST['serverList-url-' . $index], '/'), 'apikey' => $_POST['serverList-apikey-' . $index]]; + if ($_POST['serverList-name-' . $instanceId] && $_POST['serverList-url-' . $instanceId] && $_POST['serverList-apikey-' . $instanceId]) { + $serverList[$instanceId] = ['name' => $_POST['serverList-name-' . $instanceId], 'url' => rtrim($_POST['serverList-url-' . $instanceId], '/'), 'apikey' => $_POST['serverList-apikey-' . $instanceId]]; } } @@ -423,41 +431,25 @@ continue; } - list($name, $field, $index) = explode('-', $key); + list($name, $field, $instanceId) = explode('-', $key); - if (!is_numeric($index)) { + if (!is_numeric($instanceId)) { continue; } - if (!$_POST['serverList-name-' . $index] && !$_POST['serverList-url-' . $index] && !$_POST['serverList-apikey-' . $index]) { - unset($serversFile[$index]); + if (!$_POST['serverList-name-' . $instanceId] && !$_POST['serverList-url-' . $instanceId] && !$_POST['serverList-apikey-' . $instanceId]) { + $serverList[$instanceId]['remove'] = true; } } - $saveServers = setServerFile('servers', $serversFile); - - if ($saveServers['code'] != 200) { - $error = 'Error saving server list on server ' . ACTIVE_SERVER_NAME; - } - } else { - $serversFile = getFile(SERVERS_FILE); + $serversTable = apiRequest('database-setServers', [], ['serverList' => $serverList])['result']; } - $serverList = ''; - foreach ($serversFile as $serverIndex => $serverDetails) { - $ping = curl($serverDetails['url'] . '/api/?request=ping', ['x-api-key: ' . $serverDetails['apikey']]); - $disabled = ''; - if ($ping['code'] != 200) { - $disabled = ' [HTTP: ' . $ping['code'] . ']'; - } - $serverList .= ''; - } - - echo json_encode(['error' => $error, 'server' => ACTIVE_SERVER_NAME, 'serverList' => $serverList]); + echo json_encode(['error' => $error, 'server' => ACTIVE_SERVER_NAME, 'serverList' => getRemoteServerSelect()]); } //-- CALLED FROM THE NAV MENU SELECT -if ($_POST['m'] == 'updateServerIndex') { - $_SESSION['serverIndex'] = intval($_POST['index']); +if ($_POST['m'] == 'updateActiveServer') { + apiSetActiveServer(intval($_POST['id'])); $_SESSION['serverList'] = ''; -} \ No newline at end of file +} diff --git a/root/app/www/public/ajax/shared.php b/root/app/www/public/ajax/shared.php index 24c8e10..124fc6c 100644 --- a/root/app/www/public/ajax/shared.php +++ b/root/app/www/public/ajax/shared.php @@ -18,7 +18,9 @@ } require ABSOLUTE_PATH . 'loader.php'; -if (!$_SESSION['IN_DOCKWATCH']) { - http_response_code(400); - exit('Error: You should use the UI, its much prettier.'); +if (!str_contains_any($_SERVER['PHP_SELF'], ['/api/']) && !str_contains($_SERVER['PWD'], 'oneshot')) { + if (!$_SESSION['IN_DOCKWATCH']) { + http_response_code(400); + exit('Error: You should use the UI, its much prettier.'); + } } diff --git a/root/app/www/public/ajax/tasks.php b/root/app/www/public/ajax/tasks.php index 631febb..a3246b9 100644 --- a/root/app/www/public/ajax/tasks.php +++ b/root/app/www/public/ajax/tasks.php @@ -26,43 +26,43 @@ - + - + - + - + - + - + - + @@ -129,10 +129,10 @@ if ($_POST['m'] == 'runTask') { logger(SYSTEM_LOG, 'Run task: ' . $_POST['task']); - $apiResponse = apiRequest('runTask', [], ['task' => $_POST['task']]); + $apiRequest = apiRequest('server-runTask', [], ['task' => $_POST['task']]); - if ($apiResponse['code'] == 200) { - $result = $apiResponse['response']['result']; + if ($apiRequest['code'] == 200) { + $result = $apiRequest['result']; } else { $error = 'Failed to run taks on server ' . ACTIVE_SERVER_NAME; } @@ -141,17 +141,11 @@ } if ($_POST['m'] == 'updateTaskDisabled') { - if ($_POST['task'] == 'sse') { - $settingsFile['global']['sseEnabled'] = !intval($_POST['disabled']); + if ($_POST['task'] == 'sseEnabled') { + apiRequest('database-setSetting', [], ['setting' => 'sseEnabled', 'value' => !intval($_POST['disabled'])]); } else { - $settingsFile['tasks'][$_POST['task']] = ['disabled' => intval($_POST['disabled'])]; - } - - $saveSettings = setServerFile('settings', $settingsFile); - - if ($saveSettings['code'] != 200) { - $error = 'Error saving task settings on server ' . ACTIVE_SERVER_NAME; + apiRequest('database-setSetting', [], ['setting' => $_POST['task'], 'value' => intval($_POST['disabled'])]); } echo json_encode(['error' => $error, 'server' => ACTIVE_SERVER_NAME]); -} \ No newline at end of file +} diff --git a/root/app/www/public/api/index.php b/root/app/www/public/api/index.php index e000dc0..3360679 100644 --- a/root/app/www/public/api/index.php +++ b/root/app/www/public/api/index.php @@ -17,299 +17,13 @@ session_unset(); session_destroy(); -$code = 200; $apikey = $_SERVER['HTTP_X_API_KEY'] ? $_SERVER['HTTP_X_API_KEY'] : $_GET['apikey']; -if ($apikey != $serversFile[0]['apikey']) { +if ($apikey != $serversTable[APP_SERVER_ID]['apikey']) { apiResponse(401, ['error' => 'Invalid apikey']); } -$_POST = json_decode(file_get_contents('php://input'), true); - -switch (true) { - case $_GET['request']: - //-- GETTERS - switch ($_GET['request']) { - case 'dwStats': - $response = getStats(); - break; - case 'dockerAutoCompose': - if (!$_GET['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => dockerAutoCompose($_GET['name'])]; - break; - case 'dockerAutoRun': - if (!$_GET['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => dockerAutoRun($_GET['name'])]; - break; - case 'dockerContainerCreateAPI': - if (!$_GET['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $inspect = json_decode($docker->inspect($_GET['name'], false, true), true); - if (!$inspect) { - apiResponse(400, ['error' => 'Failed to get inspect for container: ' . $_GET['name']]); - } - - $response = ['docker' => json_encode($docker->apiCreateContainer($inspect))]; - break; - case 'dockerGetOrphanContainers': - $response = ['docker' => $docker->getOrphanContainers()]; - break; - case 'dockerGetOrphanVolumes': - $response = ['docker' => $docker->getOrphanVolumes()]; - break; - case 'dockerGetOrphanNetworks': - $response = ['docker' => $docker->getOrphanNetworks()]; - break; - case 'dockerImageSizes': - $response = ['docker' => $docker->getImageSizes()]; - break; - case 'dockerInspect': - if (!$_GET['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->inspect($_GET['name'], $_GET['useCache'], $_GET['format'], $_GET['params'])]; - break; - case 'dockerLogs': - if (!$_GET['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->logs($_GET['name'])]; - break; - case 'dockerNetworks': - $response = ['docker' => $docker->getNetworks($_GET['params'])]; - break; - case 'dockerPermissionCheck': - $response = ['docker' => dockerPermissionCheck()]; - break; - case 'dockerPort': - if (!$_GET['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->getContainerPort($_GET['name'], $_GET['params'])]; - break; - case 'dockerProcessList': - $response = ['docker' => $docker->processList($_GET['useCache'], $_GET['format'], $_GET['params'])]; - break; - case 'dockerPruneImage': - $response = ['docker' => $docker->pruneImage()]; - break; - case 'dockerPruneVolume': - $response = ['docker' => $docker->pruneVolume()]; - break; - case 'dockerPruneNetwork': - $response = ['docker' => $docker->pruneNetwork()]; - break; - case 'dockerState': - $response = ['docker' => dockerState()]; - break; - case 'dockerStats': - $response = ['docker' => $docker->stats($_GET['useCache'])]; - break; - case 'health': - $response = ['health' => getFile(HEALTH_FILE)]; - break; - case 'ping': - $response = ['result' => 'pong from ' . ACTIVE_SERVER_NAME]; - break; - case 'pull': - $response = ['pull' => getFile(PULL_FILE)]; - break; - case 'settings': - $response = ['settings' => getFile(SETTINGS_FILE)]; - break; - case 'state': - $response = ['state' => getFile(STATE_FILE)]; - break; - case 'stats': - $response = ['state' => getFile(STATS_FILE)]; - break; - case 'servers': - $response = ['servers' => getFile(SERVERS_FILE)]; - break; - case 'dependency': - $response = ['dependency' => getFile(DEPENDENCY_FILE)]; - break; - case 'sse': - $response = ['sse' => getFile(SSE_FILE)]; - break; - case 'viewLog': - $response = ['result' => viewLog($_GET['name'])]; - break; - default: - apiResponse(405, ['error' => 'Invalid GET request']); - break; - } - break; - case $_POST['request']: - switch ($_POST['request']) { - //-- SETTERS - case 'health': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing settings object']); - } - - setFile(HEALTH_FILE, $_POST['contents']); - $response = ['result' => HEALTH_FILE . ' updated']; - break; - case 'pull': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing pull object']); - } - - setFile(PULL_FILE, $_POST['contents']); - $response = ['result' => PULL_FILE . ' updated']; - break; - case 'settings': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing settings object']); - } - - setFile(SETTINGS_FILE, $_POST['contents']); - $response = ['result' => SETTINGS_FILE . ' updated']; - break; - case 'servers': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing servers object']); - } - - setFile(SERVERS_FILE, $_POST['contents']); - $response = ['result' => SERVERS_FILE . ' updated']; - break; - case 'state': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing state object']); - } - - setFile(STATE_FILE, $_POST['contents']); - $response = ['result' => STATE_FILE . ' updated']; - break; - case 'stats': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing stats object']); - } - - setFile(STATS_FILE, $_POST['contents']); - $response = ['result' => STATS_FILE . ' updated']; - break; - case 'sse': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing sse object']); - } - - setFile(SSE_FILE, $_POST['contents']); - $response = ['result' => SSE_FILE . ' updated']; - break; - case 'dependency': - if (!$_POST['contents']) { - apiResponse(400, ['error' => 'Missing dependency object']); - } - - setFile(DEPENDENCY_FILE, $_POST['contents']); - $response = ['result' => DEPENDENCY_FILE . ' updated']; - break; - //-- ACTIONS - case 'deleteLog': - if (!$_POST['log']) { - apiResponse(400, ['error' => 'Missing log parameter']); - } - - $response = ['result' => deleteLog($_POST['log'])]; - break; - case 'dockerPullContainer': - if (!$_POST['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->pullImage($_POST['name'])]; - break; - case 'dockerRemoveContainer': - if (!$_POST['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->removeContainer($_POST['name'])]; - break; - case 'dockerRemoveImage': - if (!$_POST['image']) { - apiResponse(400, ['error' => 'Missing image parameter']); - } - - $response = ['docker' => $docker->removeImage($_POST['image'])]; - break; - case 'dockerRemoveVolume': - if (!$_POST['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->removeVolume($_POST['name'])]; - break; - case 'dockerRemoveNetwork': - if (!$_POST['id']) { - apiResponse(400, ['error' => 'Missing id parameter']); - } - - $response = ['docker' => $docker->removeNetwork($_POST['id'])]; - break; - case 'dockerStartContainer': - if (!$_POST['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->startContainer($_POST['name'])]; - break; - case 'dockerStopContainer': - if (!$_POST['name']) { - apiResponse(400, ['error' => 'Missing name parameter']); - } - - $response = ['docker' => $docker->stopContainer($_POST['name'])]; - break; - case 'dockerCreateContainer': - if (!$_POST['inspect']) { - apiResponse(400, ['error' => 'Missing inspect parameter']); - } - - $response = ['docker' => dockerCreateContainer(json_decode($_POST['inspect'], true))]; - break; - case 'purgeLogs': - if (!$_POST['group']) { - apiResponse(400, ['error' => 'Missing group parameter']); - } - - $response = ['result' => purgeLogs($_POST['group'])]; - break; - case 'runTask': - if (!$_POST['task']) { - apiResponse(400, ['error' => 'Missing task parameter']); - } - - $response = ['result' => executeTask($_POST['task'])]; - break; - case 'testNotify': - $testNotification = sendTestNotification($_POST['platform']); - - if ($testNotification) { - $code = '400'; - } - - $response = ['result' => $testNotification]; - break; - default: - apiResponse(405, ['error' => 'Invalid POST request']); - break; - } - break; -} +$_POST = json_decode(file_get_contents('php://input'), true); +$response = ['result' => apiRequestLocal($_GET['request'], ($_POST ?: $_GET), $_POST)]; //-- RETURN -apiResponse($code, $response); +apiResponse(200, $response); diff --git a/root/app/www/public/classes/Database.php b/root/app/www/public/classes/Database.php new file mode 100644 index 0000000..374dd05 --- /dev/null +++ b/root/app/www/public/classes/Database.php @@ -0,0 +1,96 @@ +connect(DATABASE_PATH . 'dockwatch.db'); + } + + public function connect($dbFile) + { + $db = new SQLite3($dbFile, SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE); + $this->db = $db; + } + + public function query($query) + { + return $this->db->query($query); + } + + public function fetchAssoc($res) + { + return !$res ? [] : $res->fetchArray(SQLITE3_ASSOC); + } + + public function affectedRows($res) + { + return !$res ? 0 : $res->changes(SQLITE3_ASSOC); + } + + public function insertId() + { + return $this->db->lastInsertRowID(); + } + + public function error() + { + return $this->db->lastErrorMsg(); + } + + public function prepare($in) + { + $out = addslashes(stripslashes($in)); + return $out; + } + + public function migrations() + { + $database = $this; + $db = $this->db; + + if (filesize(DATABASE_PATH . 'dockwatch.db') == 0) { //-- INITIAL SETUP + logger(SYSTEM_LOG, 'Creating database and applying migration 001_initial_setup'); + require MIGRATIONS_PATH . '001_initial_setup.php'; + } else { //-- GET CURRENT MIGRATION & CHECK FOR NEEDED MIGRATIONS + $dir = opendir(MIGRATIONS_PATH); + while ($migration = readdir($dir)) { + if (substr($migration, 0, 3) > $this->getSetting('migration') && str_contains($migration, '.php')) { + logger(SYSTEM_LOG, 'Applying migration ' . $migration); + require MIGRATIONS_PATH . $migration; + } + } + closedir($dir); + } + } +} diff --git a/root/app/www/public/classes/Docker.php b/root/app/www/public/classes/Docker.php index 433bd9b..2ace1df 100644 --- a/root/app/www/public/classes/Docker.php +++ b/root/app/www/public/classes/Docker.php @@ -7,30 +7,27 @@ ---------------------------------- */ -//-- BRING IN THE TRAITS -$traits = ABSOLUTE_PATH . 'classes/traits/Docker/'; -$traitsDir = opendir($traits); -while ($traitFile = readdir($traitsDir)) { - if (str_contains($traitFile, '.php')) { - require $traits . $traitFile; - } -} -closedir($traitsDir); +//-- BRING IN THE EXTRAS +loadClassExtras('Docker'); class Docker { - use API; use Container; + use DockersApi; use Image; use Network; use Process; use Volume; protected $shell; + protected $database; public function __construct() { - $this->shell = new Shell(); + global $shell, $database; + + $this->shell = $shell ?? new Shell(); + $this->database = $database ?? new Database(); } public function stats($useCache) @@ -47,7 +44,7 @@ public function stats($useCache) $shell = $this->shell->exec($cmd . ' 2>&1'); if ($shell) { - setServerFile('stats', $shell); + apiRequest('file-stats', [], ['contents' => $shell]); } return $shell; @@ -104,45 +101,3 @@ public function isIO($name) return str_contains($name, '/') ? $name : 'library/' . $name; } } - -interface DockerApi -{ - //-- CONTAINER SPECIFIC - public const STOP_CONTAINER = '/containers/%s/stop'; - public const CREATE_CONTAINER = '/containers/create?name=%s'; -} - -//-- https://docs.docker.com/reference/cli/docker -interface DockerSock -{ - //-- GENERAL - public const VERSION = '/usr/bin/docker version %s'; - public const RUN = '/usr/bin/docker run %s'; - public const LOGS = '/usr/bin/docker logs %s'; - public const PROCESSLIST_FORMAT = '/usr/bin/docker ps --all --no-trunc --size=false --format="{{json . }}" | jq -s --tab .'; - public const PROCESSLIST_CUSTOM = '/usr/bin/docker ps %s'; - public const STATS_FORMAT = '/usr/bin/docker stats --all --no-trunc --no-stream --format="{{json . }}" | jq -s --tab .'; - public const INSPECT_FORMAT = '/usr/bin/docker inspect %s --format="{{json . }}" | jq -s --tab .'; - public const INSPECT_CUSTOM = '/usr/bin/docker inspect %s %s'; - public const IMAGE_SIZES = '/usr/bin/docker images --format=\'{"ID":"{{ .ID }}", "Size": "{{ .Size }}"}\' | jq -s --tab .'; - //-- CONTAINER SPECIFIC - public const REMOVE_CONTAINER = '/usr/bin/docker container rm -f %s'; - public const START_CONTAINER = '/usr/bin/docker container start %s'; - public const STOP_CONTAINER = '/usr/bin/docker container stop %s%s'; - public const ORPHAN_CONTAINERS = '/usr/bin/docker images -f dangling=true --format="{{json . }}" | jq -s --tab .'; - public const CONTAINER_PORT = '/usr/bin/docker port %s %s'; - //-- IMAGE SPECIFIC - public const REMOVE_IMAGE = '/usr/bin/docker image rm %s'; - public const PULL_IMAGE = '/usr/bin/docker image pull %s'; - public const PRUNE_IMAGE = '/usr/bin/docker image prune -af'; - //-- VOLUME SPECIFIC - public const ORPHAN_VOLUMES = '/usr/bin/docker volume ls -qf dangling=true --format="{{json . }}" | jq -s --tab .'; - public const PRUNE_VOLUME = '/usr/bin/docker volume prune -af'; - public const REMOVE_VOLUME = '/usr/bin/docker volume rm %s'; - //-- NETWORK SPECIFIC - public const ORPHAN_NETWORKS = '/usr/bin/docker network ls -q --format="{{json . }}" | jq -s --tab .'; - public const INSPECT_NETWORK = '/usr/bin/docker network inspect %s --format="{{json . }}" | jq -s --tab .'; - public const PRUNE_NETWORK = '/usr/bin/docker network prune -af'; - public const REMOVE_NETWORK = '/usr/bin/docker network rm %s'; - public const GET_NETWORKS = '/usr/bin/docker network %s'; -} diff --git a/root/app/www/public/classes/Maintenance.php b/root/app/www/public/classes/Maintenance.php index a60d8b8..303d66b 100644 --- a/root/app/www/public/classes/Maintenance.php +++ b/root/app/www/public/classes/Maintenance.php @@ -12,15 +12,8 @@ * It is done this way so if things need to be done per host or per maintenance it is easy to split & log */ -//-- BRING IN THE TRAITS -$traits = ABSOLUTE_PATH . 'classes/traits/Maintenance/'; -$traitsDir = opendir($traits); -while ($traitFile = readdir($traitsDir)) { - if (str_contains($traitFile, '.php')) { - require $traits . $traitFile; - } -} -closedir($traitsDir); +//-- BRING IN THE EXTRAS +loadClassExtras('Maintenance'); class Maintenance { @@ -31,26 +24,21 @@ class Maintenance protected $maintenanceContainerName = 'dockwatch-maintenance'; protected $maintenancePort; protected $maintenanceIP; - protected $settingsFile; + protected $settingsTable; protected $hostContainer = []; protected $maintenanceContainer = []; protected $processList = []; public function __construct() { - global $docker, $settingsFile; + global $docker, $settingsTable; logger(MAINTENANCE_LOG, '$maintenance->__construct() ->'); - if (!$settingsFile) { - $settingsFile = getServerFile('settings'); - $settingsFile = $settingsFile['file']; - } - $this->docker = $docker; - $this->settingsFile = $settingsFile; - $this->maintenancePort = $settingsFile['global']['maintenancePort']; - $this->maintenanceIP = $settingsFile['global']['maintenanceIP']; + $this->settingsTable = $settingsTable; + $this->maintenancePort = $settingsTable['maintenancePort']; + $this->maintenanceIP = $settingsTable['maintenanceIP']; $getExpandedProcessList = getExpandedProcessList(true, true, true, true); $this->processList = is_array($getExpandedProcessList['processList']) ? $getExpandedProcessList['processList'] : []; $imageMatch = str_replace(':main', '', APP_IMAGE); diff --git a/root/app/www/public/classes/Notifications.php b/root/app/www/public/classes/Notifications.php index d9d47b3..818d64d 100644 --- a/root/app/www/public/classes/Notifications.php +++ b/root/app/www/public/classes/Notifications.php @@ -7,38 +7,30 @@ ---------------------------------- */ -//-- BRING IN THE TRAITS -$traits = ABSOLUTE_PATH . 'classes/traits/Notifications/'; -$traitsDir = opendir($traits); -while ($traitFile = readdir($traitsDir)) { - if (str_contains($traitFile, '.php')) { - require $traits . $traitFile; - } -} -closedir($traitsDir); +//-- BRING IN THE EXTRAS +loadClassExtras('Notifications'); class Notifications { use Notifiarr; + use NotificationTemplates; protected $platforms; - protected $platformSettings; protected $headers; protected $logpath; protected $serverName; + protected $database; + public function __construct() { - global $platforms, $settingsFile; + global $platforms, $settingsTable, $database; - if (!$settingsFile) { - $settingsFile = getServerFile('settings'); - $settingsFile = $settingsFile['file']; - } + $this->database = $database ?? new Database(); + $this->platforms = $platforms; //-- includes/platforms.php + $this->logpath = LOGS_PATH . 'notifications/'; - $this->platforms = $platforms; //-- includes/platforms.php - $this->platformSettings = $settingsFile['notifications']['platforms']; - $this->logpath = LOGS_PATH . 'notifications/'; - $this->serverName = $settingsFile['global']['serverName']; + $settingsTable = $settingsTable ?? apiRequest('database-getSettings'); + $this->serverName = is_array($settingsTable) ? $settingsTable['serverName'] : ''; } public function __toString() @@ -46,34 +38,102 @@ public function __toString() return 'Notifications initialized'; } - public function notify($platform, $payload) + public function sendTestNotification($linkId) + { + $payload = ['event' => 'test', 'title' => APP_NAME . ' test', 'message' => 'This is a test message sent from ' . APP_NAME]; + $return = ''; + $result = $this->notify($linkId, 'test', $payload); + + if ($result['code'] != 200) { + $return = 'Code ' . $result['code'] . ', ' . $result['error']; + } + + return $return; + } + + public function notify($linkId, $trigger, $payload) { + $linkIds = []; + $notificationPlatformTable = $this->database->getNotificationPlatforms(); + $notificationTriggersTable = $this->database->getNotificationTriggers(); + $notificationLinkTable = $this->database->getNotificationLinks(); + $triggerFields = $this->getTemplate($trigger); + + //-- MAKE IT MATCH THE TEMPLATE + foreach ($payload as $payloadField => $payloadVal) { + if (!array_key_exists($payloadField, $triggerFields) || !$payloadVal) { + unset($payload[$payloadField]); + } + } + if ($this->serverName) { $payload['server']['name'] = $this->serverName; } - $platformData = $this->getNotificationPlatformFromId($platform); - $logfile = $this->logpath . $platformData['name'] . '.log'; + if ($linkId) { + foreach ($notificationLinkTable as $notificationLink) { + if ($notificationLink['id'] == $linkId) { + $linkIds[] = $notificationLink; + } + } + + $notificationLink = $notificationLinkTable[$linkId]; + $notificationPlatform = $notificationPlatformTable[$notificationLink['platform_id']]; + } else { + foreach ($notificationTriggersTable as $notificationTrigger) { + if ($notificationTrigger['name'] == $trigger) { + foreach ($notificationLinkTable as $notificationLink) { + $triggers = makeArray(json_decode($notificationLink['trigger_ids'], true)); + + foreach ($triggers as $trigger) { + if ($trigger == $notificationTrigger['id']) { + $linkIds[] = $notificationLink; + } + } + } + break; + } + } + } + + foreach ($linkIds as $linkId) { + $platformId = $linkId['platform_id']; + $platformParameters = json_decode($linkId['platform_parameters'], true); + $platformName = ''; + + foreach ($notificationPlatformTable as $notificationPlatform) { + if ($notificationPlatform['id'] == $platformId) { + $platformName = $notificationPlatform['platform']; + break; + } + } - logger($logfile, 'notification request to ' . $platformData['name']); - logger($logfile, 'notification payload: ' . json_encode($payload)); + $logfile = $this->logpath . $platformName . '.log'; + logger($logfile, 'notification request to ' . $platformName); + logger($logfile, 'notification payload: ' . json_encode($payload)); - /* - Everything should return an array with code => ..., error => ... (if no error, just code is fine) - */ - switch ($platform) { - case 1: //-- Notifiarr - return $this->notifiarr($logfile, $payload); + switch ($platformId) { + case NotificationPlatforms::NOTIFIARR: + return $this->notifiarr($logfile, $platformParameters['apikey'], $payload); + } } } - public function getPlatforms() + public function getNotificationPlatformNameFromId($id, $platforms) { - return $this->platforms; + foreach ($platforms as $platform) { + if ($id == $platform['id']) { + return $platform['platform']; + } + } } - public function getNotificationPlatformFromId($platform) + public function getNotificationTriggerNameFromId($id, $triggers) { - return $this->platforms[$platform]; + foreach ($triggers as $trigger) { + if ($id == $trigger['id']) { + return $trigger['label']; + } + } } } diff --git a/root/app/www/public/classes/interfaces/Docker.php b/root/app/www/public/classes/interfaces/Docker.php new file mode 100644 index 0000000..25a1c31 --- /dev/null +++ b/root/app/www/public/classes/interfaces/Docker.php @@ -0,0 +1,50 @@ +containerGroupLinksTable) { + return $this->containerGroupLinksTable; + } + + $q = "SELECT * + FROM " . CONTAINER_GROUPS_LINK_TABLE; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $containerLinks[] = $row; + } + + $this->containerGroupLinksTable = $containerLinks; + return $containerLinks ?? []; + } + + public function getGroupLinkContainersFromGroupId($groupLinks, $containers, $groupId) + { + $groupContainers = []; + foreach ($groupLinks as $groupLink) { + if ($groupLink['group_id'] == $groupId) { + $groupContainers[] = $containers[$groupLink['container_id']]; + } + } + + return $groupContainers; + } + + public function addContainerGroupLink($groupId, $containerId) + { + $q = "INSERT INTO " . CONTAINER_GROUPS_LINK_TABLE . " + (`group_id`, `container_id`) + VALUES + ('" . intval($groupId) . "', '" . intval($containerId) . "')"; + $this->query($q); + + $this->containerGroupLinksTable = ''; + } + + public function removeContainerGroupLink($groupId, $containerId) + { + $q = "DELETE FROM " . CONTAINER_GROUPS_LINK_TABLE . " + WHERE group_id = '" . intval($groupId) . "' + AND container_id = '" . intval($containerId) . "'"; + $this->query($q); + + $this->containerGroupLinksTable = ''; + } + + public function deleteGroupLinks($groupId) + { + $q = "DELETE FROM " . CONTAINER_GROUPS_LINK_TABLE . " + WHERE group_id = " . $groupId; + $this->query($q); + + $this->containerGroupLinksTable = ''; + } +} diff --git a/root/app/www/public/classes/traits/Database/ContainerGroups.php b/root/app/www/public/classes/traits/Database/ContainerGroups.php new file mode 100644 index 0000000..f3f5104 --- /dev/null +++ b/root/app/www/public/classes/traits/Database/ContainerGroups.php @@ -0,0 +1,80 @@ +containerGroupsTable) { + return $this->containerGroupsTable; + } + + $q = "SELECT * + FROM " . CONTAINER_GROUPS_TABLE; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $containerGroups[] = $row; + } + + $this->containerGroupsTable = $containerGroups; + return $containerGroups ?? []; + } + + public function getContainerGroupFromHash($hash, $groups) + { + if (!$groups) { + $groups = $this->getContainerGroups(); + } + + foreach ($groups as $group) { + if ($group['hash'] == $hash) { + return $group; + } + } + + return []; + } + + public function updateContainerGroup($groupId, $fields = []) + { + $updateList = []; + foreach ($fields as $field => $val) { + $updateList[] = $field . ' = "' . $val . '"'; + } + + $q = "UPDATE " . CONTAINER_GROUPS_TABLE . " + SET " . implode(', ', $updateList) . " + WHERE id = '" . $groupId . "'"; + $this->query($q); + + $this->containerGroupsTable = ''; + } + + public function addContainerGroup($groupName) + { + $q = "INSERT INTO " . CONTAINER_GROUPS_TABLE . " + (`hash`, `name`) + VALUES + ('" . md5($groupName) . "', '" . $this->prepare($groupName) . "')"; + $this->query($q); + + $this->containerGroupsTable = ''; + return $this->insertId(); + } + + public function deleteContainerGroup($groupId) + { + $q = "DELETE FROM " . CONTAINER_GROUPS_TABLE . " + WHERE id = " . $groupId; + $this->query($q); + + $this->containerGroupsTable = ''; + $this->deleteGroupLinks($groupId); + } +} diff --git a/root/app/www/public/classes/traits/Database/ContainerSettings.php b/root/app/www/public/classes/traits/Database/ContainerSettings.php new file mode 100644 index 0000000..1abee78 --- /dev/null +++ b/root/app/www/public/classes/traits/Database/ContainerSettings.php @@ -0,0 +1,111 @@ +containersTable) { + return $this->containersTable; + } + + $q = "SELECT * + FROM " . CONTAINER_SETTINGS_TABLE; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $containers[$row['id']] = $row; + } + + $this->containersTable = $containers; + return $containers ?? []; + } + + public function getContainerFromHash($hash, $containers = []) + { + if (!$containers) { + $containers = $this->getContainers(); + } + + foreach ($containers as $container) { + if ($container['hash'] == $hash) { + return $container; + } + } + + return []; + } + + public function getContainer($hash) + { + $q = "SELECT * + FROM " . CONTAINER_SETTINGS_TABLE . " + WHERE hash = '" . $hash . "'"; + $r = $this->query($q); + + return $this->fetchAssoc($r); + } + + public function addContainer($fields = []) + { + $settingsTable = $this->getSettings(); + + if (!array_key_exists('updates', $fields)) { + $fields['updates'] = $settingsTable['updates'] ?: 3; + } + + if (!array_key_exists('frequency', $fields)) { + $fields['frequency'] = $settingsTable['updatesFrequency'] ?: DEFAULT_CRON; + } + + if (!array_key_exists('restartUnhealthy', $fields)) { + $fields['restartUnhealthy'] = 0; + } + + if (!array_key_exists('disableNotifications', $fields)) { + $fields['disableNotifications'] = 0; + } + + if (!array_key_exists('shutdownDelay', $fields)) { + $fields['shutdownDelay'] = 0; + } + + if (!array_key_exists('shutdownDelaySeconds', $fields)) { + $fields['shutdownDelaySeconds'] = 0; + } + + $fieldList = $valList = []; + foreach ($fields as $field => $val) { + $fieldList[] = '`' . $field . '`'; + $valList[] = '"'. $val .'"'; + } + + $q = "INSERT INTO " . CONTAINER_SETTINGS_TABLE . " + (". implode(', ', $fieldList) .") + VALUES + (". implode(', ', $valList) .")"; + $this->query($q); + + $this->containersTable = ''; + } + + public function updateContainer($hash, $fields = []) + { + $updateList = []; + foreach ($fields as $field => $val) { + $updateList[] = $field . ' = "' . $val . '"'; + } + + $q = "UPDATE " . CONTAINER_SETTINGS_TABLE . " + SET " . implode(', ', $updateList) . " + WHERE hash = '" . $hash . "'"; + $this->query($q); + + $this->containersTable = ''; + } +} diff --git a/root/app/www/public/classes/traits/Database/NotificationLink.php b/root/app/www/public/classes/traits/Database/NotificationLink.php new file mode 100644 index 0000000..86b20aa --- /dev/null +++ b/root/app/www/public/classes/traits/Database/NotificationLink.php @@ -0,0 +1,81 @@ +notificationLinkTable) { + return $this->notificationLinkTable; + } + + $notificationLinkTable = []; + + $q = "SELECT * + FROM " . NOTIFICATION_LINK_TABLE; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $notificationLinkTable[$row['id']] = $row; + } + + $this->notificationLinkTable = $notificationLinkTable; + return $notificationLinkTable; + } + + public function updateNotificationLink($linkId, $triggerIds, $platformParameters, $senderName) + { + $q = "UPDATE " . NOTIFICATION_LINK_TABLE . " + SET name = '" . $this->prepare($senderName) . "', platform_parameters = '" . json_encode($platformParameters) . "', trigger_ids = '" . json_encode($triggerIds) . "' + WHERE id = '" . intval($linkId) . "'"; + $this->query($q); + + $this->notificationLinkTable = ''; + return $this->getNotificationLinks(); + } + + public function addNotificationLink($platformId, $triggerIds, $platformParameters, $senderName) + { + $q = "INSERT INTO " . NOTIFICATION_LINK_TABLE . " + (`name`, `platform_id`, `platform_parameters`, `trigger_ids`) + VALUES + ('" . $this->prepare($senderName) . "', '" . intval($platformId) . "', '" . json_encode($platformParameters) . "', '" . json_encode($triggerIds) . "')"; + $this->query($q); + + $this->notificationLinkTable = ''; + return $this->getNotificationLinks(); + } + + function deleteNotificationLink($linkId) + { + $q = "DELETE FROM " . NOTIFICATION_LINK_TABLE . " + WHERE id = " . intval($linkId); + $this->query($q); + + $this->notificationLinkTable = ''; + return $this->getNotificationLinks(); + } + + public function getNotificationLinkPlatformFromName($name) + { + $notificationLinks = $this->getNotificationLinks(); + $notificationTrigger = $this->getNotificationTriggerFromName($name); + + foreach ($notificationLinks as $notificationLink) { + if ($notificationLink['name'] == $name) { + $triggers = makeArray(json_decode($notificationLink['trigger_ids'], true)); + + foreach ($triggers as $trigger) { + if ($trigger == $notificationTrigger['id']) { + return $notificationLink['platform_id']; + } + } + } + } + } +} diff --git a/root/app/www/public/classes/traits/Database/NotificationPlatform.php b/root/app/www/public/classes/traits/Database/NotificationPlatform.php new file mode 100644 index 0000000..853452e --- /dev/null +++ b/root/app/www/public/classes/traits/Database/NotificationPlatform.php @@ -0,0 +1,30 @@ +notificationPlatformTable) { + return $this->notificationPlatformTable; + } + + $notificationPlatformTable = []; + + $q = "SELECT * + FROM " . NOTIFICATION_PLATFORM_TABLE; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $notificationPlatformTable[$row['id']] = $row; + } + + $this->notificationPlatformTable = $notificationPlatformTable; + return $notificationPlatformTable; + } +} diff --git a/root/app/www/public/classes/traits/Database/NotificationTrigger.php b/root/app/www/public/classes/traits/Database/NotificationTrigger.php new file mode 100644 index 0000000..9abe959 --- /dev/null +++ b/root/app/www/public/classes/traits/Database/NotificationTrigger.php @@ -0,0 +1,61 @@ +notificationTriggersTable) { + return $this->notificationTriggersTable; + } + + $notificationTriggersTable = []; + + $q = "SELECT * + FROM " . NOTIFICATION_TRIGGER_TABLE; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $notificationTriggersTable[$row['id']] = $row; + } + + $this->notificationTriggersTable = $notificationTriggersTable; + return $notificationTriggersTable; + } + + public function getNotificationTriggerFromName($name) + { + $triggers = $this->getNotificationTriggers(); + + foreach ($triggers as $trigger) { + if (str_compare($name, $trigger['name'])) { + return $trigger; + } + } + + return []; + } + + public function isNotificationTriggerEnabled($name) + { + $notificationLinks = $this->getNotificationLinks(); + $notificationTrigger = $this->getNotificationTriggerFromName($name); + + foreach ($notificationLinks as $notificationLink) { + $triggers = makeArray(json_decode($notificationLink['trigger_ids'], true)); + + foreach ($triggers as $trigger) { + if ($trigger == $notificationTrigger['id']) { + return true; + } + } + } + + return false; + } +} diff --git a/root/app/www/public/classes/traits/Database/Servers.php b/root/app/www/public/classes/traits/Database/Servers.php new file mode 100644 index 0000000..bddfc55 --- /dev/null +++ b/root/app/www/public/classes/traits/Database/Servers.php @@ -0,0 +1,56 @@ +serversTable; + } + + foreach ($serverList as $serverId => $serverSettings) { + switch (true) { + case $serverSettings['remove']: + $q = "DELETE FROM " . SERVERS_TABLE . " + WHERE id = " . $serverId; + break; + case !$serverId: + $q = "INSERT INTO " . SERVERS_TABLE . " + (`name`, `url`, `apikey`) + VALUES + ('" . $this->prepare($serverSettings['name']) . "', '" . $this->prepare($serverSettings['url']) . "', '" . $this->prepare($serverSettings['apikey']) . "')"; + break; + default: + $q = "UPDATE " . SERVERS_TABLE . " + SET name = '" . $this->prepare($serverSettings['name']) . "', url = '" . $this->prepare($serverSettings['url']) . "', apikey = '" . $this->prepare($serverSettings['apikey']) . "' + WHERE id = " . $serverId; + break; + } + $this->query($q); + } + + return $this->getServers(); + } + + public function getServers() + { + $serversTable = []; + + $q = "SELECT * + FROM " . SERVERS_TABLE; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $serversTable[$row['id']] = $row; + } + + $this->serversTable = $serversTable; + return $serversTable; + } +} diff --git a/root/app/www/public/classes/traits/Database/Settings.php b/root/app/www/public/classes/traits/Database/Settings.php new file mode 100644 index 0000000..7e013f4 --- /dev/null +++ b/root/app/www/public/classes/traits/Database/Settings.php @@ -0,0 +1,69 @@ +query($q); + $row = $this->fetchAssoc($r); + + return $row['value']; + } + + public function setSetting($field, $value) + { + $q = "UPDATE " . SETTINGS_TABLE . " + SET value = '" . $this->prepare($value) . "' + WHERE name = '" . $field . "'"; + $this->query($q); + + return $this->getSettings(); + } + + public function setSettings($newSettings = [], $currentSettings = []) + { + if (!$newSettings) { + return; + } + + if (!$currentSettings) { + $currentSettings = $this->getSettings(); + } + + foreach ($newSettings as $field => $value) { + if ($currentSettings[$field] != $value) { + $q = "UPDATE " . SETTINGS_TABLE . " + SET value = '" . $this->prepare($value) . "' + WHERE name = '" . $field . "'"; + $this->query($q); + } + } + + return $this->getSettings(); + } + + public function getSettings() + { + $settingsTable = []; + + $q = "SELECT * + FROM " . SETTINGS_TABLE ; + $r = $this->query($q); + while ($row = $this->fetchAssoc($r)) { + $settingsTable[$row['name']] = $row['value']; + } + + $this->settingsTable = $settingsTable; + return $settingsTable; + } +} diff --git a/root/app/www/public/classes/traits/Docker/API.php b/root/app/www/public/classes/traits/Docker/API.php index 1017695..a988957 100644 --- a/root/app/www/public/classes/traits/Docker/API.php +++ b/root/app/www/public/classes/traits/Docker/API.php @@ -13,7 +13,7 @@ $apiResponse = $docker->apiCurl($request, 'post'); */ -trait API +trait DockersApi { public function apiIsAvailable() { @@ -26,7 +26,7 @@ public function apiIsAvailable() return false; } - + if (file_exists('/var/run/docker.sock')) { return true; } @@ -197,8 +197,7 @@ public function apiCreateContainer($inspect = []) $payload['ExposedPorts'] = null; //-- MAKE SURE THE ID IS UPDATED - $dependencyFile = getServerFile('dependency'); - $dependencyFile = is_array($dependencyFile['file']) && !empty($dependencyFile['file']) ? $dependencyFile['file'] : []; + $dependencyFile = makeArray(apiRequest('file-dependency')['result']); foreach ($dependencyFile as $parent => $parentSettings) { if (in_array($containerName, $parentSettings['containers'])) { diff --git a/root/app/www/public/classes/traits/Docker/Container.php b/root/app/www/public/classes/traits/Docker/Container.php index 5580e5a..f2e07fb 100644 --- a/root/app/www/public/classes/traits/Docker/Container.php +++ b/root/app/www/public/classes/traits/Docker/Container.php @@ -29,18 +29,15 @@ public function startContainer($container) public function stopContainer($container) { - //-- GET CURRENT SETTINGS FILE - $settingsFile = getServerFile('settings'); - $settingsFile = $settingsFile['file']; - - $nameHash = md5($container); - $delay = (intval($settingsFile['containers'][$nameHash]['shutdownDelaySeconds']) >= 5 ? ' -t ' . $settingsFile['containers'][$nameHash]['shutdownDelaySeconds'] : ' -t 120'); + $nameHash = md5($container); + $container = $this->api->request('database-getContainerFromHash', ['hash' => $nameHash]); + $delay = intval($container['shutdownDelaySeconds']) >= 5 ? ' -t ' . $container['shutdownDelaySeconds'] : ' -t 120'; - if ($settingsFile['containers'][$nameHash]['shutdownDelay']) { + if ($container['shutdownDelay']) { logger(SYSTEM_LOG, 'stopContainer() delaying stop command for container ' . $container . ' with ' . $delay); } - $cmd = sprintf(DockerSock::STOP_CONTAINER, $this->shell->prepare($container), ($settingsFile['containers'][$nameHash]['shutdownDelay'] ? $this->shell->prepare($delay) : '')); + $cmd = sprintf(DockerSock::STOP_CONTAINER, $this->shell->prepare($container), ($container['shutdownDelay'] ? $this->shell->prepare($delay) : '')); return $this->shell->exec($cmd . ' 2>&1'); } @@ -62,8 +59,7 @@ public function findContainer($query = []) if ($query['hash']) { if (!$query['data']) { - $stateFile = getServerFile('state'); - $query['data'] = $stateFile['file']; + $query['data'] = apiRequest('file-state')['result']; } foreach ($query['data'] as $container) { @@ -125,7 +121,7 @@ public function setContainerDependencies($processList) } } - setServerFile('dependency', json_encode($dependencyList)); + apiRequest('file-dependency', [], ['contents' => $dependencyList]); return $dependencyList; } diff --git a/root/app/www/public/classes/traits/Maintenance/maint.php b/root/app/www/public/classes/traits/Maintenance/maint.php index 3a5f325..ee3a54b 100644 --- a/root/app/www/public/classes/traits/Maintenance/maint.php +++ b/root/app/www/public/classes/traits/Maintenance/maint.php @@ -60,9 +60,9 @@ public function createMaintenance() $this->pullMaintenance(); - $apiResponse = apiRequest('dockerInspect', ['name' => $this->hostContainer['Names'], 'useCache' => false, 'format' => true]); - logger(MAINTENANCE_LOG, 'dockerInspect:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $inspectImage = $apiResponse['response']['docker']; + $apiRequest = apiRequest('docker-inspect', ['name' => $this->hostContainer['Names'], 'useCache' => false, 'format' => true]); + logger(MAINTENANCE_LOG, 'docker-inspect:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $inspectImage = $apiRequest['result']; $inspectImage = json_decode($inspectImage, true); $inspectImage[0]['Name'] = '/' . $this->maintenanceContainerName; diff --git a/root/app/www/public/classes/traits/Notifications/Templates.php b/root/app/www/public/classes/traits/Notifications/Templates.php new file mode 100644 index 0000000..e888800 --- /dev/null +++ b/root/app/www/public/classes/traits/Notifications/Templates.php @@ -0,0 +1,64 @@ + '', + 'container' => '', + 'restarted' => '' + ]; + case 'prune': + return [ + 'event' => '', + 'network' => '', + 'volume' => '', + 'image' => '', + 'imageList' => '' + ]; + case 'added': + case 'removed': + case 'stateChange': + return [ + 'event' => '', + 'changes' => '', + 'added' => '', + 'removed' => '' + ]; + case 'test': + return [ + 'event' => '', + 'title' => '', + 'message' => '' + ]; + case 'updated': + case 'updates': + return [ + 'event' => '', + 'available' => '', + 'updated' => '' + ]; + case 'cpuHigh': + case 'memHigh': + return [ + 'event' => '', + 'cpu' => '', + 'cpuThreshold' => '', + 'mem' => '', + 'memThreshold' => '' + ]; + default: + return []; + } + } +} diff --git a/root/app/www/public/classes/traits/Notifications/notifiarr.php b/root/app/www/public/classes/traits/Notifications/notifiarr.php index a06dc15..ddcf7c1 100644 --- a/root/app/www/public/classes/traits/Notifications/notifiarr.php +++ b/root/app/www/public/classes/traits/Notifications/notifiarr.php @@ -9,9 +9,9 @@ trait Notifiarr { - public function notifiarr($logfile, $payload) + public function notifiarr($logfile, $apikey, $payload) { - $headers = ['x-api-key:' . $this->platformSettings[1]['apikey']]; + $headers = ['x-api-key:' . $apikey]; $url = 'https://notifiarr.com/api/v1/notification/dockwatch'; $curl = curl($url, $headers, 'POST', json_encode($payload)); diff --git a/root/app/www/public/crons/health.php b/root/app/www/public/crons/health.php index baa0b5a..150c393 100644 --- a/root/app/www/public/crons/health.php +++ b/root/app/www/public/crons/health.php @@ -14,7 +14,7 @@ logger(CRON_HEALTH_LOG, 'run ->'); echo date('c') . ' Cron: health ->' . "\n"; -if ($settingsFile['tasks']['health']['disabled']) { +if ($settingsTable['taskHealthDisabled']) { logger(CRON_HEALTH_LOG, 'Cron cancelled: disabled in tasks menu'); logger(CRON_HEALTH_LOG, 'run <-'); echo date('c') . ' Cron: health cancelled, disabled in tasks menu' . "\n"; @@ -22,7 +22,7 @@ exit(); } -if (!$settingsFile['global']['restartUnhealthy'] && !$settingsFile['notifications']['triggers']['health']['active']) { +if (!$settingsTable['restartUnhealthy'] && !apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'health'])['result']) { logger(CRON_HEALTH_LOG, 'Cron cancelled: restart and notify disabled'); logger(CRON_HEALTH_LOG, 'run <-'); echo date('c') . ' Cron health cancelled: restart unhealthy and notify disabled' . "\n"; @@ -69,21 +69,23 @@ logger(CRON_HEALTH_LOG, '$unhealthy=' . json_encode($unhealthy, JSON_UNESCAPED_SLASHES)); if ($unhealthy) { + $containersTable = apiRequest('database-getContainers')['result']; + foreach ($unhealthy as $nameHash => $container) { $notify = false; if ($container['restart'] || $container['notify']) { continue; } - - $skipActions = skipContainerActions($container['name'], $skipContainerActions); + $thisContainer = apiRequest('database-getContainerFromHash', ['hash' => $nameHash])['result']; + $skipActions = skipContainerActions($container['name'], $skipContainerActions); if ($skipActions) { logger(CRON_HEALTH_LOG, 'skipping: ' . $container['name'] . ', blacklisted (no state changes) container'); continue; } - if (!$settingsFile['containers'][$nameHash]['restartUnhealthy']) { + if (!$thisContainer['restartUnhealthy']) { logger(CRON_HEALTH_LOG, 'skipping: ' . $container['name'] . ', restart unhealthy option not enabled'); continue; } @@ -91,9 +93,11 @@ $unhealthy[$nameHash]['notify'] = 0; $unhealthy[$nameHash]['restart'] = 0; - if ($settingsFile['notifications']['triggers']['health']['active'] && $settingsFile['notifications']['triggers']['health']['platform']) { + if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'health'])['result']) { $unhealthy[$nameHash]['notify'] = time(); $notify = true; + } else { + logger(CRON_HEALTH_LOG, 'skipping notification for \'' . $container['name'] . '\', no notification senders with the health event enabled'); } $dependencies = $dependencyFile[$container['name']]['containers']; @@ -103,32 +107,34 @@ logger(CRON_HEALTH_LOG, 'restarting unhealthy \'' . $container['name'] . '\''); $unhealthy[$nameHash]['restart'] = time(); - $apiResult = apiRequest('dockerStopContainer', [], ['name' => $container['name']]); - logger(CRON_HEALTH_LOG, 'dockerStopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - $apiResult = apiRequest('dockerStartContainer', [], ['name' => $container['name']]); - logger(CRON_HEALTH_LOG, 'dockerStartContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-stopContainer', [], ['name' => $container['name']]); + logger(CRON_HEALTH_LOG, 'docker-stopContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-startContainer', [], ['name' => $container['name']]); + logger(CRON_HEALTH_LOG, 'docker-startContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); if ($dependencies) { logger(CRON_HEALTH_LOG, 'restarting dependenices...'); foreach ($dependencies as $dependency) { - $apiResult = apiRequest('dockerStopContainer', [], ['name' => $dependency]); - logger(CRON_HEALTH_LOG, 'dockerStopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); - $apiResult = apiRequest('dockerStartContainer', [], ['name' => $dependency]); - logger(CRON_HEALTH_LOG, 'dockerStartContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-stopContainer', [], ['name' => $dependency]); + logger(CRON_HEALTH_LOG, 'docker-stopContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-startContainer', [], ['name' => $dependency]); + logger(CRON_HEALTH_LOG, 'docker-startContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); } } - if ($settingsFile['containers'][$nameHash]['disableNotifications']) { + if ($notify && $thisContainer['disableNotifications']) { logger(CRON_HEALTH_LOG, 'skipping notification for \'' . $container['name'] . '\', container set to not notify'); $notify = false; } if ($notify) { logger(CRON_HEALTH_LOG, 'sending notification for \'' . $container['name'] . '\''); + $payload = ['event' => 'health', 'container' => $container['name'], 'restarted' => $unhealthy[$nameHash]['restart']]; + $notifications->notify(0, 'health', $payload); + logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['health']['platform'], $payload); } } @@ -140,4 +146,4 @@ } echo date('c') . ' Cron: health <-' . "\n"; -logger(CRON_HEALTH_LOG, 'run <-'); \ No newline at end of file +logger(CRON_HEALTH_LOG, 'run <-'); diff --git a/root/app/www/public/crons/housekeeper.php b/root/app/www/public/crons/housekeeper.php index 2720c56..155e331 100644 --- a/root/app/www/public/crons/housekeeper.php +++ b/root/app/www/public/crons/housekeeper.php @@ -14,7 +14,7 @@ logger(CRON_HOUSEKEEPER_LOG, 'run ->'); echo date('c') . ' Cron: housekeeper ->' . "\n"; -if ($settingsFile['tasks']['housekeeping']['disabled']) { +if ($settingsTable['tasksHousekeepingDisabled']) { logger(CRON_HOUSEKEEPER_LOG, 'Cron cancelled: disabled in tasks menu'); logger(CRON_HOUSEKEEPER_LOG, 'run <-'); echo date('c') . ' Cron: housekeeper cancelled, disabled in tasks menu' . "\n"; @@ -33,7 +33,7 @@ } logger(CRON_HOUSEKEEPER_LOG, 'removing \'' . TMP_PATH . $file . '\''); - unlink(TMP_PATH . $file); + $shell->exec('rm -rf ' . TMP_PATH . $file); } closedir($dir); } @@ -44,12 +44,12 @@ [ 'crons' => [[ 'message' => 'Cron log file cleanup (daily @ midnight)', - 'length' => ($settingsFile['global']['cronLogLength'] <= 1 ? 1 : $settingsFile['global']['cronLogLength']) + 'length' => ($settingsTable['cronLogLength'] <= 1 ? 1 : $settingsTable['cronLogLength']) ]] ],[ 'notifications' => [[ 'message' => 'Notification log file cleanup (daily @ midnight)', - 'length' => ($settingsFile['global']['notificationLogLength'] <= 1 ? 1 : $settingsFile['global']['notificationLogLength']) + 'length' => ($settingsTable['notificationLogLength'] <= 1 ? 1 : $settingsTable['notificationLogLength']) ]] ],[ 'system' => [[ @@ -59,11 +59,11 @@ ],[ 'type' => 'ui', 'message' => 'UI log file cleanup (daily @ midnight)', - 'length' => ($settingsFile['global']['uiLogLength'] <= 1 ? 1 : $settingsFile['global']['uiLogLength']) + 'length' => ($settingsTable['uiLogLength'] <= 1 ? 1 : $settingsTable['uiLogLength']) ],[ 'type' => 'api', 'message' => 'API log file cleanup (daily @ midnight)', - 'length' => ($settingsFile['global']['apiLogLength'] <= 1 ? 1 : $settingsFile['global']['apiLogLength']) + 'length' => ($settingsTable['apiLogLength'] <= 1 ? 1 : $settingsTable['apiLogLength']) ]] ] ]; @@ -87,7 +87,7 @@ if ($daysBetween > $settings['length']) { logger(CRON_HOUSEKEEPER_LOG, 'removing logfile'); - unlink($thisDir . $log); + $shell->exec('rm -rf ' . $thisDir . $log); } } } @@ -98,38 +98,40 @@ } //-- BACKUPS -createDirectoryTree(BACKUP_PATH . date('Ymd')); -$defines = get_defined_constants(); -foreach ($defines as $define => $defineValue) { - if (str_contains($define, '_FILE') && str_contains_all($defineValue, ['config/', '.json'])) { - $backupFiles[] = $defineValue; +if (!is_dir(BACKUP_PATH . date('Ymd'))) { + createDirectoryTree(BACKUP_PATH . date('Ymd')); + $defines = get_defined_constants(); + foreach ($defines as $define => $defineValue) { + if (str_contains($define, '_FILE') && str_contains_all($defineValue, ['config/', '.json'])) { + $backupFiles[] = $defineValue; + } } -} -foreach ($backupFiles as $backupFile) { - $file = explode('/', $backupFile); - $file = end($file); + foreach ($backupFiles as $backupFile) { + $file = explode('/', $backupFile); + $file = end($file); - logger(CRON_HOUSEKEEPER_LOG, 'backup file \'' . APP_DATA_PATH . $file . '\' to \'' . BACKUP_PATH . $file . '\''); - copy(APP_DATA_PATH . $file, BACKUP_PATH . date('Ymd') . '/' .$file); + logger(CRON_HOUSEKEEPER_LOG, 'backup file \'' . APP_DATA_PATH . $file . '\' to \'' . BACKUP_PATH . $file . '\''); + copy(APP_DATA_PATH . $file, BACKUP_PATH . date('Ymd') . '/' . $file); + } } $dir = opendir(BACKUP_PATH); while ($backup = readdir($dir)) { if (!is_dir(BACKUP_PATH . $backup)) { logger(CRON_HOUSEKEEPER_LOG, 'removing backup: ' . BACKUP_PATH . $backup); - unlink(BACKUP_PATH . $backup); + $shell->exec('rm -rf ' . BACKUP_PATH . $backup); } else { if ($backup[0] != '.') { $daysBetween = daysBetweenDates($backup, date('Ymd')); - if ($daysBetween > 3) { + if ($daysBetween >= APP_BACKUPS) { logger(CRON_HOUSEKEEPER_LOG, 'removing backup: ' . BACKUP_PATH . $backup); - unlink(BACKUP_PATH . $backup); + $shell->exec('rm -rf ' . BACKUP_PATH . $backup); } } } } echo date('c') . ' Cron: housekeeper <-' . "\n"; -logger(CRON_HOUSEKEEPER_LOG, 'run <-'); \ No newline at end of file +logger(CRON_HOUSEKEEPER_LOG, 'run <-'); diff --git a/root/app/www/public/crons/prune.php b/root/app/www/public/crons/prune.php index 227e4f3..4cc0991 100644 --- a/root/app/www/public/crons/prune.php +++ b/root/app/www/public/crons/prune.php @@ -16,7 +16,7 @@ logger(CRON_PRUNE_LOG, 'run ->'); echo date('c') . ' Cron: prune ->' . "\n"; -if ($settingsFile['tasks']['prune']['disabled']) { +if ($settingsTable['tasksPruneDisabled']) { logger(CRON_PRUNE_LOG, 'Cron cancelled: disabled in tasks menu'); logger(CRON_PRUNE_LOG, 'run <-'); echo date('c') . ' Cron: prune cancelled, disabled in tasks menu' . "\n"; @@ -24,7 +24,7 @@ exit(); } -$frequencyHour = $settingsFile['global']['autoPruneHour'] ? $settingsFile['global']['autoPruneHour'] : '12'; +$frequencyHour = $settingsTable['autoPruneHour'] ? $settingsTable['autoPruneHour'] : '12'; if ($frequencyHour !== date('G')) { logger(CRON_PRUNE_LOG, 'Cron: skipped, frequency setting will run at hour ' . $frequencyHour); logger(CRON_PRUNE_LOG, 'run <-'); @@ -43,7 +43,7 @@ logger(CRON_PRUNE_LOG, 'volumes=' . json_encode($volumes)); logger(CRON_PRUNE_LOG, 'networks=' . json_encode($networks)); -if ($settingsFile['global']['autoPruneImages']) { +if ($settingsTable['autoPruneImages']) { if ($images) { foreach ($images as $image) { $imagePrune[] = $image['ID']; @@ -54,7 +54,7 @@ logger(CRON_PRUNE_LOG, 'Auto prune images disabled'); } -if ($settingsFile['global']['autoPruneVolumes']) { +if ($settingsTable['autoPruneVolumes']) { if ($volumes) { foreach ($volumes as $volume) { $volumePrune[] = $volume['Name']; @@ -64,7 +64,7 @@ logger(CRON_PRUNE_LOG, 'Auto prune volumes disabled'); } -if ($settingsFile['global']['autoPruneNetworks']) { +if ($settingsTable['autoPruneNetworks']) { if ($networks) { foreach ($networks as $network) { $networkPrune[] = $network['ID']; @@ -98,11 +98,12 @@ logger(CRON_PRUNE_LOG, 'result: ' . $prune); } -if ($settingsFile['notifications']['triggers']['prune']['active'] && (count($volumePrune) > 0 || count($imagePrune) > 0 || count($networkPrune) > 0)) { +if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'prune'])['result'] && (count($volumePrune) > 0 || count($imagePrune) > 0 || count($networkPrune) > 0)) { $payload = ['event' => 'prune', 'network' => count($networkPrune), 'volume' => count($volumePrune), 'image' => count($imagePrune), 'imageList' => $imageList]; + $notifications->notify(0, 'prune', $payload); + logger(CRON_PRUNE_LOG, 'Notification payload: ' . json_encode($payload)); - $notifications->notify($settingsFile['notifications']['triggers']['prune']['platform'], $payload); } echo date('c') . ' Cron: prune <-' . "\n"; -logger(CRON_PRUNE_LOG, 'run <-'); \ No newline at end of file +logger(CRON_PRUNE_LOG, 'run <-'); diff --git a/root/app/www/public/crons/pulls.php b/root/app/www/public/crons/pulls.php index 4cbd50d..118e44e 100644 --- a/root/app/www/public/crons/pulls.php +++ b/root/app/www/public/crons/pulls.php @@ -14,9 +14,9 @@ logger(SYSTEM_LOG, 'Cron: running pulls'); logger(CRON_PULLS_LOG, 'run ->'); -echo date('c') . ' Cron: pulls' . "\n"; +echo date('c') . ' Cron: pulls ->' . "\n"; -if ($settingsFile['tasks']['pulls']['disabled']) { +if ($settingsTable['tasksPullsDisabled']) { logger(CRON_PULLS_LOG, 'Cron cancelled: disabled in tasks menu'); logger(CRON_PULLS_LOG, 'run <-'); echo date('c') . ' Cron: pulls cancelled, disabled in tasks menu' . "\n"; @@ -24,14 +24,16 @@ exit(); } -$updateSettings = $settingsFile['containers']; -$notify = []; -$startStamp = new DateTime(); +$containersTable = apiRequest('database-getContainers')['result']; +$notify = []; +$startStamp = new DateTime(); -if ($updateSettings) { +if ($containersTable) { $imagesUpdated = []; - foreach ($updateSettings as $containerHash => $containerSettings) { + foreach ($containersTable as $containerSettings) { + $containerHash = $containerSettings['hash']; + //-- SET TO IGNORE if (!$containerSettings['updates']) { continue; @@ -132,7 +134,7 @@ echo date('c') . ' ' . $msg . "\n"; } - if (!$dockerCommunicateAPI) { + if (!$isDockerApiAvailable) { $msg = 'Skipping container update: ' . $containerState['Names'] . ' (docker engine api access is not available)'; logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; @@ -160,7 +162,7 @@ ]; //-- UPDATE THE PULLS FILE BEFORE THIS CALL SINCE THIS STOPS THE PROCESS - setServerFile('pull', $pullsFile); + apiRequest('file-pull', [], ['contents' => $pullsFile]); $maintenance = new Maintenance(); $maintenance->apply('update'); @@ -240,8 +242,7 @@ logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; - $dependencyFile = getServerFile('dependency'); - $dependencyFile = $dependencyFile['file']; + $dependencyFile = apiRequest('file-dependency')['result']; $dependencies = $dependencyFile[$containerState['Names']]['containers']; if (is_array($dependencies)) { @@ -252,31 +253,31 @@ updateDependencyParentId($containerState['Names'], $update['Id']); foreach ($dependencies as $dependencyContainer) { - $msg = '[dependency] dockerInspect: ' . $dependencyContainer; + $msg = '[dependency] docker-inspect: ' . $dependencyContainer; logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; - $apiResponse = apiRequest('dockerInspect', ['name' => $dependencyContainer, 'useCache' => false, 'format' => true]); + $apiResponse = apiRequest('docker-inspect', ['name' => $dependencyContainer, 'useCache' => false, 'format' => true]); logger(CRON_PULLS_LOG, 'dockerInspect:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $inspectImage = $apiResponse['response']['docker']; + $inspectImage = $apiResponse['result']; - $msg = '[dependency] dockerStopContainer: ' . $dependencyContainer; + $msg = '[dependency] docker-stopContainer: ' . $dependencyContainer; logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; - $apiResult = apiRequest('dockerStopContainer', [], ['name' => $dependencyContainer]); - logger(CRON_PULLS_LOG, 'dockerStopContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-stopContainer', [], ['name' => $dependencyContainer]); + logger(CRON_PULLS_LOG, 'dockerStopContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); - $msg = '[dependency] dockerRemoveContainer: ' . $dependencyContainer; + $msg = '[dependency] docker-removeContainer: ' . $dependencyContainer; logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; - $apiResult = apiRequest('dockerRemoveContainer', [], ['name' => $dependencyContainer]); - logger(CRON_PULLS_LOG, 'dockerRemoveContainer:' . json_encode($apiResult, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-removeContainer', [], ['name' => $dependencyContainer]); + logger(CRON_PULLS_LOG, 'docker-removeContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); - $msg = '[dependency] dockerCreateContainer: ' . $dependencyContainer; + $msg = '[dependency] docker-createContainer: ' . $dependencyContainer; logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; - $apiResponse = apiRequest('dockerCreateContainer', [], ['inspect' => $inspectImage]); - logger(CRON_PULLS_LOG, 'dockerCreateContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); - $update = $apiResponse['response']['docker']; + $apiRequest = apiRequest('docker-createContainer', [], ['inspect' => $inspectImage]); + logger(CRON_PULLS_LOG, 'docker-createContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); + $update = $apiRequest['result']; $createResult = 'failed'; if (strlen($update['Id']) == 64) { @@ -284,11 +285,11 @@ logger(CRON_PULLS_LOG, 'Container ' . $dependencyContainer . ' re-create: ' . $createResult); if (str_contains($containerState['State'], 'running')) { - $msg = '[dependency] dockerStartContainer: ' . $dependencyContainer; + $msg = '[dependency] docker-startContainer: ' . $dependencyContainer; logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; - $apiResponse = apiRequest('dockerStartContainer', [], ['name' => $dependencyContainer]); - logger(CRON_PULLS_LOG, 'dockerStartContainer:' . json_encode($apiResponse, JSON_UNESCAPED_SLASHES)); + $apiRequest = apiRequest('docker-startContainer', [], ['name' => $dependencyContainer]); + logger(CRON_PULLS_LOG, 'docker-startContainer:' . json_encode($apiRequest, JSON_UNESCAPED_SLASHES)); } else { logger(CRON_PULLS_LOG, 'container was not running, not starting it'); } @@ -296,7 +297,7 @@ } } - if ($settingsFile['notifications']['triggers']['updated']['active'] && !$settingsFile['containers'][$containerHash]['disableNotifications']) { + if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'updated'])['result'] && !$containerSettings['disableNotifications']) { $notify['updated'][] = [ 'container' => $containerState['Names'], 'image' => $image, @@ -309,14 +310,14 @@ logger(CRON_PULLS_LOG, $msg); echo date('c') . ' ' . $msg . "\n"; - if ($settingsFile['notifications']['triggers']['updated']['active'] && !$settingsFile['containers'][$containerHash]['disableNotifications']) { + if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'updated'])['result'] && !$containerSettings['disableNotifications']) { $notify['failed'][] = ['container' => $containerState['Names']]; } } } break; case 2: //-- Check for updates - if ($settingsFile['notifications']['triggers']['updates']['active'] && $inspectImage[0]['Id'] != $inspectContainer[0]['Image'] && !$settingsFile['containers'][$containerHash]['disableNotifications']) { + if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'updates'])['result'] && !$containerSettings['disableNotifications'] && $inspectImage[0]['Id'] != $inspectContainer[0]['Image']) { $notify['available'][] = ['container' => $containerState['Names']]; } break; @@ -330,30 +331,32 @@ } } - setServerFile('pull', $pullsFile); + apiRequest('file-pull', [], ['contents' => $pullsFile]); if ($notify) { //-- IF THEY USE THE SAME PLATFORM, COMBINE THEM - if ($settingsFile['notifications']['triggers']['updated']['platform'] == $settingsFile['notifications']['triggers']['updates']['platform']) { + if (apiRequest('database-getNotificationLinkPlatformFromName', ['name' => 'updated'])['result'] == apiRequest('database-getNotificationLinkPlatformFromName', ['name' => 'updates'])['result']) { $payload = ['event' => 'updates', 'available' => $notify['available'], 'updated' => $notify['updated']]; + $notifications->notify(0, 'updates', $payload); + logger(CRON_PULLS_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['updated']['platform'], $payload); } else { if ($notify['available']) { $payload = ['event' => 'updates', 'available' => $notify['available']]; + $notifications->notify(0, 'updates', $payload); + logger(CRON_PULLS_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['updated']['platform'], $payload); } - if ($notify['usage']['mem']) { + if ($notify['updated']) { $payload = ['event' => 'updates', 'updated' => $notify['updated']]; + $notifications->notify(0, 'updated', $payload); + logger(CRON_PULLS_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['updates']['platform'], $payload); } } } } - echo date('c') . ' Cron: pulls <-' . "\n"; -logger(CRON_PULLS_LOG, 'run <-'); \ No newline at end of file +logger(CRON_PULLS_LOG, 'run <-'); diff --git a/root/app/www/public/crons/sse.php b/root/app/www/public/crons/sse.php index f4423e8..6157a22 100644 --- a/root/app/www/public/crons/sse.php +++ b/root/app/www/public/crons/sse.php @@ -14,7 +14,7 @@ logger(CRON_SSE_LOG, 'run ->'); echo date('c') . ' Cron: sse' . "\n"; -if (!$settingsFile['global']['sseEnabled']) { +if (!$settingsTable['sseEnabled']) { logger(CRON_SSE_LOG, 'Cron cancelled: disabled in tasks menu'); logger(CRON_SSE_LOG, 'run <-'); echo date('c') . ' Cron: sse cancelled, disabled in tasks menu' . "\n"; diff --git a/root/app/www/public/crons/state.php b/root/app/www/public/crons/state.php index b72a4b3..2d35229 100644 --- a/root/app/www/public/crons/state.php +++ b/root/app/www/public/crons/state.php @@ -14,7 +14,7 @@ logger(CRON_STATE_LOG, 'run ->'); echo date('c') . ' Cron: state' . "\n"; -if ($settingsFile['tasks']['state']['disabled']) { +if ($settingsTable['taskStateDisabled']) { logger(CRON_STATE_LOG, 'Cron cancelled: disabled in tasks menu'); logger(CRON_STATE_LOG, 'run <-'); echo date('c') . ' Cron: state cancelled, disabled in tasks menu' . "\n"; @@ -24,22 +24,17 @@ $notify = $added = $removed = $previousStates = $currentStates = $previousContainers = $currentContainers = []; -$stateFile = getServerFile('state'); -$stateFile = $stateFile['file']; +$stateFile = apiRequest('file-state')['result']; $previousStates = $stateFile; $currentStates = dockerState(); if ($currentStates) { - setServerFile('state', $currentStates); + apiRequest('file-state', [], ['contents' => $currentStates]); } else { logger(CRON_STATE_LOG, 'STATE_FILE update skipped, $currentStates empty'); } -//-- GET CURRENT SETTINGS FILE -$settingsFile = getServerFile('settings'); -$settingsFile = $settingsFile['file']; - logger(CRON_STATE_LOG, 'previousStates: ' . json_encode($previousStates, JSON_UNESCAPED_SLASHES)); logger(CRON_STATE_LOG, 'currentStates: ' . json_encode($currentStates, JSON_UNESCAPED_SLASHES)); @@ -55,52 +50,47 @@ logger(CRON_STATE_LOG, 'currentContainers: ' . json_encode($currentContainers, JSON_UNESCAPED_SLASHES)); //-- CHECK FOR ADDED CONTAINERS +$containersTable = apiRequest('database-getContainers')['result']; foreach ($currentContainers as $currentContainer) { if (!in_array($currentContainer, $previousContainers)) { - $containerHash = md5($currentContainer); - - $updates = is_array($settingsFile['global']) && array_key_exists('updates', $settingsFile['global']) ? $settingsFile['global']['updates'] : 3; //-- CHECK ONLY FALLBACK - $frequency = is_array($settingsFile['global']) && array_key_exists('updatesFrequency', $settingsFile['global']) ? $settingsFile['global']['updatesFrequency'] : DEFAULT_CRON; //-- DAILY FALLBACK - $settingsFile['containers'][$containerHash] = ['updates' => $updates, 'frequency' => $frequency]; + $containerHash = md5($currentContainer); + $updates = $settingsTable['updates'] ?: 3; //-- CHECK ONLY FALLBACK + $frequency = $settingsTable['updatesFrequency'] ?: DEFAULT_CRON; //-- DAILY FALLBACK + $added[] = ['container' => $currentContainer]; - if (!$settingsFile['containers'][$containerHash]['disableNotifications']) { - $added[] = ['container' => $currentContainer]; - } + apiRequest('database-addContainer', [], ['hash' => $containerHash, 'updates' => $updates, 'frequency' => $frequency]); } } -if ($added && $settingsFile['notifications']['triggers']['added']['active']) { +if ($added && apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'added'])['result']) { $notify['state']['added'] = $added; + logger(CRON_STATE_LOG, 'Added containers: ' . json_encode($added, JSON_UNESCAPED_SLASHES)); } logger(CRON_STATE_LOG, 'Added containers: ' . json_encode($added, JSON_UNESCAPED_SLASHES)); //-- CHECK FOR REMOVED CONTAINERS foreach ($previousContainers as $previousContainer) { if (!in_array($previousContainer, $currentContainers)) { - $containerHash = md5($previousContainer); + $containerHash = md5($previousContainer); + $container = apiRequest('database-getContainerFromHash', ['hash' => $containerHash])['result']; - unset($settingsFile['containers'][$containerHash]); - - if (!$settingsFile['containers'][$containerHash]['disableNotifications']) { + if (!$container['disableNotifications']) { $removed[] = ['container' => $previousContainer]; } } } -if ($removed && $settingsFile['notifications']['triggers']['removed']['active']) { - $notify['state']['removed'] = $removed; -} -logger(CRON_STATE_LOG, 'Removed containers: ' . json_encode($removed, JSON_UNESCAPED_SLASHES)); -if ($added || $removed) { - logger(CRON_STATE_LOG, 'updating settings file: ' . json_encode($settingsFile, JSON_UNESCAPED_SLASHES)); - setServerFile('settings', $settingsFile); +if ($removed && apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'removed'])['result']) { + $notify['state']['removed'] = $removed; + logger(CRON_STATE_LOG, 'Removed containers: ' . json_encode($removed, JSON_UNESCAPED_SLASHES)); } //-- CHECK FOR STATE CHANGED CONTAINERS foreach ($currentStates as $currentState) { foreach ($previousStates as $previousState) { - $containerHash = md5($currentState['Names']); + $containerHash = md5($currentState['Names']); + $container = apiRequest('database-getContainerFromHash', ['hash' => $containerHash])['result']; - if ($settingsFile['notifications']['triggers']['stateChange']['active'] && $currentState['Names'] == $previousState['Names'] && !$settingsFile['containers'][$containerHash]['disableNotifications']) { + if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'stateChange'])['result'] && !$container['disableNotifications'] && $currentState['Names'] == $previousState['Names']) { if ($previousState['State'] != $currentState['State']) { $notify['state']['changed'][] = ['container' => $currentState['Names'], 'previous' => $previousState['State'], 'current' => $currentState['State']]; } @@ -110,30 +100,31 @@ logger(CRON_STATE_LOG, 'State changed containers: ' . json_encode($notify['state']['changed'], JSON_UNESCAPED_SLASHES)); foreach ($currentStates as $currentState) { - $containerHash = md5($currentState['Names']); + $containerHash = md5($currentState['Names']); + $container = apiRequest('database-getContainerFromHash', ['hash' => $containerHash])['result']; //-- CHECK FOR HIGH CPU USAGE CONTAINERS - if ($settingsFile['notifications']['triggers']['cpuHigh']['active'] && floatval($settingsFile['global']['cpuThreshold']) > 0 && !$settingsFile['containers'][$containerHash]['disableNotifications']) { + if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'cpuHigh'])['result'] && !$container['disableNotifications'] && floatval($settingsTable['cpuThreshold']) > 0) { if ($currentState['stats']['CPUPerc']) { $cpu = floatval(str_replace('%', '', $currentState['stats']['CPUPerc'])); - $cpuAmount = intval($settingsFile['global']['cpuAmount']); + $cpuAmount = intval($settingsTable['cpuAmount']); if ($cpuAmount > 0) { $cpu = number_format(($cpu / $cpuAmount), 2); } - if ($cpu > floatval($settingsFile['global']['cpuThreshold'])) { + if ($cpu > floatval($settingsTable['cpuThreshold'])) { $notify['usage']['cpu'][] = ['container' => $currentState['Names'], 'usage' => $cpu]; } } } //-- CHECK FOR HIGH MEMORY USAGE CONTAINERS - if ($settingsFile['notifications']['triggers']['memHigh']['active'] && floatval($settingsFile['global']['memThreshold']) > 0 && !$settingsFile['containers'][$containerHash]['disableNotifications']) { + if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'memHigh'])['result'] && !$container['disableNotifications'] && floatval($settingsTable['memThreshold']) > 0) { if ($currentState['stats']['MemPerc']) { $mem = floatval(str_replace('%', '', $currentState['stats']['MemPerc'])); - if ($mem > floatval($settingsFile['global']['memThreshold'])) { + if ($mem > floatval($settingsTable['memThreshold'])) { $notify['usage']['mem'][] = ['container' => $currentState['Names'], 'usage' => $mem]; } } @@ -154,51 +145,53 @@ if ($notify['state']) { //-- IF THEY USE THE SAME PLATFORM, COMBINE THEM - if ($settingsFile['notifications']['triggers']['stateChange']['platform'] == $settingsFile['notifications']['triggers']['added']['platform'] && $settingsFile['notifications']['triggers']['stateChange']['platform'] == $settingsFile['notifications']['triggers']['removed']['platform']) { + if ( + apiRequest('database-getNotificationLinkPlatformFromName', ['name' => 'stateChange'])['result'] == apiRequest('database-getNotificationLinkPlatformFromName', ['name' => 'added'])['result'] && + apiRequest('database-getNotificationLinkPlatformFromName', ['name' => 'stateChange'])['result'] == apiRequest('database-getNotificationLinkPlatformFromName', ['name' => 'removed'])['result'] + ) { $payload = ['event' => 'state', 'changes' => $notify['state']['changed'], 'added' => $notify['state']['added'], 'removed' => $notify['state']['removed']]; + $notifications->notify(0, 'stateChange', $payload); + logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['stateChange']['platform'], $payload); } else { if ($notify['state']['changed']) { $payload = ['event' => 'state', 'changes' => $notify['state']['changed']]; + $notifications->notify(0, 'stateChange', $payload); + logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['stateChange']['platform'], $payload); } if ($notify['state']['added']) { $payload = ['event' => 'state', 'added' => $notify['state']['added']]; + $notifications->notify(0, 'added', $payload); + logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['added']['platform'], $payload); } if ($notify['state']['removed']) { $payload = ['event' => 'state', 'removed' => $notify['state']['removed']]; + $notifications->notify(0, 'removed', $payload); + logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['removed']['platform'], $payload); } } } if ($notify['usage']) { - //-- IF THEY USE THE SAME PLATFORM, COMBINE THEM - if ($settingsFile['notifications']['triggers']['cpuHigh']['platform'] == $settingsFile['notifications']['triggers']['memHigh']['platform']) { - $payload = ['event' => 'usage', 'cpu' => $notify['usage']['cpu'], 'cpuThreshold' => $settingsFile['global']['cpuThreshold'], 'mem' => $notify['usage']['mem'], 'memThreshold' => $settingsFile['global']['memThreshold']]; + if ($notify['usage']['cpu']) { + $payload = ['event' => 'usage', 'cpu' => $notify['usage']['cpu'], 'cpuThreshold' => $settingsTable['cpuThreshold']]; + $notifications->notify(0, 'cpuHigh', $payload); + logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['cpuHigh']['platform'], $payload); - } else { - if ($notify['usage']['cpu']) { - $payload = ['event' => 'usage', 'cpu' => $notify['usage']['cpu'], 'cpuThreshold' => $settingsFile['global']['cpuThreshold']]; - logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['cpuHigh']['platform'], $payload); - } + } - if ($notify['usage']['mem']) { - $payload = ['event' => 'usage', 'mem' => $notify['usage']['mem'], 'memThreshold' => $settingsFile['global']['memThreshold']]; - logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); - $notifications->notify($settingsFile['notifications']['triggers']['memHigh']['platform'], $payload); - } + if ($notify['usage']['mem']) { + $payload = ['event' => 'usage', 'mem' => $notify['usage']['mem'], 'memThreshold' => $settingsTable['memThreshold']]; + $notifications->notify(0, 'memHigh', $payload); + + logger(CRON_STATE_LOG, 'Notification payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES)); } } echo date('c') . ' Cron: state <-' . "\n"; -logger(CRON_STATE_LOG, 'run <-'); \ No newline at end of file +logger(CRON_STATE_LOG, 'run <-'); diff --git a/root/app/www/public/crons/stats.php b/root/app/www/public/crons/stats.php index 77e0efb..627a89e 100644 --- a/root/app/www/public/crons/stats.php +++ b/root/app/www/public/crons/stats.php @@ -14,7 +14,7 @@ logger(CRON_STATS_LOG, 'run ->'); echo date('c') . ' Cron: stats' . "\n"; -if ($settingsFile['tasks']['stats']['disabled']) { +if ($settingsTable['tasksStatsDisabled']) { logger(CRON_STATS_LOG, 'Cron cancelled: disabled in tasks menu'); logger(CRON_STATS_LOG, 'run <-'); echo date('c') . ' Cron: stats cancelled, disabled in tasks menu' . "\n"; @@ -23,7 +23,7 @@ } $dockerStats = $docker->stats(false); -setServerFile('stats', $dockerStats); +apiRequest('file-stats', [], ['contents' => $dockerStats]); echo date('c') . ' Cron: stats <-' . "\n"; -logger(CRON_STATS_LOG, 'run <-'); \ No newline at end of file +logger(CRON_STATS_LOG, 'run <-'); diff --git a/root/app/www/public/functions/api.php b/root/app/www/public/functions/api.php index 570fa7c..96e2fea 100644 --- a/root/app/www/public/functions/api.php +++ b/root/app/www/public/functions/api.php @@ -7,6 +7,42 @@ ---------------------------------- */ +function generateApikey($length = 32) +{ + return bin2hex(random_bytes($length)); +} + +function apiSetActiveServer($serverId, $serversTable = []) +{ + global $database; + + $serversTable = $serversTable ?: $database->getServers(); + $_SESSION['activeServerId'] = $serversTable[$serverId]['id']; + $_SESSION['activeServerName'] = $serversTable[$serverId]['name']; + $_SESSION['activeServerUrl'] = rtrim($serversTable[$serverId]['url'], '/'); + $_SESSION['activeServerApikey'] = $serversTable[$serverId]['apikey']; +} + +function apiGetActiveServer() +{ + global $database; + + if ($_SESSION['activeServerId'] && !$_SESSION['activeServerApikey']) { + $serversTable = $database->getServers(); + $_SESSION['activeServerId'] = $serversTable[$_SESSION['activeServerId']]['id']; + $_SESSION['activeServerName'] = $serversTable[$_SESSION['activeServerId']]['name']; + $_SESSION['activeServerUrl'] = rtrim($serversTable[$_SESSION['activeServerId']]['url'], '/'); + $_SESSION['activeServerApikey'] = $serversTable[$_SESSION['activeServerId']]['apikey']; + } + + return [ + 'id' => $_SESSION['activeServerId'], + 'name' => $_SESSION['activeServerName'], + 'url' => $_SESSION['activeServerUrl'], + 'apikey' => $_SESSION['activeServerApikey'] + ]; +} + function apiResponse($code, $response) { session_unset(); @@ -31,30 +67,405 @@ function apiResponse($code, $response) die(); } -function apiRequest($request, $params = [], $payload = []) +function apiRequest($endpoint, $parameters = [], $payload = []) { - global $serverOverride; + global $database; + + $activeServer = apiGetActiveServer(); + + if ($activeServer['id'] == APP_SERVER_ID) { + return ['code' => 200, 'result' => apiRequestLocal($endpoint, $parameters, $payload)]; + } - $serverUrl = is_array($serverOverride) && $serverOverride['url'] ? $serverOverride['url'] : ACTIVE_SERVER_URL; - $serverName = is_array($serverOverride) && $serverOverride['name'] ? $serverOverride['name'] : ACTIVE_SERVER_NAME; - $serverApikey = is_array($serverOverride) && $serverOverride['apikey'] ? $serverOverride['apikey'] : ACTIVE_SERVER_APIKEY; + return apiRequestRemote($endpoint, $parameters, $payload); +} - logger(SYSTEM_LOG, 'apiRequest() ' . $request . ' for server ' . $serverName); +function apiRequestRemote($endpoint, $parameters = [], $payload = []) +{ + $activeServer = apiGetActiveServer(); + logger(SYSTEM_LOG, 'apiRequestRemote() ' . $endpoint . ' for server ' . $activeServer['name']); if ($payload) { - $params = http_build_query($params); + $parameters['request'] = $endpoint; + $parameters = http_build_query($parameters); if (!$payload['request']) { - $payload['request'] = $request; + $payload['request'] = $endpoint; } - $curl = curl($serverUrl . '/api/' . ($params ? '?' . $params : ''), ['x-api-key: ' . $serverApikey], 'POST', json_encode($payload)); + $curl = curl($activeServer['url'] . '/api/' . ($parameters ? '?' . $parameters : ''), ['x-api-key: ' . $activeServer['apikey']], 'POST', json_encode($payload), [], REMOTE_SERVER_TIMEOUT); } else { - $params['request'] = $request; - $builtParams = http_build_query($params); + $parameters['request'] = $endpoint; + $builtParams = http_build_query($parameters); - $curl = curl($serverUrl . '/api/' . ($builtParams ? '?' . $builtParams : ''), ['x-api-key: ' . $serverApikey]); + $curl = curl($activeServer['url'] . '/api/' . ($builtParams ? '?' . $builtParams : ''), ['x-api-key: ' . $activeServer['apikey']], 'GET', '', [], REMOTE_SERVER_TIMEOUT); + } + + if (!str_equals_any($curl['code'], [200, 400, 401, 405])) { + if ($endpoint != 'server-ping') { + if ($curl['code'] == 200 && !is_array($curl['response'])) { + apiResponse(500, ['error' => 'Remote request failed with error: ' . $curl['response']]); + } else { + apiResponse(500, ['error' => 'Remote request failed with code ' . $curl['code']]); + } + } } - return $curl['response']; -} \ No newline at end of file + $curl['response'] = makeArray($curl['response']); + $result['code'] = $curl['response']['code']; + $result['result'] = $curl['response']['response']['result']; + $result['error'] = $curl['response']['error']; + + return $result; +} + +function apiRequestLocal($endpoint, $parameters = [], $payload = []) +{ + global $database, $docker, $notifications; + + if (!$payload) { //-- GET + switch ($endpoint) { + case 'database-getContainerFromHash': + if (!$parameters['hash']) { + apiResponse(400, ['error' => 'Missing hash parameter']); + } + + $containersTable = $database->getContainers(); + $container = $database->getContainerFromHash($parameters['hash'], $containersTable); + + if (!$container['id']) { + $database->addContainer(['hash' => $parameters['hash']]); + $container = $database->getContainerFromHash($parameters['hash']); + } + + return $container; + case 'database-getContainerGroupFromHash': + if (!$parameters['hash']) { + apiResponse(400, ['error' => 'Missing hash parameter']); + } + $containersTable = $database->getContainers(); + + return $database->getContainerGroupFromHash($parameters['hash'], $containersTable); + case 'database-getContainers': + return $database->getContainers(); + case 'database-getContainerGroups': + return $database->getContainerGroups(); + case 'database-getContainerGroupLinks': + return $database->getContainerGroupLinks(); + case 'database-getGroupLinkContainersFromGroupId'; + if (!$parameters['group']) { + apiResponse(400, ['error' => 'Missing group parameter']); + } + $containersTable = $database->getContainers(); + $containerLinksTable = $database->getContainerGroupLinks(); + + return $database->getGroupLinkContainersFromGroupId($containerLinksTable, $containersTable, $parameters['group']); + case 'database-getNotificationLinks': + return $database->getNotificationLinks(); + case 'database-getNotificationLinkPlatformFromName': + if (!$parameters['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $database->getNotificationLinkPlatformFromName($parameters['name']); + case 'database-getNotificationPlatforms': + return $database->getNotificationPlatforms(); + case 'database-getNotificationTriggers': + return $database->getNotificationTriggers(); + case 'database-getServers': + return $database->getServers(); + case 'database-getSettings': + return $database->getSettings(); + case 'database-isNotificationTriggerEnabled': + if (!$parameters['trigger']) { + apiResponse(400, ['error' => 'Missing trigger parameter']); + } + + return $database->isNotificationTriggerEnabled($parameters['trigger']); + case 'database-migrations': + $database->migrations(); + return 'migrations applied'; + case 'docker-autoCompose': + if (!$parameters['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return dockerAutoCompose($parameters['name']); + case 'docker-autoRun': + if (!$parameters['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return dockerAutoRun($parameters['name']); + case 'docker-getOrphanContainers': + return $docker->getOrphanContainers(); + case 'docker-getOrphanNetworks': + return $docker->getOrphanNetworks(); + case 'docker-getOrphanVolumes': + return $docker->getOrphanVolumes(); + case 'docker-imageSizes': + return $docker->getImageSizes(); + case 'docker-inspect': + if (!$parameters['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->inspect($parameters['name'], $parameters['useCache'], $parameters['format'], $parameters['params']); + case 'docker-logs': + if (!$parameters['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->logs($parameters['name']); + case 'docker-networks': + return $docker->getNetworks($parameters['params']); + case 'docker-permissionCheck': + return dockerPermissionCheck(); + case 'docker-port': + if (!$parameters['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->getContainerPort($parameters['name'], $parameters['params']); + case 'docker-processList': + return $docker->processList($parameters['useCache'], $parameters['format'], $parameters['params']); + case 'docker-stats': + return $docker->stats($parameters['useCache']); + case 'dockerAPI-createContainer': + if (!$parameters['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + $inspect = json_decode($docker->inspect($parameters['name'], false, true), true); + if (!$inspect) { + apiResponse(400, ['error' => 'Failed to get inspect for container: ' . $parameters['name']]); + } + + return json_encode($docker->apiCreateContainer($inspect)); + case 'file-dependency': + case 'file-pull': + case 'file-sse': + case 'file-state': + case 'file-stats': + $file = strtoupper(str_replace('file-', '', $endpoint)); + return getFile(constant($file . '_FILE')); + case 'server-log': + return viewLog($parameters['name']); + case 'server-ping': + return 'pong from ' . ACTIVE_SERVER_NAME; + default: + apiResponse(405, ['error' => 'Invalid GET request (endpoint=' . $endpoint . ')']); + break; + } + } else { //-- POST + switch ($endpoint) { + case 'database-addContainer': + if (!$payload['hash']) { + apiResponse(400, ['error' => 'Missing hash parameter']); + } + + return $database->addContainer($payload); + case 'database-addContainerGroup': + if (!$payload['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $database->addContainerGroup($payload['name']); + case 'database-addNotificationLink': + if (!$parameters['platformId']) { + apiResponse(400, ['error' => 'Missing platformId parameter']); + } + + return $database->addNotificationLink($parameters['platformId'], $payload['triggerIds'], $payload['platformParameters'], $payload['senderName']); + case 'database-deleteContainerGroup': + if (!$payload['id']) { + apiResponse(400, ['error' => 'Missing id parameter']); + } + + return $database->deleteContainerGroup($payload['id']); + case 'database-deleteNotificationLink': + if (!$payload['linkId']) { + apiResponse(400, ['error' => 'Missing linkId parameter']); + } + + return $database->deleteNotificationLink($payload['linkId']); + case 'database-updateContainerGroup': + if (!$payload['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + if (!$payload['id']) { + apiResponse(400, ['error' => 'Missing id parameter']); + } + + return $database->updateContainerGroup($payload['id'], ['name' => $database->prepare($payload['name'])]); + case 'database-addContainerGroupLink': + if (!$payload['groupId']) { + apiResponse(400, ['error' => 'Missing groupId parameter']); + } + if (!$payload['containerId']) { + apiResponse(400, ['error' => 'Missing containerId parameter']); + } + + return $database->addContainerGroupLink($payload['groupId'], $payload['containerId']); + case 'database-removeContainerGroupLink': + if (!$payload['groupId']) { + apiResponse(400, ['error' => 'Missing groupId parameter']); + } + if (!$payload['containerId']) { + apiResponse(400, ['error' => 'Missing containerId parameter']); + } + + return $database->removeContainerGroupLink($payload['groupId'], $payload['containerId']); + case 'database-setServers': + if (!$payload['serverList']) { + apiResponse(400, ['error' => 'Missing serverList parameter']); + } + + return $database->setServers($payload['serverList']); + case 'database-setSetting': + if (!$payload['setting']) { + apiResponse(400, ['error' => 'Missing setting parameter']); + } + if (!array_key_exists('value', $payload)) { + apiResponse(400, ['error' => 'Missing value parameter']); + } + + return $database->setSetting($payload['setting'], $payload['value']); + case 'database-setSettings': + if (!$payload['newSettings']) { + apiResponse(400, ['error' => 'Missing newSettings parameter']); + } + $settingsTable = $database->getSettings(); + + return $database->setSettings($payload['newSettings'], $settingsTable); + case 'database-updateContainer': + if (!$payload['hash']) { + apiResponse(400, ['error' => 'Missing hash parameter']); + } + + return $database->updateContainer($payload['hash'], $payload); + case 'database-updateNotificationLink': + if (!$payload['linkId']) { + apiResponse(400, ['error' => 'Missing linkId parameter']); + } + + return $database->updateNotificationLink($payload['linkId'], $payload['triggerIds'], $payload['platformParameters'], $payload['senderName']); + case 'docker-createContainer': + if (!$payload['inspect']) { + apiResponse(400, ['error' => 'Missing inspect parameter']); + } + + return dockerCreateContainer(json_decode($payload['inspect'], true)); + case 'docker-pullContainer': + if (!$payload['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->pullImage($payload['name']); + case 'docker-removeContainer': + if (!$payload['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->removeContainer($payload['name']); + case 'docker-removeImage': + if (!$payload['image']) { + apiResponse(400, ['error' => 'Missing image parameter']); + } + + return $docker->removeImage($payload['image']); + case 'docker-removeNetwork': + if (!$payload['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->removeVolume($payload['name']); + case 'docker-removeVolume': + if (!$payload['id']) { + apiResponse(400, ['error' => 'Missing id parameter']); + } + + return $docker->removeNetwork($payload['id']); + case 'docker-startContainer': + if (!$payload['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->startContainer($payload['name']); + case 'docker-stopContainer': + if (!$payload['name']) { + apiResponse(400, ['error' => 'Missing name parameter']); + } + + return $docker->stopContainer($payload['name']); + case 'file-dependency': + case 'file-pull': + case 'file-sse': + case 'file-state': + case 'file-stats': + $fileName = strtoupper(str_replace('file-', '', $endpoint)); + $fileConstant = constant($fileName . '_FILE'); + + if (!array_key_exists('contents', $payload)) { + apiResponse(400, ['error' => 'Missing ' . $fileName . ' object data']); + } + + setFile($fileConstant, $payload['contents']); + return $fileConstant . ' contents updated'; + case 'notify-test': + $testNotification = $notifications->sendTestNotification($payload['linkId']); + + if ($testNotification) { + apiResponse(400, ['error' => 'Test notification failed: ' . $testNotification]); + } + + return $testNotification; + case 'server-deleteLog'; + if (!$payload['log']) { + apiResponse(400, ['error' => 'Missing log parameter']); + } + + return deleteLog($payload['log']); + case 'server-purgeLogs': + if (!$payload['group']) { + apiResponse(400, ['error' => 'Missing group parameter']); + } + + return purgeLogs($payload['group']); + case 'server-runTask': + if (!$payload['task']) { + apiResponse(400, ['error' => 'Missing task parameter']); + } + + return executeTask($payload['task']); + default: + apiResponse(405, ['error' => 'Invalid POST request (endpoint=' . $endpoint . ')']); + break; + } + } +} + +function apiRequestServerPings() +{ + global $database; + + $database = $database ?? new Database(); + $serversTable = $database->getServers(); + + $servers = []; + foreach ($serversTable as $server) { + if ($server['id'] == APP_SERVER_ID) { + $servers[strtolower($server['name'])] = ['id' => $server['id'], 'name' => $server['name'], 'code' => 200]; + } else { + apiSetActiveServer($server['id'], $serversTable); + + $ping = apiRequest('server-ping'); + $servers[strtolower($server['name'])] = ['id' => $server['id'], 'name' => $server['name'], 'url' => $server['url'], 'code' => intval($ping['code'])]; + } + } + ksort($servers); + + apiSetActiveServer(APP_SERVER_ID, $serversTable); + + return $servers; +} diff --git a/root/app/www/public/functions/common.php b/root/app/www/public/functions/common.php index d897aca..e330dfe 100644 --- a/root/app/www/public/functions/common.php +++ b/root/app/www/public/functions/common.php @@ -7,6 +7,29 @@ ---------------------------------- */ +function loadClassExtras($class) +{ + $extras = ['interfaces', 'traits']; + + foreach ($extras as $extraDir) { + if (file_exists(ABSOLUTE_PATH . 'classes/' . $extraDir . '/' . $class . '.php')) { + require ABSOLUTE_PATH . 'classes/' . $extraDir . '/' . $class . '.php'; + } else { + $extraFolder = ABSOLUTE_PATH . 'classes/' . $extraDir . '/' . $class . '/'; + + if (is_dir($extraFolder)) { + $openExtraDir = opendir($extraFolder); + while ($extraFile = readdir($openExtraDir)) { + if (str_contains($extraFile, '.php')) { + require $extraFolder . $extraFile; + } + } + closedir($openExtraDir); + } + } + } +} + function isDockwatchContainer($container) { $imageMatch = str_replace(':main', '', APP_IMAGE); @@ -19,11 +42,6 @@ function isDockwatchContainer($container) function automation() { - if (!file_exists(SERVERS_FILE)) { - $servers[] = ['name' => 'localhost', 'url' => 'http://localhost', 'apikey' => generateApikey()]; - setFile(SERVERS_FILE, $servers); - } - //-- CREATE DIRECTORIES createDirectoryTree(LOGS_PATH . 'crons'); createDirectoryTree(LOGS_PATH . 'notifications'); @@ -33,6 +51,8 @@ function automation() createDirectoryTree(BACKUP_PATH); createDirectoryTree(TMP_PATH); createDirectoryTree(COMPOSE_PATH); + createDirectoryTree(DATABASE_PATH); + createDirectoryTree(MIGRATIONS_PATH); } function createDirectoryTree($tree) @@ -82,11 +102,6 @@ function linkWebroot($location) } } -function generateApikey($length = 32) -{ - return bin2hex(random_bytes($length)); -} - function trackTime($label, $microtime = 0) { $backtrace = debug_backtrace(); @@ -154,3 +169,34 @@ function formatPortRanges($ports) return $ranges; } + +function breakpoint() +{ + $backtrace = debug_backtrace(); + echo 'breakpoint ->
' . $backtrace[0]['file'] . '->' . $backtrace[0]['line'] . '
'; + exit('breakpoint <-'); +} + +function getRemoteServerSelect() +{ + $activeServer = apiGetActiveServer(); + $serverPings = apiRequestServerPings(); + $links = ''; + $serverList = ''; + $serverList .= $links; + + $_SESSION['serverList'] = $serverList; + $_SESSION['serverListUpdated'] = time(); + + return $serverList; +} diff --git a/root/app/www/public/functions/containers.php b/root/app/www/public/functions/containers.php index 4a8cd0c..c9c4724 100644 --- a/root/app/www/public/functions/containers.php +++ b/root/app/www/public/functions/containers.php @@ -9,61 +9,48 @@ function renderContainerRow($nameHash, $return) { - global $docker, $pullsFile, $settingsFile, $processList, $skipContainerActions, $groupHash; + global $docker, $pullsFile, $settingsTable, $processList, $skipContainerActions, $groupHash; - if (!$pullsFile) { - $pullsFile = getServerFile('pull'); - if ($pullsFile['code'] != 200) { - $apiError = $pullsFile['file']; - } - $pullsFile = $pullsFile['file']; - } - - if (!$settingsFile) { - $settingsFile = getServerFile('settings'); - if ($settingsFile['code'] != 200) { - $apiError = $settingsFile['file']; - } - $settingsFile = $settingsFile['file']; - } + $pullsFile = $pullsFile ?: apiRequest('file-pull')['result']; - foreach ($processList as $process) { - if (md5($process['Names']) == $nameHash) { + foreach ($processList as $thisProcess) { + if (md5($thisProcess['Names']) == $nameHash) { + $process = $thisProcess; break; } } - $isDockwatch = false; - $dockwatchWarning = ''; + $isDockwatch = false; + $dockwatchWarning = ''; if (isDockwatchContainer($process)) { $isDockwatch = true; $dockwatchWarning = ' '; } $skipActions = skipContainerActions($process['inspect'][0]['Config']['Image'], $skipContainerActions); - $containerSettings = $settingsFile['containers'][$nameHash]; + $containerSettings = apiRequest('database-getContainerFromHash', ['hash' => $nameHash])['result']; $logo = getIcon($process['inspect']); $notificationIcon = ' '; if ($process['State'] == 'running') { - $control = '
'; - $control .= ''; + $control = '
'; + $control .= ''; } else { - $control = ''; + $control = ''; } $cpuUsage = floatval(str_replace('%', '', $process['stats']['CPUPerc'])); - if (intval($settingsFile['global']['cpuAmount']) > 0) { - $cpuUsage = number_format(($cpuUsage / intval($settingsFile['global']['cpuAmount'])), 2) . '%'; + if (intval($settingsTable['cpuAmount']) > 0) { + $cpuUsage = number_format(($cpuUsage / intval($settingsTable['cpuAmount'])), 2) . '%'; } $pullData = $pullsFile[$nameHash]; $updateStatus = 'Unchecked'; if ($pullData) { - $updateStatus = ($pullData['regctlDigest'] == $pullData['imageDigest']) ? 'Up to date' : 'Outdated'; + $updateStatus = $pullData['regctlDigest'] == $pullData['imageDigest'] ? 'Up to date' : 'Outdated'; } - $restartUnhealthy = $settingsFile['containers'][$nameHash]['restartUnhealthy']; + $restartUnhealthy = $containerSettings['restartUnhealthy']; $healthyRestartClass = 'text-success'; $healthyRestartText = 'Auto restart when unhealthy'; @@ -71,8 +58,10 @@ function renderContainerRow($nameHash, $return) $healthyRestartClass = 'text-warning'; $healthyRestartText = 'Not set to auto restart when unhealthy'; } + $usesHealth = false; - $health = 'Not setup'; + $health = 'Not setup'; + if (str_contains($process['Status'], 'healthy')) { $usesHealth = true; $health = 'Healthy '; @@ -89,6 +78,7 @@ function renderContainerRow($nameHash, $return) } else { list($time, $healthMsg) = explode('(', $process['Status']); } + $time = str_replace('ago', '', $time); $parts = explode(' ', $time); @@ -101,8 +91,7 @@ function renderContainerRow($nameHash, $return) } $length = implode(' ', $length); - $mountList = ''; - $previewMount = ''; + $mountList = $previewMount = ''; if ($process['inspect'][0]['Mounts']) { $mounts = []; @@ -128,8 +117,7 @@ function renderContainerRow($nameHash, $return) } } - $portList = ''; - $previewPort = ''; + $portList = $previewPort = ''; if ($process['inspect'][0]['HostConfig']['PortBindings']) { $ports = []; @@ -159,17 +147,16 @@ function renderContainerRow($nameHash, $return) } } - $envList = ''; - $previewEnv = ''; + $envList = $previewEnv = ''; if ($process['inspect'][0]['Config']['Env']) { $env = []; foreach ($process['inspect'][0]['Config']['Env'] as $envVar) { - $envLabel = explode('=', $envVar)[0] . ' → ' . explode('=', $envVar)[1]; - $env[] = $envLabel; + $envLabel = explode('=', $envVar)[0] . ' → ' . explode('=', $envVar)[1]; + $env[] = $envLabel; if (!$previewEnv) { - $previewEnv .= truncateEnd($envLabel, 30); + $previewEnv = strlen(explode('=', $envVar)[0] . ' → ') > 30 ? explode('=', $envVar)[0] . '...' : truncateEnd($envLabel, 30); } } @@ -183,21 +170,19 @@ function renderContainerRow($nameHash, $return) } if ($return == 'json') { - $return = [ - 'control' => $control, - 'update' => $updateStatus . '
' . truncateMiddle(str_replace('sha256:', '', $pullData['imageDigest']), 15) . '', - 'state' => $process['State'], - 'mounts' => $mountList, - 'ports' => $portList, - 'env' => $envList, - 'length' => $length, - 'cpu' => $cpuUsage, - 'cpuTitle' => $process['stats']['CPUPerc'], - 'mem' => $process['stats']['MemPerc'], - 'health' => $health - ]; - - return $return; + return [ + 'control' => $control, + 'update' => $updateStatus . '
' . truncateMiddle(str_replace('sha256:', '', $pullData['imageDigest']), 15) . '', + 'state' => $process['State'], + 'mounts' => $mountList, + 'ports' => $portList, + 'env' => $envList, + 'length' => $length, + 'cpu' => $cpuUsage, + 'cpuTitle' => $process['stats']['CPUPerc'], + 'mem' => $process['stats']['MemPerc'], + 'health' => $health + ]; } else { $version = ''; foreach ($process['inspect'][0]['Config']['Labels'] as $label => $val) { @@ -252,43 +237,47 @@ function renderContainerRow($nameHash, $return) } ?> - > - - + > + + + + + + + + - - - + + + + + + + $containerSettings) { if ($containerSettings['blacklist']) { - $containerState = $docker->findContainer(['hash' => $containerHash, 'data' => $stateFileData]); + $containerState = $docker->findContainer(['hash' => $containerHash, 'data' => $stateFile]); if (str_contains($containerState['Image'], $container)) { return SKIP_OPTIONAL; @@ -350,7 +343,7 @@ function skipContainerActions($container, $containers) } } - if ($settingsFileData['global']['overrideBlacklist'] == 0) { + if ($settingsTable['overrideBlacklist'] == 0) { foreach ($containers as $skip) { if (str_contains($container, $skip)) { return SKIP_FORCE; @@ -359,4 +352,4 @@ function skipContainerActions($container, $containers) } return SKIP_OFF; -} \ No newline at end of file +} diff --git a/root/app/www/public/functions/curl.php b/root/app/www/public/functions/curl.php index fbc325f..f8d85ce 100644 --- a/root/app/www/public/functions/curl.php +++ b/root/app/www/public/functions/curl.php @@ -9,9 +9,7 @@ function curl($url, $headers = [], $method = 'GET', $payload = '', $userPass = [], $timeout = 60) { - if (!is_string($payload)) { - $payload = ''; - } + $payload = !is_string($payload) ? '' : $payload; $curlHeaders = [ 'user-agent:' . APP_NAME, @@ -63,40 +61,18 @@ function curl($url, $headers = [], $method = 'GET', $payload = '', $userPass = [ curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); } - $responseHeaders = []; - curl_setopt( - $ch, - CURLOPT_HEADERFUNCTION, - function ($curl, $header) use (&$responseHeaders) { - $len = strlen($header); - $header = explode(':', $header, 2); - - if (count($header) < 2) { - return $len; - } - - $responseHeaders[strtolower(trim($header[0]))][] = trim($header[1]); - - return $len; - } - ); - $response = curl_exec($ch); $jsonResponse = json_decode($response, true); $response = !empty($jsonResponse) ? $jsonResponse : $response; $error = json_decode(curl_error($ch), true); - $curlGetInfo = curl_getinfo($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); return [ - 'url' => $url, - 'method' => $method, - 'payload' => $payload, - 'headers' => $curlHeaders, - 'response' => $response, - 'responseHeaders' => $responseHeaders, - 'error' => $error, - 'code' => $code, - 'curlGetInfo' => $curlGetInfo + 'url' => $url, + 'method' => $method, + 'payload' => $payload, + 'response' => $response, + 'error' => $error, + 'code' => $code ]; } diff --git a/root/app/www/public/functions/docker.php b/root/app/www/public/functions/docker.php index 0572d11..59e61f6 100644 --- a/root/app/www/public/functions/docker.php +++ b/root/app/www/public/functions/docker.php @@ -28,8 +28,8 @@ function getExpandedProcessList($fetchProc, $fetchStats, $fetchInspect, $mainten $processList = json_decode($processList, true); logger(MAINTENANCE_LOG, 'dockerProcessList <-'); } else { - $processList = apiRequest('dockerProcessList', ['format' => true]); - $processList = json_decode($processList['response']['docker'], true); + $processList = apiRequest('docker-processList', ['format' => true])['result']; + $processList = json_decode($processList, true); } $loadTimes[] = trackTime('dockerProcessList <-'); @@ -41,8 +41,8 @@ function getExpandedProcessList($fetchProc, $fetchStats, $fetchInspect, $mainten $imageSizes = json_decode($imageSizes, true); logger(MAINTENANCE_LOG, 'dockerImageSizes <-'); } else { - $imageSizes = apiRequest('dockerImageSizes'); - $imageSizes = json_decode($imageSizes['response']['docker'], true); + $imageSizes = apiRequest('docker-imageSizes')['result']; + $imageSizes = json_decode($imageSizes, true); } $loadTimes[] = trackTime('dockerImageSizes <-'); } @@ -56,8 +56,7 @@ function getExpandedProcessList($fetchProc, $fetchStats, $fetchInspect, $mainten logger(MAINTENANCE_LOG, json_encode($dockerStats, JSON_UNESCAPED_SLASHES)); logger(MAINTENANCE_LOG, 'dockerStats <-'); } else { - $dockerStats = apiRequest('stats'); - $dockerStats = $dockerStats['response']['state']; + $dockerStats = apiRequest('file-stats')['result']; } if (!$dockerStats) { //-- NOT WRITTEN YET @@ -66,8 +65,8 @@ function getExpandedProcessList($fetchProc, $fetchStats, $fetchInspect, $mainten logger(MAINTENANCE_LOG, $dockerStats); $dockerStats = json_decode($dockerStats, true); } else { - $dockerStats = apiRequest('dockerStats'); - $dockerStats = json_decode($dockerStats['response']['docker'], true); + $dockerStats = apiRequest('docker-stats', $_GET)['result']; + $dockerStats = json_decode($dockerStats, true); } } @@ -91,8 +90,8 @@ function getExpandedProcessList($fetchProc, $fetchStats, $fetchInspect, $mainten logger(MAINTENANCE_LOG, '$docker->inspect ' . json_encode(json_decode($inspect))); logger(MAINTENANCE_LOG, 'dockerInspect <-'); } else { - $inspect = apiRequest('dockerInspect', ['name' => implode(' ', $inspectContainers), 'format' => true]); - $inspectResults = json_decode($inspect['response']['docker'], true); + $inspect = apiRequest('docker-inspect', ['name' => implode(' ', $inspectContainers), 'format' => true])['result']; + $inspectResults = json_decode($inspect, true); } } @@ -132,21 +131,21 @@ function updateDependencyParentId($container, $id) global $dependencyFile; $dependencyFile[$container]['id'] = $id; - setServerFile('dependency', json_encode($dependencyFile)); + apiRequest('file-dependency', [], ['contents' => $dependencyFile]); } function dockerState() { - $processList = apiRequest('dockerProcessList', ['format' => true, 'useCache' => false]); - $processList = json_decode($processList['response']['docker'], true); + $processList = apiRequest('docker-processList', ['format' => true, 'useCache' => false])['result']; + $processList = json_decode($processList, true); - $dockerStats = apiRequest('dockerStats', ['useCache' => false]); - $dockerStats = json_decode($dockerStats['response']['docker'], true); + $dockerStats = apiRequest('docker-stats', ['useCache' => false])['result']; + $dockerStats = json_decode($dockerStats, true); if (!empty($processList)) { foreach ($processList as $index => $process) { - $inspect = apiRequest('dockerInspect', ['name' => $process['Names'], 'useCache' => false]); - $processList[$index]['inspect'] = json_decode($inspect['response']['docker'], true); + $inspect = apiRequest('docker-inspect', ['name' => $process['Names'], 'useCache' => false])['result']; + $processList[$index]['inspect'] = json_decode($inspect, true); foreach ($dockerStats as $dockerStat) { if ($dockerStat['Name'] == $process['Names']) { @@ -163,10 +162,11 @@ function dockerState() function dockerPermissionCheck() { logger(UI_LOG, 'dockerPermissionCheck ->'); - $response = apiRequest('dockerProcessList', ['format' => true]); - logger(UI_LOG, '$response: ' . json_encode($response)); + $apiRequest = apiRequest('docker-processList', ['format' => true]); + logger(UI_LOG, '$apiRequest: ' . json_encode($apiRequest)); logger(UI_LOG, 'dockerPermissionCheck <-'); - return empty(json_decode($response['response']['docker'], true)) ? false : true; + + return empty(json_decode($apiRequest['result'], true)) ? false : true; } function dockerCreateContainer($inspect) diff --git a/root/app/www/public/functions/dockerAPI.php b/root/app/www/public/functions/dockerAPI.php index 8eab054..397ada6 100644 --- a/root/app/www/public/functions/dockerAPI.php +++ b/root/app/www/public/functions/dockerAPI.php @@ -196,7 +196,7 @@ function dockerContainerCreateAPI($inspect = []) $payload['ExposedPorts'] = null; //-- MAKE SURE THE ID IS UPDATED - $dependencyFile = getServerFile('dependency'); + $dependencyFile = apiRequest('file-dependency')['result']; $dependencyFile = is_array($dependencyFile['file']) && !empty($dependencyFile['file']) ? $dependencyFile['file'] : []; foreach ($dependencyFile as $parent => $parentSettings) { diff --git a/root/app/www/public/functions/files.php b/root/app/www/public/functions/files.php index 2ffc9f4..95fc301 100644 --- a/root/app/www/public/functions/files.php +++ b/root/app/www/public/functions/files.php @@ -19,35 +19,6 @@ function loadJS() closedir($dir); } -function getServerFile($file) -{ - //-- NO NEED FOR AN API REQUEST LOCALLY - if (!$_SESSION['serverIndex']) { - $localFile = constant(strtoupper($file) . '_FILE'); - return ['code' => 200, 'file' => getFile($localFile)]; - } - - $apiResponse = apiRequest($file); - $code = $apiResponse['code']; - $file = $apiResponse['response'][$file]; - - return ['code' => $code, 'file' => $file]; -} - -function setServerFile($file, $contents) -{ - //-- NO NEED FOR AN API REQUEST LOCALLY - if (!$_SESSION['serverIndex']) { - $localFile = constant(strtoupper($file) . '_FILE'); - setFile($localFile, $contents); - return ['code' => 200]; - } - - $apiResponse = apiRequest($file, [], ['contents' => $contents]); - - return $apiResponse; -} - function getFile($file) { logger(SYSTEM_LOG, 'getFile() ' . $file); diff --git a/root/app/www/public/functions/helpers/array.php b/root/app/www/public/functions/helpers/array.php index 1d3f26a..4d084bb 100644 --- a/root/app/www/public/functions/helpers/array.php +++ b/root/app/www/public/functions/helpers/array.php @@ -7,6 +7,11 @@ ---------------------------------- */ +function makeArray($array) +{ + return is_array($array) ? $array : []; +} + function array_equals_any($haystack, $needles) { if (!is_array($haystack) || !is_array($needles)) { diff --git a/root/app/www/public/functions/helpers/strings.php b/root/app/www/public/functions/helpers/strings.php index 6ca31b8..2a187b8 100644 --- a/root/app/www/public/functions/helpers/strings.php +++ b/root/app/www/public/functions/helpers/strings.php @@ -7,7 +7,7 @@ ---------------------------------- */ -function str_equals_any($haystack, array $needles): bool +function str_equals_any(string|null $haystack, array $needles): bool { if (!$haystack) { return false; @@ -16,13 +16,21 @@ function str_equals_any($haystack, array $needles): bool return in_array($haystack, $needles); } -function str_contains_any(string $haystack, array $needles): bool +function str_contains_any(string|null $haystack, array $needles): bool { + if (!$haystack) { + return false; + } + return array_reduce($needles, fn($a, $n) => $a || str_contains($haystack, $n), false); } -function str_contains_all(string $haystack, array $needles): bool +function str_contains_all(string|null $haystack, array $needles): bool { + if (!$haystack) { + return false; + } + return array_reduce($needles, fn($a, $n) => $a && str_contains($haystack, $n), true); } diff --git a/root/app/www/public/functions/notifications.php b/root/app/www/public/functions/notifications.php deleted file mode 100644 index d2dccbe..0000000 --- a/root/app/www/public/functions/notifications.php +++ /dev/null @@ -1,47 +0,0 @@ -'; - case 'text': - return ''; - } -} - -function sendTestNotification($platform) -{ - global $notifications, $settingsFile; - - //-- INITIALIZE THE NOTIFY CLASS - if (!$notifications) { - $notifications = new Notifications(); - logger(SYSTEM_LOG, 'Init class: Notifications()'); - } - - $return = ''; - $payload = [ - 'event' => 'test', - 'title' => 'DockWatch Test', - 'message' => 'This is a test message sent from DockWatch' - ]; - - $result = $notifications->notify($platform, $payload); - - if ($result['code'] != 200) { - $return = 'Code ' . $result['code'] . ', ' . $result['error']; - } - - return $return; -} \ No newline at end of file diff --git a/root/app/www/public/includes/constants.php b/root/app/www/public/includes/constants.php index 1c692e4..e236686 100644 --- a/root/app/www/public/includes/constants.php +++ b/root/app/www/public/includes/constants.php @@ -7,13 +7,31 @@ ---------------------------------- */ -define('APP_NAME', 'DockWatch'); +define('APP_NAME', 'Dockwatch'); define('APP_IMAGE', 'ghcr.io/notifiarr/dockwatch:main'); +define('APP_PORT', 9999); +define('APP_SERVER_ID', 1); +define('APP_SERVER_URL', 'http://localhost'); define('APP_MAINTENANCE_IMAGE', 'ghcr.io/notifiarr/dockwatch:develop'); +define('APP_MAINTENANCE_PORT', 9998); +define('APP_BACKUPS', 7); //-- DAYS define('ICON_REPO', 'Notifiarr/images'); define('ICON_URL', 'https://gh.notifiarr.com/images/icons/'); +//-- REMOTES +define('DEFAULT_REMOTE_SERVER_TIMEOUT', 20); + +//-- DATABASE +define('SETTINGS_TABLE', 'settings'); +define('SERVERS_TABLE', 'servers'); +define('CONTAINER_SETTINGS_TABLE', 'container_settings'); +define('CONTAINER_GROUPS_TABLE', 'container_groups'); +define('CONTAINER_GROUPS_LINK_TABLE', 'container_group_link'); +define('NOTIFICATION_PLATFORM_TABLE', 'notification_platform'); +define('NOTIFICATION_TRIGGER_TABLE', 'notification_trigger'); +define('NOTIFICATION_LINK_TABLE', 'notification_link'); + //-- CRON FREQUENCY define('DEFAULT_CRON', '0 0 * * *'); @@ -23,6 +41,8 @@ define('LOGS_PATH', APP_DATA_PATH . 'logs/'); define('TMP_PATH', APP_DATA_PATH . 'tmp/'); define('COMPOSE_PATH', APP_DATA_PATH . 'compose/'); +define('DATABASE_PATH', APP_DATA_PATH . 'database/'); +define('MIGRATIONS_PATH', ABSOLUTE_PATH . 'migrations/'); //-- DATA FILES define('SERVERS_FILE', APP_DATA_PATH . 'servers.json'); @@ -81,4 +101,4 @@ 'dockwatch', //-- IF THIS GOES DOWN, IT WILL STOP THE CONTAINER WHICH MEANS IT CAN NEVER FINISH 'cloudflared', //-- IF THIS GOES DOWN, IT WILL KILL THE NETWORK TRAFFIC TO DOCKWATCH 'swag' //-- IS THIS GOES DOWN, IT WILL KILL THE WEB SERVICE TO DOCKWATCH - ]; \ No newline at end of file + ]; diff --git a/root/app/www/public/includes/footer.php b/root/app/www/public/includes/footer.php index e1c7865..ae762bb 100644 --- a/root/app/www/public/includes/footer.php +++ b/root/app/www/public/includes/footer.php @@ -179,7 +179,7 @@
Consider not running the dockwatch update time at the same time as other containers. When dockwatch starts its update process, everything after it will be ignored since it is stopping its self!
- + Remote control of self restarts and updates is not supported. Wait 30-45 seconds after using these buttons and refresh the page so the process outlined above can complete. If you have notifications enabled you will see the maintenance container start and shortly after the dockwatch container start.

diff --git a/root/app/www/public/includes/header.php b/root/app/www/public/includes/header.php index ac51e46..bd89f53 100644 --- a/root/app/www/public/includes/header.php +++ b/root/app/www/public/includes/header.php @@ -9,38 +9,14 @@ $_SESSION['IN_DOCKWATCH'] = true; -$fetchServers = false; -if (!$_SESSION['serverList'] || ($_SESSION['serverListUpdated'] + 300) < time()) { - $fetchServers = true; -} - -if ($fetchServers) { - $serverList = ''; - $serverList .= ' '; - - $_SESSION['serverList'] = $serverList; - $_SESSION['serverListUpdated'] = time(); -} else { - $serverList = $_SESSION['serverList']; -} - +$serverList = (!$_SESSION['serverList'] || ($_SESSION['serverListUpdated'] + 300) < time()) ? getRemoteServerSelect() : $_SESSION['serverList']; ?> - Dockwatch<?= ($settingsFile['global']['serverName'] ? ' - ' . $settingsFile['global']['serverName'] : '') ?> + <?= APP_NAME ?><?= $settingsTable['serverName'] ? ' - ' . $settingsTable['serverName'] : '' ?> @@ -67,8 +43,8 @@ @@ -86,20 +62,20 @@ diff --git a/root/app/www/public/index.php b/root/app/www/public/index.php index e4f8cd8..2326622 100644 --- a/root/app/www/public/index.php +++ b/root/app/www/public/index.php @@ -13,21 +13,21 @@ if ($_SESSION['dockerPerms']) { $dockerPerms = $_SESSION['dockerPerms']; } else { - $dockerPerms = apiRequest('dockerPermissionCheck'); - $dockerPerms = json_decode($dockerPerms['response']['docker'], true); + $dockerPerms = apiRequest('docker-permissionCheck')['result']; + $dockerPerms = json_decode($dockerPerms, true); $_SESSION['dockerPerms'] = $dockerPerms; } $loadError = ''; -if (!$serversFile) { - $loadError = 'Servers file missing or corrupt'; +if (!$serversTable) { + $loadError = 'Servers table is empty, this means the migration 001 did not run or a database could not be created.'; } if (!file_exists(REGCTL_PATH . REGCTL_BINARY)) { $loadError = 'The required regctl binary is missing from \'' . REGCTL_PATH . REGCTL_BINARY . '\''; } -if (!$dockerCommunicateAPI) { +if (!$isDockerApiAvailable) { $loadError = 'There is a problem talking to the docker API. You either did not mount /var/run/docker.sock or you are passing in a DOCKER_HOST that is not valid. Try using the IP instead of container name for the docker host variable.'; if ($_SERVER['DOCKER_HOST']) { @@ -52,7 +52,7 @@ -
+
If you are seeing this, it means the user:group running this container does not have permission to run docker commands. Please fix that, restart the container and try again.

An example for Ubuntu: @@ -77,4 +77,4 @@
init variable has been set to false, you can load a page without waiting now'); +} +// ------------------------------------------------------------------------------------------- function initPage(page) { if (init) { - toast('Loading', 'A previous page load is still finishing, try again in a second', 'info'); + toast('Loading', 'A previous page load is still finishing, try again in a second', 'info'); return; } @@ -223,17 +229,15 @@ function loadingStop() } // ------------------------------------------------------------------------------------------- -function updateServerIndex() +function updateActiveServer() { - $('#external-server-icon').hide(); - if ($('#activeServer').val() != '0') { - $('#external-server-icon').show(); - } + $('[id^=external-server-icon-link-]').hide(); + $('#external-server-icon-link-' + $('#activeServerId').val()).show(); $.ajax({ type: 'POST', url: '../ajax/settings.php', - data: '&m=updateServerIndex&index=' + $('#activeServer').val(), + data: '&m=updateActiveServer&id=' + $('#activeServerId').val(), success: function (resultData) { initPage(currentPage); } @@ -256,6 +260,10 @@ function dialogOpen(p) return; } + if ($('#' + id).length) { + $('#' + id).remove(); + } + //-- CLONE IT $('#dialog-modal').clone().appendTo('#dialog-modal-container').prop('id', id); @@ -341,23 +349,16 @@ function dialogOpen(p) function dialogClose(elm) { if (!elm) { - console.log('Error: Called dialogClose on no elm'); + console.error('Error: Called dialogClose on no elm'); return; } - let id = elm; - if (typeof elm === 'object') { - id = $('#dialog-modal-container').find('.modal').find(elm).closest('.modal').attr('id'); - } - - if (!$('#' + id).length) { + if (!$('#' + elm).length) { + console.error('Error: Could not locate dialog with id \'' + elm + '\''); return; } - const modal = bootstrap.Modal.getInstance($('#' + id)) - modal.hide(); - $('#' + id).click(); - $('#' + id).remove(); + $('#' + elm).modal('hide'); } // ------------------------------------------------------------------------------------------- @@ -413,3 +414,15 @@ function pageLoadingStop() } // ------------------------------------------------------------------------------------------- +function resetSession() +{ + $.ajax({ + type: 'POST', + url: '../ajax/login.php', + data: '&m=resetSession', + success: function (resultData) { + reload(); + } + }); +} +// ------------------------------------------------------------------------------------------- diff --git a/root/app/www/public/js/containers.js b/root/app/www/public/js/containers.js index 248c726..a0eaa0e 100644 --- a/root/app/www/public/js/containers.js +++ b/root/app/www/public/js/containers.js @@ -331,7 +331,7 @@ function openContainerGroups() function loadContainerGroup() { $('#deleteGroupContainer').hide(); - if ($('#groupSelection').val() != 1) { + if ($('#groupSelection').val() != 0) { $('#deleteGroupContainer').show(); $('#groupName').val($('#groupSelection option:selected').text()); } else { @@ -341,7 +341,7 @@ function loadContainerGroup() $.ajax({ type: 'POST', url: '../ajax/containers.php', - data: '&m=loadContainerGroup&groupHash=' + $('#groupSelection').val(), + data: '&m=loadContainerGroup&groupId=' + $('#groupSelection').val(), success: function (resultData) { $('#containerGroupRows').html(resultData); } @@ -359,9 +359,10 @@ function saveContainerGroup() let groupItemCount = 0; let params = ''; $.each($('[id^=groupContainer-]'), function() { + params += '&' + $(this).attr('id') + '=' + ($(this).prop('checked') ? 1 : 0); + if ($(this).prop('checked')) { groupItemCount++; - params += '&' + $(this).attr('id') + '=1'; } }); @@ -374,7 +375,7 @@ function saveContainerGroup() $.ajax({ type: 'POST', url: '../ajax/containers.php', - data: '&m=saveContainerGroup&selection=' + $('#groupSelection').val() + '&name=' + $('#groupName').val() + '&delete=' + ($('#groupDelete').prop('checked') ? 1 : 0) + params, + data: '&m=saveContainerGroup&groupId=' + $('#groupSelection').val() + '&name=' + $('#groupName').val() + '&delete=' + ($('#groupDelete').prop('checked') ? 1 : 0) + params, success: function (resultData) { loadingStop(); @@ -393,6 +394,8 @@ function saveContainerGroup() // --------------------------------------------------------------------------------------------- function openUpdateOptions() { + $('#updateOptions-containers').html('Fetching container update settings...'); + $('#updateOptions-modal').modal({ keyboard: false, backdrop: 'static' @@ -401,12 +404,14 @@ function openUpdateOptions() $('#updateOptions-modal').hide(); $('#updateOptions-modal').modal('show'); + loadingStart(); $.ajax({ type: 'POST', url: '../ajax/containers.php', data: '&m=updateOptions', success: function (resultData) { $('#updateOptions-containers').html(resultData); + loadingStop(); } }); } @@ -494,6 +499,7 @@ function hideContainerPorts(containerHash) // --------------------------------------------------------------------------------------------- function containerLogs(container) { + pageLoadingStart(); $.ajax({ type: 'POST', url: '../ajax/containers.php', @@ -505,7 +511,7 @@ function containerLogs(container) size: 'xl', body: resultData, onOpen: function () { - + pageLoadingStop(); } }); } diff --git a/root/app/www/public/js/notification.js b/root/app/www/public/js/notification.js index 278c148..097ee82 100644 --- a/root/app/www/public/js/notification.js +++ b/root/app/www/public/js/notification.js @@ -1,8 +1,19 @@ -function saveNotificationSettings() +function saveNotification(platformId, linkId) { let requiredError = false; let params = ''; - $.each($('[id^=notifications-]'), function () { + $.each($('[id^=notificationTrigger-]'), function () { + let val = ''; + if ($(this).is(':checkbox') || $(this).is(':radio')) { + val = $(this).prop('checked') ? 1 : 0; + } else { + val = $(this).val(); + } + + params += '&' + $(this).attr('id') + '=' + val; + }); + + $.each($('[id^=notificationPlatformParameter-]'), function () { const required = $(this).attr('data-required'); let val = ''; if ($(this).is(':checkbox') || $(this).is(':radio')) { @@ -24,32 +35,127 @@ function saveNotificationSettings() } loadingStart(); - $.ajax({ type: 'POST', url: '../ajax/notification.php', - data: '&m=saveNotificationSettings' + params, + data: '&m=saveNotification&platformId=' + platformId + '&linkId=' + linkId + params, dataType: 'json', success: function (resultData) { + loadingStop(); if (resultData.error) { toast('Notifications', resultData.error, 'error'); - } else { - toast('Notifications', 'Notification settings saved on server ' + resultData.server, 'success'); + return; } + + dialogClose('openNotificationTriggers'); + toast('Notifications', 'Notification changes have been saved', 'success'); + initPage('notification'); + } + }); + +} +// --------------------------------------------------------------------------------------------- +function addNotification(platformId) +{ + let requiredError = false; + let params = ''; + $.each($('[id^=notificationTrigger-]'), function () { + let val = ''; + if ($(this).is(':checkbox') || $(this).is(':radio')) { + val = $(this).prop('checked') ? 1 : 0; + } else { + val = $(this).val(); + } + + params += '&' + $(this).attr('id') + '=' + val; + }); + + $.each($('[id^=notificationPlatformParameter-]'), function () { + const required = $(this).attr('data-required'); + let val = ''; + if ($(this).is(':checkbox') || $(this).is(':radio')) { + val = $(this).prop('checked') ? 1 : 0; + } else { + val = $(this).val(); + } + + if (required && val == '') { + requiredError = true; + } + + params += '&' + $(this).attr('id') + '=' + val; + }); + + if (requiredError) { + toast('Notifications', 'Required fields can not be empty', 'error'); + return; + } + + loadingStart(); + $.ajax({ + type: 'POST', + url: '../ajax/notification.php', + data: '&m=addNotification&platformId=' + platformId + params, + dataType: 'json', + success: function (resultData) { loadingStop(); + if (resultData.error) { + toast('Notifications', resultData.error, 'error'); + return; + } + + dialogClose('openNotificationTriggers'); + toast('Notifications', 'Notification has been added', 'success'); + initPage('notification'); } }); } // --------------------------------------------------------------------------------------------- -function testNotify(platform) +function deleteNotification(linkId) +{ + if (confirm('Are you sure you want to delete this notification?')) { + loadingStart(); + $.ajax({ + type: 'POST', + url: '../ajax/notification.php', + data: '&m=deleteNotification&linkId=' + linkId, + success: function (resultData) { + loadingStop(); + + dialogClose('openNotificationTriggers'); + toast('Notifications', 'Notification changes have been saved', 'success'); + initPage('notification'); + } + }); + } +} +// --------------------------------------------------------------------------------------------- +function openNotificationTriggers(platformId, linkId = 0) +{ + $.ajax({ + type: 'POST', + url: '../ajax/notification.php', + data: '&m=openNotificationTriggers&platformId=' + platformId + '&linkId=' + linkId, + success: function (resultData) { + dialogOpen({ + id: 'openNotificationTriggers', + title: 'Notification triggers - ' + (linkId ? 'Edit' : 'Add'), + size: 'lg', + body: resultData + }); + } + }); +} +// --------------------------------------------------------------------------------------------- +function testNotify(linkId) { loadingStart(); $.ajax({ type: 'POST', url: '../ajax/notification.php', - data: '&m=testNotify&platform=' + platform, + data: '&m=testNotify&linkId=' + linkId, dataType: 'json', success: function (resultData) { if (resultData.error) { diff --git a/root/app/www/public/js/settings.js b/root/app/www/public/js/settings.js index fcd0b4f..e0bd9b0 100644 --- a/root/app/www/public/js/settings.js +++ b/root/app/www/public/js/settings.js @@ -25,7 +25,7 @@ function saveGlobalSettings() } else { toast('Settings', 'Global settings saved on server ' + resultData.server, 'success'); initPage('settings'); - $('#activeServer').html(resultData.serverList); + $('#activeInstanceContainer').html(resultData.serverList); } loadingStop(); diff --git a/root/app/www/public/loader.php b/root/app/www/public/loader.php index c56ae76..16443d6 100644 --- a/root/app/www/public/loader.php +++ b/root/app/www/public/loader.php @@ -31,7 +31,6 @@ } } -$apiError = ''; $loadTimes = []; $start = microtime(true); @@ -59,63 +58,52 @@ session_start(); } -if (!IS_SSE) { - //-- INITIALIZE THE DOCKER CLASS - $docker = new Docker(); +define('REMOTE_SERVER_TIMEOUT', $settingsTable['remoteServerTimeout'] ?: DEFAULT_REMOTE_SERVER_TIMEOUT); +if (!IS_SSE) { //-- RUN REQUIRED CHECKS automation(); - //-- CHECK IF DOCKER IS AVAILABLE - $dockerCommunicateAPI = $docker->apiIsAvailable(); + logger(SYSTEM_LOG, 'Init class: Database()'); + $database = new Database(); + apiRequestLocal('database-migrations'); + $settingsTable = apiRequestLocal('database-getSettings'); + $serversTable = apiRequestLocal('database-getServers'); + + //-- SET ACTIVE INSTANCE + if (!$_SESSION['activeServerId'] || str_contains($_SERVER['PHP_SELF'], '/api/')) { + apiSetActiveServer(APP_SERVER_ID, $serversTable); + define('ACTIVE_SERVER_NAME', $serversTable[APP_SERVER_ID]['name']); + } else { + $activeServer = apiGetActiveServer(); + define('ACTIVE_SERVER_NAME', $serversTable[$activeServer['id']]['name']); + } //-- INITIALIZE THE SHELL CLASS + logger(SYSTEM_LOG, 'Init class: Shell()'); $shell = new Shell(); -} -//-- GET THE SERVERS LIST -$serversFile = getFile(SERVERS_FILE); -$_SESSION['serverIndex'] = is_numeric($_SESSION['serverIndex']) ? $_SESSION['serverIndex'] : 0; + //-- INITIALIZE THE DOCKER CLASS + logger(SYSTEM_LOG, 'Init class: Docker()'); + $docker = new Docker(); + + //-- CHECK IF DOCKER IS AVAILABLE + $isDockerApiAvailable = $docker->apiIsAvailable(); -define('ACTIVE_SERVER_NAME', $serversFile[$_SESSION['serverIndex']]['name']); -define('ACTIVE_SERVER_URL', rtrim($serversFile[$_SESSION['serverIndex']]['url'], '/')); -define('ACTIVE_SERVER_APIKEY', $serversFile[$_SESSION['serverIndex']]['apikey']); + //-- INITIALIZE THE NOTIFY CLASS + $notifications = new Notifications(); + logger(SYSTEM_LOG, 'Init class: Notifications()'); -if (!str_contains_any($_SERVER['PHP_SELF'], ['/api/']) && !str_contains($_SERVER['PWD'], 'oneshot')) { - if (!IS_SSE) { - //-- CHECK IF SELECTED SERVER CAN BE TALKED TO - $ping = apiRequest('ping'); - if (!is_array($ping) || $ping['code'] != 200) { - if ($_SESSION['serverIndex'] == 0) { - exit('The connection to this container in the servers file is broken'); - } else { - $_SESSION['serverIndex'] = 0; - header('Location: /'); - exit(); - } - } + if (!str_contains_any($_SERVER['PHP_SELF'], ['/api/']) && !str_contains($_SERVER['PWD'], 'oneshot')) { + $stateFile = apiRequestLocal('file-state'); + $pullsFile = apiRequestLocal('file-pull'); } +} - //-- SETTINGS - $settingsFile = getServerFile('settings'); - $apiError = $settingsFile['code'] != 200 ? $settingsFile['file'] : $apiError; - $settingsFile = $settingsFile['file']; - +if (!str_contains_any($_SERVER['PHP_SELF'], ['/api/']) && !str_contains($_SERVER['PWD'], 'oneshot')) { //-- LOGIN, DEFINE AFTER LOADING SETTINGS - define('LOGIN_FAILURE_LIMIT', ($settingsFile['global']['loginFailures'] ? $settingsFile['global']['loginFailures']: 10)); - define('LOGIN_FAILURE_TIMEOUT', ($settingsFile['global']['loginFailures'] ? $settingsFile['global']['loginTimeout']: 10)); //-- MINUTES TO DISABLE LOGINS - - if (!IS_SSE) { - //-- STATE - $stateFile = getServerFile('state'); - $apiError = $stateFile['code'] != 200 ? $stateFile['file'] : $apiError; - $stateFile = $stateFile['file']; - - //-- PULLS - $pullsFile = getServerFile('pull'); - $apiError = $pullsFile['code'] != 200 ? $pullsFile['file'] : $apiError; - $pullsFile = $pullsFile['file']; - } + define('LOGIN_FAILURE_LIMIT', ($settingsTable['loginFailures'] ? $settingsTable['loginFailures']: 10)); + define('LOGIN_FAILURE_TIMEOUT', ($settingsTable['loginFailures'] ? $settingsTable['loginTimeout']: 10)); //-- MINUTES TO DISABLE LOGINS if (file_exists(LOGIN_FILE) && !str_contains($_SERVER['PHP_SELF'], '/crons/')) { define('USE_AUTH', true); @@ -137,14 +125,20 @@ } } else { logger(SYSTEM_LOG, 'Starting'); - - //-- INITIALIZE THE NOTIFY CLASS - $notifications = new Notifications(); - logger(SYSTEM_LOG, 'Init class: Notifications()'); } } if (!IS_SSE) { + //-- FLIP TO REMOTE MANAGEMENT IF NEEDED + $activeServer = $activeServer ?: apiGetActiveServer(); + + if ($activeServer['id'] != APP_SERVER_ID) { + $settingsTable = apiRequest('database-getSettings')['result']; + $serversTable = apiRequest('database-getServers')['result']; + $stateFile = apiRequest('file-state')['result']; + $pullsFile = apiRequest('file-pull')['result']; + } + $fetchProc = in_array($_POST['page'], $getProc) || $_POST['hash'] || $_GET['request'] == 'dwStats'; $fetchStats = in_array($_POST['page'], $getStats) || $_POST['hash'] || $_GET['request'] == 'dwStats'; $fetchInspect = in_array($_POST['page'], $getInspect) || $_POST['hash'] || $_GET['request'] == 'dwStats'; @@ -159,7 +153,9 @@ //-- UPDATE THE STATE FILE WHEN EVERYTHING IS FETCHED if ($_POST['page'] == 'overview' || $_POST['page'] == 'containers' || $_GET['request'] == 'dwStats') { - setServerFile('state', json_encode($processList)); + if ($processList) { + apiRequest('file-state', [], ['contents' => $processList]); + } } //-- STATE diff --git a/root/app/www/public/migrations/001_initial_setup.php b/root/app/www/public/migrations/001_initial_setup.php new file mode 100644 index 0000000..70b93a8 --- /dev/null +++ b/root/app/www/public/migrations/001_initial_setup.php @@ -0,0 +1,274 @@ + down)', 'state'), + ('added', 'Added', 'Send a notification when a container is added', 'state'), + ('removed', 'Removed', 'Send a notification when a container is removed', 'state'), + ('prune', 'Prune', 'Send a notification when an image or volume is pruned', 'prune'), + ('cpuHigh', 'CPU usage', 'Send a notification when container CPU usage exceeds threshold (set in Settings)', 'usage'), + ('memHigh', 'Memory usage', 'Send a notification when container memory usage exceeds threshold (set in Settings)', 'usage'), + ('health', 'Health change', 'Send a notification when container becomes unhealthy', 'health')"; + +$q[] = "CREATE TABLE " . NOTIFICATION_LINK_TABLE . " ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + platform_id INTEGER NOT NULL, + platform_parameters TEXT NOT NULL, + trigger_ids TEXT NOT NULL + )"; + +$q[] = "CREATE TABLE " . SERVERS_TABLE . " ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + url TEXT NOT NULL, + apikey TEXT NOT NULL + )"; + +$globalSettings = [ + 'serverName' => '', + 'maintenanceIP' => '', + 'maintenancePort' => 9998, + 'loginFailures' => 6, + 'loginTimeout' => 60, + 'updates' => 2, + 'updatesFrequency' => '0 2 * * *', + 'autoPruneImages' => false, + 'autoPruneVolumes' => false, + 'autoPruneNetworks' => false, + 'autoPruneHour' => 12, + 'cpuThreshold' => '', + 'cpuAmount' => '', + 'memThreshold' => '', + 'sseEnabled' => false, + 'cronLogLength' => 1, + 'notificationLogLength' => 1, + 'uiLogLength' => 1, + 'apiLogLength' => 1, + 'environment' => 0, + 'overrideBlacklist' => false, + 'externalLoading' => 0, + 'taskStatsDisabled' => 0, + 'taskStateDisabled' => 0, + 'taskPullsDisabled' => 0, + 'taskHousekeepingDisabled' => 0, + 'taskHealthDisabled' => 0, + 'taskPruneDisabled' => 0 + ]; + +foreach ($globalSettings as $key => $val) { + $settingRows[] = "('" . $key . "', '" . $val . "')"; +} + +$q[] = "INSERT INTO " . SETTINGS_TABLE . " + (`name`, `value`) + VALUES " . implode(', ', $settingRows); + +//-- PRE-DB SUPPORT, POPULATE THE NEW TABLES WITH EXISTING DATA +if (file_exists(APP_DATA_PATH . 'servers.json')) { + $serversFile = getFile(SERVERS_FILE); + $serverRows = []; + + foreach ($serversFile as $server) { + $serverRows[] = "('" . $server['name'] . "', '" . $server['url'] . "', '" . $server['apikey'] . "')"; + } + + if ($serverRows) { + $q[] = "INSERT INTO " . SERVERS_TABLE . " + (`name`, `url`, `apikey`) + VALUES " . implode(', ', $serverRows); + } +} else { + $q[] = "INSERT INTO " . SERVERS_TABLE . " + (`name`, `url`, `apikey`) + VALUES + ('" . APP_NAME . "', '" . APP_SERVER_URL . "', '" . generateApikey() . "')"; +} + +if (file_exists(APP_DATA_PATH . 'settings.json')) { + $settingsFile = getFile(SETTINGS_FILE); + + if ($settingsFile['tasks']) { + foreach ($settingsFile['tasks'] as $task => $taskSettings) { + $q[] = "UPDATE " . SETTINGS_TABLE . " + SET value = '" . $taskSettings['disabled'] . "' + WHERE name = 'task" . ucfirst($task) . "Disabled'"; + } + } + + if ($settingsFile['global']) { + foreach ($settingsFile['global'] as $key => $val) { + $q[] = "UPDATE " . SETTINGS_TABLE . " + SET value = '" . $val . "' + WHERE name = '" . $key . "'"; + } + } + + if ($settingsFile['containers']) { + $containerSettingsRows = []; + + foreach ($settingsFile['containers'] as $hash => $settings) { + $containerSettingsRows[] = "('" . $hash . "', '" . intval($settings['updates']) . "', '" . $settings['frequency'] . "', '" . intval($settings['restartUnhealthy']) . "', '" . intval($settings['disableNotifications']) . "', '" . intval($settings['shutdownDelay']) . "', '" . intval($settings['shutdownDelaySeconds']) . "')"; + } + + if ($containerSettingsRows) { + $q[] = "INSERT INTO " . CONTAINER_SETTINGS_TABLE . " + (`hash`, `updates`, `frequency`, `restartUnhealthy`, `disableNotifications`, `shutdownDelay`, `shutdownDelaySeconds`) + VALUES " . implode(', ', $containerSettingsRows); + } + } + + if ($settingsFile['containerGroups']) { + $containerGroupRows = []; + + foreach ($settingsFile['containerGroups'] as $groupHash => $groupData) { + $containerGroupRows[] = "('" . $groupHash . "', '" . $groupData['name'] . "')"; + } + + if ($containerGroupRows) { + $q[] = "INSERT INTO " . CONTAINER_GROUPS_TABLE . " + (`hash`, `name`) + VALUES " . implode(', ', $containerGroupRows); + } + } + + if ($settingsFile['notifications']) { + if ($settingsFile['notifications']['platforms'] && $settingsFile['notifications']['triggers']) { + $triggerIds = []; + if ($settingsFile['notifications']['triggers']['updated']['active']) { + $triggerIds[] = 1; + } + if ($settingsFile['notifications']['triggers']['updates']['active']) { + $triggerIds[] = 2; + } + if ($settingsFile['notifications']['triggers']['stateChange']['active']) { + $triggerIds[] = 3; + } + if ($settingsFile['notifications']['triggers']['added']['active']) { + $triggerIds[] = 4; + } + if ($settingsFile['notifications']['triggers']['removed']['active']) { + $triggerIds[] = 5; + } + if ($settingsFile['notifications']['triggers']['prune']['active']) { + $triggerIds[] = 6; + } + if ($settingsFile['notifications']['triggers']['cpuHigh']['active']) { + $triggerIds[] = 7; + } + if ($settingsFile['notifications']['triggers']['memHigh']['active']) { + $triggerIds[] = 8; + } + if ($settingsFile['notifications']['triggers']['health']['active']) { + $triggerIds[] = 9; + } + + $q[] = "INSERT INTO " . NOTIFICATION_LINK_TABLE . " + (`name`, `platform_id`, `platform_parameters`, `trigger_ids`) + VALUES + ('Notifiarr', '" . NotificationPlatforms::NOTIFIARR . "', '{\"apikey\":\"" . $settingsFile['notifications']['platforms'][NotificationPlatforms::NOTIFIARR]['apikey'] . "\"}', '[" . implode(',', $triggerIds) . "]')"; + } + } +} + +//-- ALWAYS NEED TO BUMP THE MIGRATION ID +$q[] = "INSERT INTO " . SETTINGS_TABLE . " + (`name`, `value`) + VALUES + ('migration', '001')"; + +foreach ($q as $query) { + $db->query($query); +} + +//-- PRE-DB SUPPORT, POPULATE THE NEW TABLES WITH EXISTING DATA +if ($settingsFile) { + $q = $containerLinkRows = []; + $containers = apiRequest('database-getContainers')['result']; + $containerGroups = apiRequest('database-getContainerGroups')['result']; + + if ($settingsFile['containerGroups']) { + foreach ($settingsFile['containerGroups'] as $groupHash => $groupData) { + if ($groupData['containers']) { + foreach ($groupData['containers'] as $groupContainerHash) { + $container = apiRequest('database-getContainerFromHash', ['hash' => $groupContainerHash])['result']; + $group = apiRequest('database-getContainerGroupFromHash', ['hash' => $groupHash])['result']; + + if ($group['id'] && $container['id']) { + $containerLinkRows[] = "('" . $group['id'] . "', '" . $container['id'] . "')"; + } + } + } + } + + if ($containerLinkRows) { + $q[] = "INSERT INTO " . CONTAINER_GROUPS_LINK_TABLE . " + (`group_id`, `container_id`) + VALUES " . implode(', ', $containerLinkRows); + } + } + + foreach ($q as $query) { + $db->query($query); + } +} diff --git a/root/app/www/public/migrations/002_remote_timeout.php b/root/app/www/public/migrations/002_remote_timeout.php new file mode 100644 index 0000000..e95d4e1 --- /dev/null +++ b/root/app/www/public/migrations/002_remote_timeout.php @@ -0,0 +1,22 @@ +query($query); +} diff --git a/root/app/www/public/sse.php b/root/app/www/public/sse.php index 4b414c6..67bbc1a 100644 --- a/root/app/www/public/sse.php +++ b/root/app/www/public/sse.php @@ -12,14 +12,13 @@ require 'loader.php'; -$sseFile = getServerFile('sse'); -$sseFile = $sseFile['code'] != 200 ? [] : $sseFile['file']; +$sseFile = makeArray(apiRequest('file-sse')['response']); -if ($settingsFile['global']['sseEnabled']) { +if ($settingsTable['sseEnabled']) { //-- DONT SEND ALL THE DATA EVERY TIME, WASTE if (!$sseFile['pushed']) { $sseFile['pushed'] = time(); - setServerFile('sse', $sseFile); + apiRequest('file-sse', [], ['contents' => $sseFile]); } else { $sseFile = []; } diff --git a/root/app/www/public/startup.php b/root/app/www/public/startup.php index 4effedf..7ee7ca8 100644 --- a/root/app/www/public/startup.php +++ b/root/app/www/public/startup.php @@ -14,14 +14,14 @@ echo 'require_once ' . ABSOLUTE_PATH . 'loader.php' . "\n"; require_once ABSOLUTE_PATH . 'loader.php'; -//-- SETTINGS -$settingsFile = getFile(SETTINGS_FILE); +//-- INITIALIZE THE NOTIFY CLASS +$database = $database ?? new Database(); //-- INITIALIZE THE NOTIFY CLASS -$notifications = new Notifications(); +$notifications = $notifications ?? new Notifications(); //-- INITIALIZE THE MAINTENANCE CLASS -$maintenance = new Maintenance(); +$maintenance = $maintenance ?? new Maintenance(); logger(STARTUP_LOG, 'Container init (Start/Restart) ->'); @@ -29,10 +29,14 @@ //-- STARTUP NOTIFICATION $notify['state']['changed'][] = ['container' => $name, 'previous' => '.....', 'current' => 'Started/Restarted']; -if ($settingsFile['notifications']['triggers']['stateChange']['platform']) { + +if (apiRequest('database-isNotificationTriggerEnabled', ['trigger' => 'stateChange'])['result']) { $payload = ['event' => 'state', 'changes' => $notify['state']['changed']]; - $notifications->notify($settingsFile['notifications']['triggers']['stateChange']['platform'], $payload); + $notifications->notify(0, 'stateChange', $payload); + logger(STARTUP_LOG, 'Sending ' . $name . ' started notification'); +} else { + logger(STARTUP_LOG, 'Skipping ' . $name . ' started notification, no senders found with stateChange enabled'); } //-- MAINTENANCE CHECK diff --git a/root/etc/php82/conf.d/dockwatch.ini b/root/etc/php82/conf.d/dockwatch.ini index 0299379..0024d7f 100644 --- a/root/etc/php82/conf.d/dockwatch.ini +++ b/root/etc/php82/conf.d/dockwatch.ini @@ -8,4 +8,4 @@ max_input_vars = 10000 error_log = '/config/log/php/errors.log' error_reporting = -1 log_errors = 1 -session.gc_maxlifetime = 86400 +session.gc_maxlifetime = 86400 \ No newline at end of file
Orphan images - > + > Automatically try to prune all orphan images daily
Orphan volumes - > + > Automatically try to prune all orphan volumes daily
Orphan networks - > + > Automatically try to prune all orphan networks daily
CPU1 - + If a container usage is above this number, send a notification (if notification is enabled)
CPUs - + Detected count:
Memory1 - + If a container usage is above this number, send a notification (if notification is enabled)
Enabled2,3 - > + > SSE will update the container list UI every minute with current status of Updates, State, Health, CPU and Memory
Crons - + How long to store cron run log files (min 1 day)
Notifications - + How long to store logs generated when notifications are sent (min 1 day)
UI - + How long to store logs generated from using the UI (min 1 day)
API - + How long to store logs generated when api requests are made (min 1 day)
Environment Location of webroot, requires a container restart after changing. Do not change this without working files externally!
Override BlacklistOverride blacklist - > + > Generally not recommended, it's at your own risk.
Page Loading3Page loading3 Internal: On a full page refresh you will go back to the overview. (state lost)
External: On a full page refresh you will stay on this current page. (state saved in URL)
>> Stats changes 1m
>> SSE 1m
>> State changes 5m
>> Pulls 5m
>> Housekeeping 10m
>> Health 15m
>> Prune 24h