commit 5dd12b556bd027490fb1565c31b7cec8ff841b6b Author: Knah-Tsaeb Date: Fri Jun 14 17:20:01 2024 +0200 First release diff --git a/.docker/apache2/nofu.conf b/.docker/apache2/nofu.conf new file mode 100644 index 0000000..f1a6d40 --- /dev/null +++ b/.docker/apache2/nofu.conf @@ -0,0 +1,10 @@ + + ErrorLog ${APACHE_LOG_DIR}/error.log + ServerName nofu.local + DocumentRoot /var/www/public/ + + Require all granted + AllowOverride All + Options -Indexes + + diff --git a/.docker/start.sh b/.docker/start.sh new file mode 100644 index 0000000..75f025c --- /dev/null +++ b/.docker/start.sh @@ -0,0 +1,2 @@ +#/bin/bash +/usr/sbin/apache2ctl -DFOREGROUND diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c81c2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +data/* +!data/.gitkeep + +public/index.html +public/assets/css/user.css +public/assets/js/user.js +public/imgs/big_favicons/*.webp +public/imgs/favicons/*.webp +public/imgs/screenshots/*.webp +public/imgs/thumbs/*.webp +vendor/ +composer.lock \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f00c8c4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM debian:stable-slim + +MAINTAINER Knah Tsaeb + +LABEL version="0.1.0" +LABEL description="Apache 2 / PHP / Nofu" + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get -y update && apt-get install -y git composer php apache2 php-cli php-curl php-gd && apt-get clean && apt-get autoclean && apt-get autoremove + +RUN rm -r /var/www/ && mkdir /var/www/ + +WORKDIR /var/www/ + +RUN git clone https://forge.leslibres.org/Knah-Tsaeb/Nofu.git --branch main --single-branch --depth 1 . + +RUN composer install --no-dev && chown -R www-data:www-data /var/www/ + +COPY .docker/start.sh /usr/bin/start.sh +RUN chmod +x /usr/bin/start.sh + +WORKDIR / + +COPY .docker/apache2/nofu.conf /etc/apache2/sites-available/nofu.conf +RUN a2dissite 000-default.conf && a2ensite nofu.conf && a2enmod rewrite + +EXPOSE 80 + +WORKDIR /var/www/ + +ENTRYPOINT "start.sh" + +# Build image +# docker buildx build -t nofu:0.1.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.1.0 +# docker run -d --restart unless-stopped -v /opt/nofu:/var/www/data -e TZ=UTC -p 8189:80 --name nofu nofu:0.1.0 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9dc1bde --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# Nofu + + Nofu for **N**ot **O**nly **F**or **U**s is personal dashboard + +## Table of Contents + +- [Introduction](#introduction) +- [Features](#features) +- [Instalation](#instalation) +- [Licence](#licence) + + +## Introduction + +There are many impressive dashboards ([awesome-selfhosted](https://awesome-selfhosted.net/tags/personal-dashboards.html)), which are perfect for our needs. However, for non-technical people/geeks/computer enthusiasts/dev...., it can be difficult to understand all the features offered by these dashboards. That's why I created NOFU in 'scratch-an-itch' mode. Although it may not be perfect for everyone, it meets my needs and those of my family circle. + +I also wanted a place where my family could find all my services with a quick documentation on my infrastructure (software used, what it's for, where it's located, where backups are stored...), in case I stop functioning one day. So that they can recover the family data or call someone to help them. + +![screenshot]() + +## Features + +* Simple to understand +* Easy customisation +* Minimal dependance +* No database +* Easy backup and deploy +* Static page +* Fast +* No JS or only for eye candy +* Responsive + +## Instalation + +### Manual + +Classic git clone, run composer, create website with your web server, that's all. + +#### Clone + +```shell +git clone https://forge.leslibres.org/Knah-Tsaeb/Nofu.git +``` + +#### Install dep + +```shell +composer install --no-dev +``` + +Serve public folder throw your web server. + +### Docker + +Clone, build and run. + +#### Clone + +```shell +git clone https://forge.leslibres.org/Knah-Tsaeb/Nofu.git +cd nofu +``` + +#### Build + +```shell +docker buildx build -t nofu:0.1.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.1.0 +``` + +## Ressources + + * docker icon SVGICON + * docker icon SVGICON + * docker icon SVGICON + * docker icon SVGICON + * docker icon SVGICON + * docker icon SVGICON + * docker icon SVGICON + +And some code from Stack Overflow :-) + +## Licence + +WTFPL + +``` + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + +``` \ No newline at end of file diff --git a/app/App.php b/app/App.php new file mode 100644 index 0000000..7273bcb --- /dev/null +++ b/app/App.php @@ -0,0 +1,171 @@ + $colorScheme, + 'view' => $view, + 'title' => $title + ]; + + $config = Yaml::dump($userConfig); + file_put_contents('../data/config.yaml', $config); + $this->clearCache(); + } + + /** + * Get the configuration settings. + * + * @param array $defConfig The default configuration settings. + * + * @return array The configuration settings. + */ + public function getConfig(array $defConfig): array { + if (file_exists('../data/config.yaml')) { + $userConfig = Yaml::parseFile('../data/config.yaml'); + if (empty($userConfig)) { + return $defConfig; + } + $config = array_merge($defConfig, $userConfig); + $this->config = $config; + return $config; + } + $this->config = $defConfig; + return $defConfig; + } + + /** + * Clear the cache file. + * + * @return void + */ + public function clearCache(): void { + if (file_exists('../public/index.html')) { + unlink('../public/index.html'); + } + } + + /** + * Initialize the data directories. + * + * @return void + */ + static function initializeDataDir(): void { + if (!is_dir(('../data/assets/css'))) { + mkdir('../data/assets/css', 0775, true); + } + if (!is_dir(('../data/assets/icons'))) { + mkdir('../data/assets/icons', 0775, true); + } + if (!is_dir(('../data/assets/js'))) { + mkdir('../data/assets/js', 0775, true); + } + if (!is_dir(('../data/imgs/favicons'))) { + mkdir('../data/imgs/favicons', 0775, true); + } + if (!is_dir(('../data/imgs/screenshots'))) { + mkdir('../data/imgs/screenshots', 0775, true); + } + } +} diff --git a/app/generator/HTMLGenerator.php b/app/generator/HTMLGenerator.php new file mode 100644 index 0000000..9696c54 --- /dev/null +++ b/app/generator/HTMLGenerator.php @@ -0,0 +1,142 @@ +favicons = array_unique($favicons, SORT_LOCALE_STRING); + $this->screenshots = array_unique($screenshots, SORT_LOCALE_STRING); + if (isset($_SESSION['reimport'])) { + $this->resizeImg(); + unset($_SESSION['reimport']); + } + $this->copyUserFile(); + } + + /** + * Resizes the favicons and screenshots. + * + * @return void + */ + private function resizeImg(): void { + $image = new \Zebra_Image(); + foreach ($this->favicons as $favicon) { + if (!str_starts_with(strtolower($favicon), 'http')) { + if (is_file('../data/imgs/favicons/' . $favicon)) { + $image->source_path = '../data/imgs/favicons/' . $favicon; + $image->target_path = '../public/imgs/favicons/' . $favicon . '.webp'; + $image->preserve_aspect_ratio = true; + $image->enlarge_smaller_images = false; + $image->resize(32, 32); + + $image->target_path = '../public/imgs/big_favicons/' . $favicon . '.webp'; + $image->resize(128, 128); + } + } + } + + foreach ($this->screenshots as $screenshot) { + if (!str_starts_with(strtolower($screenshot), 'http')) { + if (is_file('../data/imgs/screenshots/' . $favicon)) { + $image->source_path = '../data/imgs/screenshots/' . $screenshot; + $image->target_path = '../public/imgs/screenshots/' . $screenshot . '.webp'; + $image->preserve_aspect_ratio = true; + $image->enlarge_smaller_images = false; + $image->resize(1120); + + $image->target_path = '../public/imgs/thumbs/' . $screenshot . '.webp'; + $image->resize(250); + } + } + } + } + + /** + * Copy user-provided CSS, JS, and SVG files to the public directory. + * + * @return void + */ + private function copyUserFile(): void { + if (file_exists('../data/assets/css/user.css')) { + copy('../data/assets/css/user.css', '../public/assets/css/user.css'); + } + + if (file_exists('../data/assets/js/user.js')) { + copy('../data/assets/js/user.js', '../public/assets/js/user.js'); + } + + foreach (glob("../data/assets/icons/*.svg") as $filePath) { + $filename = pathinfo($filePath, PATHINFO_BASENAME); + copy($filePath, $filename); + } + } + + /** + * Generate the user documentation in HTML format. + * + * @return string | null The HTML content of the user documentation. + */ + public function genUserDoc(): string | null { + if (file_exists('../data/help.md')) { + $configMD = [ + 'table_of_contents' => [ + 'html_class' => 'table-of-contents', + 'position' => 'top', + 'style' => 'bullet', + 'min_heading_level' => 1, + 'max_heading_level' => 6, + 'normalize' => 'relative', + 'placeholder' => null, + ], + 'heading_permalink' => [ + 'html_class' => 'heading-permalink', + 'id_prefix' => 'content', + 'apply_id_to_heading' => false, + 'heading_class' => '', + 'fragment_prefix' => 'content', + 'insert' => 'before', + 'min_heading_level' => 1, + 'max_heading_level' => 6, + 'title' => 'Permalink', + 'symbol' => '', + 'aria_hidden' => true, + ], + ]; + + $environment = new Environment($configMD); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new HeadingPermalinkExtension()); + $environment->addExtension(new TableOfContentsExtension()); + $converter = new MarkdownConverter($environment); + + return $converter->convert(file_get_contents('../data/help.md'))->getContent(); + } else { + return null; + } + } +} diff --git a/app/login/Login.php b/app/login/Login.php new file mode 100644 index 0000000..81f056f --- /dev/null +++ b/app/login/Login.php @@ -0,0 +1,93 @@ +listUser = []; + $this->loadUsers(); + } + + /** + * Add a new user to the list of users. + * + * @param string $login The login name of the new user. + * @param string $password The password of the new user. + * @param string $role The role of the new user. + * + * @return bool True if the user was successfully added, false otherwise. + */ + public function addUser(string $login, string $password, string $role): bool { + if ($this->listUser[$login]) { + return false; + } + $newUser = [$login => ['password' => password_hash($password, PASSWORD_DEFAULT), 'role' => $role]]; + $listUser = array_merge($newUser, $this->listUser); + + $yaml = Yaml::dump($listUser); + + return file_put_contents('../data/users.yaml', $yaml); + } + + /** + * Load the users from the YAML file. + * + * @return void + */ + private function loadUsers() { + if (file_exists('../data/users.yaml')) { + $listUser = Yaml::parseFile('../data/users.yaml'); + if (!empty($listUser)) { + $this->listUser = $listUser; + } + } + } + + /** + * Log in a user with the given credentials. + * + * @param string $user The username. + * @param string $password The password. + * + * @return bool True if the user is logged in successfully, false otherwise. + */ + public function logIn(string $user, string $password): bool { + if ($this->listUser[$user] && password_verify($password, $this->listUser[$user]['password'])) { + $_SESSION['isLogged'] = true; + return true; + } + return false; + } + + /** + * Log out the user and redirect to the index page. + * + * @return void + */ + static function logOut(): void { + session_destroy(); + header("Location: index.php"); + } + + /** + * Check if the user is logged in. + * + * @return bool True if the user is logged in, false otherwise. + */ + static function isLogged(): bool { + if (isset($_SESSION['isLogged']) && $_SESSION['isLogged'] === true) { + return true; + } + return false; + } +} diff --git a/app/utils/CsrfToken.php b/app/utils/CsrfToken.php new file mode 100644 index 0000000..3c0ba76 --- /dev/null +++ b/app/utils/CsrfToken.php @@ -0,0 +1,39 @@ +', $name, ''; + echo '
'; + echo '', $aBackTrace[0]['file'], ' ligne => ', $aBackTrace[0]['line'], ''; + echo '
', htmlentities(print_r($data, 1)), '
'; + echo '

