Saving files before refreshing line endings

This commit is contained in:
Adrian Preuß committed 2024-09-24 05:44:09 -07:00
1 parent 5d4c171b69
commit 85c3a5abda
94 files changed
+2522 -1694

No files matched your search

+1
View File
@@ -37,6 +37,7 @@
RewriteRule ^(.*)/$ /$1 [L,R=301]
# Handle Template Files
RewriteRule ^theme/([^/]+)/(.*)$ index.php?theme=$1&file=$2 [QSA,L]
RewriteRule ^(css|js|fonts|images|assets)/(.*)$ default/$1/$2 [QSA,L]
# Handle Front Controller
+3 -2
View File
@@ -6,7 +6,9 @@
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Accounting;
class Auth {
public static function isLoggedIn() : bool {
return AuthFactory::getInstance()->isLoggedIn();
@@ -60,5 +62,4 @@
return AuthFactory::getInstance()->getPermissions();
}
}
?>
?>
+2 -2
View File
@@ -6,6 +6,7 @@
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Accounting;
use fruithost\Localization\I18N;
@@ -236,5 +237,4 @@
return $this->permissions;
}
}
?>
?>
+1 -1
View File
@@ -8,7 +8,7 @@
*/
namespace fruithost\Accounting;
class Session {
public static function init() : void {
if(!isset($_SESSION)) {
+2 -2
View File
@@ -6,6 +6,7 @@
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Accounting;
use fruithost\Security\Encryption;
@@ -209,5 +210,4 @@
return $this->permissions;
}
}
?>
?>
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Hardware;
class NetworkAddress {
+11 -3
View File
@@ -1,8 +1,16 @@
<?php
namespace fruithost\Hardware;
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
enum NetworkFamily : string {
case IPV4 = "inet";
namespace fruithost\Hardware;
enum NetworkFamily : string {
case IPV4 = "inet";
case IPV6 = "inet6";
case UNKNOWN = "";
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Hardware;
enum NetworkFlag : string {
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Hardware;
enum NetworkState : string {
+11 -1
View File
@@ -1,5 +1,14 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Installer;
class Installer {
/* Repositorys */
public static function getRepositorys(?string $url = null) : array {
@@ -29,4 +38,5 @@
public static function getRepositorysByID(array $ids) : array {
return Repository::getInstance()->getRepositorysByID($ids);
}
}
}
?>
+4 -1
View File
@@ -6,7 +6,9 @@
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Installer;
class InstallerFactory {
private static ?InstallerFactory $instance = null;
@@ -21,4 +23,5 @@
}
public function getList() {}
}
}
?>
+3 -1
View File
@@ -6,6 +6,7 @@
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Installer;
use fruithost\Storage\Database;
@@ -171,4 +172,5 @@
return $content;
}
}
}
?>
+1
View File
@@ -8,6 +8,7 @@
*/
namespace fruithost\Localization;
use fruithost\Accounting\Auth;
use fruithost\Network\Request;
use fruithost\Storage\Database;
+1 -1
View File
@@ -17,7 +17,7 @@
private ?ModuleInfo $info = null;
private ?ModuleInterface $instance = null;
private bool $enabled = false;
private bool$locked = false;
private bool $locked = false;
public function __construct(string $path) {
$this->path = $path;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Modules;
class ModuleError {
+1 -1
View File
@@ -35,7 +35,7 @@
}
public function setContentType(string $type) : void {
$headers['Content-Type'] = $type;
$this->headers['Content-Type'] = $type;
}
public function addHeader(string $name, string $value) : void {
+11 -1
View File
@@ -6,6 +6,7 @@
* @version 1.0.0
* @license MIT
*/
namespace fruithost\Services;
use fruithost\Accounting\Auth;
@@ -43,11 +44,16 @@
} catch(\Exception $e) {
$this->debug[] = $e->getMessage();
}
if(!empty($ini)) {
if(isset($ini['www'])) {
if(isset($ini['www']['listen'])) {
$this->socket = $ini['www']['listen'];
}
} else if(isset($ini['panel'])) {
if(isset($ini['panel']['listen'])) {
$this->socket = str_replace('$pool', 'panel', $ini['panel']['listen']);
}
}
}
@@ -65,6 +71,10 @@
}
public function getHeader() : ?string {
if(empty($this->content)) {
return null;
}
return explode("\r\n\r\n", $this->content)[0];
}
@@ -274,4 +284,4 @@
I18N::__('yes')
I18N::__('no value')
*/
?>
?>
+123 -8
View File
@@ -6,6 +6,7 @@
* @version 1.0.0
* @license MIT
*/
namespace fruithost\System;
use fruithost\Accounting\Auth;
@@ -22,6 +23,7 @@
use fruithost\Storage\PropertiesCache;
use fruithost\Templating\Template;
use fruithost\UI\Icon;
use fruithost\System\Utils;
use Parsedown\Parsedown;
class Core extends Loader {
@@ -59,41 +61,65 @@
}
protected function initRoutes() : void {
$this->router->addRoute('^/theme/([^/]+)/(.*)$', function(?string $theme = null, ?string $file = null) {
$path = sprintf('%1$s%2$sthemes%2$s%3$s%2$s%4$s', dirname(PATH), DS, $theme, $file);
// @ToDo Is Secure?
if(file_exists($path)) {
Response::setContentType(Utils::getMimeType($path));
Response::header();
readfile($path);
exit();
}
$this->template->display('error/404');
});
$this->router->addRoute('/', function() {
if(Auth::isLoggedIn()) {
Response::redirect('/overview');
}
Response::redirect('/login');
});
$this->router->addRoute('/logout', function() {
if(Auth::isLoggedIn()) {
Auth::logout();
}
Response::redirect('/');
});
$this->router->addRoute('/overview', function() {
if(!Auth::isLoggedIn()) {
Response::redirect('/');
}
$this->template->display('overview');
});
$this->router->addRoute('^/settings(?:/([a-zA-Z0-9\-_]+))?$', function(?string $tab = null) {
if(!Auth::isLoggedIn()) {
Response::redirect('/');
}
$this->template->display('settings', [
'tab' => $tab,
'languages' => I18N::getLanguages(),
'timezones' => json_decode(file_get_contents(dirname(PATH).'/config/timezones.json'))
]);
});
$this->router->addRoute('^/account(?:/([a-zA-Z0-9\-_]+))?$', function(?string $tab = null) {
if(!Auth::isLoggedIn()) {
Response::redirect('/');
}
$data = Database::single('SELECT * FROM `'.DATABASE_PREFIX.'users_data` WHERE `user_id`=:user_id LIMIT 1', [
'user_id' => Auth::getID()
]);
if($data !== false) {
foreach($data as $index => $entry) {
if(in_array($index, [
@@ -105,11 +131,13 @@
$data->{$index} = Encryption::decrypt($entry, ENCRYPTION_SALT);
}
}
$this->template->display('account', [
'tab' => $tab,
'data' => $data
]);
});
$this->router->addRoute('^/app/([a-zA-Z0-9\-_]+)/(.*)$', function(?string $module = null, ?string $file = null) {
if(empty($module)) {
$this->template->display('error/module', array_merge($this->template->getAssigns(), [
@@ -118,46 +146,58 @@
return;
}
$module = $this->getModules()->getModule($module);
if(empty($module)) {
header('HTTP/1.1 403 Forbidden');
require_once(dirname(PATH).'/placeholder/errors/403.html');
exit();
}
if(!method_exists($module->getInstance(), 'frame')) {
header('HTTP/1.1 403 Forbidden');
require_once(dirname(PATH).'/placeholder/errors/403.html');
exit();
}
if(!file_exists(sprintf('%s/www/', $module->getPath()))) {
header('HTTP/1.1 403 Forbidden');
require_once(dirname(PATH).'/placeholder/errors/403.html');
exit();
}
$file = sprintf('%s/www/%s', $module->getPath(), $file);
if(!file_exists($file)) {
header('HTTP/1.1 403 Forbidden');
require_once(dirname(PATH).'/placeholder/errors/403.html');
exit();
}
Response::setContentType(mime_content_type($file));
Response::header();
readfile($file);
exit();
});
$this->router->addRoute('^/module(?:(?:/([a-zA-Z0-9\-_]+)?)(?:/([a-zA-Z0-9\-_]+)(?:/([a-zA-Z0-9\-_]+))?)?)?$', function(?string $module = null, ?string $submodule = null, ?string $action = null) {
if(!Auth::isLoggedIn()) {
Response::redirect('/');
}
if(Session::has('success')) {
$this->template->assign('success', Session::get('success'));
Session::remove('success');
}
if(Session::has('error')) {
$this->template->assign('error', Session::get('error'));
Session::remove('error');
}
$errors = [];
foreach(($this->getModules()->getList()) as $m) {
try {
if($m != null && $m->getInstance() != null && method_exists($m->getInstance(), 'preLoad')) {
@@ -171,35 +211,41 @@
];
}
}
if(count($errors) > 0) {
$message = I18N::get('Following Modules have some Errors:');
$message .= '<br />';
foreach($errors as $entry) {
$message .= $entry->name;
}
$this->template->assign('error', $message);
}
if(empty($module)) {
$this->template->display('error/module', array_merge($this->template->getAssigns(), [
'module' => $module,
'submodule' => $submodule,
'action' => $action
]));
return;
}
$module = $this->getModules()->getModule($module);
if(empty($module)) {
$this->template->display('error/module', array_merge($this->template->getAssigns(), [
'module' => $module,
'submodule' => $submodule,
'action' => $action
]));
return;
}
$permissions = $module->getInfo()->getPermissions();
$visible = true;
if(!empty($permissions)) {
$visible = false;
foreach($permissions as $permission) {
@@ -208,6 +254,7 @@
}
}
}
if(!$visible) {
$this->template->display('error/permissions', array_merge($this->template->getAssigns(), [
'module' => $module,
@@ -217,9 +264,11 @@
return;
}
if(method_exists($module->getInstance(), 'load')) {
$module->getInstance()->load($submodule, $action);
}
if(isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && method_exists($module->getInstance(), 'onPOST')) {
$module->getInstance()->onPOST(array_merge([
'module' => $module,
@@ -227,6 +276,7 @@
'action' => $action
], $_POST));
}
if(!method_exists($module->getInstance(), 'content') && !method_exists($module->getInstance(), 'frame')) {
$this->template->display('error/module_empty', array_merge($this->template->getAssigns(), [
'module' => $module,
@@ -236,42 +286,52 @@
return;
}
$this->template->display('module', array_merge($this->template->getAssigns(), [
'module' => $module,
'submodule' => $submodule,
'action' => $action
]));
});
$this->router->addRoute('^/lost-password(?:/([a-zA-Z0-9\-_]+))?$', function(?string $token = null) {
if(Auth::isLoggedIn()) {
Response::redirect('/');
}
$this->template->display('lost-password', [
'token' => $token
]);
});
$this->router->addRoute('/login', function() {
if(Auth::isLoggedIn()) {
Response::redirect('/');
}
$this->template->display('login');
});
$this->router->addRoute('/ajax', function() {
if(!Auth::isLoggedIn()) {
header('HTTP/1.1 403 Forbidden');
require_once(dirname(PATH).'/placeholder/errors/403.html');
exit();
}
if(empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') {
header('HTTP/1.1 405 Method Not Allowed');
require_once(dirname(PATH).'/placeholder/errors/405.html');
exit();
}
$this->getHooks()->runAction('ajax');
$this->router->run(true);
// Fire modals
if(!empty($_POST['modal'])) {
$modals = $this->getHooks()->applyFilter('modals', []);
if(count($modals) == 0) {
print 'No registred modals.';
} else {
@@ -279,9 +339,11 @@
if($modal->getName() === $_POST['modal']) {
$callback = $modal->getCallback('save');
$data = [];
foreach($_POST as $name => $value) {
if(is_array($value)) {
$data[trim($name)] = [];
foreach($value as $key => $entry) {
$data[trim($name)][trim($key)] = trim($entry);
}
@@ -289,31 +351,76 @@
$data[trim($name)] = trim($value);
}
}
$result = call_user_func_array($callback, [ $data ]);
if(is_bool($result)) {
print json_encode($result);
return;
}
print $result;
}
}
}
/* Module Info */
/* Modules */
} else if(!empty($_POST['search'])) {
$query = $_POST['search'];
$results = [];
$repositorys = Installer::getRepositorys();
foreach($repositorys as $entry) {
$repository = Installer::getRepository($entry->id);
$content = Installer::getFile($repository, 'modules.list');
if(!empty($content)) {
$modules = explode(PHP_EOL, $content.PHP_EOL);
foreach($modules as $name) {
if(empty($name)) {
continue;
}
// @ToDo Cache
$name = trim($name);
$info = Installer::getFile($repository, sprintf('%s/module.package', $name));
$info = json_decode($info);
if(!empty($info)) {
$info->installed = $this->getModules()->hasModule($name, true);
}
$info->icon_raw = $info->icon;
if(preg_match('/^(http|https|data):/', $info->icon)) {
$info->icon = sprintf('<img alt="" class="module-icon" src="%s" />', $info->icon);
} else if(str_starts_with($info->icon, '/')) {
$info->icon = sprintf('<img alt="" class="module-icon" src="/app/%s" />', $info->icon);
} else {
$info->icon = sprintf('<i class="bi bi-%s"></i>', $info->icon);
}
if($query === 'null' || (!empty($query) && preg_match('/'.$query.'/Uis', $info->name) || preg_match('/'.$query.'/Uis', $info->description))) {
$results[$name] = $info;
}
}
}
}
print json_encode($results);
/* Module Info */
} else if(!empty($_POST['module'])) {
if(!Auth::hasPermission('MODULES::INSTALL')) {
print 'NO_PERMISSIONS';
return;
}
$repositorys = Installer::getRepositorys();
if(count($repositorys) === 0) {
print 'NO_REPOSITORYS';
return;
}
$found = null;
$repo = null;
foreach($repositorys as $repository) {
$content = Installer::getFile($repository, 'modules.list');
if(!($content == Repository::FORBIDDEN || empty($content))) {
@@ -330,20 +437,24 @@
}
}
}
if(empty($found)) {
print 'MODULE_NOT_FOUND';
return;
}
$info = Installer::getFile($repo, sprintf('%s/module.package?time=%d', $found, time()));
if(empty($info)) {
print 'MODULE_EMPTY';
return;
}
$info = json_decode($info);
$screenshots = [];
$info->icon_raw = $info->icon;
/* Icon */
if(preg_match('/^(http|https|data):/', $info->icon)) {
$info->icon = sprintf('<img alt="" class="module-icon" src="%s" />', $info->icon);
@@ -352,26 +463,31 @@
} else {
$info->icon = sprintf('<i class="bi bi-%s"></i>', $info->icon);
}
/* Readme */
$readme = Installer::getFile($repo, sprintf('%s/README.md?time=%d', $found, time()));
if(empty($readme)) {
$readme = '';
}
/* Screenshots */
if(isset($info->screenshots)) {
// Fix Github Repos
if(preg_match('/github\.com\/([^\/]+)\/(.*)(?:\/)?$/Uis', $repo->url, $matches)) {
$repo->url = sprintf('https://raw.githubusercontent.com/%s/%s/master', $matches[1], rtrim($matches[2], '/'));
}
foreach($info->screenshots as $screenshot) {
$screenshots[] = sprintf('%s/%s/%s?raw=true', $repo->url, $found, $screenshot->file);
}
}
/* Changelog */
$changelog = Installer::getFile($repo, sprintf('%s/CHANGELOG.md?time=%d', $found, time()));
if(empty($changelog)) {
$changelog = '';
}
/* Features */
// @ToDo Analyze module files for these informations...
$features = [
@@ -439,5 +555,4 @@
$this->cache->set('settings', $name, $value);
}
}
?>
?>
+2 -2
View File
@@ -47,7 +47,7 @@
'tab' => $tab,
'action' => $action,
'admin' => true
]) == false) {
]) === false) {
$this->getTemplate()->display('error/404');
}
});
@@ -60,7 +60,7 @@
if($this->getTemplate()->display('server' . (!empty($destination) ? sprintf('/%s', $destination) : ''), [
'tab' => $tab,
'action' => $action
]) == false) {
]) === false) {
$this->getTemplate()->display('error/404');
}
});
+3 -6
View File
@@ -8,9 +8,6 @@
*/
namespace fruithost\System;
use fruithost\Storage\Database;
use fruithost\System\Core;
class Update {
protected static Core $core;
@@ -45,7 +42,7 @@
'host' => $_SERVER['SERVER_NAME'],
'ip' => $_SERVER['SERVER_ADDR'],
'admin' => $_SERVER['SERVER_ADMIN'],
'ssl' => isset($_SERVER['HTTPS']) ? ($_SERVER['HTTPS'] == 'on') : false
'ssl' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
]);
/* Refresh License */
@@ -73,7 +70,7 @@
if($json != NULL) {
$response = $json;
}
} catch(Exception $e) {
} catch(\Exception $e) {
}
@@ -93,7 +90,7 @@
'host' => $_SERVER['SERVER_NAME'],
'ip' => $_SERVER['SERVER_ADDR'],
'admin' => $_SERVER['SERVER_ADMIN'],
'ssl' => isset($_SERVER['HTTPS']) ? ($_SERVER['HTTPS'] == 'on') : false
'ssl' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'
]);
self::$core->setSettings('UPDATE_LICENSE', $result->license);
+27
View File
@@ -12,6 +12,33 @@
use fruithost\Localization\I18N;
class Utils {
public static function getMimeType($file) : string {
$mime = mime_content_type($file);
$extension = pathinfo($file, PATHINFO_EXTENSION);
$additional = [
'js' => 'application/javascript',
'json' => 'application/json',
'xml' => 'application/xml',
'css' => 'text/css'
];
/* Set MIME-Type when not given */
if(!$mime) {
if(array_key_exists($extension, $additional)) {
return $additional[$extension];
}
}
/* Override misconfigured MIME-Types */
if(array_key_exists($extension, $additional)) {
if($mime !== $additional[$extension]) {
return $additional[$extension];
}
}
return $mime;
}
public static function randomString(int $length = 10) : string {
$characters = 'abcdefghijklmonpqrstuvwxyz-_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$charactersLength = strlen($characters);
+57 -53
View File
@@ -27,16 +27,16 @@
private ?string $path = null;
public function __construct(Core $core) {
$this->path = PATH;
$this->path = dirname(PATH);
$this->core = $core;
$this->files = new TemplateFiles();
$this->navigation = new TemplateNavigation($this->core);
$this->theme = $this->core->getHooks()->applyFilter('theme_name', 'default');
$this->theme = $this->core->getHooks()->applyFilter('theme_name', $this->getDefaultTheme());
$this->assigns = [
'project_name' => $this->core->getSettings('PROJECT_NAME', 'fruithost'),
'project_copyright' => $this->core->getSettings('PROJECT_COPYRIGHT', true)
];
// gzip, deflate, br, zstd
if(isset($_SERVER['HTTP_ACCEPT_ENCODING']) && substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
@ob_start('ob_gzhandler');
@@ -55,11 +55,8 @@
$this->files->addStylesheet('bootstrap', $this->url('css/bootstrap/bootstrap.min.css'), '5.3.2');
$this->files->addStylesheet('bootstrap-icons', $this->url('fonts/bootstrap-icons/bootstrap-icons.css'), '1.11.1', [ 'bootstrap' ]);
$this->files->addStylesheet('cascadia-mono', $this->url('fonts/cascadia-mono/cascadia-mono.css'), '2111.01', [ 'bootstrap' ]);
$this->files->addStylesheet('global', $this->url('css/global.css'), '1.0.0', [ 'bootstrap' ]);
$this->files->addJavascript('bootstrap', $this->url('js/bootstrap/bootstrap.bundle.min.js'), '5.3.2', [], TemplateFiles::FOOTER);
$this->files->addJavascript('global', $this->url('js/global.js'), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER);
$this->files->addJavascript('ajax', $this->url('js/ajax.js'), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER);
$this->files->addJavascript('bootstrap', $this->url('js/bootstrap/bootstrap.bundle.min.js'), '5.3.2', [], TemplateFiles::FOOTER);
$this->navigation->addCategory('account', I18N::get('Account'));
$this->navigation->addCategory('database', I18N::get('Databases'));
$this->navigation->addCategory('domain', I18N::get('Domains'));
@@ -72,11 +69,26 @@
$this->navigation->addCategory('server', I18N::get('Server'));
$this->assign('topbar', $this->navigation->getCategory('account'));
$this->assign('navigation', $this->navigation);
$this->assign('navigation', $this->navigation);
$this->core->getHooks()->runAction('template_init');
$this->core->getHooks()->runAction('template_init', $this);
}
public function getDefaultTheme() {
$theme = 'default';
if(isset($_GET['theme'])) {
$theme = $_GET['theme'];
}
// @ToDo Check if Theme-Directory exists
if($theme != 'default') {
}
return $theme;
}
public function getCore() : Core {
return $this->core;
}
@@ -93,6 +105,23 @@
return $this->assigns;
}
public function resolveThemePath($file, $handler = false) : string {
$entry = 'default';
if($handler) {
$entry = 'handler';
$theme = sprintf('%1$s%2$sthemes%2$shandler%2$s%4$s%2$s%3$s.php', $this->path, DS, $file, $this->theme);
} else {
$theme = sprintf('%1$s%2$sthemes%2$s%4$s%2$s%3$s.php', $this->path, DS, $file, $this->theme);
}
if(!file_exists($theme)) {
$theme = sprintf('%1$s%4$s%2$s%3$s.php', PATH, DS, $file, $entry);
}
return $theme;
}
public function setPath($path) {
$this->path = $path;
}
@@ -122,7 +151,16 @@
public function display(string $file, array $arguments = [], bool $basedir = true, bool $once = true) : ?bool {
$template = $this;
$functions = $this->resolveThemePath('functions');
if(file_exists($functions)) {
if($once) {
require_once($functions);
} else {
require($functions);
}
}
$this->core->getHooks()->runAction('html_render');
foreach($arguments AS $name => $value) {
@@ -130,13 +168,13 @@
}
if($basedir) {
$path = sprintf('%1$s%2$sthemes%2$s%4$s%2$s%3$s.php', $this->path, DS, $file, $this->theme);
$path = $this->resolveThemePath($file);
} else {
$path = $file;
$path = $file;
}
$handler = sprintf('%1$shandler%2$s%3$s.php', $this->path, DS, $file);
$handler = $this->resolveThemePath($file, true);
foreach($this->assigns AS $name => $value) {
${$name} = $value;
}
@@ -165,27 +203,6 @@
}
return true;
} else {
if(!Auth::isLoggedIn()) {
$this->getFiles()->addStylesheet('login', $this->url('css/login.css'), '2.0.0', [ 'bootstrap' ]);
$this->getFiles()->addJavascript('login', $this->url('js/login.js'), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER);
} else {
$this->getFiles()->addStylesheet('style', $this->url('css/style.css'), '2.0.0', [ 'bootstrap' ]);
$this->getFiles()->addJavascript('ui', $this->url('js/ui.js'), '2.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER);
$this->getFiles()->addJavascript('codemirror', $this->url('js/codemirror/build/bundle.min.js'), '6.0.0', [ 'ui' ], TemplateFiles::FOOTER);
}
$path = sprintf('%1$s%2$sdefault%2$s%3$s.php', $this->path, DS, $file);
if(file_exists($path) && is_readable($path)) {
if($once) {
require_once($path);
} else {
require($path);
}
return true;
}
}
return false;
@@ -193,27 +210,20 @@
public function header() : void {
$template = $this;
$path = sprintf('%1$s%2$sthemes%2$s%4$s%2$s%3$s.php', $this->path, DS, 'header', $this->theme);
$path = $this->resolveThemePath('header');
foreach($this->assigns AS $name => $value) {
${$name} = $value;
}
if(file_exists($path)) {
@require_once($path);
} else {
$path = sprintf('%1$s%2$sdefault%2$s%3$s.php', $this->path, DS, 'header');
if(file_exists($path)) {
@require_once($path);
}
}
}
public function footer() : void {
$template = $this;
$path = sprintf('%1$s%2$sthemes%2$s%4$s%2$s%3$s.php', $this->path, DS, 'footer', $this->theme);
$path = $this->resolveThemePath('footer');
foreach($this->assigns AS $name => $value) {
${$name} = $value;
@@ -221,12 +231,6 @@
if(file_exists($path)) {
require_once($path);
} else {
$path = sprintf('%1$s%2$sdefault%2$s%3$s.php', $this->path, DS, 'footer');
if(file_exists($path)) {
@require_once($path);
}
}
}
@@ -17,7 +17,6 @@
}
public function head_description() : void {
$description = I18N::get('fruithost is an OpenSource Hosting-Panel for some Linux Distributions (later maybe also for Windows). With fruithost you get a real alternative to confixx/Plesk, ISPConfig, froxlor and other derivatives.');
$description .= I18N::get('Whether private or professional, with fruithost you get a clean and simple solution to manage your servers.');
$description .= I18N::get('Security comes first at fruithost - that\'s why we only use up-to-date software and do without miserable old solutions. If you have an MIT license, you can use fruithost for any scenario!');
@@ -198,5 +198,4 @@
return $results;
}
}
?>
?>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\Accounting\Auth;
+51 -70
View File
@@ -1,27 +1,33 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
$template->header();
if(!Auth::hasPermission('MODULES::*')) {
?>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<?php
$template->footer();
exit();
}
if(Auth::hasPermission('MODULES::HANDLE') && isset($_GET['settings'])) {
?>
<form method="post"
action="<?php print $this->url('/admin/modules'.(!empty($tab) ? '/'.$tab : '').'?settings='.$_GET['settings']); ?>">
<form method="post" action="<?php print $this->url('/admin/modules'.(!empty($tab) ? '/'.$tab : '').'?settings='.$_GET['settings']); ?>">
<header class="page-header d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"
style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<ol class="breadcrumb" style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item">
<a href="<?php print $this->url('/admin/modules'); ?>"><?php I18N::__('Modules'); ?></a>
</li>
@@ -35,10 +41,8 @@
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<a class="btn btn-sm btn-outline-danger"
href="<?php print $this->url('/admin/modules'.(!empty($tab) ? '/'.$tab : '')); ?>"><?php I18N::__('Cancel'); ?></a>
<button type="submit" name="action" value="settings"
class="btn btn-sm btn-outline-success"><?php I18N::__('Save'); ?></button>
<a class="btn btn-sm btn-outline-danger" href="<?php print $this->url('/admin/modules'.(!empty($tab) ? '/'.$tab : '')); ?>"><?php I18N::__('Cancel'); ?></a>
<button type="submit" name="action" value="settings" class="btn btn-sm btn-outline-success"><?php I18N::__('Save'); ?></button>
</div>
</div>
</header>
@@ -83,11 +87,9 @@
<form method="post" action="<?php print $this->url('/admin/modules'.(!empty($tab) ? '/'.$tab : '')); ?>">
<header class="page-header d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"
style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<ol class="breadcrumb" style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item active" aria-current="page">
<a class="active"
href="<?php print $this->url('/admin/modules'); ?>"><?php I18N::__('Modules'); ?></a>
<a class="active" href="<?php print $this->url('/admin/modules'); ?>"><?php I18N::__('Modules'); ?></a>
</li>
</ol>
</nav>
@@ -97,40 +99,30 @@
case 'install':
if(!Auth::hasPermission('MODULES::INSTALL')) {
?>
<a href="<?php print $this->url('/admin/modules'); ?>"
class="btn btn-sm btn-outline-secondary mx-2"><?php I18N::__('Back'); ?></a>
<a href="<?php print $this->url('/admin/modules/install'); ?>"
class="btn btn-sm btn-outline-primary mx-2"><?php I18N::__('Refresh'); ?></a>
<a href="<?php print $this->url('/admin/modules'); ?>" class="btn btn-sm btn-outline-secondary mx-2"><?php I18N::__('Back'); ?></a>
<a href="<?php print $this->url('/admin/modules/install'); ?>" class="btn btn-sm btn-outline-primary mx-2"><?php I18N::__('Refresh'); ?></a>
<?php
}
break;
case 'repositorys':
if(Auth::hasPermission('MODULES::REPOSITORYS')) {
?>
<div class="btn-group mr-2">
<button type="button" name="add_repository" data-bs-toggle="modal"
data-bs-target="#add_repository"
class="btn btn-sm btn-outline-primary"><?php I18N::__('Add new'); ?></button>
<button type="submit" name="action" value="update"
class="btn btn-sm btn-outline-success"><?php I18N::__('Update'); ?></button>
<button type="submit" name="action" value="delete"
class="btn btn-sm btn-outline-danger"><?php I18N::__('Delete'); ?></button>
</div>
<div class="btn-group mr-2">
<button type="button" name="add_repository" data-bs-toggle="modal" data-bs-target="#add_repository" class="btn btn-sm btn-outline-primary"><?php I18N::__('Add new'); ?></button>
<button type="submit" name="action" value="update" class="btn btn-sm btn-outline-success"><?php I18N::__('Update'); ?></button>
<button type="submit" name="action" value="delete" class="btn btn-sm btn-outline-danger"><?php I18N::__('Delete'); ?></button>
</div>
<?php
}
break;
default:
if(Auth::hasPermission('MODULES::INSTALL')) {
?>
<a href="<?php print $this->url('/admin/modules/install'); ?>"
class="btn btn-sm btn-outline-primary mx-2"><?php I18N::__('Install'); ?></a>
<div class="btn-group mr-2">
<button type="submit" name="action" value="upgrade"
class="btn btn-sm btn-outline-success"><?php I18N::__('Upgrade'); ?></button>
<button type="submit" name="action" value="delete"
class="btn btn-sm btn-outline-danger"><?php I18N::__('Delete'); ?></button>
</div>
<a href="<?php print $this->url('/admin/modules/install'); ?>" class="btn btn-sm btn-outline-primary mx-2"><?php I18N::__('Install'); ?></a>
<div class="btn-group mr-2">
<button type="submit" name="action" value="upgrade" class="btn btn-sm btn-outline-success"><?php I18N::__('Upgrade'); ?></button>
<button type="submit" name="action" value="delete" class="btn btn-sm btn-outline-danger"><?php I18N::__('Delete'); ?></button>
</div>
<?php
}
break;
@@ -142,33 +134,25 @@
if(Auth::hasPermission('MODULES::INSTALL') && $tab == 'install') {
?>
<div class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" role="tab"><?php I18N::__('Popular'); ?></a>
<li class="nav-item modules-popular"><a class="nav-link active" id="popular-tab" role="tab" data-bs-toggle="tab" data-bs-target="#popular" role="tab"><?php I18N::__('Popular'); ?></a></li>
<li class="nav-item modules-results"><a class="nav-link disabled" aria-disabled="true" role="tab" id="results-tab" role="tab" data-bs-toggle="tab" data-bs-target="#results" aria-disabled="true"><?php I18N::__('Results'); ?></a></li>
<li class="nav-item ms-auto">
<div class="input-group" id="module-search">
<input name="query" type="text" class="form-control form-control-sm" placeholder="<?php I18N::__('Search terms...'); ?>" aria-label="<?php I18N::__('Search'); ?>" aria-describedby="basic-addon1">
<button class="btn btn-sm btn-outline-success" type="button" id="button-addon2"><?php I18N::__('Search'); ?></button>
</div>
</li>
<!--<li class="nav-item"><a class="nav-link disabled" aria-disabled="true" role="tab"
aria-disabled="true"><?php I18N::__('Results'); ?></a></li>-->
<!--<li class="nav-item ms-auto">
<form role="search">
<div class="input-group">
<input type="text" class="form-control form-control-sm"
placeholder="<?php I18N::__('Search terms...'); ?>"
aria-label="<?php I18N::__('Search'); ?>"
aria-describedby="basic-addon1">
<button class="btn btn-sm btn-outline-success" type="button"
id="button-addon2"><?php I18N::__('Search'); ?></button>
</div>
</form>
</li>-->
</div>
<?php
} else {
?>
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link<?php print (empty($tab) ? ' active' : ''); ?>" id="globals-tab"
href="<?php print $this->url('/admin/modules'); ?>" role="tab">
<a class="nav-link<?php print (empty($tab) ? ' active' : ''); ?>" id="globals-tab" href="<?php print $this->url('/admin/modules'); ?>" role="tab">
<?php
I18N::__('Installed Modules');
$updates = count((array) $upgradeable);
if($updates > 0) {
printf('<span class="badge rounded-pill text-bg-warning">%d</span>', $updates);
}
@@ -179,23 +163,19 @@
if(Auth::hasPermission('MODULES::REPOSITORY')) {
?>
<li class="nav-item">
<a class="nav-link<?php print (!empty($tab) && $tab === 'repositorys' ? ' active' : ''); ?>"
id="security-tab" href="<?php print $this->url('/admin/modules/repositorys'); ?>"
role="tab">
<?php I18N::__('Repositorys'); ?>
</a>
<a class="nav-link<?php print (!empty($tab) && $tab === 'repositorys' ? ' active' : ''); ?>" id="security-tab" href="<?php print $this->url('/admin/modules/repositorys'); ?>" role="tab"><?php I18N::__('Repositorys'); ?></a>
</li>
<?php
}
if(Auth::hasPermission('MODULES::ERRORS')) {
?>
<li class="nav-item">
<a class="nav-link<?php print (!empty($tab) && $tab === 'errors' ? ' active' : ''); ?>"
id="errors-tab" href="<?php print $this->url('/admin/modules/errors'); ?>"
role="tab">
<a class="nav-link<?php print (!empty($tab) && $tab === 'errors' ? ' active' : ''); ?>" id="errors-tab" href="<?php print $this->url('/admin/modules/errors'); ?>" role="tab">
<?php
I18N::__('Errors');
$error_count = count((array) $errors);
if($error_count > 0) {
printf('<span class="badge rounded-pill text-bg-warning">%d</span>', $error_count);
}
@@ -208,13 +188,14 @@
</ul>
<?php
}
if(isset($error)) {
?>
<div class="alert alert-danger mt-4" role="alert"><?php print $error; ?></div>
<div class="alert alert-danger mt-4" role="alert"><?php print $error; ?></div>
<?php
} else if(isset($success)) {
?>
<div class="alert alert-success mt-4" role="alert"><?php print $success; ?></div>
<div class="alert alert-success mt-4" role="alert"><?php print $success; ?></div>
<?php
}
?>
@@ -226,7 +207,7 @@
if(Auth::hasPermission('MODULES::INSTALL')) {
$template->display('admin/modules/install');
}
break;
break;
case 'repositorys':
if(Auth::hasPermission('MODULES::REPOSITORY')) {
if(count($repositorys) === 0) {
@@ -235,7 +216,7 @@
$template->display('admin/modules/repository/list');
}
}
break;
break;
case 'errors':
if(Auth::hasPermission('MODULES::ERRORS')) {
if(count($errors) === 0) {
@@ -244,14 +225,14 @@
$template->display('admin/modules/errors/list');
}
}
break;
break;
default:
if(count($modules->getList()) === 0) {
$template->display('admin/modules/empty');
} else {
$template->display('admin/modules/list');
}
break;
break;
}
?>
</div>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
?>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
?>
+8 -2
View File
@@ -1,5 +1,12 @@
<?php
use fruithost\Accounting\Auth;
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
@@ -40,7 +47,6 @@
return $args;
}
?>
<div class="border rounded overflow-hidden mt-5 mb-5">
<table class="table table-borderless table-striped table-hover mb-0">
+18 -99
View File
@@ -1,75 +1,14 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
?>
<style>
div#module_info .module-icon {
font-size: 100px;
}
div#module_info .carousel-caption {
right: 0;
bottom: 0;
left: 0;
color: #fff !important;
background: rgba(0, 0, 0, 0.5);
}
div#module_info .modal-loading[data-fetching="true"] {
display: flex;
justify-content: center !important;
}
div#module_info .modal-loading[data-fetching="true"] [role="status"] {
display: block;
width: 5rem;
height: 5rem;
margin: 50px 0;
}
div#module_info .modal-loading[data-fetching="false"] [role="status"] {
display: none;
}
div#module_info .modal-loading[data-fetching="true"] .loaded-content {
display: none;
}
div#module_info .modal-loading[data-fetching="false"] .loaded-content {
display: block;
}
div#module_info div.markdown {
background: var(--bs-modal-footer-border-color);
padding: 20px;
font-size: 16px;
}
div#module_info div.markdown h1 {
font-size: 1.4rem;
}
div#module_info div.markdown h2 {
font-size: 1.3rem;
}
div#module_info div.markdown h3 {
font-size: 1.2rem;
}
div#module_info div.markdown h4 {
font-size: 1.1rem;
}
div#module_info div.markdown h5 {
font-size: 1.0rem;
}
div#module_info div.markdown h6 {
font-size: 0.9rem;
}
</style>
<div class="modal-loading" data-fetching="true">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden"><?php I18N::__('Loading'); ?>...</span>
@@ -82,8 +21,7 @@
<div class="col">
<h4><span class="module-name"></span> <span class="badge text-bg-light module-version"></span></h4>
<p class="small text-nowrap">
<?php I18N::__('From'); ?> <span class="module-author"></span> | <span
class="module-url"></span>
<?php I18N::__('From'); ?> <span class="module-author"></span> | <span class="module-url"></span>
</p>
<p class="module-description"></p>
</div>
@@ -93,57 +31,38 @@
<!-- Tabs -->
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item d-none details-tab" role="presentation">
<button class="nav-link" id="details-tab" data-bs-toggle="tab" data-bs-target="#details-pane"
type="button"
role="tab" aria-controls="details-pane" aria-selected="true"><?php I18N::__('Details'); ?>
</button>
<button class="nav-link" id="details-tab" data-bs-toggle="tab" data-bs-target="#details-pane" type="button" role="tab" aria-controls="details-pane" aria-selected="true"><?php I18N::__('Details'); ?></button>
</li>
<li class="nav-item d-none features-tab" role="presentation">
<button class="nav-link" id="features-tab" data-bs-toggle="tab" data-bs-target="#features-pane"
type="button"
role="tab" aria-controls="features-pane" aria-selected="false"><?php I18N::__('Features'); ?>
</button>
<button class="nav-link" id="features-tab" data-bs-toggle="tab" data-bs-target="#features-pane" type="button" role="tab" aria-controls="features-pane" aria-selected="false"><?php I18N::__('Features'); ?></button>
</li>
<li class="nav-item d-none screenshots-tab" role="presentation">
<button class="nav-link" id="screenshots-tab" data-bs-toggle="tab" data-bs-target="#screenshots-pane"
type="button"
role="tab" aria-controls="screenshots-pane"
aria-selected="false"><?php I18N::__('Screenshots'); ?>
</button>
<button class="nav-link" id="screenshots-tab" data-bs-toggle="tab" data-bs-target="#screenshots-pane" type="button" role="tab" aria-controls="screenshots-pane" aria-selected="false"><?php I18N::__('Screenshots'); ?></button>
</li>
<li class="nav-item d-none changelog-tab" role="presentation">
<button class="nav-link" id="changelog-tab" data-bs-toggle="tab" data-bs-target="#changelog-pane"
type="button"
role="tab" aria-controls="changelog-pane" aria-selected="false"><?php I18N::__('Changelog'); ?>
</button>
<button class="nav-link" id="changelog-tab" data-bs-toggle="tab" data-bs-target="#changelog-pane" type="button" role="tab" aria-controls="changelog-pane" aria-selected="false"><?php I18N::__('Changelog'); ?></button>
</li>
</ul>
<!-- Content -->
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade markdown" id="details-pane" role="tabpanel" aria-labelledby="details-tab"
tabindex="0"></div>
<div class="tab-pane fade" id="screenshots-pane" role="tabpanel" aria-labelledby="screenshots-tab"
tabindex="0">
<div class="tab-pane fade markdown" id="details-pane" role="tabpanel" aria-labelledby="details-tab" tabindex="0"></div>
<div class="tab-pane fade" id="screenshots-pane" role="tabpanel" aria-labelledby="screenshots-tab" tabindex="0">
<div id="screenshotHolder" class="carousel slide carousel-dark">
<div class="carousel-inner"></div>
<button class="carousel-control-prev" type="button" data-bs-target="#screenshotHolder"
data-bs-slide="prev">
<button class="carousel-control-prev" type="button" data-bs-target="#screenshotHolder" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden"><?php I18N::__('Previous'); ?></span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#screenshotHolder"
data-bs-slide="next">
<button class="carousel-control-next" type="button" data-bs-target="#screenshotHolder" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden"><?php I18N::__('Next'); ?></span>
</button>
</div>
</div>
<div class="tab-pane fade" id="features-pane" role="tabpanel" aria-labelledby="features-tab"
tabindex="0"></div>
<div class="tab-pane fade markdown" id="changelog-pane" role="tabpanel" aria-labelledby="changelog-tab"
tabindex="0"></div>
<div class="tab-pane fade" id="features-pane" role="tabpanel" aria-labelledby="features-tab" tabindex="0"></div>
<div class="tab-pane fade markdown" id="changelog-pane" role="tabpanel" aria-labelledby="changelog-tab" tabindex="0"></div>
</div>
</div>
</div>
+198 -93
View File
@@ -1,98 +1,203 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Installer\Installer;
use fruithost\Localization\I18N;
$results = [];
foreach($repositorys as $entry) {
$repository = Installer::getRepository($entry->id);
$content = Installer::getFile($repository, 'modules.list');
if(!empty($content)) {
$modules = explode(PHP_EOL, $content.PHP_EOL);
foreach($modules as $name) {
if(empty($name)) {
continue;
}
// @ToDo Cache
$name = trim($name);
$info = Installer::getFile($repository, sprintf('%s/module.package', $name));
$results[$name] = $info;
}
}
}
// @ToDo Make Ajax Request
use fruithost\UI\Icon;
?>
<div class="container mt-5 mb-5">
<div class="row row-cols-1 row-cols-md-3 g-4">
<?php
if(!empty($results)) {
foreach($results as $name => $result) {
if(empty($result)) {
continue;
}
$json = json_decode($result, false);
if(empty($result)) {
continue;
}
$installed = $this->getCore()->getModules()->hasModule($name, true);
?>
<div class="col">
<div class="card">
<div class="card-body row">
<div class="col-2 fs-1">
<?php
if(preg_match('/^(http|https|data):/', $json->icon)) {
print sprintf('<img alt="" class="module-icon" src="%s" />', $json->icon);
} else if(str_starts_with($json->icon, '/')) {
print sprintf('<img alt="" class="module-icon" src="/app/%s" />', $json->icon);
} else {
printf('<i class="bi bi-%s"></i>', $json->icon);
}
?>
</div>
<div class="col-6">
<h5 class="card-title"><a><?php print $json->name; ?></a></h5>
<p style="height: 120px;" class="card-text"><?php print $json->description; ?></p>
</div>
<div class="col-4">
<div class="d-grid gap-2 mx-auto">
<?php
if($installed) {
?>
<a href="<?php print $this->url(sprintf('/admin/modules/?deinstall=%s', $name)); ?>"
class="btn btn-outline-danger btn-sm"
data-confirm="<?php printf(I18N::get('Do you really wan\'t to delete the Module %s?'), sprintf('<strong>%s</strong>', $json->name)); ?>"><?php I18N::__('Deinstall'); ?></a>
<?php
} else {
?>
<a href="<?php print $this->url(sprintf('/admin/modules/?install=%s', $name)); ?>"
class="btn btn-success btn-sm"
data-loading="<?php I18N::__('Installing'); ?>"><?php I18N::__('Install'); ?></a>
<?php
}
?>
<button type="button" name="module_info" data-bs-toggle="modal"
data-bs-target="#module_info" data-module="<?php print $name; ?>"
class="btn btn-outline-light btn-sm"><?php I18N::__('Info'); ?></button>
</div>
</div>
</div>
<div class="card-footer text-body-secondary">
<div class="row">
<div class="col small text-nowrap">
<?php I18N::__('From'); ?> <?php printf('<a href="mailto:%s" class="text-decoration-none" target="_blank">%s</a>', $json->author->email, $json->author->name); ?>
| <?php printf('<a href="%1$s" class="text-decoration-none" target="_blank">%1$s</a>', $json->author->url); ?>
</div>
<div class="col text-end">
<span class="badge text-bg-secondary"><?php print $json->version; ?></span>
</div>
</div>
</div>
</div>
</div>
<?php
}
}
?>
<div id="module-results" data-fetching="true">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden"><?php I18N::__('Loading'); ?>...</span>
</div>
</div>
<div class="loaded-content tab-content">
<div class="container mt-5 mb-5 tab-pane show active" id="popular" role="tabpanel" aria-labelledby="popular-tab">
<div class="row row-cols-1 row-cols-md-3 g-4 modules-list"></div>
</div>
<div class="container mt-5 mb-5 tab-pane" id="results" role="tabpanel" aria-labelledby="results-tab">
<div class="row row-cols-1 row-cols-md-3 g-4 results-list"></div>
</div>
</div>
</div>
<script type="text/javascript">
(() => {
'use strict';
function search(query) {
document.querySelector('#module-results').dataset.fetching = true;
let container = document.querySelector('div.modules-list');
let results = document.querySelector('div.results-list');
let template = document.querySelector('template#module-entry');
let no_results = document.querySelector('template#no_results');
let res = document.querySelector('.modules-results a.nav-link');
let url = document.querySelector('input[name="url"]').value;
if (typeof (query) === 'undefined') {
query = null;
}
if (query == null) {
container.innerHTML = '';
} else {
results.innerHTML = '';
res.ariaDisabled = false;
res.classList.remove('disabled');
}
new Ajax('/ajax').onSuccess((response) => {
if (Object.keys(response).length == 0) {
const entry = no_results.content.cloneNode(true);
if (query == null) {
container.classList.remove('row-cols-1');
container.classList.remove('row-cols-md-3');
container.classList.remove('g-4');
container.appendChild(entry);
} else {
results.classList.remove('row-cols-1');
results.classList.remove('row-cols-md-3');
results.classList.remove('g-4');
entry.querySelector('.lead').innerHTML = '<?php I18N::__('Searching for "%s" did not return any results.'); ?>'.replace('%s', '<strong>' + query + '</strong>');
results.appendChild(entry);
}
} else {
if (query == null) {
container.classList.add('row-cols-1');
container.classList.add('row-cols-md-3');
container.classList.add('g-4');
} else {
results.classList.add('row-cols-1');
results.classList.add('row-cols-md-3');
results.classList.add('g-4');
}
Object.keys(response).forEach((name) => {
let module = response[name];
const entry = template.content.cloneNode(true);
entry.querySelector('div.module-icon').innerHTML = module.icon;
entry.querySelector('a.module-name').innerText = module.name;
entry.querySelector('p.module-description').innerText = module.description;
entry.querySelector('span.module-version').innerText = module.version;
entry.querySelector('span.module-author').innerHTML = '<a href="mailto:' + module.author.email + '" class="text-decoration-none" target="_blank">' + module.author.name + '</a>';
entry.querySelector('span.module-web').innerHTML = '<a href="' + module.author.url + '" class="text-decoration-none" target="_blank">' + module.author.url + '</a>';
/* Buttons */
let buttons = [];
if (module.installed) {
let deinstall = document.createElement('a');
deinstall.href = url + '?deinstall=' + name;
deinstall.classList.add('btn');
deinstall.classList.add('btn-sm');
deinstall.classList.add('btn-outline-danger');
deinstall.dataset.confirm = '<?php I18N::__('Do you really wan\\\'t to delete this Module?'); ?>';
deinstall.innerText = '<?php I18N::__('Deinstall'); ?>';
buttons.push(deinstall);
Confirm(deinstall);
} else {
let install = document.createElement('a');
install.href = url + '?install=' + name;
install.classList.add('btn');
install.classList.add('btn-sm');
install.classList.add('btn-success');
install.innerText = '<?php I18N::__('Install'); ?>';
install.dataset.loading = '<?php I18N::__('Installing'); ?>';
buttons.push(install);
Loading(install);
}
let info = document.createElement('button');
info.type = 'button';
info.name = 'module_info';
info.classList.add('btn');
info.classList.add('btn-sm');
info.classList.add('btn-outline-light');
info.innerText = '<?php I18N::__('Info'); ?>';
info.dataset.module = name;
buttons.push(info);
info.addEventListener('click', (event) => {
new bootstrap.Modal('#module_info').toggle(event.target);
});
buttons.forEach((button) => {
entry.querySelector('div.module-buttons').appendChild(button);
});
if (query == null) {
container.appendChild(entry);
} else {
results.appendChild(entry);
}
});
}
if (query != null) {
new bootstrap.Tab(res).show();
}
document.querySelector('#module-results').dataset.fetching = false;
}).post({
'search': query
});
}
window.addEventListener('DOMContentLoaded', () => {
search();
document.querySelector('div#module-search input[name="query"]').addEventListener('keypress', (event) => {
if (event.which == 13) {
event.preventDefault();
search(event.target.value);
event.target.value = '';
return false;
}
});
document.querySelector('div#module-search button[type="button"]').addEventListener('click', (event) => {
event.preventDefault();
search(event.target.value);
return false;
});
});
})();
</script>
<input type="hidden" name="url" value="<?php print $this->url('/admin/modules/'); ?>"/>
<template id="no_results">
<div class="jumbotron text-center bg-transparent text-muted">
<?php Icon::show('smiley-bad'); ?>
<h2><?php I18N::__('No Modules available!'); ?></h2>
<p class="lead"></p>
</div>
</template>
<template id="module-entry">
<div class="col">
<div class="card">
<div class="card-body row">
<div class="col-2 fs-1 module-icon"></div>
<div class="col-6">
<h5 class="card-title"><a class="module-name"></a></h5>
<p style="height: 120px;" class="card-text module-description"></p>
</div>
<div class="col-4">
<div class="d-grid gap-2 mx-auto module-buttons"></div>
</div>
</div>
<div class="card-footer text-body-secondary">
<div class="row">
<div class="col small text-nowrap">
<?php I18N::__('From'); ?> <span class="module-author"></span> | <span class="module-web"></span>
</div>
<div class="col text-end">
<span class="badge text-bg-secondary module-version"></span>
</div>
</div>
</div>
</div>
</div>
</template>
+10 -2
View File
@@ -1,6 +1,14 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
?>
<div class="text-center mt-5">
<div class="spinner-border justify-content-center text-primary mt-5" role="status"
style="width: 6rem; height: 6rem;">
<div class="spinner-border justify-content-center text-primary mt-5" role="status" style="width: 6rem; height: 6rem;">
<span class="visually-hidden">Loading...</span>
</div>
</div>
+90 -86
View File
@@ -1,100 +1,104 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
?>
<div class="border rounded overflow-hidden mt-5 mb-5">
<table class="table table-borderless table-striped table-hover mb-0">
<thead>
<tr>
<th class="bg-secondary-subtle" scope="col" colspan="3"><?php I18N::__('Module'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Description'); ?></th>
</tr>
<tr>
<th class="bg-secondary-subtle" scope="col" colspan="3"><?php I18N::__('Module'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Description'); ?></th>
</tr>
</thead>
<tbody>
<?php
foreach($modules->getList() as $module) {
$info = $module->getInfo();
?>
<tr>
<td scope="row" width="1px">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="module_<?php print $info->getName(); ?>"
name="module[]" value="<?php print $info->getName(); ?>"/>
<label class="form-check-label" for="module_<?php print $info->getName(); ?>"></label>
</div>
</td>
<td>
<span class="d-block badge badge-pill module-badge text-bg-<?php print ($module->isLocked() ? 'warning' : ($module->isEnabled() ? 'success' : 'danger')); ?>"
data-toggle="tooltip"
title="<?php ($module->isLocked() ? I18N::__('Module has depencies.') : ($module->isEnabled() ? I18N::__('Module is enabled.') : I18N::__('Module is disabled.'))); ?>"></span>
</td>
<td>
<strong><?php print $info->getName(); ?></strong>
<p class="module-actions"><?php
$links = [];
if(Auth::hasPermission('MODULES::HANDLE')) {
if($module->isEnabled()) {
$links['disable'] = sprintf('<a href="%s" data-confirm="%s" class="text-warning">%s</a>', $this->url('/admin/modules/?disable='.$module->getDirectory()), I18N::get('Do you really wan\'t to disable the module?'), I18N::get('Disable'));
} else {
$links['enable'] = sprintf('<a href="%s" class="text-success">%s</a>', $this->url('/admin/modules/?enable='.$module->getDirectory()), I18N::get('Enable'));
}
if($module->hasSettingsPath()) {
$links['settings'] = sprintf('<a href="%s" class="text-primary">%s</a>', $this->url('/admin/modules/?settings='.$module->getDirectory()), I18N::get('Settings'));
}
$links['check'] = sprintf('<a href="%s" class="text-primary">%s</a>', $this->url('/admin/modules/?check='.$module->getDirectory()), I18N::get('Check'));
}
if(Auth::hasPermission('MODULES::DEINSTALL')) {
$links['deinstall'] = sprintf('<a href="%s" data-confirm="%s" class="text-danger">%s</a>', $this->url('/admin/modules/?deinstall='.$module->getDirectory()), I18N::get('Do you really wan\'t to deinstall the module?'), I18N::get('Deinstall'));
}
print implode(' | ', $links);
?></p><?php
if($module->isLocked()) {
$list = [];
foreach($module->getInfo()->getDepencies() as $name => $version) {
$list[] = sprintf('%s (%s)', $name, $version);
}
if(empty($list)) {
printf('<small class="text-danger">%s <a href="%s">%s</a></small>', I18N::get('Module has generated an error.'), $this->url('/admin/modules/errors'), I18N::get('See details'));
} else {
printf('<small>Unresolved Depencies: %s</small>', implode(',', $list));
}
}
?>
</td>
<td>
<?php
if(isset($upgradeable->{$module->getDirectory()})) {
$upgrade = $upgradeable->{$module->getDirectory()};
?>
<div class="text-bg-warning d-flex p-2 opacity-50" role="alert">
<?php
Icon::show('update', [
'classes' => [
'text-danger',
'p-0',
'mr-2'
]
]);
?>
<p class="p-0 m-0"><?php printf(I18N::get('The module <strong>%s</strong> has an new upgrade to <strong>Version %s</strong>!'), $info->getName(), $upgrade->version); ?></p>
<button class="btn btn-outline-success btn-sm m-0 ml-3 pt-0 pb-0"><?php I18N::__('Upgrade now!'); ?></button>
</div>
<?php
}
?>
<p><?php print $info->getDescription(); ?></p>
<small><span
class="badge bg-secondary"><?php I18N::__('Version'); ?><?php print $info->getVersion(); ?></span>
| <?php I18N::__('by'); ?> <a href="<?php print $info->getAuthor()->getWebsite(); ?>"
target="_blank"><?php print $info->getAuthor()->getName(); ?></a></small>
</td>
</tr>
<?php
}
?>
<?php
foreach($modules->getList() as $module) {
$info = $module->getInfo();
?>
<tr>
<td scope="row" width="1px">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="module_<?php print $info->getName(); ?>" name="module[]" value="<?php print $info->getName(); ?>"/>
<label class="form-check-label" for="module_<?php print $info->getName(); ?>"></label>
</div>
</td>
<td>
<span class="d-block badge badge-pill module-badge text-bg-<?php print ($module->isLocked() ? 'warning' : ($module->isEnabled() ? 'success' : 'danger')); ?>" data-toggle="tooltip" title="<?php ($module->isLocked() ? I18N::__('Module has depencies.') : ($module->isEnabled() ? I18N::__('Module is enabled.') : I18N::__('Module is disabled.'))); ?>"></span>
</td>
<td>
<strong><?php print $info->getName(); ?></strong>
<p class="module-actions"><?php
$links = [];
if(Auth::hasPermission('MODULES::HANDLE')) {
if($module->isEnabled()) {
$links['disable'] = sprintf('<a href="%s" data-confirm="%s" class="text-warning">%s</a>', $this->url('/admin/modules/?disable='.$module->getDirectory()), I18N::get('Do you really wan\'t to disable the module?'), I18N::get('Disable'));
} else {
$links['enable'] = sprintf('<a href="%s" class="text-success">%s</a>', $this->url('/admin/modules/?enable='.$module->getDirectory()), I18N::get('Enable'));
}
if($module->hasSettingsPath()) {
$links['settings'] = sprintf('<a href="%s" class="text-primary">%s</a>', $this->url('/admin/modules/?settings='.$module->getDirectory()), I18N::get('Settings'));
}
$links['check'] = sprintf('<a href="%s" class="text-primary">%s</a>', $this->url('/admin/modules/?check='.$module->getDirectory()), I18N::get('Check'));
}
if(Auth::hasPermission('MODULES::DEINSTALL')) {
$links['deinstall'] = sprintf('<a href="%s" data-confirm="%s" class="text-danger">%s</a>', $this->url('/admin/modules/?deinstall='.$module->getDirectory()), I18N::get('Do you really wan\'t to deinstall the module?'), I18N::get('Deinstall'));
}
if(Auth::hasPermission('MODULES::HANDLE')) {
$links['info'] = sprintf('<a href="#" data-bs-toggle="modal" data-bs-target="#module_info" data-module="%s" class="text-info">%s</a>', $module->getDirectory(), I18N::get('Info'));
}
print implode(' | ', $links);
?></p><?php
if($module->isLocked()) {
$list = [];
foreach($module->getInfo()->getDepencies() as $name => $version) {
$list[] = sprintf('%s (%s)', $name, $version);
}
if(empty($list)) {
printf('<small class="text-danger">%s <a href="%s">%s</a></small>', I18N::get('Module has generated an error.'), $this->url('/admin/modules/errors'), I18N::get('See details'));
} else {
printf('<small>Unresolved Depencies: %s</small>', implode(',', $list));
}
}
?>
</td>
<td>
<?php
if(isset($upgradeable->{$module->getDirectory()})) {
$upgrade = $upgradeable->{$module->getDirectory()};
?>
<div class="text-bg-warning d-flex p-2 opacity-50" role="alert">
<?php
Icon::show('update', [
'classes' => [
'text-danger',
'p-0',
'mr-2'
]
]);
?>
<p class="p-0 m-0"><?php printf(I18N::get('The module <strong>%s</strong> has an new upgrade to <strong>Version %s</strong>!'), $info->getName(), $upgrade->version); ?></p>
<button class="btn btn-outline-success btn-sm m-0 ml-3 pt-0 pb-0"><?php I18N::__('Upgrade now!'); ?></button>
</div>
<?php
}
?>
<p><?php print $info->getDescription(); ?></p>
<small><span class="badge bg-secondary"><?php I18N::__('Version'); ?><?php print $info->getVersion(); ?></span> | <?php I18N::__('by'); ?> <a href="<?php print $info->getAuthor()->getWebsite(); ?>" target="_blank"><?php print $info->getAuthor()->getName(); ?></a></small>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
+7 -1
View File
@@ -1,7 +1,13 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
?>
<div class="container">
<div class="form-group row">
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
?>
+69 -63
View File
@@ -1,75 +1,81 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
?>
<div class="border rounded overflow-hidden mt-5 mb-5">
<table class="table table-borderless table-striped table-hover mb-0">
<thead>
<tr>
<th class="bg-secondary-subtle" scope="col" colspan="2"><?php I18N::__('Repository'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Status'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Actions'); ?></th>
</tr>
<tr>
<th class="bg-secondary-subtle" scope="col" colspan="2"><?php I18N::__('Repository'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Status'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Actions'); ?></th>
</tr>
</thead>
<tbody>
<?php
foreach($repositorys as $repository) {
?>
<tr>
<td scope="row" width="1px">
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="repository_<?php print $repository->id; ?>" name="repository[]"
value="<?php print $repository->id; ?>"/>
<label class="form-check-label" for="repository_<?php print $repository->id; ?>"></label>
</div>
</td>
<td>
<?php
$link = false;
if(preg_match('/^(http|https):\/\//Ui', $repository->url)) {
printf('<a href="%s" target="_blank">', $repository->url);
$link = true;
} else {
printf('<div class="alert alert-dark p-1 m-0" role="alert">');
}
print str_replace('[*** PROTECTED ***]', '<span style="font-size: 8px;" class="badge text-bg-dark text-warning">[*** PROTECTED ***]</span>', $repository->url);
if($link) {
print('</a>');
} else {
printf('</div>');
}
?>
</td>
<td>
<?php
if(empty($repository->time_updated)) {
printf('<span class="text-warning">%s</span>', I18N::get('Never updated'));
} else if($repository->time_updated == '9999-12-31 00:00:00') {
printf('<span class="text-danger">%s<br />%s (<a href="https://github.com/fruithost/Documentation/blob/main/Modules/Repositorys.md" target="_blank">%s</a>)</span>', I18N::get('Error'), I18N::get('No Access'), I18N::get('More Informations'));
} else if($repository->time_updated == '0000-00-00 00:00:00') {
printf('<span class="text-danger">%s<br />%s (<a href="https://github.com/fruithost/Documentation/blob/main/Modules/Repositorys.md" target="_blank">%s</a>)</span>', I18N::get('Error'), I18N::get('Malformed Repository'), I18N::get('More Informations'));
} else {
printf('<small>%s:</small><br />%s', I18N::get('Last updated'), date(Auth::getSettings('TIME_FORMAT', null, 'd.m.Y - H:i'), strtotime($repository->time_updated)));
}
?>
</td>
<td class="text-end">
<button class="update btn btn-sm btn-info" type="submit" name="action" value="update"
id="update_<?php print $repository->id; ?>"
value="<?php print $repository->id; ?>"><?php I18N::__('Update'); ?></button>
<button class="delete btn btn-sm btn-danger" type="submit" name="action" value="delete"
id="delete_<?php print $repository->id; ?>"
value="<?php print $repository->id; ?>"><?php I18N::__('Delete'); ?></button>
</td>
</tr>
<?php
}
?>
<?php
foreach($repositorys as $repository) {
?>
<tr>
<td scope="row" width="1px">
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="repository_<?php print $repository->id; ?>" name="repository[]"
value="<?php print $repository->id; ?>"/>
<label class="form-check-label" for="repository_<?php print $repository->id; ?>"></label>
</div>
</td>
<td>
<?php
$link = false;
if(preg_match('/^(http|https):\/\//Ui', $repository->url)) {
printf('<a href="%s" target="_blank">', $repository->url);
$link = true;
} else {
printf('<div class="alert alert-dark p-1 m-0" role="alert">');
}
print str_replace('[*** PROTECTED ***]', '<span style="font-size: 8px;" class="badge text-bg-dark text-warning">[*** PROTECTED ***]</span>', $repository->url);
if($link) {
print('</a>');
} else {
printf('</div>');
}
?>
</td>
<td>
<?php
if(empty($repository->time_updated)) {
printf('<span class="text-warning">%s</span>', I18N::get('Never updated'));
} else if($repository->time_updated == '9999-12-31 00:00:00') {
printf('<span class="text-danger">%s<br />%s (<a href="https://github.com/fruithost/Documentation/blob/main/Modules/Repositorys.md" target="_blank">%s</a>)</span>', I18N::get('Error'), I18N::get('No Access'), I18N::get('More Informations'));
} else if($repository->time_updated == '0000-00-00 00:00:00') {
printf('<span class="text-danger">%s<br />%s (<a href="https://github.com/fruithost/Documentation/blob/main/Modules/Repositorys.md" target="_blank">%s</a>)</span>', I18N::get('Error'), I18N::get('Malformed Repository'), I18N::get('More Informations'));
} else {
printf('<small>%s:</small><br />%s', I18N::get('Last updated'), date(Auth::getSettings('TIME_FORMAT', null, 'd.m.Y - H:i'), strtotime($repository->time_updated)));
}
?>
</td>
<td class="text-end">
<button class="update btn btn-sm btn-info" type="submit" name="action" value="update"
id="update_<?php print $repository->id; ?>"
value="<?php print $repository->id; ?>"><?php I18N::__('Update'); ?></button>
<button class="delete btn btn-sm btn-danger" type="submit" name="action" value="delete"
id="delete_<?php print $repository->id; ?>"
value="<?php print $repository->id; ?>"><?php I18N::__('Delete'); ?></button>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
?>
+41 -53
View File
@@ -1,4 +1,11 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Accounting\Session;
@@ -7,37 +14,28 @@
$template->header();
if(!Auth::hasPermission('USERS::*')) {
?>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<?php
$template->footer();
exit();
}
?>
<form method="post"
action="<?php print $this->url('/admin/users'.(empty($tab) ? '' : sprintf('/%s', $tab)).(empty($action) ? '' : sprintf('/%s', $action))); ?>">
<form method="post" action="<?php print $this->url('/admin/users'.(empty($tab) ? '' : sprintf('/%s', $tab)).(empty($action) ? '' : sprintf('/%s', $action))); ?>">
<header class="page-header d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"
style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item<?php print (empty($submodule) ? ' active" aria-current="page' : ''); ?>">
<a class="active"
href="<?php print $this->url('/admin/users'); ?>"><?php I18N::__('Users'); ?></a>
</li>
<ol class="breadcrumb" style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item<?php print (empty($submodule) ? ' active" aria-current="page' : ''); ?>"><a class="active" href="<?php print $this->url('/admin/users'); ?>"><?php I18N::__('Users'); ?></a></li>
<?php
if(Auth::hasPermission('USERS::EDIT') && is_numeric($tab) && $tab > 0) {
?>
<li class="breadcrumb-item active" aria-current="page">
<a href="<?php print $this->url('/admin/users/'.$user->getID()); ?>"><?php printf('%s %s', I18N::get('edit'), $user->getUsername()); ?></a>
</li>
<li class="breadcrumb-item active" aria-current="page"><a href="<?php print $this->url('/admin/users/'.$user->getID()); ?>"><?php printf('%s %s', I18N::get('edit'), $user->getUsername()); ?></a></li>
<?php
} else if(Auth::hasPermission('USERS::CREATE') && $tab == 'create') {
?>
<li class="breadcrumb-item active" aria-current="page">
<a href="<?php print $this->url('/admin/users/create'); ?>"><?php I18N::__('create User'); ?></a>
</li>
<li class="breadcrumb-item active" aria-current="page"><a href="<?php print $this->url('/admin/users/create'); ?>"><?php I18N::__('create User'); ?></a></li>
<?php
}
?>
@@ -46,51 +44,42 @@
<div class="btn-toolbar mb-2 mb-md-0">
<?php
if(is_numeric($tab) && $tab > 0) {
?>
<div class="btn-group mr-2">
<?php
if(Auth::hasPermission('USERS::EDIT')) {
?>
<button type="submit" name="action" value="save"
class="btn btn-sm btn-outline-primary"><?php I18N::__('Save'); ?></button>
<?php
}
if(Auth::hasPermission('USERS::DELETE') && $user->getID() != 1) {
?>
<button type="submit" name="action" value="delete"
class="btn btn-sm btn-outline-danger"><?php I18N::__('Delete'); ?></button>
<?php
}
?>
</div>
<div class="btn-group mr-2">
<?php
if(Auth::hasPermission('USERS::EDIT')) {
?>
<button type="submit" name="action" value="save" class="btn btn-sm btn-outline-primary"><?php I18N::__('Save'); ?></button>
<?php
}
if(Auth::hasPermission('USERS::DELETE') && $user->getID() != 1) {
?>
<button type="submit" name="action" value="delete" class="btn btn-sm btn-outline-danger"><?php I18N::__('Delete'); ?></button>
<?php
}
?>
</div>
<?php
} else if(Auth::hasPermission('USERS::CREATE') && $tab == 'create') {
?>
<div class="btn-group mr-2">
<button type="submit" name="action" value="create"
class="btn btn-sm btn-outline-primary"><?php I18N::__('Create'); ?></button>
</div>
<div class="btn-group mr-2">
<button type="submit" name="action" value="create" class="btn btn-sm btn-outline-primary"><?php I18N::__('Create'); ?></button>
</div>
<?php
} else {
?>
<div class="btn-group mr-2">
<?php
if(Auth::hasPermission('USERS::CREATE')) {
?>
<a href="<?php print $this->url('/admin/users/create'); ?>"
class="btn btn-sm btn-outline-primary"><?php I18N::__('Add new'); ?></a>
<a href="<?php print $this->url('/admin/users/create'); ?>" class="btn btn-sm btn-outline-primary"><?php I18N::__('Add new'); ?></a>
<?php
}
if(Auth::hasPermission('USERS::DELETE')) {
?>
<button type="submit" name="action" value="deletes"
class="btn btn-sm btn-outline-danger"
data-confirm="<?php I18N::__('Do you really wan\'t to delete all selected users?'); ?>"><?php I18N::__('Delete'); ?></button>
<button type="submit" name="action" value="deletes" class="btn btn-sm btn-outline-danger" data-confirm="<?php I18N::__('Do you really wan\'t to delete all selected users?'); ?>"><?php I18N::__('Delete'); ?></button>
<?php
}
?>
@@ -103,20 +92,19 @@
<?php
if(Session::has('success')) {
?>
<div class="alert alert-success mt-4" role="alert"><?php print Session::get('success'); ?></div>
<div class="alert alert-success mt-4" role="alert"><?php print Session::get('success'); ?></div>
<?php
Session::remove('success');
} else if(!is_numeric($tab) && $tab !== 'create') {
if(isset($error)) {
?>
<div class="alert alert-danger mt-4"
role="alert"><?php (is_array($error) ? var_dump($error) : print $error); ?></div>
<div class="alert alert-danger mt-4" role="alert"><?php (is_array($error) ? var_dump($error) : print $error); ?></div>
<?php
}
if(isset($success)) {
?>
<div class="alert alert-success mt-4"
role="alert"><?php (is_array($success) ? var_dump($success) : print $success); ?></div>
<div class="alert alert-success mt-4" role="alert"><?php (is_array($success) ? var_dump($success) : print $success); ?></div>
<?php
}
}
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
$template->header();
+18 -27
View File
@@ -1,61 +1,52 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
$template->header();
?>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item"><a class="nav-link<?php print (empty($action) ? ' active' : ''); ?>" id="account-tab"
href="<?php print $this->url(sprintf('/admin/users/%s', $tab)); ?>"
role="tab"><?php I18N::__('Account'); ?></a></li>
<li class="nav-item"><a
class="nav-link<?php print (!empty($action) && $action === 'password' ? ' active' : ''); ?>"
id="password-tab" href="<?php print $this->url(sprintf('/admin/users/%s/password', $tab)); ?>"
role="tab"><?php I18N::__('Password Settings'); ?></a></li>
<li class="nav-item"><a
class="nav-link<?php print (!empty($action) && $action === 'settings' ? ' active' : ''); ?>"
id="settings-tab" href="<?php print $this->url(sprintf('/admin/users/%s/settings', $tab)); ?>"
role="tab"><?php I18N::__('Settings'); ?></a></li>
<li class="nav-item"><a
class="nav-link<?php print (!empty($action) && $action === 'permissions' ? ' active' : ''); ?>"
id="permissions-tab" href="<?php print $this->url(sprintf('/admin/users/%s/permissions', $tab)); ?>"
role="tab"><?php I18N::__('Permissions'); ?></a></li>
<li class="nav-item"><a class="nav-link<?php print (empty($action) ? ' active' : ''); ?>" id="account-tab" href="<?php print $this->url(sprintf('/admin/users/%s', $tab)); ?>" role="tab"><?php I18N::__('Account'); ?></a></li>
<li class="nav-item"><a class="nav-link<?php print (!empty($action) && $action === 'password' ? ' active' : ''); ?>" id="password-tab" href="<?php print $this->url(sprintf('/admin/users/%s/password', $tab)); ?>" role="tab"><?php I18N::__('Password Settings'); ?></a></li>
<li class="nav-item"><a class="nav-link<?php print (!empty($action) && $action === 'settings' ? ' active' : ''); ?>" id="settings-tab" href="<?php print $this->url(sprintf('/admin/users/%s/settings', $tab)); ?>" role="tab"><?php I18N::__('Settings'); ?></a></li>
<li class="nav-item"><a class="nav-link<?php print (!empty($action) && $action === 'permissions' ? ' active' : ''); ?>" id="permissions-tab" href="<?php print $this->url(sprintf('/admin/users/%s/permissions', $tab)); ?>" role="tab"><?php I18N::__('Permissions'); ?></a></li>
</ul>
<?php
if(isset($error)) {
?>
<div class="alert alert-danger mt-4"
role="alert"><?php (is_array($error) ? var_dump($error) : print $error); ?></div>
<div class="alert alert-danger mt-4" role="alert"><?php (is_array($error) ? var_dump($error) : print $error); ?></div>
<?php
}
if(isset($success)) {
?>
<div class="alert alert-success mt-4"
role="alert"><?php (is_array($success) ? var_dump($success) : print $success); ?></div>
<div class="alert alert-success mt-4" role="alert"><?php (is_array($success) ? var_dump($success) : print $success); ?></div>
<?php
}
?>
<div class="tab-content" id="myTabContent">
<div class="tab-pane<?php print (empty($action) ? ' show active' : ''); ?>" id="account" role="tabpanel"
aria-labelledby="account-tab">
<div class="tab-pane<?php print (empty($action) ? ' show active' : ''); ?>" id="account" role="tabpanel" aria-labelledby="account-tab">
<?php
$template->display('admin/users/edit/account', [ 'user' => $user ]);
?>
</div>
<div class="tab-pane<?php print (!empty($action) && $action === 'password' ? ' show active' : ''); ?>"
id="password" role="tabpanel" aria-labelledby="password-tab">
<div class="tab-pane<?php print (!empty($action) && $action === 'password' ? ' show active' : ''); ?>" id="password" role="tabpanel" aria-labelledby="password-tab">
<?php
$template->display('admin/users/edit/password', [ 'user' => $user, 'timezones' => $timezones ]);
?>
</div>
<div class="tab-pane<?php print (!empty($action) && $action === 'settings' ? ' show active' : ''); ?>"
id="settings" role="tabpanel" aria-labelledby="settings-tab">
<div class="tab-pane<?php print (!empty($action) && $action === 'settings' ? ' show active' : ''); ?>" id="settings" role="tabpanel" aria-labelledby="settings-tab">
<?php
$template->display('admin/users/edit/settings', [ 'user' => $user ]);
?>
</div>
<div class="tab-pane<?php print (!empty($action) && $action === 'permissions' ? ' show active' : ''); ?>"
id="permissions" role="tabpanel" aria-labelledby="permissions-tab">
<div class="tab-pane<?php print (!empty($action) && $action === 'permissions' ? ' show active' : ''); ?>" id="permissions" role="tabpanel" aria-labelledby="permissions-tab">
<?php
$template->display('admin/users/edit/permissions', [ 'user' => $user ]);
?>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
?>
<p></p>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
?>
<p></p>
+8 -1
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
$permissions = [
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
?>
<p></p>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
?>
+11 -6
View File
@@ -1,9 +1,15 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Accounting\User;
use fruithost\Localization\I18N;
?>
<div class="border rounded overflow-hidden mb-5">
<table class="table table-borderless table-striped table-hover mb-0">
@@ -20,6 +26,7 @@
foreach($users as $u) {
$user = new User();
$user->fetch($u->id);
if($user->getID() == null) {
continue;
}
@@ -27,15 +34,12 @@
<tr>
<td scope="row" width="1px">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="user_<?php print $user->getID(); ?>"
name="user[]"
value="<?php print $user->getID(); ?>"<?php print ($user->getID() == 1 ? ' DISABLED' : ''); ?> />
<input class="form-check-input" type="checkbox" id="user_<?php print $user->getID(); ?>" name="user[]" value="<?php print $user->getID(); ?>"<?php print ($user->getID() == 1 ? ' DISABLED' : ''); ?> />
<label class="form-check-label" for="user_<?php print $user->getID(); ?>"></label>
</div>
</td>
<td width="1px">
<img src="<?php print $user->getGravatar(); ?>" class="object-fit-cover bg-dark border rounded"
alt=""/>
<img src="<?php print $user->getGravatar(); ?>" class="object-fit-cover bg-dark border rounded" alt=""/>
</td>
<td>
<small>#<?php print $user->getID(); ?></small>
@@ -53,6 +57,7 @@
if(Auth::hasPermission('USERS::EDIT')) {
printf('<a href="%s" class="btn btn-info">%s</a>', $this->url('/admin/users/'.$user->getID()), I18N::get('Edit'));
}
if(Auth::hasPermission('USERS::DELETE') && $user->getID() != 1) {
printf('<button name="action" value="deletes" data-confirm="%s" class="btn btn-danger deletes">%s</button>', I18N::get('Do you really wan\'t to delete the user?'), I18N::get('Delete'));
}
+53 -25
View File
@@ -1,39 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 10.0, SVG Export Plug-In . SVG Version: 3.0.0 Build 77) -->
<svg xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" i:viewOrigin="251 467" i:rulerOrigin="0 0" i:pageBounds="0 792 612 0" width="108.758" height="144.133" viewBox="0 0 108.758 144.133" overflow="visible" enable-background="new 0 0 108.758 144.133" xml:space="preserve">
<!--
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
-->
<svg xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/"
xmlns="http://www.w3.org/2000/svg"
i:viewOrigin="251 467" i:rulerOrigin="0 0" i:pageBounds="0 792 612 0" width="108.758" height="144.133"
viewBox="0 0 108.758 144.133" overflow="visible" enable-background="new 0 0 108.758 144.133" xml:space="preserve">
<metadata>
<variableSets xmlns="http://ns.adobe.com/Variables/1.0/">
<variableSet varSetName="binding1" locked="none">
<variables/>
<v:sampleDataSets xmlns="http://ns.adobe.com/GenericCustomNamespace/1.0/" xmlns:v="http://ns.adobe.com/Variables/1.0/"/>
<v:sampleDataSets xmlns:v="http://ns.adobe.com/Variables/1.0/"
xmlns="http://ns.adobe.com/GenericCustomNamespace/1.0/"/>
</variableSet>
</variableSets>
<sfw xmlns="http://ns.adobe.com/SaveForWeb/1.0/">
<sfw xmlns="http://ns.adobe.com/SaveForWeb/1.0/">
<slices/>
<sliceSourceBounds y="322.867" x="251" width="108.758" height="144.133" bottomLeftOrigin="true"/>
<sliceSourceBounds y="322.867" x="251" width="108.758" height="144.133" bottomLeftOrigin="true"/>
</sfw>
</metadata>
<g id="Layer_1" i:layer="yes" i:dimmedPercent="50" i:rgbTrio="#4F008000FFFF">
<g id="Layer_1" i:layer="yes" i:dimmedPercent="50" i:rgbTrio="#4F008000FFFF">
<g>
<path i:knockout="Off" fill="#A80030" d="M60.969,47.645c-1.494,0.02,0.281,0.768,2.232,1.069 c0.541-0.422,1.027-0.846,1.463-1.26C63.451,47.751,62.215,47.758,60.969,47.645"/>
<path i:knockout="Off" fill="#A80030" d="M68.986,45.646c0.893-1.229,1.541-2.573,1.77-3.963 c-0.201,0.99-0.736,1.845-1.244,2.749c-2.793,1.759-0.264-1.044-0.002-2.111C66.508,46.104,69.096,44.589,68.986,45.646"/>
<path i:knockout="Off" fill="#A80030" d="M71.949,37.942c0.182-2.691-0.529-1.839-0.768-0.814 C71.459,37.274,71.68,39.026,71.949,37.942"/>
<path i:knockout="Off" fill="#A80030" d="M55.301,1.163c0.798,0.142,1.724,0.252,1.591,0.443 C57.768,1.413,57.965,1.239,55.301,1.163"/>
<path i:knockout="Off" fill="#A80030" d="M56.893,1.606l-0.561,0.117l0.523-0.048L56.893,1.606"/>
<path i:knockout="Off" fill="#A80030" d="M81.762,38.962c0.09,2.416-0.705,3.59-1.424,5.666l-1.293,0.643 c-1.057,2.054,0.105,1.304-0.652,2.937c-1.652,1.467-5.006,4.589-6.08,4.875c-0.785-0.017,0.531-0.926,0.703-1.281 c-2.209,1.516-1.773,2.276-5.152,3.199l-0.098-0.221c-8.33,3.92-19.902-3.847-19.75-14.443c-0.088,0.672-0.253,0.504-0.437,0.774 c-0.43-5.451,2.518-10.926,7.49-13.165c4.863-2.406,10.564-1.42,14.045,1.829c-1.912-2.506-5.721-5.163-10.232-4.917 c-4.421,0.072-8.558,2.881-9.938,5.932c-2.264,1.425-2.528,5.496-3.514,6.242c-1.329,9.76,2.497,13.975,8.97,18.936 c1.016,0.686,0.286,0.791,0.422,1.313c-2.15-1.006-4.118-2.526-5.738-4.387c0.86,1.257,1.787,2.479,2.986,3.439 c-2.029-0.685-4.738-4.913-5.527-5.085c3.495,6.258,14.178,10.975,19.775,8.634c-2.59,0.096-5.879,0.053-8.787-1.022 c-1.225-0.629-2.884-1.93-2.587-2.173c7.636,2.851,15.522,2.158,22.128-3.137c1.682-1.31,3.518-3.537,4.049-3.567 c-0.799,1.202,0.137,0.578-0.477,1.639c1.672-2.701-0.729-1.1,1.73-4.664l0.908,1.25c-0.34-2.244,2.785-4.966,2.467-8.512 c0.717-1.084,0.799,1.168,0.039,3.662c1.055-2.767,0.279-3.212,0.549-5.496c0.291,0.768,0.678,1.583,0.875,2.394 c-0.688-2.675,0.703-4.503,1.049-6.058c-0.342-0.15-1.061,1.182-1.227-1.976c0.025-1.372,0.383-0.719,0.52-1.057 c-0.268-0.155-0.975-1.207-1.404-3.224c0.309-0.475,0.832,1.229,1.256,1.298c-0.273-1.603-0.742-2.826-0.762-4.057 c-1.24-2.59-0.439,0.346-1.443-1.112c-1.32-4.114,1.094-0.955,1.258-2.823c1.998,2.895,3.137,7.385,3.662,9.244 c-0.4-2.267-1.045-4.464-1.834-6.589c0.609,0.257-0.979-4.663,0.791-1.405c-1.889-6.945-8.078-13.435-13.773-16.479 c0.695,0.637,1.574,1.437,1.26,1.563c-2.834-1.685-2.336-1.818-2.742-2.53c-2.305-0.939-2.459,0.077-3.984,0.002 c-4.35-2.308-5.188-2.063-9.191-3.507l0.182,0.852c-2.881-0.96-3.357,0.362-6.47,0.002c-0.189-0.147,0.998-0.536,1.976-0.677 c-2.786,0.368-2.656-0.55-5.382,0.101c0.671-0.471,1.383-0.784,2.099-1.184c-2.271,0.138-5.424,1.322-4.451,0.244 c-3.705,1.654-10.286,3.975-13.979,7.438l-0.116-0.776c-1.692,2.031-7.379,6.066-7.832,8.699l-0.453,0.105 c-0.879,1.491-1.45,3.18-2.148,4.713c-1.151,1.963-1.688,0.756-1.524,1.064c-2.265,4.592-3.392,8.45-4.363,11.616 c0.692,1.035,0.017,6.232,0.278,10.391c-1.136,20.544,14.418,40.489,31.42,45.093c2.492,0.893,6.197,0.861,9.349,0.949 c-3.718-1.064-4.198-0.563-7.822-1.826c-2.613-1.232-3.185-2.637-5.037-4.244l0.733,1.295c-3.63-1.285-2.111-1.59-5.065-2.525 l0.783-1.021c-1.177-0.09-3.117-1.982-3.647-3.033l-1.288,0.051c-1.546-1.906-2.371-3.283-2.31-4.35l-0.416,0.742 c-0.471-0.809-5.691-7.158-2.983-5.68c-0.503-0.458-1.172-0.747-1.897-2.066l0.551-0.629c-1.301-1.677-2.398-3.826-2.314-4.542 c0.695,0.938,1.177,1.114,1.655,1.275c-3.291-8.164-3.476-0.449-5.967-8.31l0.526-0.042c-0.403-0.611-0.65-1.27-0.974-1.919 l0.23-2.285c-2.368-2.736-0.662-11.645-0.319-16.53c0.235-1.986,1.977-4.101,3.3-7.418l-0.806-0.138 c1.542-2.688,8.802-10.799,12.166-10.383c1.629-2.046-0.324-0.008-0.643-0.522c3.579-3.703,4.704-2.616,7.119-3.283 c2.603-1.545-2.235,0.604-1.001-0.589c4.503-1.149,3.19-2.614,9.063-3.197c0.62,0.352-1.437,0.544-1.953,1.001 c3.75-1.836,11.869-1.417,17.145,1.018c6.117,2.861,12.994,11.314,13.266,19.267l0.309,0.083 c-0.156,3.162,0.484,6.819-0.627,10.177L81.762,38.962"/>
<path i:knockout="Off" fill="#A80030" d="M44.658,49.695l-0.211,1.047c0.983,1.335,1.763,2.781,3.016,3.821 C46.561,52.804,45.892,52.077,44.658,49.695"/>
<path i:knockout="Off" fill="#A80030" d="M46.979,49.605c-0.52-0.576-0.826-1.268-1.172-1.956 c0.33,1.211,1.006,2.252,1.633,3.312L46.979,49.605"/>
<path i:knockout="Off" fill="#A80030" d="M88.063,40.675l-0.219,0.552c-0.402,2.858-1.273,5.686-2.605,8.309 C86.711,46.769,87.66,43.742,88.063,40.675"/>
<path i:knockout="Off" fill="#A80030" d="M55.598,0.446C56.607,0.077,58.08,0.243,59.154,0c-1.398,0.117-2.789,0.187-4.162,0.362 L55.598,0.446"/>
<path i:knockout="Off" fill="#A80030" d="M20.127,19.308c0.233,2.154-1.62,2.991,0.41,1.569 C21.627,18.423,20.113,20.2,20.127,19.308"/>
<path i:knockout="Off" fill="#A80030" d="M17.739,29.282c0.469-1.437,0.553-2.299,0.732-3.132 C17.178,27.804,17.875,28.157,17.739,29.282"/>
<path i:knockout="Off" d="M13.437,125.506c-0.045,0.047-0.045,7.506-0.138,9.453c-0.092,1.574-0.232,4.957-3.568,4.957 c-3.429,0-4.263-3.939-4.541-5.652c-0.324-1.9-0.324-3.477-0.324-4.17c0-2.225,0.139-8.436,5.375-8.436 c1.576,0,2.456,0.465,3.151,0.834L13.437,125.506z M0,130.975c0,13.066,6.951,13.066,7.97,13.066 c2.873,0,4.727-1.576,5.514-4.309l0.093,4.123c0.881-0.047,1.761-0.139,3.197-0.139c0.51,0,0.926,0,1.298,0.047 c0.371,0,0.741,0.045,1.158,0.092c-0.741-1.482-1.297-4.818-1.297-12.049c0-7.043,0-18.951,0.602-22.566 c-1.667,0.789-3.105,1.299-6.256,1.576c1.251,1.344,1.251,2.039,1.251,8.154c-0.879-0.277-1.992-0.602-3.892-0.602 C1.344,118.369,0,125.598,0,130.975"/>
<path i:knockout="Off" d="M25.13,128.609c0.047-3.846,0.835-7.275,4.124-7.275c3.615,0,3.891,3.984,3.799,7.275H25.13z M37.64,129.074c0-5.422-1.065-10.752-7.923-10.752c-9.452,0-9.452,10.475-9.452,12.697c0,9.406,4.216,13.113,11.306,13.113 c3.149,0,4.68-0.461,5.514-0.695c-0.046-1.668,0.185-2.734,0.465-4.17c-0.975,0.604-2.226,1.391-5.006,1.391 c-7.229,0-7.322-6.582-7.322-8.852H37.55L37.64,129.074"/>
<path i:knockout="Off" d="M52.715,131.066c0,4.309-0.787,10.102-6.162,10.102c-0.742,0-1.668-0.141-2.27-0.279 c-0.093-1.668-0.093-4.541-0.093-7.877c0-3.986,0.416-6.068,0.742-7.09c0.972-3.289,3.15-3.334,3.566-3.334 C52.02,122.588,52.715,127.453,52.715,131.066z M39.417,136.117c0,3.43,0,5.375-0.556,6.857c1.9,0.742,4.262,1.158,7.09,1.158 c1.807,0,7.043,0,9.869-5.791c1.344-2.688,1.807-6.303,1.807-9.037c0-1.668-0.186-5.328-1.529-7.646 c-1.296-2.176-3.382-3.289-5.605-3.289c-4.449,0-5.746,3.707-6.44,5.607c0-2.363,0.045-10.611,0.415-14.828 c-3.011,1.391-4.866,1.621-6.857,1.807c1.807,0.74,1.807,3.801,1.807,13.764V136.117"/>
<path i:knockout="Off" d="M66.535,143.855c-0.928-0.139-1.578-0.232-2.922-0.232c-1.48,0-2.502,0.094-3.566,0.232 c0.463-0.881,0.648-1.299,0.787-4.309c0.186-4.125,0.232-15.154-0.092-17.471c-0.232-1.762-0.648-2.039-1.297-2.502 c3.799-0.371,4.865-0.648,6.625-1.482c-0.369,2.037-0.418,3.059-0.418,6.162C65.561,140.242,65.514,141.955,66.535,143.855"/>
<path i:knockout="Off" d="M81.373,130.74c-0.092,2.92-0.139,4.959-0.928,6.58c-0.973,2.086-2.594,2.688-3.799,2.688 c-2.783,0-3.383-2.316-3.383-4.586c0-4.355,3.893-4.682,5.652-4.682H81.373z M68.629,136.441c0,2.92,0.881,5.838,3.477,7.09 c1.158,0.51,2.316,0.51,2.688,0.51c4.264,0,5.699-3.152,6.58-5.098c-0.047,2.039,0,3.289,0.139,4.912 c0.834-0.047,1.668-0.139,3.059-0.139c0.787,0,1.529,0.092,2.316,0.139c-0.51-0.787-0.787-1.252-0.928-3.059 c-0.092-1.76-0.092-3.521-0.092-5.977l0.047-9.453c0-3.523-0.928-6.998-7.879-6.998c-4.586,0-7.273,1.391-8.617,2.086 c0.557,1.02,1.02,1.898,1.436,3.893c1.809-1.576,4.172-2.41,6.58-2.41c3.848,0,3.848,2.549,3.848,6.162 c-0.881-0.045-1.623-0.137-2.875-0.137C72.521,127.963,68.629,130.23,68.629,136.441"/>
<path i:knockout="Off" d="M108.063,139.268c0.047,1.576,0.047,3.244,0.695,4.588c-1.021-0.092-1.623-0.232-3.521-0.232 c-1.113,0-1.715,0.094-2.596,0.232c0.184-0.602,0.279-0.834,0.371-1.623c0.139-1.064,0.232-4.633,0.232-5.885v-5.004 c0-2.178,0-5.33-0.141-6.441c-0.092-0.787-0.322-2.918-3.012-2.918c-2.641,0-3.521,1.945-3.846,3.521 c-0.369,1.621-0.369,3.383-0.369,10.24c0.045,5.932,0.045,6.486,0.508,8.109c-0.787-0.092-1.76-0.184-3.15-0.184 c-1.113,0-1.854,0.045-2.779,0.184c0.324-0.742,0.51-1.113,0.602-3.707c0.094-2.549,0.279-15.061-0.141-18.025 c-0.23-1.809-0.695-2.225-1.203-2.688c3.754-0.186,4.957-0.789,6.117-1.389v4.91c0.555-1.438,1.713-4.635,6.348-4.635 c5.793,0,5.838,4.217,5.885,6.996V139.268"/>
<path i:knockout="Off" fill="#A80030" d="M66.926,111.533l-3.838,3.836l-3.836-3.836l3.836-3.836L66.926,111.533"/>
<path i:knockout="Off" fill="#A80030"
d="M60.969,47.645c-1.494,0.02,0.281,0.768,2.232,1.069 c0.541-0.422,1.027-0.846,1.463-1.26C63.451,47.751,62.215,47.758,60.969,47.645"/>
<path i:knockout="Off" fill="#A80030"
d="M68.986,45.646c0.893-1.229,1.541-2.573,1.77-3.963 c-0.201,0.99-0.736,1.845-1.244,2.749c-2.793,1.759-0.264-1.044-0.002-2.111C66.508,46.104,69.096,44.589,68.986,45.646"/>
<path i:knockout="Off" fill="#A80030"
d="M71.949,37.942c0.182-2.691-0.529-1.839-0.768-0.814 C71.459,37.274,71.68,39.026,71.949,37.942"/>
<path i:knockout="Off" fill="#A80030"
d="M55.301,1.163c0.798,0.142,1.724,0.252,1.591,0.443 C57.768,1.413,57.965,1.239,55.301,1.163"/>
<path i:knockout="Off" fill="#A80030" d="M56.893,1.606l-0.561,0.117l0.523-0.048L56.893,1.606"/>
<path i:knockout="Off" fill="#A80030"
d="M81.762,38.962c0.09,2.416-0.705,3.59-1.424,5.666l-1.293,0.643 c-1.057,2.054,0.105,1.304-0.652,2.937c-1.652,1.467-5.006,4.589-6.08,4.875c-0.785-0.017,0.531-0.926,0.703-1.281 c-2.209,1.516-1.773,2.276-5.152,3.199l-0.098-0.221c-8.33,3.92-19.902-3.847-19.75-14.443c-0.088,0.672-0.253,0.504-0.437,0.774 c-0.43-5.451,2.518-10.926,7.49-13.165c4.863-2.406,10.564-1.42,14.045,1.829c-1.912-2.506-5.721-5.163-10.232-4.917 c-4.421,0.072-8.558,2.881-9.938,5.932c-2.264,1.425-2.528,5.496-3.514,6.242c-1.329,9.76,2.497,13.975,8.97,18.936 c1.016,0.686,0.286,0.791,0.422,1.313c-2.15-1.006-4.118-2.526-5.738-4.387c0.86,1.257,1.787,2.479,2.986,3.439 c-2.029-0.685-4.738-4.913-5.527-5.085c3.495,6.258,14.178,10.975,19.775,8.634c-2.59,0.096-5.879,0.053-8.787-1.022 c-1.225-0.629-2.884-1.93-2.587-2.173c7.636,2.851,15.522,2.158,22.128-3.137c1.682-1.31,3.518-3.537,4.049-3.567 c-0.799,1.202,0.137,0.578-0.477,1.639c1.672-2.701-0.729-1.1,1.73-4.664l0.908,1.25c-0.34-2.244,2.785-4.966,2.467-8.512 c0.717-1.084,0.799,1.168,0.039,3.662c1.055-2.767,0.279-3.212,0.549-5.496c0.291,0.768,0.678,1.583,0.875,2.394 c-0.688-2.675,0.703-4.503,1.049-6.058c-0.342-0.15-1.061,1.182-1.227-1.976c0.025-1.372,0.383-0.719,0.52-1.057 c-0.268-0.155-0.975-1.207-1.404-3.224c0.309-0.475,0.832,1.229,1.256,1.298c-0.273-1.603-0.742-2.826-0.762-4.057 c-1.24-2.59-0.439,0.346-1.443-1.112c-1.32-4.114,1.094-0.955,1.258-2.823c1.998,2.895,3.137,7.385,3.662,9.244 c-0.4-2.267-1.045-4.464-1.834-6.589c0.609,0.257-0.979-4.663,0.791-1.405c-1.889-6.945-8.078-13.435-13.773-16.479 c0.695,0.637,1.574,1.437,1.26,1.563c-2.834-1.685-2.336-1.818-2.742-2.53c-2.305-0.939-2.459,0.077-3.984,0.002 c-4.35-2.308-5.188-2.063-9.191-3.507l0.182,0.852c-2.881-0.96-3.357,0.362-6.47,0.002c-0.189-0.147,0.998-0.536,1.976-0.677 c-2.786,0.368-2.656-0.55-5.382,0.101c0.671-0.471,1.383-0.784,2.099-1.184c-2.271,0.138-5.424,1.322-4.451,0.244 c-3.705,1.654-10.286,3.975-13.979,7.438l-0.116-0.776c-1.692,2.031-7.379,6.066-7.832,8.699l-0.453,0.105 c-0.879,1.491-1.45,3.18-2.148,4.713c-1.151,1.963-1.688,0.756-1.524,1.064c-2.265,4.592-3.392,8.45-4.363,11.616 c0.692,1.035,0.017,6.232,0.278,10.391c-1.136,20.544,14.418,40.489,31.42,45.093c2.492,0.893,6.197,0.861,9.349,0.949 c-3.718-1.064-4.198-0.563-7.822-1.826c-2.613-1.232-3.185-2.637-5.037-4.244l0.733,1.295c-3.63-1.285-2.111-1.59-5.065-2.525 l0.783-1.021c-1.177-0.09-3.117-1.982-3.647-3.033l-1.288,0.051c-1.546-1.906-2.371-3.283-2.31-4.35l-0.416,0.742 c-0.471-0.809-5.691-7.158-2.983-5.68c-0.503-0.458-1.172-0.747-1.897-2.066l0.551-0.629c-1.301-1.677-2.398-3.826-2.314-4.542 c0.695,0.938,1.177,1.114,1.655,1.275c-3.291-8.164-3.476-0.449-5.967-8.31l0.526-0.042c-0.403-0.611-0.65-1.27-0.974-1.919 l0.23-2.285c-2.368-2.736-0.662-11.645-0.319-16.53c0.235-1.986,1.977-4.101,3.3-7.418l-0.806-0.138 c1.542-2.688,8.802-10.799,12.166-10.383c1.629-2.046-0.324-0.008-0.643-0.522c3.579-3.703,4.704-2.616,7.119-3.283 c2.603-1.545-2.235,0.604-1.001-0.589c4.503-1.149,3.19-2.614,9.063-3.197c0.62,0.352-1.437,0.544-1.953,1.001 c3.75-1.836,11.869-1.417,17.145,1.018c6.117,2.861,12.994,11.314,13.266,19.267l0.309,0.083 c-0.156,3.162,0.484,6.819-0.627,10.177L81.762,38.962"/>
<path i:knockout="Off" fill="#A80030"
d="M44.658,49.695l-0.211,1.047c0.983,1.335,1.763,2.781,3.016,3.821 C46.561,52.804,45.892,52.077,44.658,49.695"/>
<path i:knockout="Off" fill="#A80030"
d="M46.979,49.605c-0.52-0.576-0.826-1.268-1.172-1.956 c0.33,1.211,1.006,2.252,1.633,3.312L46.979,49.605"/>
<path i:knockout="Off" fill="#A80030"
d="M88.063,40.675l-0.219,0.552c-0.402,2.858-1.273,5.686-2.605,8.309 C86.711,46.769,87.66,43.742,88.063,40.675"/>
<path i:knockout="Off" fill="#A80030"
d="M55.598,0.446C56.607,0.077,58.08,0.243,59.154,0c-1.398,0.117-2.789,0.187-4.162,0.362 L55.598,0.446"/>
<path i:knockout="Off" fill="#A80030"
d="M20.127,19.308c0.233,2.154-1.62,2.991,0.41,1.569 C21.627,18.423,20.113,20.2,20.127,19.308"/>
<path i:knockout="Off" fill="#A80030"
d="M17.739,29.282c0.469-1.437,0.553-2.299,0.732-3.132 C17.178,27.804,17.875,28.157,17.739,29.282"/>
<path i:knockout="Off"
d="M13.437,125.506c-0.045,0.047-0.045,7.506-0.138,9.453c-0.092,1.574-0.232,4.957-3.568,4.957 c-3.429,0-4.263-3.939-4.541-5.652c-0.324-1.9-0.324-3.477-0.324-4.17c0-2.225,0.139-8.436,5.375-8.436 c1.576,0,2.456,0.465,3.151,0.834L13.437,125.506z M0,130.975c0,13.066,6.951,13.066,7.97,13.066 c2.873,0,4.727-1.576,5.514-4.309l0.093,4.123c0.881-0.047,1.761-0.139,3.197-0.139c0.51,0,0.926,0,1.298,0.047 c0.371,0,0.741,0.045,1.158,0.092c-0.741-1.482-1.297-4.818-1.297-12.049c0-7.043,0-18.951,0.602-22.566 c-1.667,0.789-3.105,1.299-6.256,1.576c1.251,1.344,1.251,2.039,1.251,8.154c-0.879-0.277-1.992-0.602-3.892-0.602 C1.344,118.369,0,125.598,0,130.975"/>
<path i:knockout="Off"
d="M25.13,128.609c0.047-3.846,0.835-7.275,4.124-7.275c3.615,0,3.891,3.984,3.799,7.275H25.13z M37.64,129.074c0-5.422-1.065-10.752-7.923-10.752c-9.452,0-9.452,10.475-9.452,12.697c0,9.406,4.216,13.113,11.306,13.113 c3.149,0,4.68-0.461,5.514-0.695c-0.046-1.668,0.185-2.734,0.465-4.17c-0.975,0.604-2.226,1.391-5.006,1.391 c-7.229,0-7.322-6.582-7.322-8.852H37.55L37.64,129.074"/>
<path i:knockout="Off"
d="M52.715,131.066c0,4.309-0.787,10.102-6.162,10.102c-0.742,0-1.668-0.141-2.27-0.279 c-0.093-1.668-0.093-4.541-0.093-7.877c0-3.986,0.416-6.068,0.742-7.09c0.972-3.289,3.15-3.334,3.566-3.334 C52.02,122.588,52.715,127.453,52.715,131.066z M39.417,136.117c0,3.43,0,5.375-0.556,6.857c1.9,0.742,4.262,1.158,7.09,1.158 c1.807,0,7.043,0,9.869-5.791c1.344-2.688,1.807-6.303,1.807-9.037c0-1.668-0.186-5.328-1.529-7.646 c-1.296-2.176-3.382-3.289-5.605-3.289c-4.449,0-5.746,3.707-6.44,5.607c0-2.363,0.045-10.611,0.415-14.828 c-3.011,1.391-4.866,1.621-6.857,1.807c1.807,0.74,1.807,3.801,1.807,13.764V136.117"/>
<path i:knockout="Off"
d="M66.535,143.855c-0.928-0.139-1.578-0.232-2.922-0.232c-1.48,0-2.502,0.094-3.566,0.232 c0.463-0.881,0.648-1.299,0.787-4.309c0.186-4.125,0.232-15.154-0.092-17.471c-0.232-1.762-0.648-2.039-1.297-2.502 c3.799-0.371,4.865-0.648,6.625-1.482c-0.369,2.037-0.418,3.059-0.418,6.162C65.561,140.242,65.514,141.955,66.535,143.855"/>
<path i:knockout="Off"
d="M81.373,130.74c-0.092,2.92-0.139,4.959-0.928,6.58c-0.973,2.086-2.594,2.688-3.799,2.688 c-2.783,0-3.383-2.316-3.383-4.586c0-4.355,3.893-4.682,5.652-4.682H81.373z M68.629,136.441c0,2.92,0.881,5.838,3.477,7.09 c1.158,0.51,2.316,0.51,2.688,0.51c4.264,0,5.699-3.152,6.58-5.098c-0.047,2.039,0,3.289,0.139,4.912 c0.834-0.047,1.668-0.139,3.059-0.139c0.787,0,1.529,0.092,2.316,0.139c-0.51-0.787-0.787-1.252-0.928-3.059 c-0.092-1.76-0.092-3.521-0.092-5.977l0.047-9.453c0-3.523-0.928-6.998-7.879-6.998c-4.586,0-7.273,1.391-8.617,2.086 c0.557,1.02,1.02,1.898,1.436,3.893c1.809-1.576,4.172-2.41,6.58-2.41c3.848,0,3.848,2.549,3.848,6.162 c-0.881-0.045-1.623-0.137-2.875-0.137C72.521,127.963,68.629,130.23,68.629,136.441"/>
<path i:knockout="Off"
d="M108.063,139.268c0.047,1.576,0.047,3.244,0.695,4.588c-1.021-0.092-1.623-0.232-3.521-0.232 c-1.113,0-1.715,0.094-2.596,0.232c0.184-0.602,0.279-0.834,0.371-1.623c0.139-1.064,0.232-4.633,0.232-5.885v-5.004 c0-2.178,0-5.33-0.141-6.441c-0.092-0.787-0.322-2.918-3.012-2.918c-2.641,0-3.521,1.945-3.846,3.521 c-0.369,1.621-0.369,3.383-0.369,10.24c0.045,5.932,0.045,6.486,0.508,8.109c-0.787-0.092-1.76-0.184-3.15-0.184 c-1.113,0-1.854,0.045-2.779,0.184c0.324-0.742,0.51-1.113,0.602-3.707c0.094-2.549,0.279-15.061-0.141-18.025 c-0.23-1.809-0.695-2.225-1.203-2.688c3.754-0.186,4.957-0.789,6.117-1.389v4.91c0.555-1.438,1.713-4.635,6.348-4.635 c5.793,0,5.838,4.217,5.885,6.996V139.268"/>
<path i:knockout="Off" fill="#A80030"
d="M66.926,111.533l-3.838,3.836l-3.836-3.836l3.836-3.836L66.926,111.533"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+19 -8
View File
@@ -1,8 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<circle fill="#f47421" cy="50" cx="50" r="45"/>
<circle fill="none" stroke="#ffffff" stroke-width="8.55" cx="50" cy="50" r="21.825"/>
<g id="friend"><circle fill="#f47421" cx="19.4" cy="50" r="8.4376"/>
<path stroke="#f47421" stroke-width="3.2378" d="M67,50H77"/>
<circle fill="#ffffff" cx="19.4" cy="50" r="6.00745"/></g>
<use xlink:href="#friend" transform="rotate(120,50,50)"/>
<use xlink:href="#friend" transform="rotate(240,50,50)"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<!--
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
-->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle fill="#f47421" cy="50" cx="50" r="45"/>
<circle fill="none" stroke="#ffffff" stroke-width="8.55" cx="50" cy="50" r="21.825"/>
<g id="friend">
<circle fill="#f47421" cx="19.4" cy="50" r="8.4376"/>
<path stroke="#f47421" stroke-width="3.2378" d="M67,50H77"/>
<circle fill="#ffffff" cx="19.4" cy="50" r="6.00745"/>
</g>
<use xlink:href="#friend" transform="rotate(120,50,50)"/>
<use xlink:href="#friend" transform="rotate(240,50,50)"/>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 763 B

+13
View File
@@ -1,3 +1,11 @@
/*
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
html, body {
height: 100%;
}
@@ -69,4 +77,9 @@ span[data-dots="true"]::after {
content: "";
width: 5px;
animation: dots 3s linear infinite;
}
/* XDebug */
.xdebug-error {
color: #000000;
}
+22 -14
View File
@@ -1,41 +1,49 @@
/*
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
.form-signin {
max-width: 330px;
padding: 1rem;
max-width: 330px;
padding: 1rem;
}
.form-signin .form-floating:focus-within {
z-index: 2;
z-index: 2;
}
.form-signin input[name="username"],
.form-signin input[name="password_new"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[name="password"],
.form-signin input[name="password_repeated"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
/* Theme Switch */
.bi {
vertical-align: -.125em;
fill: currentColor;
vertical-align: -.125em;
fill: currentColor;
}
.btn-bd-primary:active,
.btn-bd-primary:focus {
--bs-btn-active-border-color: transparent;
--bs-btn-active-border-color: transparent;
}
.bd-mode-toggle {
z-index: 1500;
z-index: 1500;
}
.bd-mode-toggle .dropdown-menu .active .bi {
display: block !important;
display: block !important;
}
+76
View File
@@ -1,3 +1,11 @@
/*
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
/* DEBUG */
.xdebug-error, .xdebug-var-dump {
z-index: 9999999999;
@@ -62,6 +70,31 @@ main {
}
}
/* Loading / Fetching */
[data-fetching="true"] {
display: flex;
justify-content: center !important;
}
[data-fetching="true"] [role="status"] {
display: block;
width: 5rem;
height: 5rem;
margin: 50px 0;
}
[data-fetching="false"] [role="status"] {
display: none;
}
[data-fetching="true"] .loaded-content {
display: none;
}
[data-fetching="false"] .loaded-content {
display: block;
}
.sidebar .nav-link {
font-weight: 500;
color: var(--bs-dark-text-emphasis);
@@ -756,4 +789,47 @@ li.empty::before {
.tree > .list-group-item:first-child {
border-top-width: 0;
}
/* Module Info */
div#module_info .module-icon {
font-size: 100px;
}
div#module_info .carousel-caption {
right: 0;
bottom: 0;
left: 0;
color: #fff !important;
background: rgba(0, 0, 0, 0.5);
}
div#module_info div.markdown {
background: var(--bs-modal-footer-border-color);
padding: 20px;
font-size: 16px;
}
div#module_info div.markdown h1 {
font-size: 1.4rem;
}
div#module_info div.markdown h2 {
font-size: 1.3rem;
}
div#module_info div.markdown h3 {
font-size: 1.2rem;
}
div#module_info div.markdown h4 {
font-size: 1.1rem;
}
div#module_info div.markdown h5 {
font-size: 1.0rem;
}
div#module_info div.markdown h6 {
font-size: 0.9rem;
}
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
function adjustBrightness($hex, $steps) {
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\Accounting\Auth;
use fruithost\UI\Icon;
+94 -86
View File
@@ -1,90 +1,98 @@
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
const AjaxState = {
NOT_INITALIZED: 0,
CONNECTION_ESTABLISHED: 1,
REQUEST_RECEIVED: 2,
PROCESSING: 3,
FINISHED: 4
NOT_INITALIZED: 0,
CONNECTION_ESTABLISHED: 1,
REQUEST_RECEIVED: 2,
PROCESSING: 3,
FINISHED: 4
};
const Ajax = function() {
let socket;
let url;
let callbacks = {
success: [],
error: []
};
this.__construct = function __construct(url) {
this.socket = new XMLHttpRequest();
this.url = url;
this.socket.onreadystatechange = this.handle.bind(this);
};
this.handle = function handle(event) {
switch(this.socket.readyState) {
case AjaxState.NOT_INITALIZED:
case AjaxState.CONNECTION_ESTABLISHED:
case AjaxState.REQUEST_RECEIVED:
case AjaxState.PROCESSING:
/* Do currently Nohting */
break;
case AjaxState.FINISHED:
if(this.socket.readyState == 4 && this.socket.status == 200) {
try {
let json = JSON.parse(event.target.responseText);
callbacks.success.forEach(function onSuccessCallback(callback) {
callback.apply(this, [ json ]);
}.bind(this));
} catch(exception) {
callbacks.success.forEach(function onSuccessCallback(callback) {
callback.apply(this, [ event.target.responseText ]);
}.bind(this));
}
} else {
callbacks.error.forEach(function onErrorCallback(callback) {
callback.apply(this, [ event, 'Error', event.target.responseText ]);
}.bind(this));
}
break;
}
};
this.setHeaders = function setHeaders() {
this.socket.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
this.socket.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
};
this.onError = function onError(callback) {
callbacks.success.push(callback);
return this;
};
this.onSuccess = function onSuccess(callback) {
callbacks.success.push(callback);
return this;
};
this.post = function post(data) {
this.socket.open('POST', this.url, true);
this.setHeaders();
this.socket.send((typeof(data) == 'string') ? data : Object.keys(data).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
}).join('&'));
return this;
};
this.get = function get() {
this.socket.open('GET', this.url, true);
this.setHeaders();
this.socket.send();
return this;
};
this.__construct.apply(this, arguments);
const Ajax = function () {
let socket;
let url;
let callbacks = {
success: [],
error: []
};
this.__construct = function __construct(url) {
this.socket = new XMLHttpRequest();
this.url = url;
this.socket.onreadystatechange = this.handle.bind(this);
};
this.handle = function handle(event) {
switch (this.socket.readyState) {
case AjaxState.NOT_INITALIZED:
case AjaxState.CONNECTION_ESTABLISHED:
case AjaxState.REQUEST_RECEIVED:
case AjaxState.PROCESSING:
/* Do currently Nohting */
break;
case AjaxState.FINISHED:
if (this.socket.readyState == 4 && this.socket.status == 200) {
try {
let json = JSON.parse(event.target.responseText);
callbacks.success.forEach(function onSuccessCallback(callback) {
callback.apply(this, [json]);
}.bind(this));
} catch (exception) {
callbacks.success.forEach(function onSuccessCallback(callback) {
callback.apply(this, [event.target.responseText]);
}.bind(this));
}
} else {
callbacks.error.forEach(function onErrorCallback(callback) {
callback.apply(this, [event, 'Error', event.target.responseText]);
}.bind(this));
}
break;
}
};
this.setHeaders = function setHeaders() {
this.socket.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
this.socket.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
};
this.onError = function onError(callback) {
callbacks.success.push(callback);
return this;
};
this.onSuccess = function onSuccess(callback) {
callbacks.success.push(callback);
return this;
};
this.post = function post(data) {
this.socket.open('POST', this.url, true);
this.setHeaders();
this.socket.send((typeof (data) == 'string') ? data : Object.keys(data).map(function (key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
}).join('&'));
return this;
};
this.get = function get() {
this.socket.open('GET', this.url, true);
this.setHeaders();
this.socket.send();
return this;
};
this.__construct.apply(this, arguments);
};
+85 -77
View File
@@ -1,79 +1,87 @@
(() => {
'use strict'
window.addEventListener('DOMContentLoaded', () => {
document.body.style.overflow = 'hidden';
let destination = document.querySelector('input[name="destination"]');
let output = document.querySelector('div.output');
let command = document.querySelector('input[name="command"]');
let parser = new Terminal();
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
function send(cmd) {
new Ajax(destination.value).onError(function(event, error, response) {
console.warn(event, error, response);
}).onSuccess(function(response) {
if(response === '\u001B[H\u001B[2J\u001B[3J' || response === 'clear') {
output.innerHTML ='';
return;
}
output.innerHTML += parser.parse(response);
command.focus();
output.scrollTo(0, output.scrollHeight);
}).post({
action: 'command',
command: cmd
});
}
let history = [];
let position = 0;
command.addEventListener('keydown', (event) => {
switch(event.keyCode) {
/* Enter */
case 13:
let text = command.value;
history.push(text);
position = history.length;
send(text);
command.value = '';
break;
/* Up */
case 38:
if(position > 0) {
--position;
if(position < 0) {
position = history.length - 1;
}
command.blur();
command.value = history[position];
command.focus();
}
break;
/* Down */
case 40:
if(position >= history.length) {
break;
}
position++;
if(position === history.length) {
command.value = '';
} else {
command.blur();
command.focus();
command.value = history[position];
}
break;
}
});
send('motd');
command.focus();
});
(() => {
'use strict'
window.addEventListener('DOMContentLoaded', () => {
document.body.style.overflow = 'hidden';
let destination = document.querySelector('input[name="destination"]');
let output = document.querySelector('div.output');
let command = document.querySelector('input[name="command"]');
let parser = new Terminal();
function send(cmd) {
new Ajax(destination.value).onError(function (event, error, response) {
console.warn(event, error, response);
}).onSuccess(function (response) {
if (response === '\u001B[H\u001B[2J\u001B[3J' || response === 'clear') {
output.innerHTML = '';
return;
}
output.innerHTML += parser.parse(response);
command.focus();
output.scrollTo(0, output.scrollHeight);
}).post({
action: 'command',
command: cmd
});
}
let history = [];
let position = 0;
command.addEventListener('keydown', (event) => {
switch (event.keyCode) {
/* Enter */
case 13:
let text = command.value;
history.push(text);
position = history.length;
send(text);
command.value = '';
break;
/* Up */
case 38:
if (position > 0) {
--position;
if (position < 0) {
position = history.length - 1;
}
command.blur();
command.value = history[position];
command.focus();
}
break;
/* Down */
case 40:
if (position >= history.length) {
break;
}
position++;
if (position === history.length) {
command.value = '';
} else {
command.blur();
command.focus();
command.value = history[position];
}
break;
}
});
send('motd');
command.focus();
});
})();
+74 -66
View File
@@ -1,81 +1,89 @@
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
(() => {
'use strict'
'use strict'
const getStoredTheme = () => localStorage.getItem('theme')
const setStoredTheme = theme => localStorage.setItem('theme', theme)
const getStoredTheme = () => localStorage.getItem('theme')
const setStoredTheme = theme => localStorage.setItem('theme', theme)
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
if (storedTheme) {
return storedTheme
}
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
if (storedTheme) {
return storedTheme
}
const setTheme = theme => {
if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
setTheme(getPreferredTheme())
const setTheme = theme => {
if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
setTheme(getPreferredTheme())
if (!themeSwitcher) {
return
}
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
const themeSwitcherText = document.querySelector('#bd-theme-text')
const themeButon = document.querySelector('.theme-icon')
const activeThemeIcon = document.querySelector('.theme-icon-active')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('i')
if (!themeSwitcher) {
return
}
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
themeButon.classList.remove(themeButon.dataset.current);
themeButon.dataset.current = svgOfActiveBtn.dataset.name;
themeButon.classList.add(svgOfActiveBtn.dataset.name);
svgOfActiveBtn.classList.add('theme-icon-active');
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
const themeSwitcherText = document.querySelector('#bd-theme-text')
const themeButon = document.querySelector('.theme-icon')
const activeThemeIcon = document.querySelector('.theme-icon-active')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('i')
if (focus) {
themeSwitcher.focus()
}
}
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
setTheme(getPreferredTheme())
}
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
window.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme())
themeButon.classList.remove(themeButon.dataset.current);
themeButon.dataset.current = svgOfActiveBtn.dataset.name;
themeButon.classList.add(svgOfActiveBtn.dataset.name);
svgOfActiveBtn.classList.add('theme-icon-active');
document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
setStoredTheme(theme)
setTheme(theme)
showActiveTheme(theme, true)
})
})
})
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
if (focus) {
themeSwitcher.focus()
}
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
setTheme(getPreferredTheme())
}
})
window.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme())
document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
setStoredTheme(theme)
setTheme(theme)
showActiveTheme(theme, true)
})
})
})
})();
+70 -62
View File
@@ -1,77 +1,85 @@
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
(() => {
'use strict'
'use strict'
const getStoredTheme = () => localStorage.getItem('theme')
const setStoredTheme = theme => localStorage.setItem('theme', theme)
const getStoredTheme = () => localStorage.getItem('theme')
const setStoredTheme = theme => localStorage.setItem('theme', theme)
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
if (storedTheme) {
return storedTheme
}
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
if (storedTheme) {
return storedTheme
}
const setTheme = theme => {
if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
setTheme(getPreferredTheme())
const setTheme = theme => {
if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
setTheme(getPreferredTheme())
if (!themeSwitcher) {
return
}
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
const themeSwitcherText = document.querySelector('#bd-theme-text')
const activeThemeIcon = document.querySelector('.theme-icon-active')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('i')
if (!themeSwitcher) {
return
}
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
activeThemeIcon.classList.remove(...activeThemeIcon.classList);
activeThemeIcon.classList.add(svgOfActiveBtn.classList[0]);
activeThemeIcon.classList.add('theme-icon-active');
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
const themeSwitcherText = document.querySelector('#bd-theme-text')
const activeThemeIcon = document.querySelector('.theme-icon-active')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('i')
if (focus) {
themeSwitcher.focus()
}
}
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
setTheme(getPreferredTheme())
}
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
activeThemeIcon.classList.remove(...activeThemeIcon.classList);
activeThemeIcon.classList.add(svgOfActiveBtn.classList[0]);
activeThemeIcon.classList.add('theme-icon-active');
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
window.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme())
if (focus) {
themeSwitcher.focus()
}
}
document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
setStoredTheme(theme)
setTheme(theme)
showActiveTheme(theme, true)
})
})
})
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
setTheme(getPreferredTheme())
}
})
window.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme())
document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
setStoredTheme(theme)
setTheme(theme)
showActiveTheme(theme, true)
})
})
})
})()
+128 -120
View File
@@ -1,122 +1,130 @@
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
const Statistics = (function Statistics() {
this.element = null;
this.canvas = null;
this.context = null;
this.stopped = false;
this.interval = null;
this.position = 0;
this.data = [ 0.00 ];
this.grid = [ 0, 0, 0, 0.5 ];
this.colors = [ [ 0, 0, 0, 0] ];
this.__constructor = function __constructor(element) {
this.element = document.querySelector(element);
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
window.addEventListener("resize", this.resize.bind(this));
this.resize();
};
this.setColor = function setColor(color) {
this.grid = color;
};
this.start = function start() {
this.stopped = false;
};
this.stop = function stop() {
this.stopped = true;
};
this.resize = function resize() {
this.canvas.width = (this.element.parentNode.innerWidth || this.element.parentNode.clientWidth || this.element.parentNode.clientWidth);
this.canvas.height = 100;
this.draw();
};
this.add = function add(value, color) {
this.colors.push(color);
this.data.push(value);
};
this.draw = function draw() {
this.context.save();
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.context.restore();
let bw = this.canvas.width - this.position;
let bh = this.canvas.height;
let c = 4;
let d = 1;
/* Vertical Lines */
this.context.save();
this.context.fillStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.strokeStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.beginPath();
for(let x = 0; x <= bw; x += 40) {
this.context.moveTo(x + this.position, c);
this.context.lineTo(x + this.position, bh + c);
}
this.context.closePath();
this.context.stroke();
this.context.fill();
this.context.restore();
/* Horizontal Lines */
this.context.save();
this.context.fillStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.strokeStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.beginPath();
for(let x = 0; x <= bh + d; x += (15 + d)) {
this.context.moveTo(this.position, x + c);
this.context.lineTo(bw + this.position, + x + c);
}
this.context.closePath();
this.context.fill();
this.context.stroke();
this.context.restore();
/* Data */
this.context.save();
this.context.beginPath();
for(let i = 1; i < this.data.length; i++) {
const height = (this.canvas.height * this.data[i]) / 100;
const width = 40 + c + d;
const x = this.canvas.width - (i * width);
const y = this.canvas.height - height;
this.context.fillStyle = 'rgba(' + this.colors[i].join(',') + ')';
this.context.strokeStyle = 'rgba(' + this.colors[i].join(',') + ')';
this.context.fillRect(x, y, width, height);
}
this.context.closePath();
this.context.restore();
window.requestAnimationFrame(this.draw.bind(this));
};
this.render = function render(interval) {
this.element.appendChild(this.canvas);
setInterval(function() {
if(this.stopped) {
return;
}
this.position -= 5;
}.bind(this), interval);
this.draw();
};
this.__constructor.apply(this, arguments);
this.element = null;
this.canvas = null;
this.context = null;
this.stopped = false;
this.interval = null;
this.position = 0;
this.data = [0.00];
this.grid = [0, 0, 0, 0.5];
this.colors = [[0, 0, 0, 0]];
this.__constructor = function __constructor(element) {
this.element = document.querySelector(element);
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
window.addEventListener("resize", this.resize.bind(this));
this.resize();
};
this.setColor = function setColor(color) {
this.grid = color;
};
this.start = function start() {
this.stopped = false;
};
this.stop = function stop() {
this.stopped = true;
};
this.resize = function resize() {
this.canvas.width = (this.element.parentNode.innerWidth || this.element.parentNode.clientWidth || this.element.parentNode.clientWidth);
this.canvas.height = 100;
this.draw();
};
this.add = function add(value, color) {
this.colors.push(color);
this.data.push(value);
};
this.draw = function draw() {
this.context.save();
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.context.restore();
let bw = this.canvas.width - this.position;
let bh = this.canvas.height;
let c = 4;
let d = 1;
/* Vertical Lines */
this.context.save();
this.context.fillStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.strokeStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.beginPath();
for (let x = 0; x <= bw; x += 40) {
this.context.moveTo(x + this.position, c);
this.context.lineTo(x + this.position, bh + c);
}
this.context.closePath();
this.context.stroke();
this.context.fill();
this.context.restore();
/* Horizontal Lines */
this.context.save();
this.context.fillStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.strokeStyle = 'rgba(' + this.grid.join(',') + ')';
this.context.beginPath();
for (let x = 0; x <= bh + d; x += (15 + d)) {
this.context.moveTo(this.position, x + c);
this.context.lineTo(bw + this.position, +x + c);
}
this.context.closePath();
this.context.fill();
this.context.stroke();
this.context.restore();
/* Data */
this.context.save();
this.context.beginPath();
for (let i = 1; i < this.data.length; i++) {
const height = (this.canvas.height * this.data[i]) / 100;
const width = 40 + c + d;
const x = this.canvas.width - (i * width);
const y = this.canvas.height - height;
this.context.fillStyle = 'rgba(' + this.colors[i].join(',') + ')';
this.context.strokeStyle = 'rgba(' + this.colors[i].join(',') + ')';
this.context.fillRect(x, y, width, height);
}
this.context.closePath();
this.context.restore();
window.requestAnimationFrame(this.draw.bind(this));
};
this.render = function render(interval) {
this.element.appendChild(this.canvas);
setInterval(function () {
if (this.stopped) {
return;
}
this.position -= 5;
}.bind(this), interval);
this.draw();
};
this.__constructor.apply(this, arguments);
});
+292 -284
View File
@@ -1,290 +1,298 @@
const Terminal = (function() {
this.colors = {
styles: {
0: 'reset',
1: 'bold',
2: 'faint',
3: 'italic',
4: 'underline',
5: 'blink',
6: 'blink',
7: 'swap-colors'
},
foreground: {
30: '#444444', /* Black */
31: '#ff8B92', /* Red */
32: '#9BC67C', /* Green */
33: '#E5C07B', /* Yellow */
34: '#6AB0EB', /* Blue */
35: '#DF8FF6', /* Magenta */
36: '#56D7D7', /* Cyan */
37: '#DDDDDD', /* White */
38: 'multicolor',
90: '#878787', /* Bright Black */
91: '#FC646D', /* Bright Red */
92: '#7AE52D', /* Bright Green */
93: '#FFDB97', /* Bright Yellow */
94: '#32A1FF', /* Bright Blue */
95: '#F0BBFF', /* Bright Magenta */
96: '#A7FFFF', /* Bright Cyan */
97: '#F4F4F4' /* Bright White */
},
background: {
40: '#444444', /* Black */
41: '#B13139', /* Red */
42: '#698754', /* Green */
43: '#AA8D57', /* Yellow */
44: '#3A6385', /* Blue */
45: '#8F53A0', /* Magenta */
46: '#339696', /* Cyan */
47: '#828282', /* White */
48: 'multicolor',
100: '#5D5D5D', /* Bright Black */
101: '#BE000C', /* Bright Red */
102: '#52B90B', /* Bright Green */
103: '#BDCA0A', /* Bright Yellow */
104: '#0E75CA', /* Bright Blue */
105: '#B622E0', /* Bright Magenta */
106: '#00B7B7', /* Bright Cyan */
107: '#CACACA' /* Bright White */
}
};
this.escape = function escape(html) {
var text = document.createTextNode(html);
var p = document.createElement('p');
p.appendChild(text);
return p.innerHTML;
};
this.tokens = function tokens(input) {
var matchingControl = null;
var matchingData = null;
var matchingText = '';
var ansiState = [];
var result = [];
var state = {};
var eraseChar = function eraseChar() {
var index, text;
if(matchingText.length) {
matchingText = matchingText.substr(0, matchingText.length - 1);
} else if(result.length) {
index = result.length - 1;
text = result[index].text;
if(text.length === 1) {
result.pop();
} else {
result[index].text = text.substr(0, text.length - 1);
}
}
};
input.split('').forEach(function(character) {
if(matchingControl != null) {
if(matchingControl == '\033' && character == '\[') {
if(matchingText) {
state.text = matchingText;
result.push(state);
state = {};
matchingText = "";
}
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
matchingControl = null;
matchingData = '';
} else {
matchingText += matchingControl + character;
matchingControl = null;
}
return;
} else if(matchingData != null) {
switch(character) {
case ';':
ansiState.push(matchingData);
matchingData = '';
break;
case 'm':
ansiState.push(matchingData);
matchingData = null;
matchingText = '';
if(ansiState.length >= 3) {
console.log(ansiState);
switch(parseInt(ansiState[0])) {
case 38:
switch(parseInt(ansiState[1])) {
/* RGB Color */
case 2:
state.foreground = 'rgb(' + [
ansiState[2],
ansiState[3],
ansiState[4]
].join(',') + ')';
break;
/* 8-bit Color */
case 5:
var color = parseInt(ansiState[2]);
state.foreground = 'rgb(' + [
(color >> 5) * 255 / 7,
((color >> 2) & 0x07) * 255 / 7,
(color & 0x03) * 255 / 3
].join(',') + ')';
console.log(state.foreground);
break;
}
break;
case 48:
switch(parseInt(ansiState[1])) {
/* RGB Color */
case 2:
state.background = 'rgb(' + [
ansiState[2],
ansiState[3],
ansiState[4]
].join(',') + ')';
break;
/* 8-bit Color */
case 5:
var color = parseInt(ansiState[2]);
state.background = 'rgb(' + [
(color >> 5) * 255 / 7,
((color >> 2) & 0x07) * 255 / 7,
(color & 0x03) * 255 / 3
].join(',') + ')';
break;
}
break;
}
} else {
ansiState.forEach(function (ansiCode) {
if(this.colors.foreground[ansiCode]) {
state.foreground = this.colors.foreground[ansiCode];
} else if(this.colors.background[ansiCode]) {
state.background = this.colors.background[ansiCode];
} else if(ansiCode == 39) {
delete state.foreground;
} else if(ansiCode == 49) {
delete state.background;
} else if(this.colors.styles[ansiCode]) {
state[this.colors.styles[ansiCode]] = true;
} else if(ansiCode == 22) {
state.bold = false;
} else if(ansiCode == 23) {
state.italic = false;
} else if(ansiCode == 24) {
state.underline = false;
}
}.bind(this));
}
ansiState = [];
break;
default:
matchingData += character;
break;
}
return;
}
const Terminal = (function () {
this.colors = {
styles: {
0: 'reset',
1: 'bold',
2: 'faint',
3: 'italic',
4: 'underline',
5: 'blink',
6: 'blink',
7: 'swap-colors'
},
foreground: {
30: '#444444', /* Black */
31: '#ff8B92', /* Red */
32: '#9BC67C', /* Green */
33: '#E5C07B', /* Yellow */
34: '#6AB0EB', /* Blue */
35: '#DF8FF6', /* Magenta */
36: '#56D7D7', /* Cyan */
37: '#DDDDDD', /* White */
38: 'multicolor',
90: '#878787', /* Bright Black */
91: '#FC646D', /* Bright Red */
92: '#7AE52D', /* Bright Green */
93: '#FFDB97', /* Bright Yellow */
94: '#32A1FF', /* Bright Blue */
95: '#F0BBFF', /* Bright Magenta */
96: '#A7FFFF', /* Bright Cyan */
97: '#F4F4F4' /* Bright White */
},
background: {
40: '#444444', /* Black */
41: '#B13139', /* Red */
42: '#698754', /* Green */
43: '#AA8D57', /* Yellow */
44: '#3A6385', /* Blue */
45: '#8F53A0', /* Magenta */
46: '#339696', /* Cyan */
47: '#828282', /* White */
48: 'multicolor',
100: '#5D5D5D', /* Bright Black */
101: '#BE000C', /* Bright Red */
102: '#52B90B', /* Bright Green */
103: '#BDCA0A', /* Bright Yellow */
104: '#0E75CA', /* Bright Blue */
105: '#B622E0', /* Bright Magenta */
106: '#00B7B7', /* Bright Cyan */
107: '#CACACA' /* Bright White */
}
};
switch(character) {
case '\033':
matchingControl = character;
break;
case '\u0008':
this.eraseChar();
break;
default:
matchingText += character;
break;
}
}.bind(this));
this.escape = function escape(html) {
var text = document.createTextNode(html);
var p = document.createElement('p');
p.appendChild(text);
return p.innerHTML;
};
if(matchingText) {
state.text = matchingText + (matchingControl ? matchingControl : '');
result.push(state);
}
return result;
};
this.tokens = function tokens(input) {
var matchingControl = null;
var matchingData = null;
var matchingText = '';
var ansiState = [];
var result = [];
var state = {};
var eraseChar = function eraseChar() {
var index, text;
this.parse = function parse(input) {
var lines = input.split('\n');
var output = "";
lines.forEach(function(entry) {
let line = '';
this.tokens(entry).forEach(function(token) {
var style = {};
if('bold' in token) {
style.fontWeight = 'bold';
}
if('italic' in token) {
style.fontStyle = 'italic';
}
if('faint' in token) {
style.opacity = 0.5;
}
if('underline' in token) {
style.textDecoration = 'underline';
}
if('blink' in token) {
style.opacity = 1;
style.animation = 'blink 1s step-end infinite';
}
if('foreground' in token) {
style.color = token.foreground;
}
if('background' in token) {
style.backgroundColor = token.background;
}
if('swap-colors' in token) {
let color = style.color;
let background = style.backgroundColor;
style.color = background;
style.backgroundColor = color;
}
if('reset' in token) {
/* Reset Style? */
style = {};
}
if(Object.keys(style).length > 0) {
let elem = document.createElement('span');
Object.keys(style).forEach(function(name) {
elem.style[name] = style[name];
});
elem.innerText = token.text;
var wrap = document.createElement('div');
wrap.appendChild(elem.cloneNode(true));
line += wrap.innerHTML;
} else {
line += this.escape(token.text);
}
}.bind(this));
output += '<div class="user-select-all">' + line + '</div>';
}.bind(this));
if (matchingText.length) {
matchingText = matchingText.substr(0, matchingText.length - 1);
} else if (result.length) {
index = result.length - 1;
text = result[index].text;
return output;
};
if (text.length === 1) {
result.pop();
} else {
result[index].text = text.substr(0, text.length - 1);
}
}
};
input.split('').forEach(function (character) {
if (matchingControl != null) {
if (matchingControl == '\033' && character == '\[') {
if (matchingText) {
state.text = matchingText;
result.push(state);
state = {};
matchingText = "";
}
matchingControl = null;
matchingData = '';
} else {
matchingText += matchingControl + character;
matchingControl = null;
}
return;
} else if (matchingData != null) {
switch (character) {
case ';':
ansiState.push(matchingData);
matchingData = '';
break;
case 'm':
ansiState.push(matchingData);
matchingData = null;
matchingText = '';
if (ansiState.length >= 3) {
console.log(ansiState);
switch (parseInt(ansiState[0])) {
case 38:
switch (parseInt(ansiState[1])) {
/* RGB Color */
case 2:
state.foreground = 'rgb(' + [
ansiState[2],
ansiState[3],
ansiState[4]
].join(',') + ')';
break;
/* 8-bit Color */
case 5:
var color = parseInt(ansiState[2]);
state.foreground = 'rgb(' + [
(color >> 5) * 255 / 7,
((color >> 2) & 0x07) * 255 / 7,
(color & 0x03) * 255 / 3
].join(',') + ')';
console.log(state.foreground);
break;
}
break;
case 48:
switch (parseInt(ansiState[1])) {
/* RGB Color */
case 2:
state.background = 'rgb(' + [
ansiState[2],
ansiState[3],
ansiState[4]
].join(',') + ')';
break;
/* 8-bit Color */
case 5:
var color = parseInt(ansiState[2]);
state.background = 'rgb(' + [
(color >> 5) * 255 / 7,
((color >> 2) & 0x07) * 255 / 7,
(color & 0x03) * 255 / 3
].join(',') + ')';
break;
}
break;
}
} else {
ansiState.forEach(function (ansiCode) {
if (this.colors.foreground[ansiCode]) {
state.foreground = this.colors.foreground[ansiCode];
} else if (this.colors.background[ansiCode]) {
state.background = this.colors.background[ansiCode];
} else if (ansiCode == 39) {
delete state.foreground;
} else if (ansiCode == 49) {
delete state.background;
} else if (this.colors.styles[ansiCode]) {
state[this.colors.styles[ansiCode]] = true;
} else if (ansiCode == 22) {
state.bold = false;
} else if (ansiCode == 23) {
state.italic = false;
} else if (ansiCode == 24) {
state.underline = false;
}
}.bind(this));
}
ansiState = [];
break;
default:
matchingData += character;
break;
}
return;
}
switch (character) {
case '\033':
matchingControl = character;
break;
case '\u0008':
this.eraseChar();
break;
default:
matchingText += character;
break;
}
}.bind(this));
if (matchingText) {
state.text = matchingText + (matchingControl ? matchingControl : '');
result.push(state);
}
return result;
};
this.parse = function parse(input) {
var lines = input.split('\n');
var output = "";
lines.forEach(function (entry) {
let line = '';
this.tokens(entry).forEach(function (token) {
var style = {};
if ('bold' in token) {
style.fontWeight = 'bold';
}
if ('italic' in token) {
style.fontStyle = 'italic';
}
if ('faint' in token) {
style.opacity = 0.5;
}
if ('underline' in token) {
style.textDecoration = 'underline';
}
if ('blink' in token) {
style.opacity = 1;
style.animation = 'blink 1s step-end infinite';
}
if ('foreground' in token) {
style.color = token.foreground;
}
if ('background' in token) {
style.backgroundColor = token.background;
}
if ('swap-colors' in token) {
let color = style.color;
let background = style.backgroundColor;
style.color = background;
style.backgroundColor = color;
}
if ('reset' in token) {
/* Reset Style? */
style = {};
}
if (Object.keys(style).length > 0) {
let elem = document.createElement('span');
Object.keys(style).forEach(function (name) {
elem.style[name] = style[name];
});
elem.innerText = token.text;
var wrap = document.createElement('div');
wrap.appendChild(elem.cloneNode(true));
line += wrap.innerHTML;
} else {
line += this.escape(token.text);
}
}.bind(this));
output += '<div class="user-select-all">' + line + '</div>';
}.bind(this));
return output;
};
});
+16 -4
View File
@@ -1,3 +1,11 @@
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
(() => {
function show(event) {
[].map.call(document.querySelectorAll('.bi[data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"], [data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"] .bi'), function (icon) {
@@ -117,7 +125,7 @@
});
});
[].map.call(document.querySelectorAll('[data-loading]'), function (element) {
window.Loading = function Loading(element) {
element.addEventListener('click', function (event) {
let element = event.target;
@@ -136,7 +144,9 @@
dots.dataset.dots = 'true';
element.append(dots);
}, false);
});
};
[].map.call(document.querySelectorAll('[data-loading]'), Loading);
document.querySelector('div#module_info').addEventListener('show.bs.modal', (event) => {
let modal = event.target;
@@ -283,7 +293,7 @@
});
});
[].map.call(document.querySelectorAll('[data-confirm]'), function (element) {
window.Confirm = function Confirm(element) {
element.addEventListener('mousedown', function (event) {
let target = event.target;
@@ -343,7 +353,9 @@
return false;
}
});
});
};
[].map.call(document.querySelectorAll('[data-confirm]'), Confirm);
window.showing.forEach((modal) => {
+26 -26
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
@@ -7,25 +14,22 @@
?>
<main class="form-signin w-100 m-auto text-center">
<form method="post" action="<?php print $template->url(true); ?>">
<img class="mb-4"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE4AAABjCAYAAAFCnwZjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMjkwNjdGQTdEMzMxMUU4QkQ0QkUzMTYwNTRFNTQzOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMjkwNjdGQjdEMzMxMUU4QkQ0QkUzMTYwNTRFNTQzOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMyOTA2N0Y4N0QzMzExRThCRDRCRTMxNjA1NEU1NDM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMyOTA2N0Y5N0QzMzExRThCRDRCRTMxNjA1NEU1NDM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+wx6ypAAAEYNJREFUeNpi/P//PwMIqO6a3cAAAQ233VIZ0AEjTKHYnPb//HIiYPbHR28Ygez/QJrhVUolI0iMCaaDW5TvLkgCBECKvr7+hGIiXCELJ5sKUIETkNkI4v/5/gtsI4bVh3p2/4eKPb1w9ryMgbEhjM9oV+LKwILmZj4gBtkJU3QLbuLB7l0MMAmgTkYkk8EmwfggN96EuWVSZBdc0a9fv/5D+cEwhepAfP/iuQsMQHfBPHCfjY0NxucEYnZ0z4AU/Qd6BlkTyEmI4IEpAtEgRQ/u3WdA8hQieGAAyeSfQJPY4eHMgAmmwExGFgQIIEa0RAFOGMQmCkZQnIPiG8SGaUL2DAM0UfyHpRxkAHcjUJIRZjVQUT07Pxd2hXMvKYDdAAxDYPCYtDF8YPgJ9pQbAyOG1VDw/+H9Bz9hUYecKP4jBTh6AukEMstAcjATGYFWgqLuP1oCKYPFGAuUsQYatyCwBhqNIIVdQAxK6ozIiQKUaD+CEoSegT4DEzMT2CSQE9A98xGI74BMfvjgIQN6NKIkCqCpcJ/DTMLIhcjBg0WMASCAMJIZckEABSDOW1icAxMPinr0BIQtOTIgFRgg6g3UIkZogmL49PgtA8gh0OTBgNcryIpABn97+xleGv358RssBjIMPW3hdeGaDyYw5h1gWlO1i3RFDuRAIF4HFGdI1nsAFgPlZUJZlR+5oALFPQjoGxkAUwYjenJkRDEQWpAhA2ZQEYpWeGCN3TevXzM8efQEVPIwInt5NRCHQDUKAPF7mItAAJp0BYH4A8iwD+8/wEokcBEmIip6C9kilKIW2VXQYhanHFqBDwNhKGEIzHEMSLUAPzRbgAsDqIuigdQSaF7vACVL9DBESTZ5y8sY0PIYuLpBElsNpddCDSOcU9BiHMPLoDojd1kpIy49TAzEgduwIg2YdGLxKQQIwGn5rDQQA2E8qVbYw0otgocuUgV9Cp9C8AGkV19AQapX7+JJvIriU3j2LupB0CIISlk9+S/OrMnydTZJtxaGNLvZYTKZ/L6pRAjqr2LAr3VLvj+/+lPN8nFf1mAMDBVn709DlXbaTMKygBkIhb5n7UvdaKzZ5/su8mje3gavyvwYCQrjHElFGnsI+eOLiqGsljOKYCH0Ic7p/14dTD2jfgYc67GnycV6rLp/TFMF0zTpLTGtODENxcx211u8XwnWGS2e4ZZE1qKjC9xj9xvQPc1COcthG9peegPROCC4bXZiB9Cz47pt+LwHSpYieWIUxq0ckm2FCG17vxECu1bDB0Tp/MApOr+zOTSyp7mx7emIwEBXwg6ueY17111eUq25lkIZcDnbrgGFHZwkSYLTIdbZBbKd6Htut7JBwxluhd5pkQIjD4BZf0I0bbJ2Yo7IdhkmZJtkpx6JZAm7YuWTResVEE/RluKDLZassyM78nVajRRtXoca81Brxkaj8UbYiIxHxSrOPqzqY05ueXts2AEUjY9S2SSS9yA/YIEWWltb7jKIcNYj2v/SzjSU9EmcaYGm6djiXwFYs5qdKIIg3DNAFkPW3Y1kSRCSJXrSSDh4woSbL+APBMHEl+Dg0cR4NCa+AifRizwA8eLFi0e8kRBNUKOAAhl3d5qq3u6hpqZ7pmfWTjpsZqmpmpqu+r6vNjdnuGbfvJLjzQnbV13Y04Yfu8Ca42yGLuQA/GhRKqLjM7Xr0y0Rjo7Qr8Zg/yBp/Qt4LSyA15FxvAkUdyEQQU0KGQLb3YCvXpZmHK7159vvwSAACEQwknl7qAY+wr5DeUDyesKQK5LLlehQ0ToGQqIczE5yTF4csJwrm0EYrKUf7JeIe3FZV8XVYNNVKgv7P5Nspg4xCQyPA2arSmDemdMBtk8Ojr72om5iE/f6ynmtfkmMty6KJu7HivLxhZovOjo104rhglOYNH9BviamGmP8f5A3IfQtrdwVuiCeIallzj9AQDsQmNUHlS2lMgfOOadw9S4c5CUaQIMtse2krQ7dTNI3uC0CPIanCMM5DMIhAH1eTYEQ5Si5tnkBFhXEPPn8NPPcOYtxnlK2lYWkZS1jhvm58bFFUQ8ZVkzHBrCU9CYCgXEEIwawRTzXFPw97EeabxjRep3Z3if3y9hCQFGj2RRz1+aEyycGZxMbfCEjmmHjDfyT4SuWVQO7f/QC2Hr5DBnVaBDKRg/qVcYNzaxxhlVtQLmQeq9SRtRucElGPj5DIoYMf7yngfwLe1p6g4BqGFKRdW2fPDCA/GPmHC4F6z4+zZnjIy5hGWmt6L6F3XUb9hN95l4zMcZbVV8XDbftu3wageesVj2k8qlWVD1vq1Sry9a3Ce85qo863rI14Ru3bgr8jaPItlIT1n3nE7n0gnxuFjXQ05MUjpay9c3cQ60vN6jE5r2SSO30mObgu2xPtUWe7VB8jvwc4drSueE8q1c+6I822+rwVUin0tloEdzcJUD/DoJ7kKf/htWFPuvQMfi4XSWw/xHcMqtAs+mwpVP15sMGZ1pBTQfU1e1n3fdc5a1zAYi3ntcmgig8TckPRNoUTxVj9aCoCIpY0LsePFQR7UGsJz0VBW9eRIJXzy14E2o9qC34X3jwKHqotClUoj20SZtgsqGt85LZzWT3vZnZnVk6ENLu5u2+zOz75nvfe1E+czwhhlCbRU7NQzDGSZDTSHBmhVoeHnDsK39dVxnbZPtGy6rQs6+xfqkklWGUGkKO2k2io2NEcLoS/IPJESOlY2/52y3OREr8Eeq0tpvDXqM1bJ0aynEIxUt+I0rT+sJfd0K57lHuTJ07FayOt9vKcsfcRysEzs7GFnX6dsgxCKRd2TEoqLZqzfSg5GB/v6t7EKMsOfZXPrHP7Zqb9fRxDnSPRnVbNcV/IhIUPeNWzpWxg3udPcafHQ/RVMp+qdkg2u1nDlqDsOP/tho5Cxhy4xy0I1EdAaqbU+fk0p6TZ64wemSImkHMCYAdyjH++U2nIOzPoADOMvER2G9nIZqx/TquY8qNH8gk9GJo5LHuu589UUQBq77CF+3VRG/azRy/iE4L8TdymJmy39UDX65neypMHu49PllZtlpWMTMUp1oSyc8LhT1py88pbbXLaiHUGNvyZfWSRuuJUALjs9tA06ht1yJCDSLyoLZ8fHC1fckJzGTwYBdHE9vyMZVGDhGEWKfTSWzLR9WVc7LEFWRX1d/VxLZ8vATBSO4viAMlkPp99FMChtQ0J05PDD2ae4IF07TOlj+bC6xXDcejFWmECA8T/S22LXRQAVIgIiS6rCuI3gHL8EyRl4JjdxG7FXFN1Ba0E3Ds/MULjLjnwMypBJMuWBLioFF1R7aVe0J19wTnUlXEmYUKn0kKtOKYHI2+3Zh03ESFJwE6kxRoQ8cmiWtYAXQmKdB67YFVvuEoyR8A6IwJWOZyuUgtIV/Iz4QYiov6xQBAQ0DIYElGXbh4lrRohwA0eU8fSpRgeenKZcCqd4JgrvLXK6HNkcUV1u9kyxG2WnDPUGC5vlbpCs3jx8eZgIwH4kbn2GAr2JAA5YqwXRLXeipwkLJ9rgF3nGz61ZvK6toBVJx1008A9H3W7xw2tjXe+EOOwZZ0VUSm/ODO8C/zHmHCn0xsVRt/HMp0VjCLZSb9AMPzvAWCCWttWa9AHI/PGUjzAZXJZrNxsUymQeOuyKZcs/oW5KS1emJb1itvJs5bN6R9ENW9imPFQpgAIJs7pZk9tHGupAHaN+AYVpgTPy1jOlvbZQXc+YUcnxZcLkI22+120MDL/2YKWyfK5hkWrfh9FiAbkRXy+XzQUfzz+w9G2CY Line truncated
alt=""/>
<img class="mb-4" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE4AAABjCAYAAAFCnwZjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMjkwNjdGQTdEMzMxMUU4QkQ0QkUzMTYwNTRFNTQzOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMjkwNjdGQjdEMzMxMUU4QkQ0QkUzMTYwNTRFNTQzOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMyOTA2N0Y4N0QzMzExRThCRDRCRTMxNjA1NEU1NDM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMyOTA2N0Y5N0QzMzExRThCRDRCRTMxNjA1NEU1NDM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+wx6ypAAAEYNJREFUeNpi/P//PwMIqO6a3cAAAQ233VIZ0AEjTKHYnPb//HIiYPbHR28Ygez/QJrhVUolI0iMCaaDW5TvLkgCBECKvr7+hGIiXCELJ5sKUIETkNkI4v/5/gtsI4bVh3p2/4eKPb1w9ryMgbEhjM9oV+LKwILmZj4gBtkJU3QLbuLB7l0MMAmgTkYkk8EmwfggN96EuWVSZBdc0a9fv/5D+cEwhepAfP/iuQsMQHfBPHCfjY0NxucEYnZ0z4AU/Qd6BlkTyEmI4IEpAtEgRQ/u3WdA8hQieGAAyeSfQJPY4eHMgAmmwExGFgQIIEa0RAFOGMQmCkZQnIPiG8SGaUL2DAM0UfyHpRxkAHcjUJIRZjVQUT07Pxd2hXMvKYDdAAxDYPCYtDF8YPgJ9pQbAyOG1VDw/+H9Bz9hUYecKP4jBTh6AukEMstAcjATGYFWgqLuP1oCKYPFGAuUsQYatyCwBhqNIIVdQAxK6ozIiQKUaD+CEoSegT4DEzMT2CSQE9A98xGI74BMfvjgIQN6NKIkCqCpcJ/DTMLIhcjBg0WMASCAMJIZckEABSDOW1icAxMPinr0BIQtOTIgFRgg6g3UIkZogmL49PgtA8gh0OTBgNcryIpABn97+xleGv358RssBjIMPW3hdeGaDyYw5h1gWlO1i3RFDuRAIF4HFGdI1nsAFgPlZUJZlR+5oALFPQjoGxkAUwYjenJkRDEQWpAhA2ZQEYpWeGCN3TevXzM8efQEVPIwInt5NRCHQDUKAPF7mItAAJp0BYH4A8iwD+8/wEokcBEmIip6C9kilKIW2VXQYhanHFqBDwNhKGEIzHEMSLUAPzRbgAsDqIuigdQSaF7vACVL9DBESTZ5y8sY0PIYuLpBElsNpddCDSOcU9BiHMPLoDojd1kpIy49TAzEgduwIg2YdGLxKQQIwGn5rDQQA2E8qVbYw0otgocuUgV9Cp9C8AGkV19AQapX7+JJvIriU3j2LupB0CIISlk9+S/OrMnydTZJtxaGNLvZYTKZ/L6pRAjqr2LAr3VLvj+/+lPN8nFf1mAMDBVn709DlXbaTMKygBkIhb5n7UvdaKzZ5/su8mje3gavyvwYCQrjHElFGnsI+eOLiqGsljOKYCH0Ic7p/14dTD2jfgYc67GnycV6rLp/TFMF0zTpLTGtODENxcx211u8XwnWGS2e4ZZE1qKjC9xj9xvQPc1COcthG9peegPROCC4bXZiB9Cz47pt+LwHSpYieWIUxq0ckm2FCG17vxECu1bDB0Tp/MApOr+zOTSyp7mx7emIwEBXwg6ueY17111eUq25lkIZcDnbrgGFHZwkSYLTIdbZBbKd6Htut7JBwxluhd5pkQIjD4BZf0I0bbJ2Yo7IdhkmZJtkpx6JZAm7YuWTResVEE/RluKDLZassyM78nVajRRtXoca81Brxkaj8UbYiIxHxSrOPqzqY05ueXts2AEUjY9S2SSS9yA/YIEWWltb7jKIcNYj2v/SzjSU9EmcaYGm6djiXwFYs5qdKIIg3DNAFkPW3Y1kSRCSJXrSSDh4woSbL+APBMHEl+Dg0cR4NCa+AifRizwA8eLFi0e8kRBNUKOAAhl3d5qq3u6hpqZ7pmfWTjpsZqmpmpqu+r6vNjdnuGbfvJLjzQnbV13Y04Yfu8Ca42yGLuQA/GhRKqLjM7Xr0y0Rjo7Qr8Zg/yBp/Qt4LSyA15FxvAkUdyEQQU0KGQLb3YCvXpZmHK7159vvwSAACEQwknl7qAY+wr5DeUDyesKQK5LLlehQ0ToGQqIczE5yTF4csJwrm0EYrKUf7JeIe3FZV8XVYNNVKgv7P5Nspg4xCQyPA2arSmDemdMBtk8Ojr72om5iE/f6ynmtfkmMty6KJu7HivLxhZovOjo104rhglOYNH9BviamGmP8f5A3IfQtrdwVuiCeIallzj9AQDsQmNUHlS2lMgfOOadw9S4c5CUaQIMtse2krQ7dTNI3uC0CPIanCMM5DMIhAH1eTYEQ5Si5tnkBFhXEPPn8NPPcOYtxnlK2lYWkZS1jhvm58bFFUQ8ZVkzHBrCU9CYCgXEEIwawRTzXFPw97EeabxjRep3Z3if3y9hCQFGj2RRz1+aEyycGZxMbfCEjmmHjDfyT4SuWVQO7f/QC2Hr5DBnVaBDKRg/qVcYNzaxxhlVtQLmQeq9SRtRucElGPj5DIoYMf7yngfwLe1p6g4BqGFKRdW2fPDCA/GPmHC4F6z4+zZnjIy5hGWmt6L6F3XUb9hN95l4zMcZbVV8XDbftu3wageesVj2k8qlWVD1vq1Sry9a3Ce85qo863rI14Ru3bgr8jaPItlIT1n3nE7n0gnxuFjXQ05MUjpay9c3cQ60vN6jE5r2SSO30mObgu2xPtUWe7VB8jvwc4drSueE8q1c+6I822+rwVUin0tloEdzcJUD/DoJ7kKf/htWFPuvQMfi4XSWw/xHcMqtAs+mwpVP15sMGZ1pBTQfU1e1n3fdc5a1zAYi3ntcmgig8TckPRNoUTxVj9aCoCIpY0LsePFQR7UGsJz0VBW9eRIJXzy14E2o9qC34X3jwKHqotClUoj20SZtgsqGt85LZzWT3vZnZnVk6ENLu5u2+zOz75nvfe1E+czwhhlCbRU7NQzDGSZDTSHBmhVoeHnDsK39dVxnbZPtGy6rQs6+xfqkklWGUGkKO2k2io2NEcLoS/IPJESOlY2/52y3OREr8Eeq0tpvDXqM1bJ0aynEIxUt+I0rT+sJfd0K57lHuTJ07FayOt9vKcsfcRysEzs7GFnX6dsgxCKRd2TEoqLZqzfSg5GB/v6t7EKMsOfZXPrHP7Zqb9fRxDnSPRnVbNcV/IhIUPeNWzpWxg3udPcafHQ/RVMp+qdkg2u1nDlqDsOP/tho5Cxhy4xy0I1EdAaqbU+fk0p6TZ64wemSImkHMCYAdyjH++U2nIOzPoADOMvER2G9nIZqx/TquY8qNH8gk9GJo5LHuu589UUQBq77CF+3VRG/azRy/iE4L8TdymJmy39UDX65neypMHu49PllZtlpWMTMUp1oSyc8LhT1py88pbbXLaiHUGNvyZfWSRuuJUALjs9tA06ht1yJCDSLyoLZ8fHC1fckJzGTwYBdHE9vyMZVGDhGEWKfTSWzLR9WVc7LEFWRX1d/VxLZ8vATBSO4viAMlkPp99FMChtQ0J05PDD2ae4IF07TOlj+bC6xXDcejFWmECA8T/S22LXRQAVIgIiS6rCuI3gHL8EyRl4JjdxG7FXFN1Ba0E3Ds/MULjLjnwMypBJMuWBLioFF1R7aVe0J19wTnUlXEmYUKn0kKtOKYHI2+3Zh03ESFJwE6kxRoQ8cmiWtYAXQmKdB67YFVvuEoyR8A6IwJWOZyuUgtIV/Iz4QYiov6xQBAQ0DIYElGXbh4lrRohwA0eU8fSpRgeenKZcCqd4JgrvLXK6HNkcUV1u9kyxG2WnDPUGC5vlbpCs3jx8eZgIwH4kbn2GAr2JAA5YqwXRLXeipwkLJ9rgF3nGz61ZvK6toBVJx1008A9H3W7xw2tjXe+EOOwZZ0VUSm/ODO8C/zHmHCn0xsVRt/HMp0VjCLZSb9AMPzvAWCCWttWa9AHI/PGUjzAZXJZrNxsUymQeOuyKZcs/oW5KS1emJb1itvJs5bN6R9ENW9imPFQpgAIJs7pZk9tHGupAHaN+AYVpgTPy1jOlvbZQXc+YUcnxZcLkI22+120MDL/2YKWyfK5hkWrfh9FiAbkRXy+X Line truncated
<h1 class="h3 mb-3 fw-normal"><?php I18N::__('Please sign in'); ?></h1>
<?php
if(isset($error)) {
?>
<div class="alert alert-danger" role="alert"><?php print $error; ?></div>
<div class="alert alert-danger" role="alert"><?php print $error; ?></div>
<?php
}
if(isset($two_factor) && $two_factor) {
?>
<div class="form-floating">
<p><?php I18N::__('Please enter your 2FA code:'); ?></p>
</div>
<div class="form-floating">
<input type="code" class="form-control" id="code" name="code"
placeholder="<?php I18N::__('Code'); ?>" autocomplete="off" required autofocus/>
<input type="code" class="form-control" id="code" name="code" placeholder="<?php I18N::__('Code'); ?>" autocomplete="off" required autofocus/>
<br/>
<label for="code"><?php I18N::__('Code'); ?></label>
</div>
@@ -35,20 +39,16 @@
} else {
?>
<div class="form-floating">
<input type="text" class="form-control" id="username" name="username"
placeholder="<?php I18N::__('Username'); ?>"
value="<?php print (isset($username) ? $username : ''); ?>" required autofocus/>
<input type="text" class="form-control" id="username" name="username" placeholder="<?php I18N::__('Username'); ?>" value="<?php print (isset($username) ? $username : ''); ?>" required autofocus/>
<label for="username"><?php I18N::__('Username'); ?></label>
</div>
<div class="form-floating">
<input type="password" class="form-control" id="password" name="password"
placeholder="<?php I18N::__('Password'); ?>" required/>
<input type="password" class="form-control" id="password" name="password" placeholder="<?php I18N::__('Password'); ?>" required/>
<label for="password"><?php I18N::__('Password'); ?></label>
</div>
<div class="mb-3 mt-3 text-end">
<a class="icon-link icon-link-hover" style="--bs-link-hover-color-rgb: 25, 135, 84;"
href="<?php print $template->url('/lost-password'); ?>">
<a class="icon-link icon-link-hover" style="--bs-link-hover-color-rgb: 25, 135, 84;" href="<?php print $template->url('/lost-password'); ?>">
<?php I18N::__('Lost password'); ?>
<i class="bi-arrow-right"></i>
</a>
@@ -61,20 +61,15 @@
<?php
if(isset($two_factor_unknown) && $two_factor_unknown) {
?>
<button class="btn btn-primary col-9" name="action" value="back" type="submit">
<?php I18N::__('Back'); ?>
</button>
<button class="btn btn-primary col-9" name="action" value="back" type="submit"><?php I18N::__('Back'); ?></button>
<?php
} else {
?>
<button class="btn btn-primary col-9" name="action" value="login" type="submit">
<?php I18N::__('Sign in'); ?>
</button>
<button class="btn btn-primary col-9" name="action" value="login" type="submit"><?php I18N::__('Sign in'); ?></button>
<?php
}
?>
<button type="button" class="col-3 btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false">
<button type="button" class="col-3 btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<?php Icon::show('language'); ?>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
@@ -93,15 +88,20 @@
<?php
if($project_copyright) {
print('<p class="mt-5 mb-3 text-muted">');
printf('Powered by <a href="%2$s" target="_blank" class="text-decoration-none" style="color: #c48dbc;">%1$s</a>', 'fruithost', 'https://fruithost.de/');
Icon::show('dot');
printf('<a href="%2$s" target="_blank" class="icon-link text-decoration-none text-dark-emphasis">%1$s GitHub</a>', Icon::render('github', [
printf('Powered by <a href="%2$s" target="_blank" class="text-decoration-none" style="color: #c48dbc;">%1$s</a>', 'fruithost', 'https://fruithost.de/');
Icon::show('dot');
printf('<a href="%2$s" target="_blank" class="icon-link text-decoration-none text-dark-emphasis">%1$s GitHub</a>', Icon::render('github', [
'classes' => [ 'mb-2' ]
]), 'https://github.com/fruithost');
if(defined('DEMO') && DEMO) {
Icon::show('dot');
printf('<span class="badge text-bg-danger"><small>%s</small></span>', I18N::get('Demoversion'));
}
print('</p>');
}
?>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
if(count($modals) > 0) {
foreach($modals AS $modal) {
?>
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\UI\Icon;
use fruithost\UI\Button;
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\Accounting\Auth;
use fruithost\UI\Icon;
+8 -1
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
+8 -1
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
+21 -9
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Hardware\NetworkFamily;
use fruithost\Hardware\NetworkFlag;
@@ -209,6 +216,10 @@
<td>
<?php
foreach($device->getFlags() as $flag) {
if($flag === null) {
continue;
}
$color = 'light';
switch($flag) {
case NetworkFlag::UP:
@@ -221,6 +232,7 @@
$color = 'warning';
break;
}
printf('<span class="badge m-1 text-bg-%s">%s</span>', $color, NetworkFlag::for($flag));
}
?>
@@ -326,16 +338,16 @@
<?php
} else {
?>
<div class="jumbotron text-center bg-transparent text-muted">
<?php Icon::show('networking'); ?>
<h2><?php I18N::__('No Device selected!'); ?></h2>
<p class="lead"><?php I18N::__('Please select an network device for more informations.'); ?></p>
</div>
<div class="jumbotron text-center bg-transparent text-muted">
<?php Icon::show('networking'); ?>
<h2><?php I18N::__('No Device selected!'); ?></h2>
<p class="lead"><?php I18N::__('Please select an network device for more informations.'); ?></p>
</div>
<?php
}
}
?>
</div>
}
?>
</div>
<?php
$this->footer();
?>
+49 -44
View File
@@ -1,19 +1,27 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
$template->header();
if(!Auth::hasPermission('SERVER::PACKAGES')) {
?>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<?php
$template->footer();
exit();
}
$result = shell_exec('dpkg-query -W -f=\'${binary:Package};${Version};${binary:Summary};${Maintainer}\n\'');
$lines = (empty($result) ? [] : explode(PHP_EOL, $result));
// @ToDo group by names (for sample php-*)
@@ -21,56 +29,53 @@
<form method="post" action="<?php print $this->url('/server/packages'.(!empty($tab) ? '/'.$tab : '')); ?>">
<header class="page-header d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"
style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item active" aria-current="page">
<a href="<?php print $this->url('/server/packages'); ?>"><?php I18N::__('Services'); ?></a>
</li>
<ol class="breadcrumb" style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item active" aria-current="page"><a href="<?php print $this->url('/server/packages'); ?>"><?php I18N::__('Services'); ?></a></li>
</ol>
</nav>
<div class="btn-toolbar mb-2 mb-md-0">
<button type="submit" name="action" value="refresh"
class="btn btn-sm btn-outline-primary"><?php I18N::__('Refresh'); ?></button>
<button type="submit" name="action" value="refresh" class="btn btn-sm btn-outline-primary"><?php I18N::__('Refresh'); ?></button>
</div>
</header>
<div class="border rounded overflow-hidden mb-5">
<table class="table table-borderless table-striped table-hover mb-0">
<thead>
<tr>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Package'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Version'); ?></th>
</tr>
<tr>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Package'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Version'); ?></th>
</tr>
</thead>
<tbody>
<?php
if(empty($lines)) {
?>
<tr>
<td scope="col" colspan="2"><?php I18N::__('No packages found.'); ?></td>
</tr>
<?php
}
foreach($lines as $line) {
if(empty($line)) {
continue;
}
$info = explode(';', $line);
if(empty(trim($info[0]))) {
continue;
}
?>
<tr>
<td scope="col">
<?php print $info[0]; ?>
<p><small><?php print ($info[2] ?? ''); ?></small></p>
</td>
<td scope="col" class="text-right"><span
class="badge badge-secondary"><?php print ($info[1] ?? ''); ?></span></td>
</tr>
<?php
}
?>
<?php
if(empty($lines)) {
?>
<tr>
<td scope="col" colspan="2"><?php I18N::__('No packages found.'); ?></td>
</tr>
<?php
}
foreach($lines as $line) {
if(empty($line)) {
continue;
}
$info = explode(';', $line);
if(empty(trim($info[0]))) {
continue;
}
?>
<tr>
<td scope="col">
<?php print $info[0]; ?>
<p><small><?php print ($info[2] ?? ''); ?></small></p>
</td>
<td scope="col" class="text-right"><span class="badge badge-secondary"><?php print ($info[1] ?? ''); ?></span></td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
+12 -5
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
@@ -7,10 +14,10 @@
$template->header();
if(!Auth::hasPermission('SERVER::REBOOT')) {
?>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<?php
$template->footer();
exit();
+38 -40
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\System\Utils;
@@ -8,10 +15,10 @@
$template->header();
if(!Auth::hasPermission('SERVER::SERVER')) {
?>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<?php
$template->footer();
exit();
@@ -38,14 +45,12 @@
]);
?>
<span class="align-self-start flex-fill p-0 ml-2 mt-1">
<small><?php I18N::__('IP Address'); ?></small>
<h4 class="p-0 m-o"><?php print $network->getIPAddress(); ?></h4>
</span>
<small><?php I18N::__('IP Address'); ?></small>
<h4 class="p-0 m-o"><?php print $network->getIPAddress(); ?></h4>
</span>
</div>
<div class="card-body p-1 text-center">
<a class="icon-link icon-link-hover text-decoration-none"
style="--bs-link-hover-color-rgb: 25, 135, 84;"
href="<?php print $template->url('/server/network'); ?>">
<a class="icon-link icon-link-hover text-decoration-none" style="--bs-link-hover-color-rgb: 25, 135, 84;" href="<?php print $template->url('/server/network'); ?>">
<?php
Icon::show('network', [
'classes' => [ 'mb-2' ]
@@ -74,14 +79,12 @@
]);
?>
<span class="align-self-start flex-fill p-0 ml-2 mt-1">
<small><?php I18N::__('Uptime'); ?></small>
<h4 class="p-0 m-o"><?php print $uptime; ?></h4>
</span>
<small><?php I18N::__('Uptime'); ?></small>
<h4 class="p-0 m-o"><?php print $uptime; ?></h4>
</span>
</div>
<div class="card-body p-1 text-center">
<a class="icon-link icon-link-hover text-decoration-none"
style="--bs-link-hover-color-rgb: 25, 135, 84;"
href="<?php print $template->url('/server/reboot'); ?>">
<a class="icon-link icon-link-hover text-decoration-none" style="--bs-link-hover-color-rgb: 25, 135, 84;" href="<?php print $template->url('/server/reboot'); ?>">
<?php
Icon::show('restart', [
'classes' => [ 'mb-1' ]
@@ -101,9 +104,7 @@
<table class="table m-0 table-borderless table-striped">
<tr>
<td rowspan="4" class="text-center align-middle border-end">
<img height="50px" class="m-2"
src="<?php print $template->url(sprintf('/assets/systems/%s.svg', $id)); ?>"
alt="<?php print $id; ?>"/>
<img height="50px" class="m-2" src="<?php print $template->url(sprintf('/assets/systems/%s.svg', $id)); ?>" alt="<?php print $id; ?>"/>
</td>
<td>
<strong><?php I18N::__('System'); ?></strong>
@@ -152,6 +153,7 @@
<td>
<?php
print $network->getHostname();
if(sprintf('my.%s', $network->getHostname()) !== $network->getPanelHostname()) {
Icon::show('warning', [
'classes' => [
@@ -204,7 +206,7 @@
<strong><?php I18N::__('Version'); ?></strong>
</td>
<td>
<?php
<?php
if(version_compare($version, $update_version, '>')) {
Icon::show('error', [
'classes' => [
@@ -220,7 +222,9 @@
]
]);
}
print ' '.$version;
if(version_compare($version, $update_version, '>')) {
Icon::show('warning', [
'classes' => [
@@ -266,18 +270,16 @@
<strong><?php I18N::__('Status'); ?></strong>
</td>
<td>
<span class="d-inline-block badge badge-pill module-badge text-bg-<?php print ($daemon['status'] ? 'success' : 'danger'); ?>"
data-toggle="tooltip"
title="<?php ($daemon['status'] ? I18N::__('Running normally.') : I18N::__('Error during execution!')); ?>"></span>
<span class="d-inline-block badge badge-pill module-badge text-bg-<?php print ($daemon['status'] ? 'success' : 'danger'); ?>" data-toggle="tooltip" title="<?php ($daemon['status'] ? I18N::__('Running normally.') : I18N::__('Error during execution!')); ?>"></span>
<span class="d-inline-block text-<?php print ($daemon['status'] ? 'success' : 'danger fw-bolder'); ?>">
<?php
if($daemon['status']) {
I18N::__('Okay');
} else {
I18N::__('Error');
}
?>
</span>
<?php
if($daemon['status']) {
I18N::__('Okay');
} else {
I18N::__('Error');
}
?>
</span>
</td>
</tr>
<tr>
@@ -335,10 +337,8 @@
<strong class="ml-2"><?php print $disk['name']; ?></strong>
</div>
</div>
<div class="bg-secondary" style="height: 15px;"
data-percentage="<?php printf('%s %%', $disk['percent']); ?>">
<div class="bg-success"
style="height: 100%; width: <?php print $disk['percent']; ?>%"></div>
<div class="bg-secondary" style="height: 15px;" data-percentage="<?php printf('%s %%', $disk['percent']); ?>">
<div class="bg-success" style="height: 100%; width: <?php print $disk['percent']; ?>%"></div>
</div>
<small class="text-muted"><?php printf(I18N::get('Type: %s | FileSystem: %s | Size: %s / %s'), $disk['type'], $disk['filesystem'], Utils::getFileSize($disk['used']), Utils::getFileSize($disk['size'])); ?></small>
</div>
@@ -374,10 +374,8 @@
<strong class="ml-2"><?php I18N::__('RAM'); ?></strong>
</div>
</div>
<div data-name="mem_visual" class="bg-secondary" style="height: 15px;"
data-percentage="<?php print $memory->getPercentage(); ?> %">
<div class="bg-success"
style="height: 100%; width: <?php print $memory->getPercentage(); ?>%"></div>
<div data-name="mem_visual" class="bg-secondary" style="height: 15px;" data-percentage="<?php print $memory->getPercentage(); ?> %">
<div class="bg-success" style="height: 100%; width: <?php print $memory->getPercentage(); ?>%"></div>
</div>
</div>
<div id="memory" class="bg-light-subtle overflow-hidden"></div>
+79 -81
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\UI\Icon;
@@ -7,10 +14,10 @@
$template->header();
if(!Auth::hasPermission('SERVER::SERVICES')) {
?>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<?php
$template->footer();
exit();
@@ -18,95 +25,86 @@
?>
<header class="page-header d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"
style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<ol class="breadcrumb" style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item active" aria-current="page">
<a href="<?php print $this->url('/server/services'); ?>"><?php I18N::__('Services'); ?></a>
</li>
</ol>
</nav>
<div class="btn-toolbar mb-2 mb-md-0">
<a href="<?php print $this->url('/server/services'.(!empty($tab) ? '/'.$tab : '')); ?>"
class="btn btn-sm btn-outline-primary"><?php I18N::__('Refresh'); ?></a>
<a href="<?php print $this->url('/server/services'.(!empty($tab) ? '/'.$tab : '')); ?>" class="btn btn-sm btn-outline-primary"><?php I18N::__('Refresh'); ?></a>
</div>
</header>
<?php
if(isset($error)) {
?>
<div class="alert alert-danger mt-4" role="alert"><?php print $error; ?></div>
<?php
} else if(isset($success)) {
?>
<div class="alert alert-success mt-4" role="alert"><?php print $success; ?></div>
<?php
}
?>
<?php
if(isset($error)) {
?>
<div class="alert alert-danger mt-4" role="alert"><?php print $error; ?></div>
<?php
} else if(isset($success)) {
?>
<div class="alert alert-success mt-4" role="alert"><?php print $success; ?></div>
<?php
}
?>
<div class="border rounded overflow-hidden mb-5">
<table class="table table-borderless table-striped table-hover mb-0">
<thead>
<tr>
<th class="bg-secondary-subtle" scope="col" colspan="2"><?php I18N::__('Service'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Actions'); ?></th>
</tr>
<tr>
<th class="bg-secondary-subtle" scope="col" colspan="2"><?php I18N::__('Service'); ?></th>
<th class="bg-secondary-subtle" scope="col"><?php I18N::__('Actions'); ?></th>
</tr>
</thead>
<tbody>
<?php
if(empty($services)) {
?>
<tr>
<td scope="col" colspan="3"><?php I18N::__('No services found.'); ?></td>
</tr>
<?php
} else {
foreach($services as $service) {
if(empty($service) || empty(trim($service->unit))) {
continue;
}
?>
<tr>
<td width="1px">
<span class="d-block badge module-badge text-bg-<?php print ($service->sub === 'running' ? 'success' : 'danger'); ?>"
data-bs-toggle="tooltip"
title="<?php print ($service->sub == 'running' ? 'Service is running.' : 'Service is stopped.'); ?>"></span>
</td>
<td>
<?php
print $service->unit;
if(!empty($service->description)) {
printf('<p><small>%s</small></p>', $service->description);
}
?>
</td>
<td class="text-end">
<form method="post"
action="<?php print $this->url('/server/services'.(!empty($tab) ? '/'.$tab : '')); ?>">
<div class="btn-group mr-2">
<?php
if($service->sub === 'running') {
?>
<button type="submit" name="action" value="restart"
class="btn btn-sm btn-outline-warning m-0"><?php Icon::show('restart'); ?></button>
<button type="submit" name="action" value="stop"
class="btn btn-sm btn-outline-danger m-0"><?php Icon::show('stop'); ?></button>
<?php
} else {
?>
<button type="submit" name="action" value="start"
class="btn btn-sm btn-outline-success m-0"><?php Icon::show('start'); ?></button>
<?php
}
?>
</div>
<button type="submit" name="action" value="status"
class="btn btn-sm btn-outline-info"><?php I18N::__('Info'); ?></button>
<input type="hidden" name="service" value="<?php print $service->unit; ?>"/>
</form>
</td>
</tr>
<?php
}
}
?>
<?php
if(empty($services)) {
?>
<tr>
<td scope="col" colspan="3"><?php I18N::__('No services found.'); ?></td>
</tr>
<?php
} else {
foreach($services as $service) {
if(empty($service) || empty(trim($service->unit))) {
continue;
}
?>
<tr>
<td width="1px">
<span class="d-block badge module-badge text-bg-<?php print ($service->sub === 'running' ? 'success' : 'danger'); ?>" data-bs-toggle="tooltip" title="<?php print ($service->sub == 'running' ? 'Service is running.' : 'Service is stopped.'); ?>"></span>
</td>
<td>
<?php
print $service->unit;
if(!empty($service->description)) {
printf('<p><small>%s</small></p>', $service->description);
}
?>
</td>
<td class="text-end">
<form method="post" action="<?php print $this->url('/server/services'.(!empty($tab) ? '/'.$tab : '')); ?>">
<div class="btn-group mr-2">
<?php
if($service->sub === 'running') {
?>
<button type="submit" name="action" value="restart" class="btn btn-sm btn-outline-warning m-0"><?php Icon::show('restart'); ?></button>
<button type="submit" name="action" value="stop" class="btn btn-sm btn-outline-danger m-0"><?php Icon::show('stop'); ?></button>
<?php
} else {
?>
<button type="submit" name="action" value="start" class="btn btn-sm btn-outline-success m-0"><?php Icon::show('start'); ?></button>
<?php
}
?>
</div>
<button type="submit" name="action" value="status" class="btn btn-sm btn-outline-info"><?php I18N::__('Info'); ?></button>
<input type="hidden" name="service" value="<?php print $service->unit; ?>"/>
</form>
</td>
</tr>
<?php
}
}
?>
</tbody>
</table>
</div>
+25 -25
View File
@@ -1,15 +1,22 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
$template->header();
if(!Auth::hasPermission('SERVER::SETTINGS')) {
?>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<div class="alert alert-danger mt-4" role="alert">
<strong><?php I18N::__('Access denied!'); ?></strong>
<p class="pb-0 mb-0"><?php I18N::__('You have no permissions for this page.'); ?></p>
</div>
<?php
$template->footer();
exit();
@@ -18,49 +25,43 @@
<form method="post" action="<?php print $this->url('/server/settings'.(!empty($tab) ? '/'.$tab : '')); ?>">
<header class="page-header d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"
style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<ol class="breadcrumb" style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath d='M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z' fill='%236c757d'/%3E%3C/svg%3E&#34;);">
<li class="breadcrumb-item active" aria-current="page">
<a href="<?php print $this->url('/server/settings'); ?>"><?php I18N::__('Settings'); ?></a>
</li>
</ol>
</nav>
<div class="btn-toolbar mb-2 mb-md-0">
<button type="submit" name="action" value="save"
class="btn btn-sm btn-outline-primary"><?php I18N::__('Save'); ?></button>
<button type="submit" name="action" value="save" class="btn btn-sm btn-outline-primary"><?php I18N::__('Save'); ?></button>
</div>
</header>
<?php
if(isset($error)) {
?>
<div class="alert alert-danger mt-4" role="alert"><?php print $error; ?></div>
<div class="alert alert-danger mt-4" role="alert"><?php print $error; ?></div>
<?php
} else if(isset($success)) {
?>
<div class="alert alert-success mt-4" role="alert"><?php print $success; ?></div>
<div class="alert alert-success mt-4" role="alert"><?php print $success; ?></div>
<?php
}
?>
<div class="tab-content">
<div class="tab-pane<?php print (empty($tab) ? ' show active' : ''); ?>" id="globals" role="tabpanel"
aria-labelledby="globals-tab">
<div class="tab-pane<?php print (empty($tab) ? ' show active' : ''); ?>" id="globals" role="tabpanel" aria-labelledby="globals-tab">
<p class="text-secondary p-4"><?php I18N::__('Global settings for the Panel'); ?></p>
<div class="container">
<div class="form-group row">
<label for="project_name" class="col-3 col-lg-2 col-form-label"><?php I18N::__('Project name'); ?>:</label>
<div class="col-9 col-lg-10">
<input type="text" class="form-control" id="project_name" name="project_name"
value="<?php print $template->getCore()->getSettings('PROJECT_NAME', 'fruithost'); ?>"/>
<input type="text" class="form-control" id="project_name" name="project_name" value="<?php print $template->getCore()->getSettings('PROJECT_NAME', 'fruithost'); ?>"/>
</div>
</div>
<div class="form-group row">
<label for="project_copyright"
class="col-3 col-lg-2 col-form-label"><?php I18N::__('Show Copyright'); ?>:</label>
<label for="project_copyright" class="col-3 col-lg-2 col-form-label"><?php I18N::__('Show Copyright'); ?>:</label>
<div class="col-9 col-lg-10">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="project_copyright" value="true"
id="project_copyright"<?php print ($template->getCore()->getSettings('PROJECT_COPYRIGHT', true) ? ' CHECKED' : ''); ?>/>
<input class="form-check-input" type="checkbox" name="project_copyright" value="true" id="project_copyright"<?php print ($template->getCore()->getSettings('PROJECT_COPYRIGHT', true) ? ' CHECKED' : ''); ?>/>
<label class="form-check-label" for="project_copyright">
<?php I18N::__('Shows the copyright in the footer'); ?>
</label>
@@ -82,11 +83,9 @@
</div>
<hr class="mb-4"/>
<div class="form-group row">
<label for="time_format"
class="col-3 col-lg-2 col-form-label"><?php I18N::__('Default Time Format'); ?>:</label>
<label for="time_format" class="col-3 col-lg-2 col-form-label"><?php I18N::__('Default Time Format'); ?>:</label>
<div class="col-9 col-lg-10">
<input type="text" class="form-control" id="time_format" name="time_format"
value="<?php print $template->getCore()->getSettings('TIME_FORMAT', 'd.m.Y - H:i:s'); ?>"/>
<input type="text" class="form-control" id="time_format" name="time_format" value="<?php print $template->getCore()->getSettings('TIME_FORMAT', 'd.m.Y - H:i:s'); ?>"/>
</div>
</div>
<div class="form-group row">
@@ -96,9 +95,11 @@
<?php
foreach(json_decode(file_get_contents(dirname(PATH).'/config/timezones.json'), false) as $category) {
printf('<optgroup label="%s">', $category->group);
foreach($category->zones as $zone) {
printf('<option value="%1$s"%2$s>%3$s</option>', $zone->value, ($template->getCore()->getSettings('TIME_ZONE', date_default_timezone_get()) === $zone->value ? ' SELECTED' : ''), $zone->name);
}
print('</optgroup>');
}
?>
@@ -109,8 +110,7 @@
$template->getCore()->getHooks()->runAction('SERVER_SETTINGS');
?>
<div class="form-group text-end">
<button type="submit" name="action" value="save"
class="btn btn-outline-success"><?php I18N::__('Save'); ?></button>
<button type="submit" name="action" value="save" class="btn btn-outline-success"><?php I18N::__('Save'); ?></button>
</div>
</div>
</div>
+10 -2
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
@@ -111,7 +119,7 @@
<?php
$template->getCore()->getHooks()->runAction('ACCOUNT_SETTINGS_GLOBAL');
?>
<div class="form-group text-right">
<div class="form-group text-end">
<button type="submit" name="action" value="save" class="btn btn-outline-success"><?php I18N::__('Save'); ?></button>
</div>
</div>
@@ -173,7 +181,7 @@
</select>
</div>
</div>
<div class="form-group text-right">
<div class="form-group text-end">
<button type="submit" name="action" value="save" class="btn btn-outline-success"><?php I18N::__('Save'); ?></button>
</div>
</div>
+12 -5
View File
@@ -1,11 +1,18 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\Security\Encryption;
use fruithost\Storage\Database;
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\Security\Encryption;
use fruithost\Storage\Database;
if(isset($_POST['action']) && $_POST['action'] === 'save') {
if(isset($_POST['action']) && $_POST['action'] === 'save') {
switch($tab) {
case 'password':
if(empty($_POST['password_current'])) {
+64 -13
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Installer\Installer;
use fruithost\Installer\Repository;
@@ -26,6 +33,7 @@
return true;
}));
/* Module Informations */
$template->getAdminCore()->addModal((new Modal('module_info', '', 'admin/modules/info'))->addButton([
(new Button())->setName('cancel')->setLabel(I18N::get('Close'))->addClass('btn-outline-secondary')->setDismissable()
@@ -37,6 +45,7 @@
if(file_exists($list)) {
$upgradeable = json_decode(file_get_contents($list));
}
/* Module :: Settings */
if(isset($_GET['settings'])) {
if(Auth::hasPermission('MODULES::HANDLE')) {
@@ -49,7 +58,8 @@
} else {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
/* Module :: Disable */
/* Module :: Disable */
} else if(isset($_GET['disable'])) {
if(Auth::hasPermission('MODULES::HANDLE')) {
if($modules->hasModule($_GET['disable'], true)) {
@@ -70,7 +80,8 @@
} else {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
/* Mdoule :: Enable */
/* Mdoule :: Enable */
} else if(isset($_GET['enable'])) {
if(Auth::hasPermission('MODULES::HANDLE')) {
if($modules->hasModule($_GET['enable'], true)) {
@@ -91,7 +102,8 @@
} else {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
/* Module :: Install */
/* Module :: Install */
} else if(isset($_GET['install'])) {
if(Auth::hasPermission('MODULES::INSTALL')) {
if($modules->hasModule($_GET['install'], true)) {
@@ -143,6 +155,9 @@
// Check ZIP-Package
if($zip->open($zip_path) !== true) {
$template->assign('error', I18N::get('The Module-Package is broken!'));
} else if(!is_writable(sprintf('%s%s%s%s', dirname(PATH), DS, 'modules', DS))) {
$template->assign('error', sprintf(I18N::get('The Path %s is not writeable!'), sprintf('%s%s%s%s', dirname(PATH), DS, 'modules', DS)));
} else {
// Extract ZIP-Package
if(!$zip->extractTo(sprintf('%s%s%s%s', dirname(PATH), DS, 'modules', DS))) {
@@ -169,6 +184,7 @@
'REQUEST_URI' => '/',
'MODULE' => sprintf('%s/setup/install.php', $module_path)
]);
if(preg_match('/(File Not Found|404 Not Found|500 Internal Server Error)/', $php->getHeader())) {
$template->assign('error', sprintf(I18N::get('Installscript not found: %s/setup/install.php'), $module_path));
// Register Module
@@ -206,7 +222,8 @@
} else {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
/* Module :: Deinstall */
/* Module :: Deinstall */
} else if(isset($_GET['deinstall'])) {
if(Auth::hasPermission('MODULES::DEINSTALL')) {
if($modules->hasModule($_GET['deinstall'], true)) {
@@ -227,10 +244,41 @@
} else {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
/* Module :: Reinstall */
/* Module :: Reinstall */
} else if(isset($_GET['reinstall'])) {
// Not implemented?
/* Module :: Check */
if($modules->hasModule($_GET['reinstall'], true)) {
$module = $modules->getModule($_GET['reinstall'], true);
$install = sprintf('%s%s/setup/install.php', $module->getPath(), DS);
if(file_exists($install)) {
try {
$php = new PHP();
$php->setPath(PATH);
$php->execute('/classes/System/Loader.class.php', [
'DAEMON' => true,
'REQUEST_URI' => '/',
'MODULE' => $install
]);
if(preg_match('/(File Not Found|404 Not Found|500 Internal Server Error)/', $php->getHeader())) {
throw new \Exception('Installscript not found: ' . $install . PHP_EOL . $php->getHeader());
}
if(!empty($php->getBody())) {
$template->assign('error', I18N::get('The module has an problem:') . '<br />' . $php->getBody());
}
} catch(\Exception $e) {
$template->assign('error', I18N::get('The module has an problem:') . '<br />' . $e->getMessage());
}
}
$template->assign('success', I18N::get('The module was successfully reinstalled!'));
} else {
$template->assign('error', I18N::get('The module not exists!'));
}
/* Module :: Check */
} else if(isset($_GET['check'])) {
if(Auth::hasPermission('MODULES::HANDLE')) {
if($modules->hasModule($_GET['check'], true)) {
@@ -276,23 +324,25 @@
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
}
switch($tab) {
case 'install':
if(!Auth::hasPermission('MODULES::INSTALL')) {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
break;
break;
case 'repositorys':
if(!Auth::hasPermission('MODULES::REPOSITORY')) {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
break;
break;
case 'errors':
if(!Auth::hasPermission('MODULES::ERRORS')) {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
break;
break;
}
if(isset($_POST['action'])) {
switch($_POST['action']) {
/* Repositorys :: Update */
@@ -362,7 +412,8 @@
} else {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
break;
break;
/* Repository :: Delete */
case 'delete':
if(Auth::hasPermission('MODULES::DEINSTALL')) {
@@ -381,7 +432,7 @@
} else {
$template->assign('error', I18N::get('You have no permissions for this page.'));
}
break;
break;
}
}
$template->assign('module', $module);
+25 -11
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Accounting\Session;
use fruithost\Accounting\User;
@@ -12,9 +19,11 @@
$users = Database::fetch('SELECT *, \'[*** PROTECTED ***]\' AS `password` FROM `'.DATABASE_PREFIX.'users` WHERE `deleted`=\'NO\' ORDER BY `id`');
if(isset($_POST['action'])) {
$user = new User();
if(is_numeric($tab) && $tab > 0) {
$user->fetch($tab);
}
switch($_POST['action']) {
/* Save user */
case 'save':
@@ -38,7 +47,7 @@
$this->assign('success', sprintf(I18N::get('Settings for <strong>%s</strong> was successfully updated.'), $user->getUsername()));
I18N::reload();
}
break;
break;
case 'password':
if(!Auth::hasPermission('USERS::EDIT')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
@@ -151,7 +160,7 @@
}
$template->assign('success', sprintf(I18N::get('Security-Settings for <strong>%s</strong> was successfully updated.'), $user->getUsername()));
}
break;
break;
case 'permissions':
if(!Auth::hasPermission('USERS::PERMISSIONS')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
@@ -198,7 +207,7 @@
}
$template->assign('success', sprintf(I18N::get('The user\'s permissions for the user <strong>%s</strong> have been adjusted.'), $user->getUsername()));
}
break;
break;
default:
if(!Auth::hasPermission('USERS::EDIT')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
@@ -220,9 +229,10 @@
/* @ToDo rename User, with all hosted files! */
$template->assign('success', sprintf(I18N::get('User <strong>%s</strong> was successfully edited.'), $user->getUsername()));
}
break;
break;
}
break;
break;
/* delete user */
case 'delete':
if(!Auth::hasPermission('USERS::DELETE')) {
@@ -235,7 +245,8 @@
Response::redirect('/admin/users');
exit();
}
break;
break;
/* Delete users */
case 'deletes':
if(!Auth::hasPermission('USERS::DELETE')) {
@@ -263,7 +274,8 @@
}
$users = Database::fetch('SELECT *, \'[*** PROTECTED ***]\' AS `password` FROM `'.DATABASE_PREFIX.'users` WHERE `deleted`=\'NO\'');
}
break;
break;
/* create user */
case 'create':
if(!Auth::hasPermission('USERS::CREATE')) {
@@ -339,20 +351,22 @@
]);
Session::set('success', sprintf(I18N::get('The user <strong>%s</strong> was successfully created.'), $_POST['username']));
Response::redirect('/admin/users');
exit();
break;
exit();
break;
}
}
if(!Auth::hasPermission('USERS::*')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
return;
}
if(!Auth::hasPermission('USERS::EDIT') && is_numeric($tab) && $tab > 0) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
} else if(!Auth::hasPermission('USERS::CREATE') && $tab == 'create') {
$this->assign('error', I18N::get('You have no permissions for this action!'));
}
if(Auth::hasPermission('USERS::EDIT') && is_numeric($tab) && $tab > 0) {
$user = new User();
$user->fetch($tab);
+8
View File
@@ -1,4 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Accounting\Session;
use fruithost\Network\Response;
+12 -5
View File
@@ -1,11 +1,18 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Localization\I18N;
use fruithost\Security\Encryption;
use fruithost\Storage\Database;
use PHPMailer\PHPMailer;
use fruithost\Localization\I18N;
use fruithost\Security\Encryption;
use fruithost\Storage\Database;
use PHPMailer\PHPMailer;
if(!empty($token)) {
if(!empty($token)) {
$found = null;
$result = Database::fetch('SELECT `id`, `username`, \'[*** PROTECTED ***]\' AS `password`, `email`, `lost_token`, UPPER(SHA2(CONCAT(`id`, :salt, `email`), 512)) AS `crypted_mail` FROM `' . DATABASE_PREFIX . 'users` WHERE `lost_enabled`=\'YES\' AND `lost_time` > DATE_SUB(NOW(), INTERVAL 24 HOUR)', [
'salt' => RESET_PASSWORD_SALT
+28 -2
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\Network\Response;
@@ -10,22 +17,26 @@
$this->assign('error', I18N::get('You have no permissions for this action!'));
exit();
}
Response::addHeader('Content-Type', 'text/plain; charset=UTF-8');
$user = trim(shell_exec('whoami'));
$directory = trim(shell_exec('pwd'));
$hostname = $_SERVER['SERVER_NAME'];
$prefix = sprintf("\033[38;2;200;110;110m%s\033[34m@\033[1;32m%s\033[90m:\033[39m%s\033[90m#", $user, $hostname, $directory);
$command = escapeshellcmd($_POST['command']);
if(defined('DEMO') && DEMO) {
if(defined('DEMO') && DEMO) {
if($command === 'motd') {
$output = 'Welcome to the Demoversion of fruithost!';
printf("%s\n\033[39m%s", $prefix, $output);
exit();
}
$output = "\033[31mERROR:\033[39m Your command is forbidden at the demo version.";
printf("%s %s\n\033[39m%s", $prefix, $command, $output);
exit();
}
if($command === 'ColorTest') {
$tests = [
'styles' => [
@@ -75,6 +86,7 @@
107 => 'bright_white',
]
];
foreach($tests as $name => $values) {
printf("--- %s\n", $name);
foreach($values as $code => $color) {
@@ -84,8 +96,14 @@
}
exit();
}
setlocale(LC_ALL, I18N::set().'.UTF-8');
putenv('LC_ALL='.I18N::set().'.UTF-8');
if($command === 'motd' && !file_exists('/etc/motd')) {
$command = '';
}
$build = [
'export MAN_KEEP_FORMATTING=1;',
'export SHELL=/bin/bash;',
@@ -99,6 +117,7 @@
#'"',
' 2>&1'
];
// @ToDo try to fork it to /dev/ttys000, /dev/pts/0 or other for coloring output?
// Or using SSH? https://www.php.net/manual/en/function.ssh2-connect
$process = proc_open(implode('', $build), [
@@ -118,9 +137,11 @@
]
// stderr
], $pipes);
if($command === 'motd') {
$command = '';
}
if(is_resource($process)) {
$stdin = $pipes[0];
$stdout = $pipes[1];
@@ -132,9 +153,11 @@
$errEof = false;
$result = '';
$error = '';
if(!posix_isatty($stdout)) {
#$result = "NOT TTY : " . posix_ttyname($stdout);
}
do {
$read = [
$stdout,
@@ -152,9 +175,11 @@
$error .= fgets($stderr);
}
} while(!$outEof || !$errEof);
fclose($stdout);
fclose($stderr);
proc_close($process);
if($result == "\x1B[H\x1B[2J\x1B[3J") {
print $result;
} else {
@@ -168,6 +193,7 @@
}
exit();
}
$template->getFiles()->addJavascript('terminal', $this->url('js/terminal.js'), '1.0.0', [ 'ajax' ], TemplateFiles::FOOTER);
$template->getFiles()->addJavascript('console', $this->url('js/console.js'), '1.0.0', [ 'terminal' ], TemplateFiles::FOOTER);
?>
+22 -3
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\Network\Response;
@@ -10,29 +17,36 @@
'gz',
'xy'
];
if(!is_dir($sourceDir)) {
return $list;
}
$fp = opendir($sourceDir);
$sourceDir = rtrim($sourceDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
while(false !== ($file = readdir($fp))) {
if($file === '.' || $file === '..') {
continue;
}
$parts = pathinfo($file);
if(isset($parts['extension']) && in_array($parts['extension'], $exclude)) {
continue;
}
if(is_dir($sourceDir.$file)) {
if(!is_readable($sourceDir.$file)) {
$list[] = $sourceDir.$file.DIRECTORY_SEPARATOR;
continue;
}
$list = array_merge($list, directory_map($sourceDir.$file));
} else {
$list[] = $sourceDir.$file;
}
}
closedir($fp);
return $list;
@@ -47,17 +61,20 @@
$list = directory_map('/var/log/');
}
}
if(isset($_POST['action'])) {
if(!Auth::hasPermission('SERVER::LOGFILES')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
exit();
}
if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
Response::addHeader('Content-Type', 'text/plain; charset=UTF-8');
switch($_POST['action']) {
case 'refresh':
print json_encode($list);
break;
break;
case 'file':
$file = $_POST['file'];
$content = null;
@@ -84,11 +101,13 @@
'file' => $file,
'content' => $content
]);
break;
break;
}
exit();
}
}
$template->assign('list', $list);
$template->getFiles()->addJavascript('terminal', $this->url('js/terminal.js'), '1.0.0');
?>
+23 -12
View File
@@ -1,64 +1,75 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Hardware\NetworkInterfaces;
use fruithost\Localization\I18N;
$template->assign('network', NetworkInterfaces::get());
switch($action) {
case 'start':
if(!Auth::hasPermission('SERVER::NETWORK')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
return;
}
if(defined('DEMO') && DEMO) {
$this->assign('error', I18N::get('DEMO-VERSION: This action can\'t be used!'));
return;
}
$device = NetworkInterfaces::getDevice($tab);
if(empty($device)) {
$this->assign('error', sprintf(I18N::get('Unknown Network-Interface: %s'), $tab));
return;
}
$this->assign('success', sprintf(I18N::get('The Network-Interface "%s" will be started!'), $tab));
$device->enable();
break;
break;
case 'stop':
if(!Auth::hasPermission('SERVER::NETWORK')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
return;
}
if(defined('DEMO') && DEMO) {
$this->assign('error', I18N::get('DEMO-VERSION: This action can\'t be used!'));
return;
}
$device = NetworkInterfaces::getDevice($tab);
if(empty($device)) {
$this->assign('error', sprintf(I18N::get('Unknown Network-Interface: %s'), $tab));
return;
}
$this->assign('success', sprintf(I18N::get('The Network-Interface "%s" will be stopped!'), $tab));
$device->disable();
break;
break;
case 'info':
if(!Auth::hasPermission('SERVER::NETWORK')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
return;
}
$device = NetworkInterfaces::getDevice($tab);
if(empty($device)) {
$this->assign('error', sprintf(I18N::get('Unknown Network-Interface: %s'), $tab));
return;
}
$this->assign('device', $device);
break;
break;
}
?>
+13 -4
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
@@ -8,21 +15,23 @@
case 'reboot':
if(!Auth::hasPermission('SERVER::REBOOT')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
return;
}
if(defined('DEMO') && DEMO) {
$this->assign('error', I18N::get('DEMO-VERSION: This action can\'t be used!'));
return;
}
$this->assign('success', I18N::get('The System will be rebooted now!'));
$template->getCore()->setSettings('REBOOT', date('Y-m-d H:i:s', time()));
$template->getCore()->getHooks()->runAction('SERVER_REBOOT', time());
break;
break;
}
}
$rebooting = $template->getCore()->getSettings('REBOOT', null);
if(!empty($rebooting)) {
$this->assign('success', I18N::get('The System will be rebooted now!').sprintf('<p><small>%s</small></p>', $rebooting));
}
+35 -7
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Hardware\Memory;
use fruithost\Hardware\NetworkInterfaces;
@@ -15,7 +22,9 @@
$this->assign('error', I18N::get('You have no permissions for this action!'));
exit();
}
Response::addHeader('Content-Type', 'text/plain; charset=UTF-8');
switch($_POST['command']) {
case 'get_live_usage':
print json_encode([
@@ -28,17 +37,35 @@
]);
break;
}
exit();
}
$time_format = Auth::getSettings('TIME_FORMAT', null, 'd.m.Y - H:i');
$time_update = new \DateTime($this->getCore()->getSettings('UPDATE_TIME', null));
$time_status = new \DateTime($this->getCore()->getSettings('DAEMON_TIME_END', null));
$time_status->add(\DateInterval::createFromDateString('15 minutes'));
$time_format = Auth::getSettings('TIME_FORMAT', null, 'd.m.Y - H:i');
$from = $this->getCore()->getSettings('UPDATE_TIME', null);
$to = $this->getCore()->getSettings('DAEMON_TIME_END', null);
$status = null;
if($from !== null) {
$time_update = new \DateTime($from);
}
if($to !== null) {
$time_status = new \DateTime($to);
}
if($to !== null && $from !== null) {
$time_status->add(\DateInterval::createFromDateString('15 minutes'));
$template->assign('time_update', ($time_update->format($time_format)));
$status = $time_update < new \DateTime();
} else {
$template->assign('time_update', null);
}
$template->assign('update_version', $this->getCore()->getSettings('UPDATE_VERSION'));
$template->assign('update_license', Update::getLicense());
$template->assign('time_php', date($time_format));
$template->assign('time_system', OperatingSystem::getTime($time_format));
$template->assign('time_update', ($time_update->format($time_format)));
$template->assign('os', OperatingSystem::getPrettyName());
$template->assign('id', OperatingSystem::getID());
$template->assign('kernel', OperatingSystem::getKernel());
@@ -49,12 +76,13 @@
$template->assign('disks', PhysicalDrives::getDevices());
$template->assign('version', file_get_contents('.version'));
$template->assign('daemon', [
'status' => $time_update < new \DateTime(),
'status' => $status,
'started' => strtotime($this->getCore()->getSettings('DAEMON_TIME_START', 0)),
'start' => date($time_format, strtotime($this->getCore()->getSettings('DAEMON_TIME_START', 0))),
'end' => date($time_format, strtotime($this->getCore()->getSettings('DAEMON_TIME_END', 0))),
'ended' => strtotime($this->getCore()->getSettings('DAEMON_TIME_END', 0)),
'time' => number_format($this->getCore()->getSettings('DAEMON_RUNNING_END', 0) - $this->getCore()->getSettings('DAEMON_RUNNING_START', 0), 4, ',', '.')
]);
$template->getFiles()->addJavascript('statistics', $this->url('js/statistics.js'), '1.0.0', [ 'ajax' ], TemplateFiles::FOOTER);
?>
+9
View File
@@ -1,3 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
?>
<pre>
<?php
print $result;
+24 -9
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\UI\Button;
@@ -12,6 +19,7 @@
} else {
$command = 'systemctl list-units --type service --full --all --no-pager';
$json = shell_exec($command.' --output json');
if(empty($json)) {
$type = 'init.d';
$services = [];
@@ -40,18 +48,19 @@
if(isset($_POST['action'])) {
if(!Auth::hasPermission('SERVER::SERVICES')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
return;
}
switch($_POST['action']) {
case 'start':
if(defined('DEMO') && DEMO) {
$this->assign('error', I18N::get('DEMO-VERSION: This action can\'t be used!'));
return;
}
$service = $_POST['service'];
$command = '';
switch($type) {
case 'init.d':
$command = sprintf('service %s start', $service);
@@ -65,17 +74,19 @@
$this->assign('success', I18N::get('Unsupported operating system?'));
return;
}
shell_exec($command);
$this->assign('success', sprintf(I18N::get('The Service "%s" will be started!'), $service));
break;
break;
case 'stop':
if(defined('DEMO') && DEMO) {
$this->assign('error', I18N::get('DEMO-VERSION: This action can\'t be used!'));
return;
}
$service = $_POST['service'];
$command = '';
switch($type) {
case 'init.d':
$command = sprintf('service %s stop', $service);
@@ -89,17 +100,19 @@
$this->assign('success', I18N::get('Unsupported operating system?'));
return;
}
shell_exec($command);
$this->assign('success', sprintf(I18N::get('The Service "%s" will be stopped!'), $service));
break;
break;
case 'restart':
if(defined('DEMO') && DEMO) {
$this->assign('error', I18N::get('DEMO-VERSION: This action can\'t be used!'));
return;
}
$service = $_POST['service'];
$command = '';
switch($type) {
case 'init.d':
$command = sprintf('service %s restart', $service);
@@ -113,12 +126,14 @@
$this->assign('success', I18N::get('Unsupported operating system?'));
return;
}
shell_exec($command);
$this->assign('success', sprintf(I18N::get('The Service "%s" will be restarted!'), $service));
break;
break;
case 'status':
$service = $_POST['service'];
$command = '';
switch($type) {
case 'init.d':
$command = sprintf('service %s status', $service);
@@ -142,7 +157,7 @@
$modal->show([
'result' => $result
]);
break;
break;
}
}
$template->assign('services', $services);
+10 -3
View File
@@ -1,5 +1,12 @@
<?php
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
@@ -8,9 +15,9 @@
case 'save':
if(!Auth::hasPermission('SERVER::SETTINGS')) {
$this->assign('error', I18N::get('You have no permissions for this action!'));
return;
}
$template->getCore()->setSettings('PROJECT_NAME', $_POST['project_name']);
$template->getCore()->setSettings('PROJECT_COPYRIGHT', isset($_POST['project_copyright']) && $_POST['project_copyright'] == 'true');
$template->getCore()->setSettings('LANGUAGE', $_POST['language']);
@@ -18,7 +25,7 @@
$template->getCore()->setSettings('TIME_ZONE', $_POST['time_zone']);
$template->getCore()->getHooks()->runAction('SAVE_SERVER_SETTINGS', $_POST);
$this->assign('success', I18N::get('The system settings has been saved.'));
break;
break;
}
}
?>
+10 -46
View File
@@ -1,55 +1,19 @@
<?php
use fruithost\Accounting\Auth;
use fruithost\Localization\I18N;
use fruithost\Storage\Database;
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
if(isset($_POST['action']) && $_POST['action'] === 'save') {
switch($tab) {
case 'security':
$selected = null;
foreach($template->getCore()->getHooks()->applyFilter('2FA_METHODS', [
(object) [
'id' => 'mail',
'name' => I18N::get('E-Mail'),
'enabled' => true
]
]) AS $index => $method) {
if($method->id == $_POST['2fa_type']) {
$selected = $method;
break;
}
}
if($selected == null) {
$template->assign('error', I18N::get('The selected 2FA method is not known.'));
return;
}
if(!$selected->enabled) {
$template->assign('error', sprintf(I18N::get('The 2FA method "%s" is currently deactivated and can therefore not be used.'), $selected->name));
return;
}
if(isset($_POST['2fa_enabled'])) {
Auth::setSettings('2FA_ENABLED', NULL, 'true');
} else {
Auth::setSettings('2FA_ENABLED', NULL, 'false');
}
Auth::setSettings('2FA_TYPE', NULL, $_POST['2fa_type']);
$template->getCore()->getHooks()->runAction('SAVE_ACCOUNT_SETTINGS_SECURITY', $_POST);
$template->assign('success', I18N::get('Your security settings has been updated.'));
require_once('settings/security.php');
break;
default:
Auth::setSettings('LANGUAGE', NULL, $_POST['language']);
Auth::setSettings('TIME_FORMAT', NULL, $_POST['time_format']);
Auth::setSettings('TIME_ZONE', NULL, $_POST['time_zone']);
$template->getCore()->getHooks()->runAction('SAVE_ACCOUNT_SETTINGS_GLOBAL', $_POST);
$template->assign('success', I18N::get('Your settings has been updated.'));
I18N::reload();
require_once('settings/default.php');
break;
}
}
+15 -3
View File
@@ -1,6 +1,18 @@
<?php
require_once(sprintf('%s/classes/System/Loader.class.php', dirname(__FILE__)));
require_once(sprintf('%s/classes/System/Core.class.php', dirname(__FILE__)));
/**
* fruithost | OpenSource Hosting
*
* @author Adrian Preuß
* @version 1.0.0
* @license MIT
*/
foreach([
'Loader',
'Core'
] as $file) {
require_once(sprintf('%1$s/classes/System/%2$s.class.php', dirname(__FILE__), $file));
}
new fruithost\System\Core();
?>