Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
166cf410fb | |||
0d1b22d2f9 | |||
58ec0cb655 |
16 changed files with 831 additions and 145 deletions
|
@ -2,7 +2,7 @@ FROM debian:stable-slim
|
|||
|
||||
MAINTAINER Knah Tsaeb <knah-tsaeb_soshot@knah-tsaeb.org>
|
||||
|
||||
LABEL version="0.2.1"
|
||||
LABEL version="0.3.0"
|
||||
LABEL description="Apache 2 / PHP / Nofu"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
@ -30,7 +30,7 @@ WORKDIR /var/www/
|
|||
ENTRYPOINT "start.sh"
|
||||
|
||||
# Build image
|
||||
# docker buildx build -t nofu:0.2.1 .
|
||||
# docker buildx build -t nofu:0.3.0 .
|
||||
# Run container
|
||||
# docker run -d --restart unless-stopped -v nofu_data:/var/www/data -e TZ=UTC -p 8189:80 --name nofu nofu:0.2.1
|
||||
# docker run -d --restart unless-stopped -v /opt/nofu:/var/www/data -e TZ=UTC -p 8189:80 --name nofu nofu:0.2.1
|
||||
# docker run -d --restart unless-stopped -v nofu_data:/var/www/data -e TZ=UTC -p 8189:80 --name nofu nofu:0.3.0
|
||||
# docker run -d --restart unless-stopped -v /opt/nofu:/var/www/data -e TZ=UTC -p 8189:80 --name nofu nofu:0.3.0
|
24
README.md
24
README.md
|
@ -1,6 +1,6 @@
|
|||
# Nofu
|
||||
|
||||
Nofu for **N**ot **O**nly **F**or **U**s is personal dashboard
|
||||
Nofu for **N**ot **O**nly **F**or **U**s is personal dashboard (V0.3.0)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
@ -67,13 +67,13 @@ wget https://forge.leslibres.org/Knah-Tsaeb/Nofu/raw/branch/main/Dockerfile
|
|||
```
|
||||
|
||||
```shell
|
||||
docker buildx build -t nofu:0.2.1 .
|
||||
docker buildx build -t nofu:0.3.0 .
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
```shell
|
||||
docker run -d --restart unless-stopped -v nofu_data:/var/www/data -e TZ=UTC -p 8189:80 --name nofu nofu:0.2.1
|
||||
docker run -d --restart unless-stopped -v nofu_data:/var/www/data -e TZ=UTC -p 8189:80 --name nofu nofu:0.3.0
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:8189
|
||||
|
@ -99,12 +99,13 @@ Put screenshot of your service.
|
|||
#### Favicons
|
||||
|
||||
Put favicon of your service. If you can prefer 128x128 favicon size (or higher).
|
||||
You can find many icon for your app in [Dashboard-Icons](https://github.com/walkxcode/Dashboard-Icons).
|
||||
|
||||
### Services file
|
||||
|
||||
The services file contain a list of your service. Is simple text file, you can edit it with simple text editor (notepad, Pluma, Kate, Vim, Nano....). Nofu use[YAML](https://en.wikipedia.org/wiki/YAML) markup.
|
||||
|
||||
Create new file "/data/services.yaml" and edit it or create it localy and upload after on your server.
|
||||
Create new file "/data/services.yaml" and edit it or create it localy and upload after on your server. You can use a basic editor include in Nofu, <img alt="docker icon" src="public/assets/icons/edit.svg" width="20">.
|
||||
|
||||
Example
|
||||
|
||||
|
@ -150,7 +151,11 @@ Replace the default data direcory by your backup, go to settings pages and check
|
|||
|
||||
## Ressources
|
||||
|
||||
### Images
|
||||
|
||||
* <a href="https://www.svgrepo.com/svg/448401/docker"><img alt="docker icon" src="public/assets/icons/docker.svg" width="24"> svgrepo.com (MLP licence)</a>
|
||||
* <a href="https://www.svgrepo.com/svg/448409/edit"><img alt="docker icon" src="public/assets/icons/edit.svg" width="24"> svgrepo.com Logo (MLP License)</a>
|
||||
* <a href="https://www.svgrepo.com/svg/308923/visual-interface-image-picture-tap"><img alt="docker icon" src="public/assets/icons/editImage.svg" width="24"> svgrepo.com Logo (CC0 License)</a>
|
||||
* <a href="https://www.svgrepo.com/svg/375064/question-mark"><img alt="docker icon" src="public/assets/icons/help.svg" width="24"> svgrepo.com (CC Attribution License)</a>
|
||||
* <a href="https://www.svgrepo.com/svg/56164/logout"><img alt="logout icon" src="public/assets/icons/logout.svg" width="24"> svgrepo.com (CCO licence)</a>
|
||||
* <a href="https://www.svgrepo.com/svg/195725/moon"><img alt="logout icon" src="public/assets/icons/moon.svg" width="24"> svgrepo.com (CCO licence)</a>
|
||||
|
@ -161,6 +166,17 @@ Replace the default data direcory by your backup, go to settings pages and check
|
|||
* <a href="https://www.svgrepo.com/svg/306622/qemu"><img alt="docker icon" src="public/assets/icons/vm.svg" width="24"> svgrepo.com (Logo License)</a>
|
||||
* <a href="https://www.svgrepo.com/svg/521261/web"><img alt="docker icon" src="public/assets/icons/web.svg" width="24"> svgrepo.com Logo (CC Attribution License)</a>
|
||||
* <a href="https://www.svgrepo.com/svg/105529/hard-drive-interior"><img alt="docker icon" src="public/assets/icons/webapp.svg" width="24"> svgrepo.com Logo (CC0 License)</a>
|
||||
|
||||
### Js libs
|
||||
|
||||
* <a href="https://github.com/nodeca/js-yaml">js-yaml (MIT licence)</a>
|
||||
|
||||
### PHP libs
|
||||
|
||||
* <a href="https://github.com/symfony/yaml">symfony/yaml (MIT License)</a>
|
||||
* <a href="https://github.com/stefangabos/zebra_image">stefangabos/zebra_image (GNU License)</a>
|
||||
* <a href="https://github.com/thephpleague/commonmark">thephpleague/commonmark (BSD 3-Clause "New" or "Revised" License)</a>
|
||||
* <a href="https://github.com/stefangabos/zebra_image">stefangabos/zebra_image (GNU License)</a>
|
||||
|
||||
And some piece of code from Stack Overflow :-)
|
||||
|
||||
|
|
58
app/App.php
58
app/App.php
|
@ -3,6 +3,7 @@
|
|||
namespace KTH;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
|
||||
class App {
|
||||
|
||||
|
@ -30,7 +31,11 @@ class App {
|
|||
* @return array An array containing the unique types and locations of the services.
|
||||
*/
|
||||
public function makeMenu(array $services): array {
|
||||
$menu = [];
|
||||
foreach ($services as $service) {
|
||||
if(!is_array($service)){
|
||||
return $menu;
|
||||
}
|
||||
$menu['type'][] = $service['type'];
|
||||
$menu['location'][] = $service['location'];
|
||||
}
|
||||
|
@ -47,11 +52,11 @@ class App {
|
|||
*
|
||||
* @return string The path to the image file or a default image.
|
||||
*/
|
||||
public function returnImg(string $file, string $type): string {
|
||||
public function returnImg(string|null $file, string $type): string {
|
||||
$defaultFile = 'assets/icons/missing.svg';
|
||||
$permitType = ['favicons', 'big_favicons', 'screenshots', 'thumbs'];
|
||||
|
||||
if (empty($file)) {
|
||||
if (empty($file) || is_null($file)) {
|
||||
return $defaultFile;
|
||||
}
|
||||
|
||||
|
@ -118,6 +123,24 @@ class App {
|
|||
$this->clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save services list
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function saveServices(): array {
|
||||
try {
|
||||
$value = Yaml::parse(trim($_POST['services'], 2));
|
||||
} catch (ParseException $exception) {
|
||||
return ['status' => 'error', 'message' => $exception->getMessage(), 'content' => $_POST['services']];
|
||||
}
|
||||
|
||||
file_put_contents('../data/services.yaml', $_POST['services']);
|
||||
$this->clearCache();
|
||||
return ['status' => 'success', 'message' => ''];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the configuration settings.
|
||||
*
|
||||
|
@ -172,4 +195,35 @@ class App {
|
|||
mkdir('../data/imgs/screenshots', 0775, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function getServices() {
|
||||
if (file_exists('../data/services.yaml')) {
|
||||
$services = Yaml::parseFile('../data/services.yaml');
|
||||
} else {
|
||||
$services = [
|
||||
0 => [
|
||||
'title' => 'Wikipedia',
|
||||
'screenshot' => 'wikipedia.png',
|
||||
'favicon' => 'wikipedia.png',
|
||||
'link' => 'https://en.wikipedia.org/wiki/Dashboard_(computing)',
|
||||
'appHome' => 'https://www.mediawiki.org/wiki/MediaWiki',
|
||||
'location' => 'web',
|
||||
'desc' => 'Wikipedia, the free encyclopedia',
|
||||
'type' => 'webapp'
|
||||
],
|
||||
[
|
||||
'title' => 'Awesome-Selfhosted',
|
||||
'screenshot' => 'Awesome-Selfhosted.png',
|
||||
'favicon' => 'Awesome-Selfhosted.ico',
|
||||
'link' => 'https://awesome-selfhosted.net/',
|
||||
'appHome' => 'https://www.mediawiki.org/wiki/MediaWiki',
|
||||
'location' => 'web',
|
||||
'desc' => 'A list of Free Software network services and web applications which can be hosted on your own servers',
|
||||
'type' => 'webapp'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $services;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ class Debug {
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
function n_print(mixed $data, string $name = ''): void {
|
||||
static function n_print(mixed $data, string $name = ''): void {
|
||||
error_reporting(-1);
|
||||
$aBackTrace = debug_backtrace();
|
||||
echo '<h2>', $name, '</h2>';
|
||||
|
@ -21,4 +21,4 @@ class Debug {
|
|||
echo '<pre>', htmlentities(print_r($data, 1)), '</pre>';
|
||||
echo '</fieldset><br />';
|
||||
}
|
||||
}
|
||||
}
|
175
app/utils/File.php
Normal file
175
app/utils/File.php
Normal file
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
|
||||
namespace Utils;
|
||||
|
||||
use Utils\SanitizeName;
|
||||
|
||||
use Utils\Debug;
|
||||
|
||||
class File {
|
||||
|
||||
private $path = __DIR__ . '/../../data/';
|
||||
public $subPath;
|
||||
|
||||
/**
|
||||
* Define a subpath for $path
|
||||
* example for file
|
||||
* - imilo
|
||||
* - region
|
||||
* - local
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
public $filterType = '*';
|
||||
|
||||
private $mimePermit = [
|
||||
'png' => 'png',
|
||||
'jpg' => 'jpg',
|
||||
'bmp' => 'bmp',
|
||||
'gif' => 'gif',
|
||||
'webp' => 'webp'
|
||||
];
|
||||
|
||||
public function __construct() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*
|
||||
* @todo define list of authorized path
|
||||
*/
|
||||
public function setPath($path) {
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
public function getFile($filename) {
|
||||
ob_end_clean();
|
||||
$path = realpath($this->path . $this->type);
|
||||
$file = $path . '/' . $filename;
|
||||
if (file_exists($file)) {
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Type: ' . mime_content_type($file));
|
||||
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: must-revalidate');
|
||||
header('Pragma: public');
|
||||
header('Content-Length: ' . filesize($file));
|
||||
readfile($file);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public function getFileList(): array {
|
||||
$path = realpath($this->path . $this->type);
|
||||
|
||||
foreach (glob($path . '/' . $this->filterType) as $filename) {
|
||||
$path_parts = pathinfo($filename);
|
||||
$fileList[] = [
|
||||
'file' => basename($filename),
|
||||
'path' => $this->subPath . '/' . $this->type . '/' . basename($filename),
|
||||
'type' => mime_content_type($filename),
|
||||
'ext' => $path_parts['extension'],
|
||||
'icon' => $this->defineIcon($path_parts['extension']),
|
||||
'size' => $this->human_filesize(filesize($filename)),
|
||||
'addTime' => date("d-m-Y H:i", filectime($filename))
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($fileList)) {
|
||||
$fileList = [];
|
||||
}
|
||||
|
||||
return $fileList;
|
||||
}
|
||||
|
||||
private function defineIcon(string $mimeFile): string {
|
||||
if (array_key_exists($mimeFile, $this->mimePermit)) {
|
||||
return $this->mimePermit[$mimeFile];
|
||||
} else {
|
||||
return 'raw';
|
||||
}
|
||||
}
|
||||
|
||||
private function human_filesize(int $bytes, int $decimals = 2): string {
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
if ($factor > 0) $sz = 'KMGT';
|
||||
|
||||
return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . @$sz[$factor - 1] . 'B';
|
||||
}
|
||||
|
||||
public function saveFile($file, $replace = true): array {
|
||||
$fileType = strtolower(pathinfo($_FILES["fileName"]["name"], PATHINFO_EXTENSION));
|
||||
$fileName = strtolower(pathinfo($_FILES["fileName"]["name"], PATHINFO_FILENAME));
|
||||
$file = SanitizeName::sanitizeName($fileName);
|
||||
$cleanName = $file . '.' . $fileType;
|
||||
|
||||
$target_file = $this->path . '/' . $this->type . '/' . $cleanName;
|
||||
|
||||
|
||||
/*
|
||||
* @todo permit replace
|
||||
*/
|
||||
if (file_exists($target_file) && $replace === false) {
|
||||
return [
|
||||
'status' => 'danger',
|
||||
'msg' => 'Le fichier existe déjà.'
|
||||
];
|
||||
}
|
||||
|
||||
$upload_max_size = ini_get('upload_max_filesize');
|
||||
|
||||
/*
|
||||
* @Todo return max upload
|
||||
*/
|
||||
if ($_FILES["fileName"]["size"] > $upload_max_size) {
|
||||
return [
|
||||
'status' => 'danger',
|
||||
'msg' => 'Le fichier soumis est trop volumineux.'
|
||||
];
|
||||
}
|
||||
|
||||
if (!array_key_exists($fileType, $this->mimePermit)) {
|
||||
return [
|
||||
'status' => 'danger',
|
||||
'msg' => 'Les fichiers ' . $fileType . ' ne sont pas autorisés.'
|
||||
];
|
||||
}
|
||||
|
||||
if (move_uploaded_file($_FILES["fileName"]["tmp_name"], $target_file)) {
|
||||
return [
|
||||
'status' => 'success',
|
||||
'msg' => 'Le fichier ' . htmlspecialchars($cleanName) . ' à bien été envoyé.'
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'status' => 'danger',
|
||||
'msg' => 'Une erreur c\'est produite.'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteFile($filename) {
|
||||
$path = realpath($this->path . $this->type);
|
||||
$file = $path . '/' . $filename;
|
||||
|
||||
if (file_exists($file)) {
|
||||
if (unlink($file)) {
|
||||
return [
|
||||
'status' => 'success',
|
||||
'msg' => 'Le fichier a été supprimé avec succès.'
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'status' => 'danger',
|
||||
'msg' => 'Une erreur c\'est produite lors de la suppression du fichier.'
|
||||
];
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
'status' => 'danger',
|
||||
'msg' => 'Le fichier n\'existe pas.'
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
104
app/utils/SanitizeName.php
Normal file
104
app/utils/SanitizeName.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
namespace Utils;
|
||||
|
||||
class SanitizeName {
|
||||
|
||||
private static $reservedWindowsNames = [
|
||||
'con',
|
||||
'prn',
|
||||
'aux',
|
||||
'nul',
|
||||
'com1',
|
||||
'com2',
|
||||
'com3',
|
||||
'com4',
|
||||
'com5',
|
||||
'com6',
|
||||
'com7',
|
||||
'com8',
|
||||
'com9',
|
||||
'lpt1',
|
||||
'lpt2',
|
||||
'lpt3',
|
||||
'lpt4',
|
||||
'lpt5',
|
||||
'lpt6',
|
||||
'lpt7',
|
||||
'lpt8',
|
||||
'lpt9',
|
||||
];
|
||||
|
||||
/**
|
||||
* https://github.com/GravityPDF/Upload
|
||||
* Set file name (without extension)
|
||||
*
|
||||
* Sanitize the filename (if outputting the filename to HTML you still need to escape)
|
||||
*
|
||||
* @param string $name
|
||||
* @return FileInfo Self
|
||||
*
|
||||
* @link https://stackoverflow.com/a/42058764
|
||||
* @internal 1. file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
* phpcs:ignore
|
||||
* @internal 2. control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
||||
* @internal 3. URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2
|
||||
* @internal 4. URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
||||
*/
|
||||
|
||||
static function sanitizeName(string $name): string {
|
||||
$name = str_replace(['%20', '+', '.'], '-', $name); //replaces encoded space, +, or .
|
||||
$name = (string) preg_replace('/[\r\n\t-]+/', '-', $name); //replace tab or new line characters
|
||||
$name = (string) preg_replace(
|
||||
'~
|
||||
[%<>:"/\\\|?*]| # @internal 1.
|
||||
[\x00-\x1F]| # @internal 2.
|
||||
[#\[\]@!$&\'()+,;=]| # @internal 3.
|
||||
[{}^\~`] # @internal 4.
|
||||
~x',
|
||||
'-',
|
||||
$name
|
||||
);
|
||||
|
||||
// reduce consecutive characters
|
||||
$name = (string) preg_replace(
|
||||
[
|
||||
'/ +/', // "file name.zip" becomes "file name.zip"
|
||||
'/_+/', // "file___name.zip" becomes "file_name.zip"
|
||||
'/ - -+/', // "file - -name.zip" becomes "file--name.zip"
|
||||
'/-+/', // "file--name.zip" becomes "file-name.zip"
|
||||
],
|
||||
[
|
||||
' ',
|
||||
'_',
|
||||
'-',
|
||||
'-',
|
||||
],
|
||||
$name
|
||||
);
|
||||
|
||||
$name = trim((string)$name, '.-_ '); //remove dot, hyphen, underscore, or space from start and end of string
|
||||
|
||||
/* Ensure filename is not a reserved Windows name, otherwise remove */
|
||||
if (in_array(strtolower($name), self::$reservedWindowsNames, true)) {
|
||||
$name = '';
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure filename is not longer than 255 bytes http://serverfault.com/a/9548/44086, otherwise shorten
|
||||
*/
|
||||
$extension = 'php';
|
||||
$maxLength = 255 - ($extension ? strlen($extension) + 1 : 0);
|
||||
|
||||
/* Use multibyte aware functions, if the server supports it */
|
||||
if (function_exists('mb_strcut') && function_exists('mb_detect_encoding')) {
|
||||
$name = mb_strcut($name, 0, $maxLength, (string)mb_detect_encoding($name));
|
||||
} else {
|
||||
$name = substr($name, 0, $maxLength);
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
$name = 'unnamed-file';
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
}
|
|
@ -6,40 +6,85 @@
|
|||
*/
|
||||
|
||||
:root {
|
||||
--background-color: #dadada;
|
||||
--border-radius: 10px;
|
||||
--light-color: #ececec;
|
||||
--nav-background-color: #34495e;
|
||||
--text-color: #1d1e22;
|
||||
--margin: .7em;
|
||||
color-scheme: dark light;
|
||||
|
||||
h1 {
|
||||
color: var(--text-color);
|
||||
}
|
||||
--primary: #cc2027;
|
||||
--primary-darken: #8E161B;
|
||||
--primary-lighten: #D64C52;
|
||||
--primary-text-contrast: #FFF;
|
||||
|
||||
.login {
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--text-color);
|
||||
}
|
||||
--secondary: #20ccc5;
|
||||
--secondary-darken: #168E89;
|
||||
--secondary-lighten: #4CD6D0;
|
||||
--secondary-text-contrast: #000;
|
||||
|
||||
--error: #c43933;
|
||||
--error-darken: #892723;
|
||||
--error-lighten: #CF605B;
|
||||
--error-text-contrast: #FFF;
|
||||
|
||||
--info: #206ccc;
|
||||
--info-darken: #164B8E;
|
||||
--info-lighten: #4C89D6;
|
||||
--info-text-contrast: #FFF;
|
||||
|
||||
--success: #7dcc20;
|
||||
--success-darken: #578E16;
|
||||
--success-lighten: #97D64C;
|
||||
--success-text-contrast: #000;
|
||||
|
||||
--warning: #cc5e20;
|
||||
--warning-darken: #8E4116;
|
||||
--warning-lighten: #D67E4C;
|
||||
--warning-text-contrast: #FFF;
|
||||
|
||||
--background-color: light-dark(#fffbfb, #171414);
|
||||
--background-color-darken: light-dark(#B2AFAF, #100E0E);
|
||||
--background-color-lighten: light-dark(#FFFBFB, #454343);
|
||||
|
||||
--header-background-color: light-dark(#171414, #fffbfb);
|
||||
--header-background-color-darken: light-dark(#100E0E, #B2AFAF);
|
||||
--header-background-color-lighten: light-dark(#454343, #FFFBFB);
|
||||
|
||||
--header-text-color: light-dark(#fffbfb, #171414);
|
||||
--header-text-color-secondary: #ffffffb3;
|
||||
--header-text-color-disable: light-dark(#ffffff80, #454343);
|
||||
|
||||
--text-color: light-dark(#171414, #fffbfb);
|
||||
--text-color-secondary: #ffffffb3;
|
||||
--text-color-disable: light-dark(#454343, #ffffff80);
|
||||
|
||||
--text-color-inverse: light-dark(#fffbfb, #171414);
|
||||
--text-color-secondary-inverse: #ffffffb3;
|
||||
--text-color-disable-inverse: light-dark(#ffffff80, #454343);
|
||||
|
||||
--h1-color: var(--primary);
|
||||
--h2-color: #c33d35;
|
||||
--h3-color: #b94f44;
|
||||
--h4-color: #ae5e52;
|
||||
--h5-color: #a16a61;
|
||||
--h6-color: #927671;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--background-color: #171414;
|
||||
--text-color: #fffbfb;
|
||||
--text-color-inverse: #171414;
|
||||
}
|
||||
|
||||
--background-color: #1d1e22;
|
||||
--border-radius: 10px;
|
||||
--light-color: #ececec;
|
||||
--nav-background-color: #34495e;
|
||||
--text-color: #1d1e22;
|
||||
[data-theme="light"] {
|
||||
--background-color: #fffbfb;
|
||||
--text-color: #171414;
|
||||
--text-color-inverse: #fffbfb;
|
||||
|
||||
.card, .modal-content, .modal-header {
|
||||
background: var(--background-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--margin: .7em;
|
||||
|
||||
h1 {
|
||||
color: var(--light-color);
|
||||
}
|
||||
|
||||
.login {
|
||||
color: var(--light-color);
|
||||
border: 1px solid var(--light-color);
|
||||
}
|
||||
--border-radius: 10px;
|
||||
}
|
||||
|
||||
*,
|
||||
|
@ -58,6 +103,24 @@ body {
|
|||
margin: var(--margin);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--h1-color);
|
||||
font-size: 2.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--primary);
|
||||
height: 1.2em;
|
||||
transition: .3s all ease;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
svg:hover {
|
||||
background-color: var(--primary-lighten);
|
||||
fill: var(--light-color);
|
||||
}
|
||||
|
||||
.titleBar {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -69,10 +132,12 @@ body {
|
|||
height: 2.5em;
|
||||
width: 2.5em;
|
||||
margin: var(--margin);
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
a {
|
||||
text-align: none;
|
||||
svg:hover {
|
||||
background-color: inherit;
|
||||
fill: var(--primary-lighten);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,24 +148,6 @@ body {
|
|||
height: 2.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-color);
|
||||
font-size: 2.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--nav-background-color);
|
||||
height: 1.2em;
|
||||
transition: .3s all ease;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
svg:hover {
|
||||
background-color: var(--nav-background-color);
|
||||
fill: var(--light-color);
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
@ -110,18 +157,19 @@ svg:hover {
|
|||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
.card a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-container h3 a {
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card-container .card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-container .card {
|
||||
background: var(--light-color);
|
||||
background: var(--background-color-lighten);
|
||||
box-shadow: 0 0 2px 2px rgba(0, 0, 0, .05);
|
||||
cursor: pointer;
|
||||
margin: var(--margin);
|
||||
transition: .3s all ease;
|
||||
width: calc(100% / 7 - 20px);
|
||||
|
@ -213,7 +261,7 @@ svg:hover {
|
|||
|
||||
.modal {
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: none;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
|
@ -228,7 +276,7 @@ svg:hover {
|
|||
.modal-content {
|
||||
animation-duration: 0.3s;
|
||||
animation-name: animatetop;
|
||||
background-color: var(--light-color);
|
||||
background: var(--background-color-lighten);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
font-size: 150%;
|
||||
|
@ -250,12 +298,12 @@ svg:hover {
|
|||
|
||||
.modal-header a,
|
||||
.modal-header a:visited {
|
||||
color: var(--nav-background-color);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.modal-header a:hover,
|
||||
.modal-header a:visited:hover {
|
||||
filter: brightness(1.5);
|
||||
color: var(--secondary-lighten);
|
||||
}
|
||||
|
||||
.userDoc {
|
||||
|
@ -286,7 +334,7 @@ svg:hover {
|
|||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: var(--nav-background-color);
|
||||
color: var(--text-color-disable);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transform: rotate(90deg);
|
||||
|
@ -301,22 +349,27 @@ svg:hover {
|
|||
}
|
||||
|
||||
.modal-header {
|
||||
background-color: var(--light-color);
|
||||
color: var(--text-color);
|
||||
padding: 1px 15px;
|
||||
background: var(--background-color-lighten);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 2px 16px;
|
||||
}
|
||||
|
||||
.modal-body a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal-body img {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-background-color);
|
||||
background-color: var(--primary);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--primary-text-contrast);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
|
@ -349,33 +402,15 @@ nav span {
|
|||
width: fit-content;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: var(--nav-background-color);
|
||||
border: 1px solid var(--light-color);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--light-color);
|
||||
padding: .2em .3em;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--light-color);
|
||||
border: 1px solid var(--nav-background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
nav a {
|
||||
background-clip: text;
|
||||
background-image: linear-gradient(to right,
|
||||
var(--background-color),
|
||||
var(--background-color) 50%,
|
||||
var(--light-color) 50%);
|
||||
background-position: -100%;
|
||||
background-size: 200% 100%;
|
||||
color: transparent;
|
||||
color: var(--text-color-inverse);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.5rem;
|
||||
margin: auto var(--margin);
|
||||
padding: 5px 0;
|
||||
|
@ -399,16 +434,10 @@ nav .active::before {
|
|||
width: 0;
|
||||
}
|
||||
|
||||
nav .active {
|
||||
background-image: linear-gradient(to right,
|
||||
var(--background-color),
|
||||
var(--background-color) 50%,
|
||||
var(--light-color) 50%);
|
||||
}
|
||||
|
||||
nav a:hover,
|
||||
nav .active {
|
||||
background-position: 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
nav a:hover::before,
|
||||
|
@ -417,22 +446,22 @@ nav .active::before {
|
|||
}
|
||||
|
||||
.login {
|
||||
border: 1px solid var(--light-color);
|
||||
border: 1px solid var(--background-color-lighten);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--light-color);
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 2rem;
|
||||
gap: 1rem;
|
||||
margin: var(--margin) auto 0;
|
||||
padding: 1rem;
|
||||
width: 30vw;
|
||||
width: 40vw;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=password],
|
||||
select {
|
||||
border: 1px solid var(--nav-background-color);
|
||||
border: 1px solid var(--background-color-lighten);
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
margin: 0 0 var(--margin) 0;
|
||||
|
@ -440,29 +469,121 @@ select {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
button[type=submit] {
|
||||
height: 3em;
|
||||
margin: auto;
|
||||
width: 30%;
|
||||
input[type=text]:active,
|
||||
input[type=password]:active,
|
||||
select:active,
|
||||
input[type=text]:focus,
|
||||
input[type=password]:focus,
|
||||
select:focus,
|
||||
input[type=text]:focus-visible,
|
||||
input[type=password]:focus-visible,
|
||||
select:focus-visible,
|
||||
textarea:focus-visible {
|
||||
border: 1px solid red;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button,
|
||||
.button {
|
||||
border-radius: var(--border-radius);
|
||||
line-height: 2.5em;
|
||||
height: 2.5em;
|
||||
margin: 0 auto;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
width: 30%;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
button[type=submit] {
|
||||
background-color: var(--success);
|
||||
border-color: var(--success-lighten);
|
||||
color: var(--success-text-contrast);
|
||||
}
|
||||
|
||||
button[type=submit]:hover {
|
||||
background-color: var(--success-lighten);
|
||||
border-color: var(--success-darken);
|
||||
color: var(--success-text-contrast);
|
||||
}
|
||||
|
||||
|
||||
.button {
|
||||
background-color: var(--primary-darken);
|
||||
border-color: var(--primary-lighten);
|
||||
color: var(--primary-text-contrast);
|
||||
transition: all 0.3s ease-in-out;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--primary-lighten);
|
||||
border-color: var(--primary-darken);
|
||||
}
|
||||
|
||||
.button-error {
|
||||
background-color: var(--error);
|
||||
border-color: var(--error-lighten);
|
||||
color: var(--error-text-contrast);
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.readMore .button {
|
||||
width: auto;
|
||||
padding: .2em .3em;
|
||||
line-height: inherit;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.table-list {
|
||||
font-size: 1.7rem;
|
||||
margin: auto;
|
||||
width: 80vw;
|
||||
color: var(--background-color-lighten);
|
||||
border-collapse: collapse;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--background-color-lighten);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--background-color-lighten);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.card-container:not(.icons) .card {
|
||||
width: calc(100% / 1 - 20px);
|
||||
}
|
||||
|
||||
|
||||
.modal-content {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.login {
|
||||
width: 80vw;
|
||||
width: 90vw;
|
||||
}
|
||||
|
||||
.table-list {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -476,7 +597,7 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 70vw;
|
||||
width: 80vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -490,7 +611,12 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 60vw;
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
.table-list {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,7 +630,7 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 50vw;
|
||||
width: 60vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,7 +644,7 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 40vw;
|
||||
width: 50vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -532,8 +658,14 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 30vw;
|
||||
width: 40vw;
|
||||
}
|
||||
|
||||
.table-list {
|
||||
margin: auto;
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.hide {
|
||||
|
|
14
public/assets/icons/edit.svg
Normal file
14
public/assets/icons/edit.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#000000">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<g>
|
||||
<path fill-rule="evenodd"
|
||||
d="M11.436 1.005A1.75 1.75 0 0113.902.79l.702.589a1.75 1.75 0 01.216 2.465l-5.704 6.798a4.75 4.75 0 01-1.497 1.187l-2.572 1.299a.75.75 0 01-1.056-.886l.833-2.759a4.75 4.75 0 01.908-1.68l5.704-6.798zm1.502.934a.25.25 0 00-.353.03l-.53.633 1.082.914.534-.636a.25.25 0 00-.031-.352l-.703-.59zm-.765 2.726l-1.082-.914-4.21 5.016a3.25 3.25 0 00-.621 1.15L5.933 11l1.01-.51a3.249 3.249 0 001.024-.812l4.206-5.013z"
|
||||
clip-rule="evenodd"></path>
|
||||
<path
|
||||
d="M3.25 3.5a.75.75 0 00-.75.75v9.5c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75V9A.75.75 0 0115 9v4.75A2.25 2.25 0 0112.75 16h-9.5A2.25 2.25 0 011 13.75v-9.5A2.25 2.25 0 013.25 2H6a.75.75 0 010 1.5H3.25z">
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
11
public/assets/icons/editImage.svg
Normal file
11
public/assets/icons/editImage.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg fill="#000000" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 260 260" enable-background="new 0 0 260 260"
|
||||
xml:space="preserve">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path
|
||||
d="M65.829,33.826c8.837-0.019,16.016,7.128,16.035,15.965s-7.128,16.016-15.965,16.035s-16.016-7.128-16.035-15.965 S56.992,33.845,65.829,33.826z M208.061,183.361L178,170v-38c0-5.523-4.477-10-10-10s-10,4.477-10,10v58c0,2.209-1.791,4-4,4 s-4-1.791-4-4v-14c0-5.523-4.477-10-10-10s-10,4.477-10,10v31.496c0,4.227,1.339,8.345,3.825,11.763L162,258h52v-65.501 C214,188.547,211.673,184.966,208.061,183.361z M258,2H2v192h120v-18c0-9.925,8.075-18,18-18c3.697,0,7.138,1.121,10,3.04V132 c0-9.925,8.075-18,18-18s18,8.075,18,18v32.801l25.31,11.249c6.494,2.886,10.69,9.342,10.69,16.449V194h36V2z M242,161.302 L161.861,49.62l-38.659,57.238L98.026,81.762L18.036,162H18V18h224V161.302z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -21,10 +21,22 @@ function showReadMore(modal) {
|
|||
divModal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
window.onclick = function (event) {
|
||||
if (event.target == divModal) {
|
||||
divModal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
const input = document.body;
|
||||
input.onkeydown = function (event) {
|
||||
if (event.key == "Escape") {
|
||||
divModal.style.display = "none";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toggleFilter(element, filter) {
|
||||
|
||||
let filterListNav = document.getElementsByTagName('nav')
|
||||
for (var i = 0; i < filterListNav.length; i++) {
|
||||
for (var i2 = 0; i2 < filterListNav[i].children.length; i2++) {
|
||||
|
@ -56,7 +68,15 @@ function switchTheme(e) {
|
|||
let actualTheme = document.documentElement.getAttribute('data-theme');
|
||||
if (actualTheme === null || actualTheme === 'light') {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null;
|
||||
if (currentTheme) {
|
||||
document.documentElement.setAttribute('data-theme', currentTheme);
|
||||
}
|
||||
|
|
2
public/assets/js/js-yaml.min.js
vendored
Normal file
2
public/assets/js/js-yaml.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,15 @@ use Symfony\Component\Yaml\Yaml;
|
|||
use KTH\App;
|
||||
use KTH\HTMLGenerator\HTMLGenerator;
|
||||
use Login\Login;
|
||||
use Utils\CsrfToken;
|
||||
|
||||
/*
|
||||
#############################################################
|
||||
################ DO NOT EDIT THIS FILE ######################
|
||||
################ USE data/config.yaml ######################
|
||||
################ OR USE CONFIG PAGE ######################
|
||||
#############################################################
|
||||
*/
|
||||
|
||||
$defConfig['title'] = 'KT-HomePage';
|
||||
$defConfig['desc'] = 'Dashboard de Knah Tsaeb';
|
||||
|
@ -38,44 +47,55 @@ if ($config['visibility'] === 'private') {
|
|||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['settings'])) {
|
||||
if (CsrfToken::validateToken($_POST['token'])) {
|
||||
$KTH->saveUserConfig();
|
||||
header("Location: /");
|
||||
} else {
|
||||
session_destroy();
|
||||
header("Location: /");
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
if (isset($_POST['edit'])) {
|
||||
if (CsrfToken::validateToken($_POST['token'])) {
|
||||
$status = $KTH->saveServices();
|
||||
if ($status['status'] === 'error') {
|
||||
$error = $status['message'];
|
||||
$content = $status['content'];
|
||||
//header("Location: ?edit=1");
|
||||
$_GET['edit']=1;
|
||||
} else {
|
||||
header("Location: /");
|
||||
}
|
||||
} else {
|
||||
session_destroy();
|
||||
header("Location: /");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isset($_GET['settings'])) {
|
||||
require('../template/default/settings.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
if (isset($_POST['settings'])) {
|
||||
$KTH->saveUserConfig();
|
||||
header("Location: /");
|
||||
if (isset($_GET['edit'])) {
|
||||
$services = $KTH->getServices();
|
||||
require('../template/default/edit.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
if (isset($_GET['editImg'])) {
|
||||
require('../template/default/editImg.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($KTH->cacheExist()) {
|
||||
echo file_get_contents('../cache/index.html');
|
||||
} else {
|
||||
if (file_exists('../data/services.yaml')) {
|
||||
$services = Yaml::parseFile('../data/services.yaml');
|
||||
} else {
|
||||
$services = [0 => [
|
||||
'title' => 'Wikipedia',
|
||||
'screenshot' => 'wikipedia.png',
|
||||
'favicon' => 'wikipedia.png',
|
||||
'link' => 'https://en.wikipedia.org/wiki/Dashboard_(computing)',
|
||||
'appHome' => 'https://www.mediawiki.org/wiki/MediaWiki',
|
||||
'location' => 'web',
|
||||
'desc' => 'Wikipedia, the free encyclopedia',
|
||||
'type' => 'webapp'
|
||||
],
|
||||
[
|
||||
'title' => 'Awesome-Selfhosted',
|
||||
'screenshot' => 'Awesome-Selfhosted.png',
|
||||
'favicon' => 'Awesome-Selfhosted.ico',
|
||||
'link' => 'https://awesome-selfhosted.net/',
|
||||
'appHome' => 'https://www.mediawiki.org/wiki/MediaWiki',
|
||||
'location' => 'web',
|
||||
'desc' => 'A list of Free Software network services and web applications which can be hosted on your own servers',
|
||||
'type' => 'webapp'
|
||||
]];
|
||||
}
|
||||
$services = $KTH->getServices();
|
||||
$generator = new HTMLGenerator($services);
|
||||
$userDoc = $generator->genUserDoc();
|
||||
$menuData = $KTH->makeMenu($services);
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</h3>
|
||||
<?php if ($config['view'] !== 'icons') : ?>
|
||||
<p class="readMore">
|
||||
<a class="button" onclick="showReadMore('<?= md5($service['link']); ?>')">More info</a>
|
||||
<a tabindex=0 class="button" onkeydown="showReadMore('<?= md5($service['link']); ?>')" onclick="showReadMore('<?= md5($service['link']); ?>')">More info</a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@
|
|||
<!-- Modal content -->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<span class="close" id="close-<?= md5($service['link']); ?>">×</span>
|
||||
<a class="close" id="close-<?= md5($service['link']); ?>" tabindex=0 >×</a>
|
||||
<h2>
|
||||
<img class="favicon" loading="lazy" src="<?= $KTH->returnImg($service['favicon'], 'favicons'); ?>" alt="Favicon" height="32px" />
|
||||
<?= $service['title']; ?>
|
||||
|
|
114
template/default/edit.php
Normal file
114
template/default/edit.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Utils\CsrfToken;
|
||||
use Utils\Select;
|
||||
use Utils\Debug;
|
||||
|
||||
if (!isset($error)) {
|
||||
$error = null;
|
||||
}
|
||||
|
||||
if (!isset($content)) {
|
||||
$content = Yaml::dump($services, 2);
|
||||
}
|
||||
|
||||
$breadcrumbs = ' / Edit';
|
||||
$debug = new Debug;
|
||||
|
||||
|
||||
require 'header.php';
|
||||
require 'titleBar.php';
|
||||
?>
|
||||
<?php
|
||||
/*<table class="table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Link</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($services as $service) : ?>
|
||||
<tr>
|
||||
<td><?= $service['title']; ?></td>
|
||||
<td><?= $service['link']; ?></td>
|
||||
<td>Delete</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>*/
|
||||
?>
|
||||
<form action="index.php" class="login" method="post">
|
||||
|
||||
<div class="alert" style="color: red;" id="alert">
|
||||
<?= $error; ?>
|
||||
</div>
|
||||
|
||||
<textarea rows="50" name="services" id="services" spellcheck="false"><?= $content; ?></textarea>
|
||||
|
||||
<input type="hidden" name="token" value="<?= CsrfToken::generateToken(); ?>">
|
||||
<input type="hidden" name="edit" value="1" />
|
||||
|
||||
<div class="flex">
|
||||
<a class="button" href="?">Cancel</a>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
<!--
|
||||
<form action="index.php" class="login" method="post">
|
||||
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" id="title">
|
||||
|
||||
<label for="screenshot">Screenshot name</label>
|
||||
<input type="text" name="screenshot" id="screenshot">
|
||||
|
||||
<label for="favicon">Favicon name</label>
|
||||
<input type="text" name="favicon" id="favicon">
|
||||
|
||||
<label for="link">Link</label>
|
||||
<input type="text" name="link" id="link">
|
||||
|
||||
<label for="appHome">Home application</label>
|
||||
<input type="text" name="appHome" id="appHome">
|
||||
|
||||
<label for="desc">Desc</label>
|
||||
<input type="text" name="desc" id="desc">
|
||||
|
||||
<label for="location">Location</label>
|
||||
<input type="text" name="location" id="location">
|
||||
|
||||
<label for="type">Install instalation type</label>
|
||||
<select name="type" id="type">
|
||||
<option value="docker">Docker</option>
|
||||
<option value="vm">Virtual machine</option>
|
||||
<option value="webapp">Webapp</option>
|
||||
<option value="redirection">Redirection</option>
|
||||
</select>
|
||||
|
||||
<input type="hidden" name="token" value="">
|
||||
<input type="hidden" name="settings" value="1" />
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
!-->
|
||||
|
||||
<script src="assets/js/js-yaml.min.js"></script>
|
||||
<script>
|
||||
function validateYAML() {
|
||||
console.log('here')
|
||||
const inputText = document.getElementById('services').value;
|
||||
try {
|
||||
jsyaml.load(inputText);
|
||||
document.getElementById('alert').textContent = "Your YAML is valid!";
|
||||
} catch (e) {
|
||||
document.getElementById('alert').textContent = "Your YAML is not valid!" + "\n\n" + "Error message: " + e.message;
|
||||
}
|
||||
}
|
||||
document.getElementById('services').addEventListener('keyup', validateYAML);
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require('footer.php');
|
||||
?>
|
|
@ -29,6 +29,7 @@ require 'titleBar.php';
|
|||
|
||||
<label for="colorScheme">Color scheme</label>
|
||||
<select name="colorScheme" id="colorScheme">
|
||||
<option value="auto" <?= Select::isSelected('auto', $config['colorScheme']); ?>>Auto</option>
|
||||
<option value="light" <?= Select::isSelected('light', $config['colorScheme']); ?>>Light</option>
|
||||
<option value="dark" <?= Select::isSelected('dark', $config['colorScheme']); ?>>Dark</option>
|
||||
</select>
|
||||
|
@ -42,12 +43,16 @@ require 'titleBar.php';
|
|||
|
||||
<p class="checkbox">
|
||||
<label for="reimport">Reimport images and user files</label>
|
||||
<input type="checkbox" name="reimport" id="reimport" value="1"/>
|
||||
<input type="checkbox" name="reimport" id="reimport" value="1" />
|
||||
</p>
|
||||
|
||||
<input type="hidden" name="token" value="<?= CsrfToken::generateToken(); ?>">
|
||||
<input type="hidden" name="settings" value="1" />
|
||||
<button type="submit">Save</button>
|
||||
|
||||
<div class="flex">
|
||||
<a class="button" href="?">Cancel</a>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
|
|
|
@ -23,6 +23,25 @@
|
|||
</g>
|
||||
</g>
|
||||
</svg></a>
|
||||
<a title="Services" href="index.php?edit=1">
|
||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#000000">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<g>
|
||||
<path fill-rule="evenodd" d="M11.436 1.005A1.75 1.75 0 0113.902.79l.702.589a1.75 1.75 0 01.216 2.465l-5.704 6.798a4.75 4.75 0 01-1.497 1.187l-2.572 1.299a.75.75 0 01-1.056-.886l.833-2.759a4.75 4.75 0 01.908-1.68l5.704-6.798zm1.502.934a.25.25 0 00-.353.03l-.53.633 1.082.914.534-.636a.25.25 0 00-.031-.352l-.703-.59zm-.765 2.726l-1.082-.914-4.21 5.016a3.25 3.25 0 00-.621 1.15L5.933 11l1.01-.51a3.249 3.249 0 001.024-.812l4.206-5.013z" clip-rule="evenodd"></path>
|
||||
<path d="M3.25 3.5a.75.75 0 00-.75.75v9.5c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75V9A.75.75 0 0115 9v4.75A2.25 2.25 0 0112.75 16h-9.5A2.25 2.25 0 011 13.75v-9.5A2.25 2.25 0 013.25 2H6a.75.75 0 010 1.5H3.25z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg></a>
|
||||
<!--<a title="Images" href="index.php?editImg=1">
|
||||
<svg fill="#000000" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 260 260" enable-background="new 0 0 260 260" xml:space="preserve">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M65.829,33.826c8.837-0.019,16.016,7.128,16.035,15.965s-7.128,16.016-15.965,16.035s-16.016-7.128-16.035-15.965 S56.992,33.845,65.829,33.826z M208.061,183.361L178,170v-38c0-5.523-4.477-10-10-10s-10,4.477-10,10v58c0,2.209-1.791,4-4,4 s-4-1.791-4-4v-14c0-5.523-4.477-10-10-10s-10,4.477-10,10v31.496c0,4.227,1.339,8.345,3.825,11.763L162,258h52v-65.501 C214,188.547,211.673,184.966,208.061,183.361z M258,2H2v192h120v-18c0-9.925,8.075-18,18-18c3.697,0,7.138,1.121,10,3.04V132 c0-9.925,8.075-18,18-18s18,8.075,18,18v32.801l25.31,11.249c6.494,2.886,10.69,9.342,10.69,16.449V194h36V2z M242,161.302 L161.861,49.62l-38.659,57.238L98.026,81.762L18.036,162H18V18h224V161.302z"></path>
|
||||
</g>
|
||||
</svg></a>-->
|
||||
<a title="Help" href="#" onclick="showReadMore('userDoc')">
|
||||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
|
|
Loading…
Reference in a new issue