'; + } +} diff --git a/app/utils/Select.php b/app/utils/Select.php new file mode 100644 index 0000000..73ae1de --- /dev/null +++ b/app/utils/Select.php @@ -0,0 +1,20 @@ + +# Header set Cache-Control "max-age=290304000, public" + + + + mod_gzip_on Yes + mod_gzip_dechunk Yes + mod_gzip_item_include file \.(html?|txt|css|js|php)$ + mod_gzip_item_include mime ^application/x-javascript.* + mod_gzip_item_exclude mime ^image/.* + mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.* + \ No newline at end of file diff --git a/public/assets/css/style.css b/public/assets/css/style.css new file mode 100644 index 0000000..81d7929 --- /dev/null +++ b/public/assets/css/style.css @@ -0,0 +1,543 @@ +/* +######################################################## +############# DO NOT EDIT THIS FILE #################### +############# USE data/user.css #################### +######################################################## +*/ + + +:root { + --background-color: #dadada; + --border-radius: 10px; + --light-color: #ececec; + --nav-background-color: #34495e; + --text-color: #1d1e22; + --margin: .7em; + + h1 { + color: var(--text-color); + } + + .login { + color: var(--text-color); + border: 1px solid var(--text-color); + } +} + +[data-theme="dark"] { + + --background-color: #1d1e22; + --border-radius: 10px; + --light-color: #ececec; + --nav-background-color: #34495e; + --text-color: #1d1e22; + --margin: .7em; + + h1 { + color: var(--light-color); + } + + .login { + color: var(--light-color); + border: 1px solid var(--light-color); + } +} + +*, +*:after, +*:before { + box-sizing: border-box; +} + +html { + font-size: 59%; +} + +body { + background: var(--background-color); + color: var(--text-color); + 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; + 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; + justify-content: center; + margin: 0 auto; + max-width: 100dvw; + padding: 0 .5em; +} + +.card-container h3 a { + color: var(--text-color); + text-decoration: none; +} + +.card-container .card { + cursor: pointer; +} + +.card-container .card { + background: var(--light-color); + box-shadow: 0 0 2px 2px rgba(0, 0, 0, .05); + margin: var(--margin); + transition: .3s all ease; + width: calc(100% / 7 - 20px); +} + +.card-container .card:hover { + border-radius: 15px 15px var(--border-radius) var(--border-radius); + box-shadow: 0 0 30px 15px rgba(0, 0, 0, 0.56); + transform: scale(1.20); + z-index: 1; +} + +.card-container .card .thumb { + margin: 0; + padding: 0; + transition: .3s all ease; + width: 100%; +} + +.card-container .card .thumb:hover { + border-top-left-radius: var(--border-radius); + border-top-right-radius: var(--border-radius); +} + +.card-container .card .content { + padding: .5em; +} + +.card-container .card h3 { + font: 2.6rem/3.2rem 'Bree Serif', serif; + letter-spacing: -.075rem; + margin: 0 0 5px; + padding: 0; +} + +.card-container .card p { + color: var(--text-color); + font: 400 1.6rem/2.2rem 'Open Sans script=all', sans-serif; + margin: 0; + padding: 0; +} + +.card-container .readMore { + text-align: right; +} + +.card .favicon { + height: 20px; + width: 20px; + margin-right: .1em; +} + +.icons { + .card { + margin: 0; + padding: 0; + width: auto; + } + + .card .content { + padding: 0; + margin: 0; + } + + .card .content h3 { + margin: 0; + padding: 0; + } + + .card .content .favicon { + margin: 0; + width: 128px; + height: 128px; + padding: 0; + } + + a { + display: block; + height: 128px; + } + + img { + transition: .3s all ease; + } + + img:hover { + border-radius: 10px; + } +} + +.modal { + background-color: rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.4); + display: none; + height: 100%; + left: 0; + overflow: auto; + padding-top: 100px; + position: fixed; + top: 0; + width: 100%; + z-index: 2; +} + +.modal-content { + animation-duration: 0.3s; + animation-name: animatetop; + background-color: var(--light-color); + 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%; + height: calc(100% / 1 - 100px); + margin: auto; + padding: .2em; + position: relative; + width: calc(100% / 2 - 20px); +} + +.modal-content .screenshot { + margin: 0 auto; + text-align: center; +} + +.screenshot { + width: 100%; +} + +.modal-header a, +.modal-header a:visited { + color: var(--nav-background-color); +} + +.modal-header a:hover, +.modal-header a:visited:hover { + filter: brightness(1.5); +} + +.userDoc { + overflow: scroll; + font-size: 1.8em; +} + +@keyframes animatetop { + from { + opacity: 0; + top: -400px; + } + + to { + opacity: 1; + top: 0; + } +} + +.close { + color: var(--text-color); + display: block; + float: right; + font-size: 32px; + font-weight: bold; + transition: .3s all ease; +} + +.close:hover, +.close:focus { + color: var(--nav-background-color); + cursor: pointer; + text-decoration: none; + transform: rotate(90deg); +} + +.modal h2 { + font-size: 160%; +} + +.modal-content h2 img { + vertical-align: text-bottom; +} + +.modal-header { + background-color: var(--light-color); + color: var(--text-color); + padding: 1px 15px; +} + +.modal-body { + padding: 2px 16px; +} + +.modal-body img { + position: relative; +} + +nav { + background-color: var(--nav-background-color); + border-radius: var(--border-radius); + display: flex; + flex-wrap: wrap; + height: auto; + margin: .5em 0; + padding: .5em 0; + position: relative; + width: fit-content; +} + +nav img { + display: inline-block; + vertical-align: text-bottom; +} + +nav span { + font-size: 1.5rem; + font-weight: bold; + line-height: 1.5rem; + margin: auto var(--margin); + ; +} + +.readMore { + display: flex; + flex-wrap: wrap; + height: auto; + margin: var(--margin); + padding: var(--margin); + position: relative; + 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; + cursor: pointer; + display: inline-block; + font-size: 1.5rem; + line-height: 1.5rem; + margin: auto var(--margin); + padding: 5px 0; + position: relative; + text-align: center; + text-decoration: none; + text-transform: uppercase; + transition: all 0.3s ease-in-out; +} + +nav a:before, +nav .active::before { + background: var(--text-color); + bottom: -3px; + content: ''; + display: block; + height: 3px; + left: 0; + position: absolute; + transition: all 0.3s ease-in-out; + 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; +} + +nav a:hover::before, +nav .active::before { + width: 100%; +} + +.login { + border: 1px solid var(--light-color); + border-radius: var(--border-radius); + color: var(--light-color); + display: flex; + flex-direction: column; + font-size: 2rem; + gap: 1rem; + margin: var(--margin) auto 0; + padding: 1rem; + width: 30vw; +} + +input[type=text], +input[type=password], +select { + border: 1px solid var(--nav-background-color); + box-sizing: border-box; + display: inline-block; + margin: 0 0 var(--margin) 0; + padding: 12px 20px; + width: 100%; +} + +button[type=submit] { + height: 3em; + margin: auto; + width: 30%; +} + +.checkbox { + display: flex; + gap: 10px; + justify-content: center; +} + +@media only screen and (max-width: 600px) { + .card-container:not(.icons) .card { + width: calc(100% / 1 - 20px); + } + + .modal-content { + width: 90%; + } + + .login { + width: 80vw; + } +} + +@media only screen and (min-width: 600px) { + .card-container:not(.icons) .card { + width: calc(100% / 2 - 20px); + } + + .modal-content { + width: calc(100% / 1); + } + + .login { + width: 70vw; + } +} + +@media only screen and (min-width: 768px) { + .card-container:not(.icons) .card { + width: calc(100% / 3 - 20px); + } + + .modal-content { + width: calc(100% / 2 + 150px); + } + + .login { + width: 60vw; + } +} + +@media only screen and (min-width: 992px) { + .card-container:not(.icons) .card { + width: calc(100% / 4 - 20px); + } + + .modal-content { + width: calc(100% / 2 + 100px); + } + + .login { + width: 50vw; + } +} + +@media only screen and (min-width: 1200px) { + .card-container:not(.icons) .card { + width: calc(100% / 5 - 20px); + } + + .modal-content { + width: calc(100% / 2 + 50px); + } + + .login { + width: 40vw; + } +} + +@media only screen and (min-width: 1600px) { + .card-container:not(.icons) .card { + width: calc(100% / 7 - 20px); + } + + .modal-content { + width: calc(100% / 2 + 200px); + } + + .login { + width: 30vw; + } +} + +.hide { + display: none; +} \ No newline at end of file diff --git a/public/assets/icons/docker.svg b/public/assets/icons/docker.svg new file mode 100644 index 0000000..a5f8bea --- /dev/null +++ b/public/assets/icons/docker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/logout.svg b/public/assets/icons/logout.svg new file mode 100644 index 0000000..fa1572d --- /dev/null +++ b/public/assets/icons/logout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/missing.svg b/public/assets/icons/missing.svg new file mode 100644 index 0000000..2033101 --- /dev/null +++ b/public/assets/icons/missing.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/public/assets/icons/redirection.svg b/public/assets/icons/redirection.svg new file mode 100644 index 0000000..32c0bb5 --- /dev/null +++ b/public/assets/icons/redirection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/server.svg b/public/assets/icons/server.svg new file mode 100644 index 0000000..c6665a8 --- /dev/null +++ b/public/assets/icons/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/vm.svg b/public/assets/icons/vm.svg new file mode 100644 index 0000000..fd04997 --- /dev/null +++ b/public/assets/icons/vm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/web.svg b/public/assets/icons/web.svg new file mode 100644 index 0000000..f00decb --- /dev/null +++ b/public/assets/icons/web.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/webapp.svg b/public/assets/icons/webapp.svg new file mode 100644 index 0000000..94f7b24 --- /dev/null +++ b/public/assets/icons/webapp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/js/app.js b/public/assets/js/app.js new file mode 100644 index 0000000..933a92d --- /dev/null +++ b/public/assets/js/app.js @@ -0,0 +1,62 @@ +function showReadMore(modal) { + + // Get the modal + var divModal = document.getElementById('modal-' + modal); + + divModal.style.display = "block"; + + // Get the element that closes the modal + var close = document.getElementById('close-' + modal); + + // When the user clicks the button, open the modal + + // When the user clicks on (x), close the modal + close.onclick = function () { + divModal.style.display = "none"; + } + + // When the user clicks anywhere outside of the modal, close it + window.onclick = function (event) { + if (event.target == divModal) { + 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++) { + if (!filterListNav[i].children[i2].classList.contains(filter)) { + filterListNav[i].children[i2].classList.remove('active') + } + } + } + + element.classList.toggle('active') + + let filterList = document.getElementsByClassName('card') + let filterListLength = filterList.length + + for (var i = 0; i < filterListLength; i++) { + if (element.classList.contains('active')) { + if (!filterList[i].classList.contains(filter)) { + filterList[i].style.opacity = .3 + } else { + filterList[i].style.opacity = 1 + } + } else { + filterList[i].style.opacity = 1 + } + } +} + +function switchTheme(e) { + let actualTheme = document.documentElement.getAttribute('data-theme'); + if (actualTheme === null || actualTheme === 'light') { + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + document.documentElement.setAttribute('data-theme', 'light'); + } +} \ No newline at end of file diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..aeaca65 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/imgs/big_favicons/.gitkeep b/public/imgs/big_favicons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/imgs/favicons/.gitkeep b/public/imgs/favicons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/imgs/screenshots/.gitkeep b/public/imgs/screenshots/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/imgs/thumbs/.gitkeep b/public/imgs/thumbs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..0a103aa --- /dev/null +++ b/public/index.php @@ -0,0 +1,83 @@ +getConfig($defConfig); + +if (isset($_GET['logout'])) { + $logout = Login::logOut(); + exit(); +} + +if ($config['public'] === false) { + if (!$KTH->canByPassAuth($defConfig['noAuth'])) { + if (!Login::isLogged()) { + if (file_exists('../data/users.yaml')) { + require('../template/default/login.php'); + } else { + require('../template/default/register.php'); + } + exit(); + } + } +} + +if (isset($_GET['settings'])) { + require('../template/default/settings.php'); + exit(); +} + +if (isset($_POST['settings'])) { + $KTH->saveUserConfig(); + header("Location: /"); + exit(); +} + +if ($KTH->cacheExist()) { + echo file_get_contents('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' + ]]; + } + $generator = new HTMLGenerator($services); + $userDoc = $generator->genUserDoc(); + $menuData = $KTH->makeMenu($services); + + ob_start(); + + require('../template/default/header.php'); + require('../template/default/titleBar.php'); + require('../template/default/nav.php'); + require('../template/default/content.php'); + require('../template/default/footer.php'); + + $out = ob_get_contents(); + file_put_contents('index.html', $out); +} diff --git a/template/default/content.php b/template/default/content.php new file mode 100644 index 0000000..02126ca --- /dev/null +++ b/template/default/content.php @@ -0,0 +1,70 @@ +
+ +
+ + + + + Thumbshot + + + +
+

