diff options
author | Simon Rettberg | 2014-05-15 18:28:24 +0200 |
---|---|---|
committer | Simon Rettberg | 2014-05-15 18:28:24 +0200 |
commit | 63c0cf521f8097b0dadaf1228176dc38c7d897f6 (patch) | |
tree | 83f5da6dc130ac7db575b0eee41ed6c7a2f994fb | |
parent | Fix handle leak in downloading, better error reporting on failed downloads, a... (diff) | |
download | slx-admin-63c0cf521f8097b0dadaf1228176dc38c7d897f6.tar.gz slx-admin-63c0cf521f8097b0dadaf1228176dc38c7d897f6.tar.xz slx-admin-63c0cf521f8097b0dadaf1228176dc38c7d897f6.zip |
Working on config.tgz composition through config modules
-rw-r--r-- | api.php | 2 | ||||
-rw-r--r-- | inc/database.inc.php (renamed from inc/db.inc.php) | 31 | ||||
-rw-r--r-- | inc/message.inc.php | 14 | ||||
-rw-r--r-- | inc/render.inc.php | 18 | ||||
-rw-r--r-- | inc/request.inc.php | 45 | ||||
-rw-r--r-- | inc/session.inc.php | 6 | ||||
-rw-r--r-- | inc/taskmanager.inc.php | 86 | ||||
-rw-r--r-- | inc/util.inc.php | 19 | ||||
-rw-r--r-- | index.php | 38 | ||||
-rw-r--r-- | modules/sysconfig.inc.php | 81 | ||||
-rw-r--r-- | modules/sysconfig/addmodule.inc.php | 196 | ||||
-rw-r--r-- | modules/syslog.inc.php | 35 | ||||
-rw-r--r-- | script/custom.js | 11 | ||||
-rw-r--r-- | style/default.css | 3 | ||||
-rw-r--r-- | templates/dialog-generic.html | 15 | ||||
-rw-r--r-- | templates/page-sysconfig-main.html | 86 | ||||
-rw-r--r-- | templates/page-tgz-list.html | 33 | ||||
-rw-r--r-- | templates/sysconfig/custom-fileselect.html | 26 | ||||
-rw-r--r-- | templates/sysconfig/custom-upload.html | 16 |
19 files changed, 642 insertions, 119 deletions
@@ -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/db.inc.php b/inc/database.inc.php index a797ae93..70f50116 100644 --- a/inc/db.inc.php +++ b/inc/database.inc.php @@ -43,6 +43,16 @@ class Database 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 @@ -53,16 +63,19 @@ class Database 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())); + 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; } - return self::$statements[$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 @@ <?php // TODO: Move to extra file +global $error_text; $error_text = array( 'loginfail' => '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 <html> <head> <title>', RENDER_DEFAULT_TITLE, self::$title, '</title> + <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Bootstrap --> <link href="style/bootstrap.min.css" rel="stylesheet" media="screen"> @@ -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 @@ +<?php + +/** + * Wrapper for getting fields from the request (GET, POST, ...) + */ +class Request +{ + + /** + * + * @param string $key Key of field to get from $_GET + * @param string $default Value to return if $_GET does not contain $key + * @return mixed Field from $_GET, or $default if not set + */ + public static function get($key, $default = false) + { + if (!isset($_GET[$key])) return $default; + return $_GET[$key]; + } + + /** + * + * @param string $key Key of field to get from $_POST + * @param string $default Value to return if $_POST does not contain $key + * @return mixed Field from $_POST, or $default if not set + */ + public static function post($key, $default = false) + { + if (!isset($_POST[$key])) return $default; + return $_POST[$key]; + } + + /** + * + * @param string $key Key of field to get from $_REQUEST + * @param string $default Value to return if $_REQUEST does not contain $key + * @return mixed Field from $_REQUEST, or $default if not set + */ + public static function any($key, $default = false) + { + if (!isset($_REQUEST[$key])) return $default; + return $_REQUEST[$key]; + } + +} diff --git a/inc/session.inc.php b/inc/session.inc.php index 3ba614f2..a0f8ab4c 100644 --- a/inc/session.inc.php +++ b/inc/session.inc.php @@ -52,7 +52,11 @@ class Session public static function set($key, $value) { if (self::$data === false) Util::traceError('Tried to set session data with no active session'); - self::$data[$key] = $value; + if ($value === false) { + unset(self::$data[$key]); + } else { + self::$data[$key] = $value; + } } private static function loadSessionId() diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php new file mode 100644 index 00000000..f2f337be --- /dev/null +++ b/inc/taskmanager.inc.php @@ -0,0 +1,86 @@ +<?php + +/** + * Interface to the external task manager. + */ +class Taskmanager +{ + + private static $sock = false; + + private static function init() + { + if (self::$sock !== false) return; + self::$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + socket_set_option(self::$sock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 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]; + } } @@ -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 <lowercasename>.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 @@ <?php +/* +@include_once('Archive/Tar.php'); +if (!class_exists('Archive_Tar')) { + Message::addError('Broken php installation: pear extension Archive_Tar missing!'); + Util::redirect('?do=main'); +} + */ + User::load(); -if (isset($_POST['action']) && $_POST['action'] === 'upload') { - if (!Util::verifyToken()) { - Util::redirect('?do=sysconfig'); - } - if (!User::hasPermission('superadmin')) { - Message::addError('no-permission'); - Util::redirect('?do=sysconfig'); - } - if (!isset($_FILES['customtgz'])) { - Message::addError('missing-file'); - Util::redirect('?do=sysconfig'); - } - $dest = $_FILES['customtgz']['name']; - $dest = preg_replace('/[^a-z0-9\-_]/', '', $dest); - $dest = substr($dest, 0, 30); - if (substr($dest, -3) === 'tgz') $dest = substr($dest, 0, -3); - $dest .= '.tgz'; - # TODO: Validate its a (compressed) tar? - if (move_uploaded_file($_FILES['customtgz']['tmp_name'], CONFIG_TGZ_LIST_DIR . '/' . $dest)) { - Message::addSuccess('upload-complete', $dest); - } else { - Message::addError('upload-failed', $dest); - } - Util::redirect('?do=sysconfig'); +// Read request vars +$action = Request::any('action', 'list'); +$step = Request::any('step', 0); +$nextStep = $step + 1; + +// Action: "addmodule" (upload new module) +if ($action === 'addmodule') { + require_once 'modules/sysconfig/addmodule.inc.php'; + $handler = AddModule_Base::get($step); + $handler->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 = '<none>'; - 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 @@ +<?php + +/** + * Addmodule subpage base - makes sure + * we have the two required methods preprocess and render + */ +abstract class AddModule_Base +{ + + /** + * + * @param type $step + * @return \AddModule_Base + */ + public static function get($step) + { + switch ($step) { + case 0: // Upload form + return new AddModule_UploadForm(); + case 1: // Handle config module uploading + return new AddModule_ProcessUpload(); + case 2: // ? + return new AddModule_CompressModule(); + } + Message::addError('invalid-action', $step); + Util::redirect('?do=sysconfig'); + } + + protected function tmError() + { + Message::addError('taskmanager-error'); + Util::redirect('?do=sysconfig'); + } + + protected function taskError($status) + { + if (isset($status['data']['error'])) { + $error = $status['data']['error']; + } elseif (isset($status['statusCode'])) { + $error = $status['statusCode']; + } else { + $error = 'Unbekannter Taskmanager-Fehler'; // TODO: No text + } + Message::addError('task-error', $error); + Util::redirect('?do=sysconfig'); + } + + /** + * Called before any HTML rendering happens, so you can + * pepare stuff, validate input, and optionally redirect + * early if something is wrong, or you received post + * data etc. + */ + public function preprocess() + { + // void + } + + /** + * Do page rendering + */ + public function render() + { + // void + } + +} + +class AddModule_UploadForm extends AddModule_Base +{ + + public function render() + { + global $nextStep; + Session::set('mod_temp', false); + Render::addDialog('Eigenes Modul hinzufügen', false, 'sysconfig/custom-upload', array('step' => $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 @@ +<div class="container"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">{{title}}</h4> + </div> + <div class="modal-body"> + {{{body}}} + </div> + <div class="modal-footer"> + {{#next}}<a class="btn btn-primary" href="{{next}}">Weiter »</a>{{/next}} + </div> + </div> + </div> +</div>
\ 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 @@ +<ol class="breadcrumb"> + <li><a href="?do=main">Start</a></li> + <li class="active">SystemKonfiguration</li> +</ol> +<div class="container"> + <div class="panel panel-default"> + <div class="panel-heading"> + Verfügbare Systemkonfigurationen + <a class="btn btn-default" data-toggle="modal" data-target="#help-config"><span class="glyphicon glyphicon-question-sign"></span></a> + </div> + <table class="table table-condensed"> + {{#files}} + <tr> + <td class=col-md-8">{{file}}</td> + <td class="col-md-4"> + {{^current}} + <a class="btn btn-primary" href="?do=sysconfig&action=activate&file={{file}}&token={{token}}">Aktivieren</a> + {{/current}} + {{#current}} + <span class="btn btn-success">Bereits aktiv</span> + {{/current}} + </td> + </tr> + {{/files}} + </table> + {{^files}} + <div class="alert alert-warning">Keine Systemkonfigurationen gefunden!</div> + {{/files}} + <div class="panel-body"> + <a class="btn btn-primary">Neue Konfiguration zusammenstellen</a> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading"> + Verfügbare Konfigurationsmodule + <a class="btn btn-default" data-toggle="modal" data-target="#help-module"><span class="glyphicon glyphicon-question-sign"></span></a> + </div> + <table class="table table-condensed"> + {{#modules}} + <tr> + <td>{{module}}</td> + <td nowrap> + <a class="btn btn-default btn-xs">Bearbeiten</a> + <a class="btn btn-danger btn-xs">Löschen</a> + </td> + </tr> + {{/modules}} + </table> + {{^modules}} + <div class="alert alert-warning">Keine Konfigurationsmodule gefunden!</div> + {{/modules}} + <div class="panel-body"> + <a class="btn btn-primary" href="?do=sysconfig&action=addmodule">Neues Modul erstellen</a> + </div> + </div> +</div> + +<div class="modal fade" id="help-config" tabindex="-1" role="dialog"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header">Systemkonfiguration</div> + <div class="modal-body"> + Ü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.<br> + Eine Systemkonfiguration setzt sich aus einem oder mehreren Konfigurationsmodulen zusammen, + welche im unteren Bereich dieser Seite verwaltet werden können. + </div> + <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">Schließen</a></div> + </div> + </div> +</div> + +<div class="modal fade" id="help-module" tabindex="-1" role="dialog"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header">Konfigurationsmodule</div> + <div class="modal-body"> + 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). + </div> + <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">Schließen</a></div> + </div> + </div> +</div>
\ 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 @@ -<ol class="breadcrumb"> - <li><a href="?do=main">Start</a></li> - <li class="active">SystemKonfiguration</li> -</ol> -<div class="container"> - {{#files}} - <div class="row well well-sm"> - {{file}} - {{^current}} - <a class="btn btn-primary" href="?do=sysconfig&action=activate&file={{file}}&token={{token}}">Aktivieren</a> - {{/current}} - {{#current}} - <span class="btn btn-success">Bereits aktiv</span> - {{/current}} - </div> - {{/files}} - {{^files}} - <div class="row well well-sm">Keine Konfigurationspakete gefunden!</div> - {{/files}} - <a class="btn btn-md btn-primary" href="?do=sysconfig&action=remotelist">Konfigurationsvolagen</a> - <a class="btn btn-md btn-primary" href="#" data-toggle="collapse" data-target="#uploadform">Eigene Konfiguration hochladen</a> - <a class="btn btn-md btn-primary" href="/boot/default/config.tgz">Aktive Konfiguration herunterladen</a> - <div class="collapse" id="uploadform"> - <div class="well well-sm" style="margin: 5px 0px"> - <form method="post" action="?do=sysconfig" enctype="multipart/form-data"> - <input type="file" size="40" class="form-control" name="customtgz"> - <input type="hidden" name="action" value="upload"> - <input type="hidden" name="token" value="{{token}}"> - <button class="btn btn-primary form-control-addon" type="submit">Hochladen</button> - </form> - </div> - </div> -</div> 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 @@ +<form role="form" method="post" action="?do=sysconfig&action=addmodule&step={{step}}"> + <input type="hidden" name="modid" value="{{modid}}"> + <div class="input-group"> + <span class="input-group-addon">Modulname</span> + <input type="text" name="title" class="form-control" placeholder="Mein Konfigurationsmodul" autofocus="autofocus"> + </div> + <hr> + <p>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.</p> + <table class="table table-bordered table-condensed"> + {{#files}} + <tr> + {{#isdir}} + <td class="fileEntry isdir" colspan="2">{{name}}</td> + {{/isdir}} + {{^isdir}} + <td class="fileEntry">{{name}}</td> + <td>{{size}}</td> + {{/isdir}} + </tr> + {{/files}} + </table> + <div class="pull-right"> + <button type="submit" class="btn btn-primary">Weiter »</button> + </div> +</form> 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 @@ +<p>Ü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.</p> + +<p>Beispiel: Enthält das hochgeladene Archiv eine Datei <strong>etc/beispiel.conf</strong>, + so wird auf einem gebooteten Client diese Datei als <strong>/etc/beispiel.conf</strong> zu finden sein.</p> + +<form role="form" enctype="multipart/form-data" method="post" action="?do=sysconfig&action=addmodule&step={{step}}"> + <div class="input-group"> + <span class="input-group-addon">Archiv</span> + <input class="form-control" type="file" name="modulefile"> + </div> + <p class="help-block">Unterstützte Archivformate: .tar.gz, .tar.bz2, .zip</p> + <button type="submit" class="btn btn-primary">Hochladen</button> +</form>
\ No newline at end of file |