From 06d9d5f70e8475e768b528b1f46e1005b8c2e1ee Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 29 Jan 2015 20:41:41 +0100 Subject: Config.tgz improvements, automatic rebuilds etc. --- inc/configmodule.inc.php | 34 +++++- inc/configmodule/adauth.inc.php | 7 ++ inc/configmodule/branding.inc.php | 5 + inc/configmodule/customodule.inc.php | 6 + inc/configtgz.inc.php | 143 ++++++++++++++++++++--- inc/taskmanagercallback.inc.php | 15 +++ inc/util.inc.php | 13 +++ lang/de/messages.json | 1 + lang/de/settings/setting.json | 2 +- lang/de/templates/sysconfig/cfg-start.json | 4 +- lang/en/messages.json | 1 + lang/en/settings/setting.json | 2 +- lang/en/templates/sysconfig/cfg-start.json | 4 +- lang/pt/templates/sysconfig/cfg-start.json | 1 - modules/sysconfig.inc.php | 6 +- modules/sysconfig/addconfig.inc.php | 2 +- modules/sysconfig/addmodule.inc.php | 16 ++- modules/sysconfig/addmodule_adauth.inc.php | 47 +++++--- modules/sysconfig/addmodule_branding.inc.php | 44 ++++--- modules/sysconfig/addmodule_custommodule.inc.php | 2 +- templates/sysconfig/_page.html | 4 +- templates/sysconfig/ad-checkconnection.html | 1 + templates/sysconfig/ad-start.html | 3 +- templates/sysconfig/branding-check.html | 3 +- templates/sysconfig/branding-start.html | 1 + templates/sysconfig/cfg-start.html | 2 +- 26 files changed, 304 insertions(+), 65 deletions(-) diff --git a/inc/configmodule.inc.php b/inc/configmodule.inc.php index ae2201fc..31c19953 100644 --- a/inc/configmodule.inc.php +++ b/inc/configmodule.inc.php @@ -163,6 +163,14 @@ abstract class ConfigModule * @return boolean true if data was successfully set, false otherwise (i.e. invalid data being set) */ public abstract function setData($key, $value); + + /** + * Get module specific data + * + * @param string $key key, name or id of data to get + * @return mixed Module specific data + */ + public abstract function getData($key); /** * Module specific version of generate. @@ -260,6 +268,28 @@ abstract class ConfigModule )); return true; } + + /** + * Update the given module in database. This will not regenerate + * the module's tgz. + * + * @return boolean true on success, false otherwise + */ + public final function update() + { + if ($this->moduleId === 0) + Util::traceError('ConfigModule::update called when moduleId == 0'); + if (!$this->validateConfig()) + return false; + // Update + Database::exec("UPDATE configtgz_module SET contents = :contents, status = :status " + . " WHERE moduleid = :moduleid LIMIT 1", array( + 'moduleid' => $this->moduleId, + 'contents' => json_encode($this->moduleData), + 'status' => 'OUTDATED' + )); + return true; + } /** * Generate the module's tgz, don't wait for completion. @@ -283,7 +313,7 @@ abstract class ConfigModule if ($ret === true || (isset($ret['statusCode']) && $ret['statusCode'] === TASK_FINISHED)) { // Already Finished if (file_exists($this->moduleArchive) && !file_exists($tmpTgz)) - $tmpTgz = false; + $tmpTgz = false; // If generateInternal succeeded and there's no tmpTgz, it means the file didn't have to be updated return $this->markUpdated($tmpTgz); } if (!is_array($ret) || !isset($ret['id']) || Taskmanager::isFailed($ret)) { @@ -362,6 +392,7 @@ abstract class ConfigModule // Update related config.tgzs $configs = ConfigTgz::getAllForModule($this->moduleId); foreach ($configs as $config) { + $config->markOutdated(); $config->generate(); } return $retval; @@ -406,7 +437,6 @@ abstract class ConfigModule { self::loadDb(); // Quick hack: Hard code AdAuth, should be a property of the config module class.... $list = self::getAll('AdAuth'); - error_log('Changed: Telling ' - count($list) . ' modules'); foreach ($list as $mod) { $mod->event_serverIpChanged(); } diff --git a/inc/configmodule/adauth.inc.php b/inc/configmodule/adauth.inc.php index 828469c3..11087286 100644 --- a/inc/configmodule/adauth.inc.php +++ b/inc/configmodule/adauth.inc.php @@ -47,6 +47,13 @@ class ConfigModule_AdAuth extends ConfigModule return true; } + public function getData($key) + { + if (!is_array($this->moduleData) || !isset($this->moduleData[$key])) + return false; + return $this->moduleData[$key]; + } + // ############## Callbacks ############################# /** diff --git a/inc/configmodule/branding.inc.php b/inc/configmodule/branding.inc.php index 6e452a93..b2f28c2f 100644 --- a/inc/configmodule/branding.inc.php +++ b/inc/configmodule/branding.inc.php @@ -47,4 +47,9 @@ class ConfigModule_Branding extends ConfigModule $this->tmpFile = $value; } + public function getData($key) + { + return false; + } + } diff --git a/inc/configmodule/customodule.inc.php b/inc/configmodule/customodule.inc.php index 195f738f..89f63549 100644 --- a/inc/configmodule/customodule.inc.php +++ b/inc/configmodule/customodule.inc.php @@ -48,4 +48,10 @@ class ConfigModule_CustomModule extends ConfigModule return false; $this->tmpFile = $value; } + + public function getData($key) + { + return false; + } + } diff --git a/inc/configtgz.inc.php b/inc/configtgz.inc.php index 783f8b80..2082155c 100644 --- a/inc/configtgz.inc.php +++ b/inc/configtgz.inc.php @@ -22,6 +22,21 @@ class ConfigTgz { return $this->configTitle; } + + public function areAllModulesUpToDate() + { + if (!$this->configId > 0) + Util::traceError('ConfigTgz::areAllModulesUpToDate called on un-inserted config.tgz!'); + foreach ($this->modules as $module) { + if (!empty($module['filepath']) && file_exists($module['filepath'])) { + if ($module['status'] !== 'OK') + return false; + } else { + return false; + } + } + return true; + } public static function insert($title, $moduleIds) { @@ -104,21 +119,73 @@ class ConfigTgz } return $list; } + + /** + * Called when (re)generating a config tgz failed, so we can + * update the status in the DB and add a server log entry. + * + * @param array $task + * @param array $args contains 'configid' and optionally 'deleteOnError' + */ + public static function generateFailed($task, $args) + { + if (!isset($args['configid']) || !is_numeric($args['configid'])) { + EventLog::warning('Ignoring generateFailed event as it has no configid assigned.'); + return; + } + $config = self::get($args['configid']); + if ($config === false) { + EventLog::warning('generateFailed callback for config id ' . $args['configid'] . ', but no instance could be generated.'); + return; + } + if (isset($task['data']['error'])) + $error = $task['data']['error']; + elseif (isset($task['data']['messages'])) + $error = $task['data']['messages']; + else + $error = ''; + EventLog::failure("Generating config.tgz '" . $config->configTitle . "' failed.", $error); + if ($args['deleteOnError']) + $config->delete(); + else + $config->markFailed(); + } + + /** + * (Re)generating a config tgz succeeded. Update db entry. + * + * @param array $args contains 'configid' and optionally 'deleteOnError' + */ + public static function generateSucceeded($args) + { + if (!isset($args['configid']) || !is_numeric($args['configid'])) { + EventLog::warning('Ignoring generateSucceeded event as it has no configid assigned.'); + return; + } + $config = self::get($args['configid']); + if ($config === false) { + EventLog::warning('generateSucceeded callback for config id ' . $args['configid'] . ', but no instance could be generated.'); + return; + } + $config->markUpdated(); + } - public function generate() + /** + * + * @param type $deleteOnError + * @param type $timeoutMs + * @return string - OK (success) + * - OUTDATED (updating failed, but old version still exists) + * - MISSING (failed and no old version available) + */ + public function generate($deleteOnError = false, $timeoutMs = 0) { if (!($this->configId > 0) || !is_array($this->modules) || $this->file === false) Util::traceError ('configId <= 0 or modules not array in ConfigTgz::rebuild()'); $files = array(); - $successStatus = 'OK'; foreach ($this->modules as $module) { - if (!empty($module['filepath']) && file_exists($module['filepath'])) { + if (!empty($module['filepath']) && file_exists($module['filepath'])) $files[] = $module['filepath']; - if ($module['status'] !== 'OK') - $successStatus = 'OUTDATED'; - } else { - $successStatus = 'OUTDATED'; - } } // Hand over to tm $task = Taskmanager::submit('RecompressArchive', array( @@ -126,18 +193,28 @@ class ConfigTgz 'outputFile' => $this->file )); // Wait for completion - if (!Taskmanager::isFailed($task) && !Taskmanager::isFinished($task)) - $task = Taskmanager::waitComplete($task, 5000); - // Failed... - if (Taskmanager::isFailed($task)) { + if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task)) + $task = Taskmanager::waitComplete($task, $timeoutMs); + if ($task === true || (isset($task['statusCode']) && $task['statusCode'] === TASK_FINISHED)) { + // Success! + $this->markUpdated(); + return true; + } + if (!is_array($task) || !isset($task['id']) || Taskmanager::isFailed($task)) { + // Failed... Taskmanager::addErrorMessage($task); - $successStatus = file_exists($this->file) ? 'OUTDATED' : 'MISSING'; + if (!$deleteOnError) + $this->markFailed(); + else + $this->delete(); + return false; } - Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", array( + // Still running, add callback + TaskmanagerCallback::addCallback($task, 'cbConfTgzCreated', array( 'configid' => $this->configId, - 'status' => $successStatus + 'deleteOnError' => $deleteOnError )); - return $successStatus; + return $task['id']; } public function delete() @@ -157,4 +234,38 @@ class ConfigTgz return $ret; } + public function markOutdated() + { + if ($this->configId === 0) + Util::traceError('ConfigTgz::markOutdated called with invalid config id!'); + return $this->mark('OUTDATED'); + } + + private function markUpdated() + { + if ($this->configId === 0) + Util::traceError('ConfigTgz::markUpdated called with invalid config id!'); + if ($this->areAllModulesUpToDate()) + return $this->mark('OK'); + return $this->mark('OUTDATED'); + } + + private function markFailed() + { + if ($this->configId === 0) + Util::traceError('ConfigTgz::markFailed called with invalid config id!'); + if ($this->file === false || !file_exists($this->file)) + return $this->mark('MISSING'); + return $this->mark('OUTDATED'); + } + + private function mark($status) + { + Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", array( + 'configid' => $this->configId, + 'status' => $status + )); + return $status; + } + } diff --git a/inc/taskmanagercallback.inc.php b/inc/taskmanagercallback.inc.php index 8550d845..546fbbc9 100644 --- a/inc/taskmanagercallback.inc.php +++ b/inc/taskmanagercallback.inc.php @@ -130,5 +130,20 @@ class TaskmanagerCallback ConfigModule::generateSucceeded($args); } } + + /** + * Generating a config.tgz has finished. + * + * @param array $task task obj + * @param array $args has keys 'configid' and optionally 'deleteOnError' + */ + public static function cbConfTgzCreated($task, $args) + { + if (Taskmanager::isFailed($task)) { + ConfigTgz::generateFailed($task, $args); + } else { + ConfigTgz::generateSucceeded($args); + } + } } diff --git a/inc/util.inc.php b/inc/util.inc.php index d2ecba6f..4378a084 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -2,6 +2,7 @@ class Util { + private static $redirectParams = array(); /** * Displays an error message and stops script execution. @@ -69,9 +70,21 @@ SADFACE; $location .= '&' . $messages; } } + if (!empty(self::$redirectParams)) { + if (strpos($location, '?') === false) { + $location .= '?' . implode('&', self::$redirectParams); + } else { + $location .= '&' . implode('&', self::$redirectParams); + } + } Header('Location: ' . $location); exit(0); } + + public static function addRedirectParam($key, $value) + { + self::$redirectParams[] = $key .= '=' . urlencode($value); + } /** * Verify the user's token that protects agains CSRF. diff --git a/lang/de/messages.json b/lang/de/messages.json index 51540c15..b344fd41 100644 --- a/lang/de/messages.json +++ b/lang/de/messages.json @@ -28,6 +28,7 @@ "missing-title": "Kein Titel eingegeben", "module-added": "Modul erfolgreich hinzugef\u00fcgt", "module-deleted": "Modul {{0}} wurde gel\u00f6scht", + "module-edited": "Modul wurde aktualisiert", "module-in-use": "Modul {{0}} wird noch durch Konfiguration {{1}} verwendet", "module-rebuild-failed": "Neubau des Moduls fehlgeschlagen", "module-rebuilding": "Modul wird neu generiert", diff --git a/lang/de/settings/setting.json b/lang/de/settings/setting.json index e2fbe145..63a4d881 100644 --- a/lang/de/settings/setting.json +++ b/lang/de/settings/setting.json @@ -11,7 +11,7 @@ "SLX_PROXY_IP": "Die Adresse des zu verwendenden Proxy Servers.", "SLX_PROXY_MODE": "Legt fest, ob zum Zugriff aufs Internet ein Proxy-Server ben\u00f6tigt wird.\r\n*off* = keinen Proxy benutzen.\r\n*on* = Proxy immer benutzen.\r\n*auto* = Proxy nur benutzen, wenn sich der Client-PC in einem privaten Adressbereich befindet.", "SLX_PROXY_PORT": "Der Port des zu verwendenden Proxy Servers.", - "SLX_PROXY_TYPE": "Art des Proxys: *socks4*, *socks5*, *http-connect* (HTTP Proxy mit Unterst\u00fctzung der CONNECT-Methode),*http-relay* (Klassischer HTTP Proxy)", + "SLX_PROXY_TYPE": "Art des Proxys: *socks4*, *socks5*, *http-connect* (HTTP Proxy mit Unterst\u00fctzung der CONNECT-Methode), *http-relay* (Klassischer HTTP Proxy)", "SLX_REMOTE_LOG_SESSIONS": "Legt fest, ob Logins und Logouts der Benutzer an den Satelliten gemeldet werden sollen.\r\n*yes* = Mit Benutzerkennung loggen\r\n*anonymous* = Anonym loggen\r\n*no* = Nicht loggen", "SLX_ROOT_PASS": "Das root-Passwort des Grundsystems. Wird nur f\u00fcr Diagnosezwecke am Client ben\u00f6tigt.\r\nFeld leer lassen, um root-Logins zu verbieten.\r\n\/Hinweis\/: Das Passwort wird im Klartext in der lokalen Datenbank hinterlegt, jedoch immer gehasht an die Clients \u00fcbermittelt (SHA-512 mit Salt). Wenn Sie das Passwort auch im Satelliten nicht im Klartext speichern wollen, k\u00f6nnen Sie hier auch ein vorgehashtes Passwort eintragen (im *$6$....*-Format).", "SLX_SHUTDOWN_SCHEDULE": "Feste Uhrzeit, zu der sich die Rechner ausschalten, auch wenn noch ein Benutzer aktiv ist.Mehrere Zeitpunkte k\u00f6nnen durch Leerzeichen getrennt angegeben werden.", diff --git a/lang/de/templates/sysconfig/cfg-start.json b/lang/de/templates/sysconfig/cfg-start.json index c5f4c5fa..c22a96c3 100644 --- a/lang/de/templates/sysconfig/cfg-start.json +++ b/lang/de/templates/sysconfig/cfg-start.json @@ -2,6 +2,6 @@ "lang_configuration": "Konfiguration", "lang_configurationChoose": "Bitte w\u00e4hlen Sie, welche Module f\u00fcr diese Konfiguration verwendet werden sollen.", "lang_name": "Name", - "lang_new": "Neu", - "lang_next": "Weiter" + "lang_next": "Weiter", + "lang_noModuleOfType": "Kein Modul dieser Art vorhanden." } \ No newline at end of file diff --git a/lang/en/messages.json b/lang/en/messages.json index 8d3ac4df..889b5406 100644 --- a/lang/en/messages.json +++ b/lang/en/messages.json @@ -28,6 +28,7 @@ "missing-title": "No title given", "module-added": "Module successfully added", "module-deleted": "Module {{0}} was deleted", + "module-edited": "Module has been edited", "module-in-use": "Module {{0}} is still used by Configuration {{1}}", "module-rebuild-failed": "Rebuilding module failed", "module-rebuilding": "Module is rebuilding...", diff --git a/lang/en/settings/setting.json b/lang/en/settings/setting.json index 7a9db4a9..a17edfa6 100644 --- a/lang/en/settings/setting.json +++ b/lang/en/settings/setting.json @@ -11,7 +11,7 @@ "SLX_PROXY_IP": "The address to use for the proxy server.", "SLX_PROXY_MODE": "Determines whether a proxy server is required to access the Internet.\r\n*off* = do not use a Proxy.\r\n*on* = Always use proxy.\r\n*auto* = Only use proxy when the client PC is in a private address space.", "SLX_PROXY_PORT": "The port to use for the proxy server.", - "SLX_PROXY_TYPE": "Type of the proxy.*socks4*, *socks5*,*http-connect* (HTTP proxy with support from the CONNECT method),*http-relay* (Classic HTTP proxy)", + "SLX_PROXY_TYPE": "Type of the proxy.*socks4*, *socks5*, *http-connect* (HTTP proxy with support from the CONNECT method), *http-relay* (Classic HTTP proxy)", "SLX_REMOTE_LOG_SESSIONS": "Determines whether logins and logouts of the users should be reported to the satellite.\r\n*yes* = log with user ID\r\n*anonymous* = anonymous logging\r\n*no* = no logging", "SLX_ROOT_PASS": "The root password of the basic system. Only required for diagnostic purposes on the client.Leave field blank to disallow root logins.\/Hint\/: The password is encrypted with $6$ hash, so it is no longer readable after saving!", "SLX_SHUTDOWN_SCHEDULE": "Fixed time to turn off the computer, even if there is a user active.Several times can be specified, separated by spaces.", diff --git a/lang/en/templates/sysconfig/cfg-start.json b/lang/en/templates/sysconfig/cfg-start.json index 8ab46f37..a00a6720 100644 --- a/lang/en/templates/sysconfig/cfg-start.json +++ b/lang/en/templates/sysconfig/cfg-start.json @@ -2,6 +2,6 @@ "lang_configuration": "Configuration", "lang_configurationChoose": "Please select which modules will be used for this configuration.", "lang_name": "Name", - "lang_new": "New", - "lang_next": "Next" + "lang_next": "Next", + "lang_noModuleOfType": "No module of this type found." } \ No newline at end of file diff --git a/lang/pt/templates/sysconfig/cfg-start.json b/lang/pt/templates/sysconfig/cfg-start.json index 7b980f3c..27260a59 100644 --- a/lang/pt/templates/sysconfig/cfg-start.json +++ b/lang/pt/templates/sysconfig/cfg-start.json @@ -2,6 +2,5 @@ "lang_configuration": "Configura\u00e7\u00e3o", "lang_configurationChoose": "Por favor, selecione qual os m\u00f3dulos ser\u00e3o usados \u200b\u200bpara esta configura\u00e7\u00e3o.", "lang_name": "Nome", - "lang_new": "Nova", "lang_next": "Pr\u00f3ximo" } \ No newline at end of file diff --git a/modules/sysconfig.inc.php b/modules/sysconfig.inc.php index 94e2455a..e9b7e677 100644 --- a/modules/sysconfig.inc.php +++ b/modules/sysconfig.inc.php @@ -127,7 +127,8 @@ class Page_SysConfig extends Page } Render::addTemplate('sysconfig/_page', array( 'configs' => $configs, - 'modules' => $modules + 'modules' => $modules, + 'havemodules' => (count($modules) > 0) )); Render::addScriptTop('custom'); Render::addFooter('