+ + + Favicon + + Favicon + + + +

+ +

+ More info +

+ +
+
+ + + + + + +
\ No newline at end of file diff --git a/template/default/footer.php b/template/default/footer.php new file mode 100644 index 0000000..8ff9d68 --- /dev/null +++ b/template/default/footer.php @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/template/default/header.php b/template/default/header.php new file mode 100644 index 0000000..4c52b4e --- /dev/null +++ b/template/default/header.php @@ -0,0 +1,18 @@ + + + + + + + + + + <?= $config['title']; ?> + + + + + + + + \ No newline at end of file diff --git a/template/default/login.php b/template/default/login.php new file mode 100644 index 0000000..fb69a20 --- /dev/null +++ b/template/default/login.php @@ -0,0 +1,47 @@ +LogIn($_POST['login'], $_POST['password'])) { + header('Location: index.php'); + } else { + $error = 'Wrong password ar login.'; + } + } else { + $error = 'Error 06 : Wrong token'; + } + } +} +require 'header.php'; +?> +
+

/ Login

+
+ + + + \ No newline at end of file diff --git a/template/default/nav.php b/template/default/nav.php new file mode 100644 index 0000000..7e97233 --- /dev/null +++ b/template/default/nav.php @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/template/default/register.php b/template/default/register.php new file mode 100644 index 0000000..efa5779 --- /dev/null +++ b/template/default/register.php @@ -0,0 +1,54 @@ +addUser($_POST['login'], $_POST['password'], $_POST['role']); + if ($addUser === true) { + header('Location: index.php'); + } else { + $error = 'Error 02 - This user already exist'; + } + } else { + $error = 'Error 07 : Wrong token'; + } + } +} +require 'header.php'; +?> +
+

+
+ + + + \ No newline at end of file diff --git a/template/default/settings.php b/template/default/settings.php new file mode 100644 index 0000000..8050652 --- /dev/null +++ b/template/default/settings.php @@ -0,0 +1,45 @@ + + + + + +

+ \ No newline at end of file