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 @@
+
+
+
+
+
{{title}}
+
+
+ {{{body}}}
+
+
+
+
+
\ 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 @@
+
+
+ Über eine Systemkonfiguration wird die grundlegende Lokalisierung des bwLehrpool-Systems
+ durchgeführt. Dazu gehören Aspekte wie das Authentifizierungsverfahren für Benutzer
+ (z.B. Active Directory, LDAP), Druckerkonfiguration, Home-Verzeichnisse, etc.
+ Eine Systemkonfiguration setzt sich aus einem oder mehreren Konfigurationsmodulen zusammen,
+ welche im unteren Bereich dieser Seite verwaltet werden können.
+
+
+
+
+
+
+
+
+
+
Konfigurationsmodule
+
+ Konfigurationsmodule sind die Bausteine, aus denen eine Systemkonfiguration erstellt wird.
+ Hier lassen sich sowohl generische Module durch einen Wizard anlegen, als auch komplett eigene
+ Module erstellen (fortgeschritten, Linuxkenntnisse erforderlich).
+
+
+
+
+
\ 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 @@
-
-
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 @@
+
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.
+
+
\ No newline at end of file
--
cgit v1.2.3-55-g7522