Add visual editor for services.yaml
Some adjust CSS
This commit is contained in:
parent
58ec0cb655
commit
0d1b22d2f9
13 changed files with 637 additions and 71 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
|
23
README.md
23
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
|
||||
|
@ -105,7 +105,7 @@ You can find many icon for your app in [Dashboard-Icons](https://github.com/walk
|
|||
|
||||
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
|
||||
|
||||
|
@ -151,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>
|
||||
|
@ -163,6 +167,17 @@ Replace the default data direcory by your backup, go to settings pages and check
|
|||
* <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 :-)
|
||||
|
||||
## Licence
|
||||
|
|
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>';
|
||||
|
|
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;
|
||||
}
|
||||
}
|
|
@ -40,6 +40,18 @@
|
|||
color: var(--light-color);
|
||||
border: 1px solid var(--light-color);
|
||||
}
|
||||
|
||||
.titleBar .linkList {
|
||||
float: right;
|
||||
|
||||
svg {
|
||||
fill: var(--nav-background-color);
|
||||
}
|
||||
|
||||
svg:hover {
|
||||
fill: var(--light-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
|
@ -58,31 +70,6 @@ body {
|
|||
margin: var(--margin);
|
||||
}
|
||||
|
||||
.titleBar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.titleBar .linkList {
|
||||
float: right;
|
||||
|
||||
svg {
|
||||
height: 2.5em;
|
||||
width: 2.5em;
|
||||
margin: var(--margin);
|
||||
}
|
||||
|
||||
a {
|
||||
text-align: none;
|
||||
}
|
||||
}
|
||||
|
||||
.titleBar div {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 2.5em;
|
||||
height: 2.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--light-color);
|
||||
font-size: 2.5rem;
|
||||
|
@ -101,6 +88,33 @@ svg:hover {
|
|||
fill: var(--light-color);
|
||||
}
|
||||
|
||||
.titleBar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.titleBar .linkList {
|
||||
float: right;
|
||||
|
||||
svg {
|
||||
height: 2.5em;
|
||||
width: 2.5em;
|
||||
margin: var(--margin);
|
||||
fill: var(--nav-background-color);
|
||||
}
|
||||
|
||||
svg:hover {
|
||||
background-color: inherit;
|
||||
fill: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.titleBar div {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 2.5em;
|
||||
height: 2.5em;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
@ -426,7 +440,7 @@ nav .active::before {
|
|||
gap: 1rem;
|
||||
margin: var(--margin) auto 0;
|
||||
padding: 1rem;
|
||||
width: 30vw;
|
||||
width: 40vw;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
|
@ -452,17 +466,42 @@ button[type=submit] {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.table-list {
|
||||
font-size: 1.7rem;
|
||||
margin: auto;
|
||||
width: 80vw;
|
||||
color: var(--light-color);
|
||||
border-collapse: collapse;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--light-color);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--nav-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
@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 +515,7 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 70vw;
|
||||
width: 80vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -490,7 +529,12 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 60vw;
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
.table-list {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,7 +548,7 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 50vw;
|
||||
width: 60vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,7 +562,7 @@ button[type=submit] {
|
|||
}
|
||||
|
||||
.login {
|
||||
width: 40vw;
|
||||
width: 50vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -532,8 +576,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 |
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,7 @@ use Symfony\Component\Yaml\Yaml;
|
|||
use KTH\App;
|
||||
use KTH\HTMLGenerator\HTMLGenerator;
|
||||
use Login\Login;
|
||||
use Utils\CsrfToken;
|
||||
|
||||
$defConfig['title'] = 'KT-HomePage';
|
||||
$defConfig['desc'] = 'Dashboard de Knah Tsaeb';
|
||||
|
@ -38,44 +39,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);
|
||||
|
|
110
template/default/edit.php
Normal file
110
template/default/edit.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?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"><?= $content; ?></textarea>
|
||||
|
||||
<input type="hidden" name="token" value="<?= CsrfToken::generateToken(); ?>">
|
||||
<input type="hidden" name="edit" value="1" />
|
||||
<button type="submit">Save</button>
|
||||
</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');
|
||||
?>
|
|
@ -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