From 63c0cf521f8097b0dadaf1228176dc38c7d897f6 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 15 May 2014 18:28:24 +0200 Subject: Working on config.tgz composition through config modules --- api.php | 2 +- inc/database.inc.php | 92 ++++++++++++++ inc/db.inc.php | 79 ------------ inc/message.inc.php | 14 ++- inc/render.inc.php | 18 +++ inc/request.inc.php | 45 +++++++ inc/session.inc.php | 6 +- inc/taskmanager.inc.php | 86 +++++++++++++ inc/util.inc.php | 19 +++ index.php | 38 +++--- modules/sysconfig.inc.php | 81 ++++++------ modules/sysconfig/addmodule.inc.php | 196 +++++++++++++++++++++++++++++ modules/syslog.inc.php | 35 ++++-- script/custom.js | 11 ++ style/default.css | 3 + templates/dialog-generic.html | 15 +++ templates/page-sysconfig-main.html | 86 +++++++++++++ templates/page-tgz-list.html | 33 ----- templates/sysconfig/custom-fileselect.html | 26 ++++ templates/sysconfig/custom-upload.html | 16 +++ 20 files changed, 712 insertions(+), 189 deletions(-) create mode 100644 inc/database.inc.php delete mode 100644 inc/db.inc.php create mode 100644 inc/request.inc.php create mode 100644 inc/taskmanager.inc.php create mode 100644 modules/sysconfig/addmodule.inc.php create mode 100644 templates/dialog-generic.html create mode 100644 templates/page-sysconfig-main.html delete mode 100644 templates/page-tgz-list.html create mode 100644 templates/sysconfig/custom-fileselect.html create mode 100644 templates/sysconfig/custom-upload.html diff --git a/api.php b/api.php index 9491cd37..16408440 100644 --- a/api.php +++ b/api.php @@ -4,7 +4,7 @@ error_reporting(E_ALL); require_once('inc/user.inc.php'); require_once('inc/util.inc.php'); -require_once('inc/db.inc.php'); +require_once('inc/database.inc.php'); require_once('inc/permission.inc.php'); require_once('inc/crypto.inc.php'); require_once('inc/validator.inc.php'); diff --git a/inc/database.inc.php b/inc/database.inc.php new file mode 100644 index 00000000..70f50116 --- /dev/null +++ b/inc/database.inc.php @@ -0,0 +1,92 @@ + "SET NAMES utf8")); + } catch (PDOException $e) { + Util::traceError('Connecting to the local database failed: ' . $e->getMessage()); + } + } + + /** + * If you just need the first row of a query you can use this. + * Will return an associative array, or false if no row matches the query + */ + public static function queryFirst($query, $args = array()) + { + $res = self::simpleQuery($query, $args); + if ($res === false) return false; + return $res->fetch(PDO::FETCH_ASSOC); + } + + /** + * Execute the given query and return the number of rows affected. + * Mostly useful for UPDATEs or INSERTs + */ + public static function exec($query, $args = array()) + { + $res = self::simpleQuery($query, $args); + if ($res === false) return false; + return $res->rowCount(); + } + + /** + * Get id (promary key) of last row inserted. + * + * @return int the id + */ + public static function lastInsertId() + { + return self::$dbh->lastInsertId(); + } + + /** + * Execute the given query and return the corresponding PDOStatement object + * Note that this will re-use PDOStatements, so if you run the same + * query again with different params, do not rely on the first PDOStatement + * still being valid. If you need to do something fancy, use Database::prepare + */ + public static function simpleQuery($query, $args = array()) + { + self::init(); + try { + if (!isset(self::$statements[$query])) { + self::$statements[$query] = self::$dbh->prepare($query); + } else { + self::$statements[$query]->closeCursor(); + } + if (self::$statements[$query]->execute($args) === false) { + Util::traceError("Database Error: \n" . implode("\n", self::$statements[$query]->errorInfo())); + } + return self::$statements[$query]; + } catch (Exception $e) { + return false; + } + } + + /** + * Simply calls PDO::prepare and returns the PDOStatement. + * You must call PDOStatement::execute manually on it. + */ + public static function prepare($query) + { + self:init(); + return self::$dbh->prepare($query); + } + +} + diff --git a/inc/db.inc.php b/inc/db.inc.php deleted file mode 100644 index a797ae93..00000000 --- a/inc/db.inc.php +++ /dev/null @@ -1,79 +0,0 @@ - "SET NAMES utf8")); - } catch (PDOException $e) { - Util::traceError('Connecting to the local database failed: ' . $e->getMessage()); - } - } - - /** - * If you just need the first row of a query you can use this. - * Will return an associative array, or false if no row matches the query - */ - public static function queryFirst($query, $args = array()) - { - $res = self::simpleQuery($query, $args); - if ($res === false) return false; - return $res->fetch(PDO::FETCH_ASSOC); - } - - /** - * Execute the given query and return the number of rows affected. - * Mostly useful for UPDATEs or INSERTs - */ - public static function exec($query, $args = array()) - { - $res = self::simpleQuery($query, $args); - if ($res === false) return false; - return $res->rowCount(); - } - - /** - * Execute the given query and return the corresponding PDOStatement object - * Note that this will re-use PDOStatements, so if you run the same - * query again with different params, do not rely on the first PDOStatement - * still being valid. If you need to do something fancy, use Database::prepare - */ - public static function simpleQuery($query, $args = array()) - { - self::init(); - //if (empty($args)) Util::traceError('Query with zero arguments!'); - if (!isset(self::$statements[$query])) { - self::$statements[$query] = self::$dbh->prepare($query); - } else { - self::$statements[$query]->closeCursor(); - } - if (self::$statements[$query]->execute($args) === false) { - Util::traceError("Database Error: \n" . implode("\n", self::$statements[$query]->errorInfo())); - } - return self::$statements[$query]; - } - - /** - * Simply calls PDO::prepare and returns the PDOStatement. - * You must call PDOStatement::execute manually on it. - */ - public static function prepare($query) - { - self:init(); - return self::$dbh->prepare($query); - } - -} - diff --git a/inc/message.inc.php b/inc/message.inc.php index 3117630b..6c95764c 100644 --- a/inc/message.inc.php +++ b/inc/message.inc.php @@ -1,6 +1,7 @@ 'Benutzername oder Kennwort falsch', 'token' => 'Ungültiges Token. CSRF Angriff?', @@ -21,11 +22,21 @@ $error_text = array( 'upload-failed' => 'Upload von {{0}} schlug fehl!', 'config-activated' => 'Konfiguration wurde aktiviert', 'error-write' => 'Fehler beim Schreiben von {{0}}', + 'error-read' => 'Fehler beim Lesen von {{0}}', + 'error-archive' => 'Korruptes Archiv oder nicht unterstütztes Format', + 'error-rename' => 'Konnte {{0}} nicht in {{1}} umbenennen', + 'error-nodir' => 'Das Verzeichnis {{0}} existiert nicht.', + 'empty-archive' => 'Das Archiv enthält keine Dateien oder Verzeichnisse', + 'error-extract' => 'Konnte Archiv nicht nach {{0}} entpacken - {{1}}', + 'module-added' => 'Modul erfolgreich hinzugefügt', + 'taskmanager-error' => 'Verbindung zum Taskmanager fehlgeschlagen', + 'task-error' => 'Ausführung fehlgeschlagen: {{0}}', ); class Message { private static $list = array(); + private static $alreadyDisplayed = array(); private static $flushed = false; /** @@ -83,6 +94,7 @@ class Message $message = str_replace('{{' . $index . '}}', $text, $message); } Render::addTemplate('messagebox-' . $item['type'], array('message' => $message)); + self::$alreadyDisplayed[] = $item; } self::$list = array(); self::$flushed = true; @@ -109,7 +121,7 @@ class Message public static function toRequest() { $parts = array(); - foreach (self::$list as $item) { + foreach (array_merge(self::$list, self::$alreadyDisplayed) as $item) { $str = 'message[]=' . urlencode($item['type'] . '|' .$item['id']); if (!empty($item['params'])) { $str .= '|' . implode('|', $item['params']); diff --git a/inc/render.inc.php b/inc/render.inc.php index a3cef516..dff32798 100644 --- a/inc/render.inc.php +++ b/inc/render.inc.php @@ -39,6 +39,7 @@ class Render ', RENDER_DEFAULT_TITLE, self::$title, ' + @@ -93,6 +94,23 @@ class Render { self::$body .= self::$mustache->render(self::getTemplate($template), $params); } + + /** + * Add a dialog to the page output. + * + * @param string $title Title of the dialog window + * @param boolean $next URL to next dialog step, or false to hide the next button + * @param string $template template used to fill the dialog body + * @param array $params parameters for rendering the body template + */ + public static function addDialog($title, $next, $template, $params = false) + { + self::addTemplate('dialog-generic', array( + 'title' => $title, + 'next' => $next, + 'body' => self::$mustache->render(self::getTemplate($template), $params) + )); + } /** * Add error message to page diff --git a/inc/request.inc.php b/inc/request.inc.php new file mode 100644 index 00000000..bb212dfd --- /dev/null +++ b/inc/request.inc.php @@ -0,0 +1,45 @@ + 0, 'usec' => 100000)); + socket_set_option(self::$sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 0, 'usec' => 100000)); + socket_connect(self::$sock, '127.0.0.1', 9215); + } + + public static function submit($task, $data, $async) + { + self::init(); + $seq = (string)mt_rand(); + $data = json_encode($data); + $message = "$seq, $task, $data"; + $sent = socket_send(self::$sock, $message, strlen($message), 0); + if ($async) return true; + $reply = self::readReply($seq); + if (!is_array($reply)) return false; + return $reply; + } + + public static function status($taskId) + { + self::init(); + $seq = (string)mt_rand(); + $message = "$seq, status, $taskId"; + $sent = socket_send(self::$sock, $message, strlen($message), 0); + $reply = self::readReply($seq); + if (!is_array($reply)) return false; + return $reply; + } + + public static function waitComplete($taskId) + { + for ($i = 0; $i < 10; ++$i) { + $status = self::status($taskId); + if (!isset($status['statusCode'])) break; + if ($status['statusCode'] != TASK_PROCESSING && $status['statusCode'] != TASK_WAITING) break; + usleep(150000); + } + return $status; + } + + public static function release($taskId) + { + self::init(); + $seq = (string)mt_rand(); + $message = "$seq, release, $taskId"; + socket_send(self::$sock, $message, strlen($message), 0); + } + + /** + * + * @param type $seq + * @return mixed the decoded json data for that message as an array, or null on error + */ + private static function readReply($seq) + { + $tries = 0; + while (($bytes = socket_recvfrom(self::$sock, $buf, 90000, 0, $bla1, $bla2)) !== false) { + $parts = explode(',', $buf, 2); + if (count($parts) == 2 && $parts[0] == $seq) { + return json_decode($parts[1], true); + } + if (++$tries > 10) return false; + } + //error_log(socket_strerror(socket_last_error(self::$sock))); + return false; + } + +} + +foreach (array('TASK_FINISHED', 'TASK_ERROR', 'TASK_WAITING', 'NO_SUCH_TASK', 'TASK_PROCESSING') as $i) { + define($i, $i); +} diff --git a/inc/util.inc.php b/inc/util.inc.php index 67e4b73d..8235edd0 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -150,6 +150,25 @@ class Util } return true; } + + /** + * Convert given number to human readable file size string. + * Will append Bytes, KiB, etc. depending on magnitude of number. + * + * @param type $bytes numeric value of the filesize to make readable + * @param type $decimals number of decimals to show, -1 for automatic + * @return type human readable string representing the given filesize + */ + public static function readableFileSize($bytes, $decimals = -1) { + static $sz = array('Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'); + $factor = floor((strlen($bytes) - 1) / 3); + if ($factor == 0) { + $decimals = 0; + } elseif ($decimals === -1) { + $decimals = 2 - floor((strlen($bytes) - 1) % 3); + } + return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . $sz[$factor]; + } } diff --git a/index.php b/index.php index d693b9e8..d50d1d71 100644 --- a/index.php +++ b/index.php @@ -2,37 +2,41 @@ error_reporting(E_ALL); -require_once('inc/user.inc.php'); -require_once('inc/render.inc.php'); -require_once('inc/menu.inc.php'); -require_once('inc/util.inc.php'); -require_once('inc/message.inc.php'); -require_once('inc/db.inc.php'); -require_once('inc/permission.inc.php'); -require_once('inc/crypto.inc.php'); -require_once('inc/validator.inc.php'); +// Autoload classes from ./inc which adhere to naming scheme .inc.php +function slxAutoloader($class) { + $file = 'inc/' . preg_replace('/[^a-z0-9]/', '', mb_strtolower($class)) . '.inc.php'; + if (!file_exists($file)) return; + require_once $file; +} + +spl_autoload_register('slxAutoloader'); if (empty($_REQUEST['do'])) { // No specific module - set default - $module = 'main'; + $moduleName = 'main'; } else { - $module = preg_replace('/[^a-z]/', '', $_REQUEST['do']); + $moduleName = preg_replace('/[^a-z]/', '', $_REQUEST['do']); } -$module = 'modules/' . $module . '.inc.php'; +$modulePath = 'modules/' . $moduleName . '.inc.php'; -if (!file_exists($module)) { - Util::traceError('Invalid module: ' . $module); +if (!file_exists($modulePath)) { + Util::traceError('Invalid module: ' . $moduleName); } -// Display any messages +// Deserialize any messages if (isset($_REQUEST['message'])) { Message::fromRequest(); } +// CSRF/XSS +if ($_SERVER['REQUEST_METHOD'] === 'POST' && !Util::verifyToken()) { + Util::redirect('?do=' . $moduleName); +} + // Load module - it will execute pre-processing, or act upon request parameters -require_once($module); -unset($module); +require_once($modulePath); +unset($modulePath); // Main menu $menu = new Menu; diff --git a/modules/sysconfig.inc.php b/modules/sysconfig.inc.php index c883eb68..faa26787 100644 --- a/modules/sysconfig.inc.php +++ b/modules/sysconfig.inc.php @@ -1,37 +1,29 @@ preprocess(); } -if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'activate') { - if (!Util::verifyToken()) { - Util::redirect('?do=sysconfig'); - } +// Action "activate" (set sysconfig as active) +if ($action === 'activate') { if (!User::hasPermission('superadmin')) { Message::addError('no-permission'); Util::redirect('?do=sysconfig'); @@ -55,15 +47,19 @@ if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'activate') { Util::redirect('?do=sysconfig'); } +/** + * Render module; called by main script when this module page should render + * its content. + */ function render_module() { - if (!isset($_REQUEST['action'])) $_REQUEST['action'] = 'list'; - switch ($_REQUEST['action']) { - case 'remotelist': - list_remote_configs(); + global $action, $handler; + switch ($action) { + case 'addmodule': + $handler->render(); break; case 'list': - list_configs(); + rr_list_configs(); break; default: Message::addError('invalid-action', $_REQUEST['action']); @@ -71,25 +67,23 @@ function render_module() } } -function list_configs() +function rr_list_configs() { if (!User::hasPermission('superadmin')) { Message::addError('no-permission'); return; } - $current = ''; - if (file_exists(CONFIG_HTTP_DIR . '/default/config.tgz')) $current = realpath(CONFIG_HTTP_DIR . '/default/config.tgz'); - $files = array(); - foreach (glob(CONFIG_TGZ_LIST_DIR . '/*.tgz') as $file) { - $files[] = array( - 'file' => basename($file), - 'current' => ($current === realpath($file)) + $res = Database::simpleQuery("SELECT title FROM configtgz_module ORDER BY title ASC"); + $modules = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $modules[] = array( + 'module' => $row['title'] ); } - Render::addTemplate('page-tgz-list', array('files' => $files, 'token' => Session::get('token'))); + Render::addTemplate('page-sysconfig-main', array('modules' => $modules, 'token' => Session::get('token'))); } -function list_remote_configs() +function rr_list_remote_configs() { if (!User::hasPermission('superadmin')) { Message::addError('no-permission'); @@ -111,4 +105,3 @@ function list_remote_configs() } Render::addTemplate('page-remote-tgz-list', array('files' => $list)); } - diff --git a/modules/sysconfig/addmodule.inc.php b/modules/sysconfig/addmodule.inc.php new file mode 100644 index 00000000..aca2f762 --- /dev/null +++ b/modules/sysconfig/addmodule.inc.php @@ -0,0 +1,196 @@ + $nextStep)); + } + +} + +/** + * Some file has just been uploaded. Try to store it, then try to unpack/analyze it. + * If this succeeds, we proceed to the next step where we present the user our findings + * and ask what to do with this. + */ +class AddModule_ProcessUpload extends AddModule_Base +{ + + private $taskId = false; + + public function preprocess() + { + if (!isset($_FILES['modulefile'])) { + Message::addError('missing-file'); + return; + } + if ($_FILES['modulefile']['error'] != UPLOAD_ERR_OK) { + Message::addError('upload-failed', $_FILE['modulefile']['name']); + return; + } + $tempfile = $_FILES['modulefile']['tmp_name'] . '.tmp'; + if (!move_uploaded_file($_FILES['modulefile']['tmp_name'], $tempfile)) { + Message:addError('error-write', $tempfile); + return; + } + $this->taskId = 'tgzmod' . mt_rand() . '-' . microtime(true); + Taskmanager::submit('ListArchive', array( + 'id' => $this->taskId, + 'file' => $tempfile + ), true); + Session::set('mod_temp', $tempfile); + } + + public function render() + { + $status = Taskmanager::waitComplete($this->taskId); + Taskmanager::release($this->taskId); + $tempfile = Session::get('mod_temp'); + if (!isset($status['statusCode'])) { + unlink($tempfile); + $this->tmError(); + } + if ($status['statusCode'] != TASK_FINISHED) { + unlink($tempfile); + $this->taskError($status); + } + // Sort files for better display + $dirs = array(); + foreach ($status['data']['entries'] as $file) { + if ($file['isdir']) continue; + $dirs[dirname($file['name'])][] = $file; + } + ksort($dirs); + $list = array(); + foreach ($dirs as $dir => $files) { + $list[] = array( + 'name' => $dir, + 'isdir' => true + ); + sort($files); + foreach ($files as $file) { + $file['size'] = Util::readableFileSize($file['size']); + $list[] = $file; + } + } + global $nextStep; + Render::addDialog('Eigenes Modul hinzufügen', false, 'sysconfig/custom-fileselect', array( + 'step' => $nextStep, + 'files' => $list, + )); + Session::save(); + } + +} + +class AddModule_CompressModule extends AddModule_Base +{ + + private $taskId = false; + + public function preprocess() + { + $title = Request::post('title'); + $tempfile = Session::get('mod_temp'); + if (empty($title) || empty($tempfile) || !file_exists($tempfile)) { + Message::addError('empty-field'); + return; + } + // Recompress using task manager + $this->taskId = 'tgzmod' . mt_rand() . '-' . microtime(true); + $destFile = CONFIG_TGZ_LIST_DIR . '/modules/mod-' . preg_replace('/[^a-z0-9_\-]+/is', '_', $title) . '-' . microtime(true) . '.tgz'; + Taskmanager::submit('RecompressArchive', array( + 'id' => $this->taskId, + 'inputFiles' => array($tempfile), + 'outputFile' => $destFile + ), true); + $status = Taskmanager::waitComplete($this->taskId); + unlink($tempfile); + if (!isset($status['statusCode'])) { + $this->tmError(); + } + if ($status['statusCode'] != TASK_FINISHED) { + $this->taskError($status); + } + // Seems ok, create entry in DB + $ret = Database::exec("INSERT INTO configtgz_module (title, moduletype, filename, contents) VALUES (:title, 'custom', :file, '')", + array('title' => $title, 'file' => $destFile)); + if ($ret === false) { + unlink($destFile); + Util::traceError("Could not insert module into Database"); + } + Message::addSuccess('module-added'); + Util::redirect('?do=sysconfig'); + } + +} diff --git a/modules/syslog.inc.php b/modules/syslog.inc.php index f8d99de3..62c94edf 100644 --- a/modules/syslog.inc.php +++ b/modules/syslog.inc.php @@ -12,26 +12,35 @@ function render_module() { Render::setTitle('Client Log'); - $filter = ''; - $not = ''; - if (isset($_POST['filter'])) $filter = $_POST['filter']; + if (isset($_GET['filter'])) { + $filter = $_GET['filter']; + $not = isset($_GET['not']) ? 'NOT' : ''; + } elseif (isset($_POST['filter'])) { + $filter = $_POST['filter']; + $not = isset($_POST['not']) ? 'NOT' : ''; + Session::set('log_filter', $filter); + Session::set('log_not', $not); + Session::save(); + } else { + $filter = Session::get('log_filter'); + $not = Session::get('log_not') ? 'NOT' : ''; + } if (!empty($filter)) { - $parts = explode(',', $filter); - $opt = array(); - foreach ($parts as $part) { - $part = preg_replace('/[^a-z0-9_\-]/', '', trim($part)); - if (empty($part) || in_array($part, $opt)) continue; - $opt[] = "'$part'"; + $filterList = explode(',', $filter); + $whereClause = array(); + foreach ($filterList as $filterItem) { + $filterItem = preg_replace('/[^a-z0-9_\-]/', '', trim($filterItem)); + if (empty($filterItem) || in_array($filterItem, $whereClause)) continue; + $whereClause[] = "'$filterItem'"; } - if (isset($_POST['not'])) $not = 'NOT'; - if (!empty($opt)) $opt = ' WHERE logtypeid ' . $not . ' IN (' . implode(', ', $opt) . ')'; + if (!empty($whereClause)) $whereClause = ' WHERE logtypeid ' . $not . ' IN (' . implode(', ', $whereClause) . ')'; } - if (!isset($opt) || empty($opt)) $opt = ''; + if (!isset($whereClause) || empty($whereClause)) $whereClause = ''; $today = date('d.m.Y'); $yesterday = date('d.m.Y', time() - 86400); $lines = array(); - $paginate = new Paginate("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog $opt ORDER BY logid DESC", 50); + $paginate = new Paginate("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog $whereClause ORDER BY logid DESC", 50); $res = $paginate->exec(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { $day = date('d.m.Y', $row['dateline']); diff --git a/script/custom.js b/script/custom.js index f2906813..3269749e 100644 --- a/script/custom.js +++ b/script/custom.js @@ -4,3 +4,14 @@ function loadContent(elem, source) $(elem).load(source); } +function selectDir(obj) +{ + dirname = $(obj).parent().parent().find('td.isdir').text() + '/'; + console.log("CALLED! Dirname: " + dirname); + $('td.fileEntry').each(function() { + var text = $(this).text(); + if (text.length < dirname.length) return; + if (text.substr(0, dirname.length) !== dirname) return; + $(this).parent().find('.fileBox')[0].checked = obj.checked; + }); +} \ No newline at end of file diff --git a/style/default.css b/style/default.css index 1a4a0be0..98ecadd6 100644 --- a/style/default.css +++ b/style/default.css @@ -57,3 +57,6 @@ body { height: 34px; } +.isdir { + font-weight: bold; +} \ No newline at end of file diff --git a/templates/dialog-generic.html b/templates/dialog-generic.html new file mode 100644 index 00000000..5e875e8b --- /dev/null +++ b/templates/dialog-generic.html @@ -0,0 +1,15 @@ +
+ +
\ No newline at end of file diff --git a/templates/page-sysconfig-main.html b/templates/page-sysconfig-main.html new file mode 100644 index 00000000..c160fd0b --- /dev/null +++ b/templates/page-sysconfig-main.html @@ -0,0 +1,86 @@ + +
+
+
+ Verfügbare Systemkonfigurationen + +
+ + {{#files}} + + + + + {{/files}} +
{{file}} + {{^current}} + Aktivieren + {{/current}} + {{#current}} + Bereits aktiv + {{/current}} +
+ {{^files}} +
Keine Systemkonfigurationen gefunden!
+ {{/files}} + +
+
+
+ Verfügbare Konfigurationsmodule + +
+ + {{#modules}} + + + + + {{/modules}} +
{{module}} + Bearbeiten + Löschen +
+ {{^modules}} +
Keine Konfigurationsmodule gefunden!
+ {{/modules}} + +
+
+ + + + \ No newline at end of file diff --git a/templates/page-tgz-list.html b/templates/page-tgz-list.html deleted file mode 100644 index fdc11933..00000000 --- a/templates/page-tgz-list.html +++ /dev/null @@ -1,33 +0,0 @@ - -
- {{#files}} -
- {{file}} - {{^current}} - Aktivieren - {{/current}} - {{#current}} - Bereits aktiv - {{/current}} -
- {{/files}} - {{^files}} -
Keine Konfigurationspakete gefunden!
- {{/files}} - Konfigurationsvolagen - Eigene Konfiguration hochladen - Aktive Konfiguration herunterladen -
-
-
- - - - -
-
-
-
diff --git a/templates/sysconfig/custom-fileselect.html b/templates/sysconfig/custom-fileselect.html new file mode 100644 index 00000000..c61edc7f --- /dev/null +++ b/templates/sysconfig/custom-fileselect.html @@ -0,0 +1,26 @@ +
+ +
+ Modulname + +
+
+

Hier haben Sie die Möglichkeit, den Inhalt des Archivs noch einmal zu überprüfen, und + die Liste der zu übernehmenden Dateien bei Bedarf noch einschränken.

+ + {{#files}} + + {{#isdir}} + + {{/isdir}} + {{^isdir}} + + + {{/isdir}} + + {{/files}} +
{{name}}{{name}}{{size}}
+
+ +
+
diff --git a/templates/sysconfig/custom-upload.html b/templates/sysconfig/custom-upload.html new file mode 100644 index 00000000..2c0ff723 --- /dev/null +++ b/templates/sysconfig/custom-upload.html @@ -0,0 +1,16 @@ +

Über ein benutzerdefiniertes Modul ist es möglich, beliebige Dateien + zum Linux-Grundsystem, das auf den Clients gebootet wird, hinzuzufügen. + Dazu kann ein Archiv mit einer Dateisystemstruktur hochgeladen werden, die + in dieser Form 1:1 in das gebootete Linux extrahiert wird.

+ +

Beispiel: Enthält das hochgeladene Archiv eine Datei etc/beispiel.conf, + so wird auf einem gebooteten Client diese Datei als /etc/beispiel.conf zu finden sein.

+ +
+
+ Archiv + +
+

Unterstützte Archivformate: .tar.gz, .tar.bz2, .zip

+ +
\ No newline at end of file -- cgit v1.2.3-55-g7522