summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--inc/property.inc.php42
-rw-r--r--inc/taskmanager.inc.php16
-rw-r--r--inc/taskmanagercallback.inc.php9
-rw-r--r--modules-available/minilinux/hooks/cron.inc.php6
-rw-r--r--modules-available/minilinux/hooks/ipxe-bootentry.inc.php42
-rw-r--r--modules-available/minilinux/inc/minilinux.inc.php228
-rw-r--r--modules-available/minilinux/install.inc.php40
-rw-r--r--modules-available/minilinux/lang/de/messages.json5
-rw-r--r--modules-available/minilinux/lang/de/module.json6
-rw-r--r--modules-available/minilinux/lang/de/template-tags.json29
-rw-r--r--modules-available/minilinux/lang/en/messages.json5
-rw-r--r--modules-available/minilinux/lang/en/module.json7
-rw-r--r--modules-available/minilinux/page.inc.php299
-rw-r--r--modules-available/minilinux/permissions/permissions.json3
-rw-r--r--modules-available/minilinux/templates/branches.html73
-rw-r--r--modules-available/minilinux/templates/download.html1
-rw-r--r--modules-available/minilinux/templates/filelist.html125
-rw-r--r--modules-available/minilinux/templates/page-minilinux.html26
-rw-r--r--modules-available/minilinux/templates/sources.html42
-rw-r--r--modules-available/minilinux/templates/versionlist.html39
-rw-r--r--style/default.css2
21 files changed, 790 insertions, 255 deletions
diff --git a/inc/property.inc.php b/inc/property.inc.php
index b69be1f8..3911b0d4 100644
--- a/inc/property.inc.php
+++ b/inc/property.inc.php
@@ -145,43 +145,6 @@ class Property
self::set('ipxe-menu', json_encode($value));
}
- public static function getVersionCheckTaskId()
- {
- return self::get('versioncheck-task');
- }
-
- public static function setVersionCheckTaskId($value)
- {
- self::set('versioncheck-task', $value);
- }
-
- public static function getVersionCheckInformation()
- {
- $data = json_decode(self::get('versioncheck-data', '[]'), true);
- if (isset($data['time']) && $data['time'] + 60 > time())
- return $data;
- $task = Taskmanager::submit('DownloadText', array(
- 'url' => CONFIG_REMOTE_ML . '/list.php'
- ));
- if (!isset($task['id']))
- return 'Could not start list download (' . Message::asString() . ')';
- if (!Taskmanager::isFinished($task)) {
- $task = Taskmanager::waitComplete($task['id'], 5000);
- }
- if ($task['statusCode'] !== Taskmanager::TASK_FINISHED || !isset($task['data']['content'])) {
- return isset($task['data']['error']) ? $task['data']['error'] : 'Timeout';
- }
- $data = json_decode($task['data']['content'], true);
- $data['time'] = time();
- self::setVersionCheckInformation($data);
- return $data;
- }
-
- public static function setVersionCheckInformation($value)
- {
- self::set('versioncheck-data', json_encode($value), 1);
- }
-
public static function getVmStoreConfig()
{
return json_decode(self::get('vmstore-config'), true);
@@ -251,9 +214,4 @@ class Property
return self::get('password-type', 'password');
}
- public static function getIpxeDefault()
- {
- return self::get('default-ipxe');
- }
-
}
diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php
index 8fe70d00..547a75d4 100644
--- a/inc/taskmanager.inc.php
+++ b/inc/taskmanager.inc.php
@@ -175,6 +175,22 @@ class Taskmanager
return false;
}
+ /**
+ * Check whether the given task is running, that is either waiting for execution
+ * or currently executing.
+ *
+ * @param array $task struct representing task, obtained by ::status
+ * @return boolean true if task is waiting or executing, false if waiting for execution or currently executing, no valid task, etc.
+ */
+ public static function isRunning($task)
+ {
+ if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id']))
+ return false;
+ if ($task['statusCode'] === Taskmanager::TASK_WAITING || $task['statusCode'] === Taskmanager::TASK_PROCESSING)
+ return true;
+ return false;
+ }
+
public static function addErrorMessage($task)
{
static $failure = false;
diff --git a/inc/taskmanagercallback.inc.php b/inc/taskmanagercallback.inc.php
index 8e253962..d1152bfd 100644
--- a/inc/taskmanagercallback.inc.php
+++ b/inc/taskmanagercallback.inc.php
@@ -184,6 +184,15 @@ class TaskmanagerCallback
}
}
+ public static function mlDownload($task, $args)
+ {
+ $mod = Module::get('minilinux');
+ if ($mod === false)
+ return;
+ $mod->activate(1, false);
+ MiniLinux::listDownloadCallback($task, $args);
+ }
+
public static function uploadimg($task)
{
//$string=var_export($task, true);
diff --git a/modules-available/minilinux/hooks/cron.inc.php b/modules-available/minilinux/hooks/cron.inc.php
new file mode 100644
index 00000000..fa6e4c70
--- /dev/null
+++ b/modules-available/minilinux/hooks/cron.inc.php
@@ -0,0 +1,6 @@
+<?php
+
+$ret = explode(':', date('G:i'));
+if ((int)$ret[0] === 6 && (int)$ret[1] < 5) {
+ MiniLinux::updateList();
+} \ No newline at end of file
diff --git a/modules-available/minilinux/hooks/ipxe-bootentry.inc.php b/modules-available/minilinux/hooks/ipxe-bootentry.inc.php
new file mode 100644
index 00000000..4e2cbb5e
--- /dev/null
+++ b/modules-available/minilinux/hooks/ipxe-bootentry.inc.php
@@ -0,0 +1,42 @@
+<?php
+
+class Wurst extends BootEntryHook
+{
+
+ public function name()
+ {
+ return 'Wurst';
+ }
+
+ /**
+ * @return HookEntryGroup[]
+ */
+ protected function groupsInternal()
+ {
+ return [
+ new HookEntryGroup('Senf Gruppe', [
+ new HookEntry('senf-1', 'Senf v1'),
+ new HookEntry('senf-2', 'Senf v2'),
+ ]),
+ new HookEntryGroup('Schnecke Gruppe', [
+ new HookEntry('s-1', 'Trulla'),
+ new HookEntry('s-2', 'Herbert'),
+ ]),
+ ];
+ }
+
+ /**
+ * @param $id
+ * @return BootEntry the actual boot entry instance for given entry, false if invalid id
+ */
+ public function getBootEntry($id)
+ {
+ $bios = new ExecData();
+ $bios->executable = 'mspaint.exe';
+ $bios->initRd = 'www.google.de';
+ $bios->commandLine = '-q';
+ return BootEntry::newStandardBootEntry($bios, false, 'agnostic');
+ }
+}
+
+return new Wurst(); \ No newline at end of file
diff --git a/modules-available/minilinux/inc/minilinux.inc.php b/modules-available/minilinux/inc/minilinux.inc.php
new file mode 100644
index 00000000..46c63c94
--- /dev/null
+++ b/modules-available/minilinux/inc/minilinux.inc.php
@@ -0,0 +1,228 @@
+<?php
+
+class MiniLinux
+{
+
+ const PROPERTY_KEY_FETCHTIME = 'ml-list-fetch';
+
+ public static function updateList()
+ {
+ $stamp = time();
+ $last = Property::get(self::PROPERTY_KEY_FETCHTIME);
+ error_log('Last: ' . $last);
+ if ($last !== false && $last + 10 > $stamp)
+ return 0; // In progress...
+ Property::set(self::PROPERTY_KEY_FETCHTIME, $stamp, 1);
+ Database::exec('LOCK TABLES callback WRITE,
+ minilinux_source WRITE, minilinux_branch WRITE, minilinux_version WRITE');
+ Database::exec('UPDATE minilinux_source SET taskid = UUID()');
+ $cutoff = time() - 3600;
+ Database::exec("UPDATE minilinux_version
+ INNER JOIN minilinux_branch USING (branchid)
+ INNER JOIN minilinux_source USING (sourceid)
+ SET orphan = orphan + 1 WHERE minilinux_source.lastupdate < $cutoff");
+ $list = Database::queryAll('SELECT sourceid, url, taskid FROM minilinux_source');
+ foreach ($list as $source) {
+ Taskmanager::submit('DownloadText', array(
+ 'id' => $source['taskid'],
+ 'url' => $source['url'],
+ ), true);
+ TaskmanagerCallback::addCallback($source['taskid'], 'mlDownload', $source['sourceid']);
+ }
+ Database::exec('UNLOCK TABLES');
+ return count($list);
+ }
+
+ public static function listDownloadCallback($task, $sourceid)
+ {
+ $taskId = $task['id'];
+ $data = json_decode($task['data']['content'], true);
+ if (!is_array($data)) {
+ EventLog::warning('Cannot download Linux version meta data for ' . $sourceid);
+ error_log(print_r($task, true));
+ $lastupdate = 'lastupdate';
+ } else {
+ if (isset($data['systems']) && is_array($data['systems'])) {
+ self::addBranches($sourceid, $data['systems']);
+ }
+ $lastupdate = 'UNIX_TIMESTAMP()';
+ }
+ Database::exec("UPDATE minilinux_source SET lastupdate = $lastupdate, taskid = NULL
+ WHERE sourceid = :sourceid AND taskid = :taskid",
+ ['sourceid' => $sourceid, 'taskid' => $taskId]);
+ }
+
+ private static function addBranches($sourceid, $systems)
+ {
+ foreach ($systems as $system) {
+ if (!self::isValidIdPart($system['id']))
+ continue;
+ $branchid = $sourceid . '/' . $system['id'];
+ $title = empty($system['title']) ? $branchid : $system['title'];
+ $description = empty($system['description']) ? '' : $system['description'];
+ Database::exec('INSERT INTO minilinux_branch (branchid, sourceid, title, description)
+ VALUES (:branchid, :sourceid, :title, :description)
+ ON DUPLICATE KEY UPDATE title = VALUES(title), description = VALUES(description)', [
+ 'branchid' => $branchid,
+ 'sourceid' => $sourceid,
+ 'title' => $title,
+ 'description' => $description,
+ ]);
+ if (isset($system['versions']) && is_array($system['versions'])) {
+ self::addVersions($branchid, $system['versions']);
+ }
+ }
+ }
+
+ private static function addVersions($branchid, $versions)
+ {
+ foreach ($versions as $version) {
+ self::addVersion($branchid, $version);
+ }
+ }
+
+ private static function addVersion($branchid, $version)
+ {
+ if (!self::isValidIdPart($version['version'])) {
+ error_log("Ignoring version {$version['version']} from $branchid: Invalid characters in ID");
+ return;
+ }
+ if (empty($version['files']) && empty($version['cmdline'])) {
+ error_log("Ignoring version {$version['version']} from $branchid: Neither file list nor command line");
+ return;
+ }
+ $versionid = $branchid . '/' . $version['version'];
+ $title = empty($version['title']) ? '' : $version['title'];
+ $dateline = empty($version['releasedate']) ? time() : (int)$version['releasedate'];
+ unset($version['version'], $version['title'], $version['releasedate']);
+ // Sanitize files array
+ if (!isset($version['files']) || !is_array($version['files'])) {
+ unset($version['files']);
+ } else {
+ foreach (array_keys($version['files']) as $key) {
+ $file =& $version['files'][$key];
+ if (empty($file['name'])) {
+ error_log("Ignoring version {$version['version']} from $branchid: Entry in file list has missing file name");
+ return;
+ }
+ if ($file['name'] === 'menu.txt' || $file['name'] === 'menu-debug.txt') {
+ unset($version['files'][$key]);
+ continue;
+ }
+ if (empty($file['gpg'])) {
+ error_log("Ignoring version {$version['version']} from $branchid: {$file['name']} has no GPG signature");
+ return;
+ }
+ if (preg_match(',/\.\.|\.\./|/|\x00,', $file['name']) > 0) { // Invalid chars
+ error_log("Ignoring version {$version['version']} from $branchid: {$file['name']} contains invalid characters");
+ return;
+ }
+ if (isset($file['md5'])) {
+ $file['md5'] = strtolower($file['md5']);
+ }
+ }
+ unset($file);
+ $version['files'] = array_values($version['files']);
+ }
+ $data = json_encode($version);
+ Database::exec('INSERT INTO minilinux_version (versionid, branchid, title, dateline, data, orphan)
+ VALUES (:versionid, :branchid, :title, :dateline, :data, 0)
+ ON DUPLICATE KEY UPDATE title = VALUES(title), data = VALUES(data), orphan = 0', [
+ 'versionid' => $versionid,
+ 'branchid' => $branchid,
+ 'title' => $title,
+ 'dateline' => $dateline,
+ 'data' => $data,
+ ]);
+ }
+
+ private static function isValidIdPart($str)
+ {
+ return preg_match('/^[a-z0-9_\-]+$/', $str) > 0;
+ }
+
+ public static function validateDownloadTask($versionid, $taskid)
+ {
+ if ($taskid === null)
+ return false;
+ $task = Taskmanager::status($taskid);
+ if (Taskmanager::isTask($task) && !Taskmanager::isFailed($task)
+ && (is_dir(CONFIG_HTTP_DIR . '/' . $versionid) || !Taskmanager::isFinished($task)))
+ return $task['id'];
+ Database::exec('UPDATE minilinux_version SET taskid = NULL
+ WHERE versionid = :versionid AND taskid = :taskid',
+ ['versionid' => $versionid, 'taskid' => $taskid]);
+ return false;
+ }
+
+ /**
+ * Download the files for the given version id
+ * @param $versionid
+ * @return bool
+ */
+ public static function downloadVersion($versionid)
+ {
+ $ver = Database::queryFirst('SELECT s.url, s.pubkey, v.versionid, v.taskid, v.data FROM minilinux_version v
+ INNER JOIN minilinux_branch b USING (branchid)
+ INNER JOIN minilinux_source s USING (sourceid)
+ WHERE versionid = :versionid',
+ ['versionid' => $versionid]);
+ if ($ver === false)
+ return false;
+ $taskid = self::validateDownloadTask($versionid, $ver['taskid']);
+ if ($taskid !== false)
+ return $taskid;
+ $data = json_decode($ver['data'], true);
+ if (!is_array($data)) {
+ EventLog::warning("Cannot download Linux '$versionid': Corrupted meta data.", $ver['data']);
+ return false;
+ }
+ if (empty($data['files']))
+ return false;
+ $list = [];
+ $legacyDir = preg_replace(',^[^/]*/,', '', $versionid);
+ foreach ($data['files'] as $file) {
+ if (empty($file['name']))
+ continue;
+ $list[] = [
+ 'id' => self::fileToId($versionid, $file['name']),
+ 'url' => empty($file['url'])
+ ? ($ver['url'] . '/' . $legacyDir . '/' . $file['name'])
+ : ($ver['url'] . '/' . $file['url']),
+ 'fileName' => $file['name'],
+ 'gpg' => $file['gpg'],
+ ];
+ }
+ error_log(print_r($list, true));
+ $uuid = Util::randomUuid();
+ Database::exec('LOCK TABLES minilinux_version WRITE');
+ $aff = Database::exec('UPDATE minilinux_version SET taskid = :taskid WHERE versionid = :versionid AND taskid IS NULL',
+ ['taskid' => $uuid, 'versionid' => $versionid]);
+ if ($aff > 0) {
+ $task = Taskmanager::submit('DownloadFiles', [
+ 'id' => $uuid,
+ 'baseDir' => CONFIG_HTTP_DIR . '/' . $versionid,
+ 'gpgPubKey' => $ver['pubkey'],
+ 'files' => $list,
+ ]);
+ if (Taskmanager::isFailed($task)) {
+ error_log(print_r($task, true));
+ $task = false;
+ } else {
+ $task = $task['id'];
+ }
+ } else {
+ $task = false;
+ }
+ Database::exec('UNLOCK TABLES');
+ if ($aff === 0)
+ return self::downloadVersion($versionid);
+ return $task;
+ }
+
+ public static function fileToId($versionid, $fileName)
+ {
+ return 'x' . substr(md5($fileName . $versionid), 0, 8);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/minilinux/install.inc.php b/modules-available/minilinux/install.inc.php
new file mode 100644
index 00000000..50be13a5
--- /dev/null
+++ b/modules-available/minilinux/install.inc.php
@@ -0,0 +1,40 @@
+<?php
+
+$result[] = tableCreate('minilinux_source', "
+ `sourceid` varchar(8) CHARACTER SET ascii NOT NULL,
+ `title` varchar(100) NOT NULL,
+ `url` varchar(200) NOT NULL,
+ `lastupdate` int(10) UNSIGNED NOT NULL,
+ `taskid` char(36) CHARACTER SET ascii DEFAULT NULL,
+ `pubkey` blob NOT NULL,
+ PRIMARY KEY (`sourceid`),
+ KEY (`title`, `sourceid`)
+");
+$result[] = tableCreate('minilinux_branch', "
+ `sourceid` varchar(8) CHARACTER SET ascii DEFAULT NULL,
+ `branchid` varchar(40) CHARACTER SET ascii NOT NULL,
+ `title` varchar(100) NOT NULL,
+ `description` blob NOT NULL,
+ PRIMARY KEY (`branchid`),
+ KEY (`title`)
+");
+$result[] = tableCreate('minilinux_version', "
+ `branchid` varchar(40) CHARACTER SET ascii NOT NULL,
+ `versionid` varchar(72) CHARACTER SET ascii NOT NULL,
+ `title` varchar(100) NOT NULL,
+ `dateline` int(10) UNSIGNED NOT NULL,
+ `data` blob NOT NULL,
+ `orphan` tinyint(3) UNSIGNED NOT NULL,
+ `taskid` char(36) CHARACTER SET ascii DEFAULT NULL,
+ PRIMARY KEY (`versionid`),
+ KEY (`title`),
+ KEY (`branchid`, `dateline`, `versionid`)
+");
+
+$result[] = tableAddConstraint('minilinux_version', 'branchid', 'minilinux_branch', 'branchid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$result[] = tableAddConstraint('minilinux_branch', 'sourceid', 'minilinux_source', 'sourceid',
+ 'ON UPDATE CASCADE ON DELETE SET NULL');
+
+responseFromArray($result);
diff --git a/modules-available/minilinux/lang/de/messages.json b/modules-available/minilinux/lang/de/messages.json
index c91772c7..1c38a8c1 100644
--- a/modules-available/minilinux/lang/de/messages.json
+++ b/modules-available/minilinux/lang/de/messages.json
@@ -1,3 +1,6 @@
{
- "please-download-minilinux": "Wichtige Dateien der MiniLinux-Installation fehlen."
+ "delete-error": "Fehler beim L\u00f6schen der Version {{0}}: {{1}}",
+ "no-such-version": "Ung\u00fcltige\/Unbekannte Version: {{0}}",
+ "please-download-minilinux": "Wichtige Dateien der MiniLinux-Installation fehlen",
+ "version-deleted": "Version {{0}} wurde gel\u00f6scht"
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/de/module.json b/modules-available/minilinux/lang/de/module.json
index e5284ac0..3ad539ca 100644
--- a/modules-available/minilinux/lang/de/module.json
+++ b/modules-available/minilinux/lang/de/module.json
@@ -1,4 +1,8 @@
{
+ "file-checksum-bad": "Pr\u00fcfsummenfehler",
+ "file-missing": "Datei fehlt",
+ "file-ok": "OK",
+ "file-size-mismatch": "Dateigr\u00f6\u00dfe stimmt nicht",
"module_name": "bwLehrpool MiniLinux",
- "page_title": "MiniLinux verwalten und aktualisieren"
+ "page_title": "Linuxvarianten f\u00fcr Netboot verwalten"
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/de/template-tags.json b/modules-available/minilinux/lang/de/template-tags.json
index 60a11db9..144887c8 100644
--- a/modules-available/minilinux/lang/de/template-tags.json
+++ b/modules-available/minilinux/lang/de/template-tags.json
@@ -1,15 +1,18 @@
{
- "lang_canUpdate1": "Mindestens eine Komponente von",
- "lang_canUpdate2": "kann aktualisiert werden. F\u00fcr einen reibungslosen Betrieb wird empfohlen, alle Komponenten auf dem aktuellen Stand zu halten.",
- "lang_configurationPackageNotFound": "Keine Konfigurationspakete gefunden!",
- "lang_desiredVersion": "Gew\u00fcnschte Version",
- "lang_errorGetting": "Fehler beim Herunterladen der Liste!",
- "lang_filesInVersion": "Dateien zu Version",
- "lang_listObtained": "Liste wird heruntergeladen...",
- "lang_outdated": "Veraltet",
- "lang_redownload": "Erneut herunterladen",
- "lang_systemUpdated": "Das System ist auf dem aktuellen Stand.",
- "lang_update": "Aktualisieren",
- "lang_updateAll": "Alle Module aktualisieren",
- "lang_uptodate": "Aktuell"
+ "lang_confirmDeleteVersion": "Diese Version wirklich l\u00f6schen?",
+ "lang_download": "Herunterladen",
+ "lang_id": "ID",
+ "lang_introText": "Hier gibts MiniLinux.",
+ "lang_key": "GPG-Key",
+ "lang_lastUpdate": "Zuletzt \u00fcberpr\u00fcft",
+ "lang_minilinuxHeading": "Netboot Linux verwalten",
+ "lang_orphanedVersion": "Verwaiste Version",
+ "lang_releaseDate": "Ver\u00f6ffentlichungsdatum",
+ "lang_sources": "Quellen",
+ "lang_title": "Titel",
+ "lang_updateSourcesButton": "Nach neuen Updates suchen",
+ "lang_url": "URL",
+ "lang_verify": "Integrit\u00e4t \u00fcberpr\u00fcfen",
+ "lang_verifyToolTip": "Dateiintegrit\u00e4t anhand von Pr\u00fcfsummen verifizieren",
+ "lang_version": "Version"
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/messages.json b/modules-available/minilinux/lang/en/messages.json
index 8d7fa76d..6dc736a4 100644
--- a/modules-available/minilinux/lang/en/messages.json
+++ b/modules-available/minilinux/lang/en/messages.json
@@ -1,3 +1,6 @@
{
- "please-download-minilinux": "Important files from the mini Linux installation are missing."
+ "delete-error": "Error deleting version {{0}}: {{1}}",
+ "no-such-version": "No such version: {{0}}",
+ "please-download-minilinux": "Important files from the mini Linux installation are missing.",
+ "version-deleted": "Deleted version {{0}}"
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/module.json b/modules-available/minilinux/lang/en/module.json
index 01cb3d00..899435e7 100644
--- a/modules-available/minilinux/lang/en/module.json
+++ b/modules-available/minilinux/lang/en/module.json
@@ -1,3 +1,8 @@
{
- "module_name": "Minilinux"
+ "file-checksum-bad": "Bad checksum",
+ "file-missing": "File missing",
+ "file-ok": "OK",
+ "file-size-mismatch": "File size mismatch",
+ "module_name": "Minilinux",
+ "page_title": "Manage Netboot Linux flavors"
} \ No newline at end of file
diff --git a/modules-available/minilinux/page.inc.php b/modules-available/minilinux/page.inc.php
index e973ee6e..b54050bf 100644
--- a/modules-available/minilinux/page.inc.php
+++ b/modules-available/minilinux/page.inc.php
@@ -12,120 +12,225 @@ class Page_MiniLinux extends Page
Util::redirect('?do=Main');
}
+ if (Request::isPost()) {
+ $show = Request::post('show', false, 'string');
+ if ($show === 'delete') {
+ $this->deleteVersion();
+ } elseif ($show === 'updatesources') {
+ $this->updateSources();
+ }
+ Util::redirect('?do=minilinux');
+ }
+
User::assertPermission('view');
}
protected function doRender()
{
- Render::addTemplate('page-minilinux', array(
- 'listurl' => '?do=MiniLinux&async=true&action=list'
- ));
+ Render::addTemplate('page-minilinux');
+ // List branches and versions
+ $branches = Database::queryAll('SELECT sourceid, branchid, title FROM minilinux_branch ORDER BY title ASC');
+ $versions = $this->queryAllVersionsByBranch();
+ foreach ($branches as &$branch) {
+ if (isset($versions[$branch['branchid']])) {
+ $branch['versionlist'] = $this->renderVersionList($versions[$branch['branchid']]);
+ }
+ }
+ Render::addTemplate('branches', ['branches' => $branches]);
+ // List sources
+ $res = Database::simpleQuery('SELECT sourceid, title, url, lastupdate, pubkey FROM minilinux_source ORDER BY title, sourceid');
+ $data = ['list' => [], 'show_refresh' => true];
+ $tooOld = strtotime('-7 days');
+ $showRefresh = strtotime('-10 minutes');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['lastupdate_s'] = Util::prettyTime($row['lastupdate']);
+ if ($row['lastupdate'] != 0 && $row['lastupdate'] < $tooOld) {
+ $row['update_class'] = 'text-danger';
+ }
+ if ($row['lastupdate'] > $showRefresh) {
+ $data['show_refresh'] = false;
+ }
+ $data['list'][] = $row;
+ }
+ Render::addTemplate('sources', $data);
}
protected function doAjax()
{
- $data = Property::getVersionCheckInformation();
- if (!is_array($data) || !isset($data['systems'])) {
- echo Render::parse('messagebox', array(
- 'type' => 'danger',
- 'message' => 'Failed to retrieve the list: ' . print_r($data, true)
- ), 'main');
- return;
+ User::load();
+ $show = Request::post('show', false, 'string');
+ if ($show === 'version') {
+ $this->ajaxVersion();
+ } elseif ($show === 'download') {
+ $this->ajaxDownload();
}
- $action = Request::any('action');
- $selectedVersion = (int)Request::any('version', 0);
- switch ($action) {
- case 'list':
- $count = 0;
- foreach ($data['systems'] as &$system) {
- // Get latest version, build simple array of all version numbers
- $versionNumbers = array();
- $selected = false;
- foreach ($system['versions'] as $version) {
- if (!is_numeric($version['version']) || $version['version'] < 1)
- continue;
- if ($selectedVersion === 0 && ($selected === false || $selected['version'] < $version['version']))
- $selected = $version;
- elseif ($version['version'] == $selectedVersion)
- $selected = $version;
- $versionNumbers[(int)$version['version']] = array(
- 'version' => $version['version']
- );
+ }
+
+ private function queryAllVersionsByBranch()
+ {
+ $list = [];
+ $res = Database::simpleQuery('SELECT branchid, versionid, title, dateline, orphan, taskid
+ FROM minilinux_version ORDER BY branchid, dateline, versionid');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $list[$row['branchid']][] = $row;
+ }
+ return $list;
+ }
+
+ private function renderVersionList($versions)
+ {
+ foreach ($versions as &$version) {
+ $version['dateline_s'] = Util::prettyTime($version['dateline']);
+ $version['orphan'] = ($version['orphan'] > 5);
+ $version['installed'] = is_dir(CONFIG_HTTP_DIR . '/' . $version['versionid']);
+ $version['downloading'] = $version['taskid'] && Taskmanager::isRunning(Taskmanager::status($version['taskid']));
+ }
+ return Render::parse('versionlist', ['versions' => $versions]);
+ }
+
+ private function ajaxVersion()
+ {
+ User::assertPermission('view');
+ $verify = Request::post('verify', false, 'bool');
+ $versionid = Request::post('version', false, 'string');
+ if ($versionid === false) {
+ die('What!');
+ }
+ $ver = Database::queryFirst('SELECT versionid, taskid, data FROM minilinux_version WHERE versionid = :versionid',
+ ['versionid' => $versionid]);
+ if ($ver === false) {
+ die('No such version');
+ }
+ $versionid = $ver['versionid']; // Just to be sure -- should be safe for building a path either way
+ $data = json_decode($ver['data'], true);
+ if (!is_array($data)) {
+ die('Corrupted data');
+ }
+ $data['versionid'] = $versionid;
+ $data['dltask'] = MiniLinux::validateDownloadTask($versionid, $ver['taskid']);
+ $data['verify_button'] = !$verify && $data['dltask'] === false;
+ if (is_array($data['files'])) {
+ $sort = [];
+ foreach ($data['files'] as &$file) {
+ if (empty($file['name'])) {
+ $sort[] = 'zzz' . implode(',', $file);
+ continue;
}
- if ($selected === false) continue; // No versions for this system!?
- ksort($versionNumbers);
- // Mark latest version as selected
- $versionNumbers[(int)$selected['version']]['selected'] = true;
- // Add status information to system and its files
- foreach ($selected['files'] as &$file) {
- $file['uid'] = 'dlid' . $count++;
- $local = CONFIG_HTTP_DIR . '/' . $system['id'] . '/' . $file['name'];
- if (!file_exists($local) || filesize($local) !== $file['size'] || filemtime($local) < $file['mtime']
- || md5_file($local) !== $file['md5']) {
- $file['fileChanged'] = true;
- $system['systemChanged'] = true;
- }
- $taskId = Property::getDownloadTask($file['md5']);
- if ($taskId !== false) {
- $task = Taskmanager::status($taskId);
- if (isset($task['data']['progress'])) {
- $file['download'] = Render::parse('download', array(
- 'task' => $taskId,
- 'name' => $file['name']
- ));
- }
- }
+ $sort[] = $file['name'];
+ $s = $this->getFileState($versionid, $file, $verify);
+ if ($s !== self::FILE_OK) {
+ $data['verify_button'] = false;
+ $data['download_button'] = !$data['dltask'];
}
- unset($system['versions']);
- $system['files'] = $selected['files'];
- $system['version'] = $selected['version'];
- }
- $data['versions'] = array_values($versionNumbers);
- Permission::addGlobalTags($data['perms'], null, ['update']);
- echo Render::parse('filelist', $data);
- return;
- case 'download':
- User::assertPermission('update');
- $id = Request::post('id');
- $name = Request::post('name');
- if (!$id || !$name || strpos("$id$name", '/') !== false) {
- echo "Invalid download request";
- return;
- }
- $file = false;
- $gpg = 'missing';
- foreach ($data['systems'] as &$system) {
- if ($system['id'] !== $id) continue;
- foreach ($system['versions'] as &$version) {
- if ($version['version'] != $selectedVersion) continue;
- foreach ($version['files'] as &$f) {
- if ($f['name'] !== $name) continue;
- $file = $f;
- if (!empty($f['gpg'])) $gpg = $f['gpg'];
- break;
- }
+ if ($s !== self::FILE_MISSING) {
+ $data['delete_button'] = true;
+ }
+ $file['state'] = $this->fileStateToString($s);
+ if (isset($file['size'])) {
+ $file['size_s'] = Util::readableFileSize($file['size']);
+ }
+ if (isset($file['mtime'])) {
+ $file['mtime_s'] = Util::prettyTime($file['mtime']);
+ }
+ if ($data['dltask']) {
+ $file['fileid'] = MiniLinux::fileToId($versionid, $file['name']);
}
}
- if ($file === false) {
- echo "Nonexistent system/file: $id / $name";
- return;
- }
- $task = Taskmanager::submit('DownloadFile', array(
- 'url' => CONFIG_REMOTE_ML . '/' . $id . '/' . $selectedVersion . '/' . $name,
- 'destination' => CONFIG_HTTP_DIR . '/' . $id . '/' . $name,
- 'gpg' => $gpg
- ));
- if (!isset($task['id'])) {
- echo 'Error launching download task: ' . $task['statusCode'];
- return;
+ unset($file);
+ array_multisort($sort, SORT_ASC, $data['files']);
+ }
+ echo Render::parse('filelist', $data);
+ }
+
+ const FILE_OK = 0;
+ const FILE_MISSING = 1;
+ const FILE_SIZE_MISMATCH = 2;
+ const FILE_CHECKSUM_BAD = 3;
+
+ private function getFileState($versionid, $file, $verify)
+ {
+ $path = CONFIG_HTTP_DIR . '/' . $versionid . '/' . $file['name'];
+ if (!is_file($path))
+ return self::FILE_MISSING;
+ if (isset($file['size']) && filesize($path) != $file['size'])
+ return self::FILE_SIZE_MISMATCH;
+ if ($verify) {
+ // TODO: Others
+ if (isset($file['md5'])) {
+ if (md5_file($path) !== $file['md5'])
+ return self::FILE_CHECKSUM_BAD;
}
- Property::setDownloadTask($file['md5'], $task['id']);
- echo Render::parse('download', array(
- 'name' => $name,
- 'task' => $task['id']
- ));
+ }
+ return self::FILE_OK;
+ }
+
+ private function fileStateToString($state)
+ {
+ switch ($state) {
+ case self::FILE_CHECKSUM_BAD:
+ return Dictionary::translate('file-checksum-bad', true);
+ case self::FILE_SIZE_MISMATCH:
+ return Dictionary::translate('file-size-mismatch', true);
+ case self::FILE_MISSING:
+ return Dictionary::translate('file-missing', true);
+ case self::FILE_OK:
+ return Dictionary::translate('file-ok', true);
+ }
+ return '???';
+ }
+
+ private function ajaxDownload()
+ {
+ User::assertPermission('update');
+ $version = Request::post('version', false, 'string');
+ if ($version === false) {
+ die('No version');
+ }
+ $task = MiniLinux::downloadVersion($version);
+ if ($task === false) {
+ Message::addError('no-such-version', $version);
+ Message::renderList();
+ } else {
+ $this->ajaxVersion();
+ }
+ }
+
+ private function deleteVersion()
+ {
+ User::assertPermission('delete');
+ $versionid = Request::post('version', false, 'string');
+ if ($versionid === false) {
+ Message::addError('main.parameter-missing', 'versionid');
+ return;
+ }
+ $version = Database::queryFirst('SELECT versionid FROM minilinux_version WHERE versionid = :versionid',
+ ['versionid' => $versionid]);
+ if ($version === false) {
+ Message::addError('no-such-version');
return;
}
+ $path = CONFIG_HTTP_DIR . '/' . $version['versionid'];
+ $task = Taskmanager::submit('DeleteDirectory', [
+ 'path' => $path,
+ 'recursive' => true,
+ ]);
+ if ($task !== false) {
+ $task = Taskmanager::waitComplete($task, 2500);
+ if (Taskmanager::isFailed($task)) {
+ Message::addError('delete-error', $versionid, $task['data']['error']);
+ } else {
+ Message::addSuccess('version-deleted', $versionid);
+ }
+ }
+ }
+
+ private function updateSources()
+ {
+ $ret = MiniLinux::updateList();
+ if ($ret > 0) {
+ sleep(2);
+ Trigger::checkCallbacks();
+ }
}
}
diff --git a/modules-available/minilinux/permissions/permissions.json b/modules-available/minilinux/permissions/permissions.json
index b018ee72..ec311047 100644
--- a/modules-available/minilinux/permissions/permissions.json
+++ b/modules-available/minilinux/permissions/permissions.json
@@ -4,5 +4,8 @@
},
"update": {
"location-aware": false
+ },
+ "delete": {
+ "location-aware": false
}
} \ No newline at end of file
diff --git a/modules-available/minilinux/templates/branches.html b/modules-available/minilinux/templates/branches.html
new file mode 100644
index 00000000..1ba9497c
--- /dev/null
+++ b/modules-available/minilinux/templates/branches.html
@@ -0,0 +1,73 @@
+{{#branches}}
+<div id="ibm-mainframe" class="panel panel-default">
+ <div class="panel-heading">
+ <div class="pull-right">
+ {{sourceid}} {{branchid}}
+ </div>
+ {{title}}
+ </div>
+ <div class="panel-body">
+
+ </div>
+ {{{versionlist}}}
+</div>
+{{/branches}}
+<script>
+ document.addEventListener('DOMContentLoaded', function () {
+ var addHandlers = function(parent) {
+ parent.find('.btn-verify').click(function() {
+ loadDetails($(this).data('version'), { show: "version", verify: 1 });
+ });
+ parent.find('.btn-download').click(function() {
+ loadDetails($(this).data('version'), { show: "download" });
+ $(this).remove();
+ });
+ };
+ var loadDetails = function(version, params) {
+ var c = $('.version-container[data-version="' + version + '"]');
+ c.show();
+ if (c.is(':empty')) {
+ c.html('<span class="glyphicon glyphicon-refresh slx-rotation"></span>');
+ } else {
+ c.addClass('slx-fade');
+ c.find('button, a').addClass('disabled').prop('disabled', true);
+ }
+ var data = { version: version, token: TOKEN };
+ $.extend(data, params);
+ c.load('?do=minilinux', data,
+ function () {
+ c.removeClass('slx-fade');
+ addHandlers(c);
+ c.find('button[data-confirm]').click(slxModalConfirmHandler);
+ tmInit();
+ });
+ };
+ $('.version-link').click(function(e) {
+ e.preventDefault();
+ $(this).removeClass('version-link').off().removeAttr('href');
+ loadDetails($(this).data('version'), { show: "version" });
+ });
+ addHandlers($('#ibm-mainframe'));
+ $('[data-autoclick="true"]').click();
+ });
+ var taskDone = {};
+ function dlTmCb(task) {
+ if (!task.data || !task.data.files)
+ return;
+ for (var i = 0; i < task.data.files.length; ++i) {
+ var f = task.data.files[i];
+ var id = task.id + f.id;
+ if (taskDone[id] === true)
+ continue;
+ var $div = $('#' + f.id);
+ if (f.error) {
+ $div.text(f.error).addClass('text-danger');
+ taskDone[id] = true;
+ } else {
+ var wasDone = (taskDone[id] === 100);
+ tmSetProgress($div, f.progress, wasDone ? 'TASK_FINISHED' : task.statusCode);
+ taskDone[id] = wasDone ? true : f.progress;
+ }
+ }
+ }
+</script> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/download.html b/modules-available/minilinux/templates/download.html
deleted file mode 100644
index 2e32df5a..00000000
--- a/modules-available/minilinux/templates/download.html
+++ /dev/null
@@ -1 +0,0 @@
-<div data-tm-id="{{task}}" data-tm-log="error" data-tm-progress="progress">{{name}}</div> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/filelist.html b/modules-available/minilinux/templates/filelist.html
index 234b6c41..2c26edf9 100644
--- a/modules-available/minilinux/templates/filelist.html
+++ b/modules-available/minilinux/templates/filelist.html
@@ -1,74 +1,53 @@
- {{#systems}}
- <h1>{{title}}</h1>
- <div id="download-{{id}}">
- <div class="input-group pull-right" style="max-width: 400px">
- <span class="input-group-addon slx-ga">{{lang_desiredVersion}}</span>
- <select id="versionbox" class="form-control">
- {{#versions}}
- {{#selected}}
- <option value="{{version}}" selected>{{version}}</option>
- {{/selected}}
- {{^selected}}
- <option value="{{version}}">{{version}}</option>
- {{/selected}}
- {{/versions}}
- </select>
+<div class="text-right">
+ <form method="post" action="?do=minilinux">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="version" value="{{versionid}}">
+ {{#verify_button}}
+ <button type="button" class="btn btn-default btn-xs btn-verify" data-version="{{versionid}}" title="{{lang_verifyToolTip}}">
+ <span class="glyphicon glyphicon-search"></span>
+ {{lang_verify}}
+ </button>
+ {{/verify_button}}
+ {{#download_button}}
+ <button type="button" class="btn btn-xs btn-success btn-download" data-version="{{versionid}}">
+ <span class="glyphicon glyphicon-download"></span>
+ {{lang_download}}
+ </button>
+ {{/download_button}}
+ {{#delete_button}}
+ <button type="submit" name="show" value="delete" class="btn btn-xs btn-danger"
+ data-confirm="{{lang_confirmDeleteVersion}}">
+ <span class="glyphicon glyphicon-trash"></span>
+ {{lang_delete}}
+ </button>
+ {{/delete_button}}
+ </form>
+</div>
+<div class="clearfix slx-smallspace"></div>
+<table class="slx-table" style="width:100%">
+{{#files}}
+ <tr>
+ <td class="text-nowrap">&nbsp;</td>
+ <td class="text-nowrap">{{name}}</td>
+ <td class="text-nowrap text-right">{{size_s}}</td>
+ <td class="text-nowrap text-right">{{mtime_s}}</td>
+ <td style="width:100%">
+ {{^dltask}}
+ {{state}}
+ {{/dltask}}
+ {{#dltask}}
+ <div id="{{fileid}}">
+ <div class="progress" style="margin-bottom:0">
+ <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
+ style="width:0"></div>
+ </div>
</div>
- {{#systemChanged}}
- <p>
- {{lang_canUpdate1}} <b>{{title}}</b> {{lang_canUpdate2}}
- </p>
- <button {{perms.update.disabled}} class="btn btn-primary" onclick="slxUpdateAll(this, 'download-{{id}}')"><span class="glyphicon glyphicon-refresh"></span> {{lang_updateAll}}<span></span></button>
- {{/systemChanged}}
- {{^systemChanged}}
- <p>{{lang_systemUpdated}}</p>
- {{/systemChanged}}
- <hr>
- <p><b>{{lang_filesInVersion}} {{version}}</b></p>
- <ul class="list-group">
- {{#files}}
- <li class="list-group-item" id="{{uid}}">
- <div class="row">
- <div class="col-sm-2">{{name}}</div>
- <div class="col-xs-2">
- {{^fileChanged}}<span class="glyphicon glyphicon-ok"></span> <b>{{lang_uptodate}}</b>{{/fileChanged}}
- {{#fileChanged}}<span class="glyphicon glyphicon-exclamation-sign"></span> <b>{{lang_outdated}}</b>{{/fileChanged}}
- </div>
- <div class="col-xs-2">
- {{#fileChanged}}<button {{perms.update.disabled}} class="btn btn-primary btn-xs update-button" onclick="slxUpdate('{{uid}}', '{{id}}', '{{name}}')"><span class="glyphicon glyphicon-refresh"></span> {{lang_update}}</button> {{/fileChanged}}
- {{^fileChanged}}<button {{perms.update.disabled}} class="btn btn-default btn-xs" onclick="slxUpdate('{{uid}}', '{{id}}', '{{name}}')"><span class="glyphicon glyphicon-download-alt"></span> {{lang_redownload}}</button> {{/fileChanged}}
- </div>
- </div>
- {{{download}}}
- </li>
- {{/files}}
- </ul>
- </div>
- </div>
- {{/systems}}
- {{^systems}}
- <div class="row well well-sm">{{lang_configurationPackageNotFound}}</div>
- {{/systems}}
-
-<script type="text/javascript">
-function slxUpdate(uid, id, name)
-{
- $('#' + uid).html('');
- $('#' + uid).load('?do=MiniLinux',
- { action : "download", token : TOKEN, id : id, name : name, version : $('#versionbox').val() },
- function(response, status, xhr) {
- if (status === "error") {
- var msg = "Fehler beim Abruf: ";
- $('#' + uid).html(msg + xhr.status + " " + xhr.statusText);
- } else {
- setTimeout(tmInit, 100);
- }
- });
-}
-function slxUpdateAll(t, uid)
-{
- $(t).hide(0);
- $('#' + uid).find('.update-button').click();
-}
-tmInit();
-</script>
+ {{/dltask}}
+ </td>
+ </tr>
+{{/files}}
+</table>
+{{#dltask}}
+<div class="hidden" data-tm-id="{{dltask}}" data-tm-callback="dlTmCb"></div>
+{{/dltask}}
+<div class="slx-space"></div> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/page-minilinux.html b/modules-available/minilinux/templates/page-minilinux.html
index afccf230..2cbde608 100644
--- a/modules-available/minilinux/templates/page-minilinux.html
+++ b/modules-available/minilinux/templates/page-minilinux.html
@@ -1,25 +1,3 @@
-<div id="systemlist">
- <div class="panel panel-default">{{lang_listObtained}}</div>
-</div>
+<h1>{{lang_minilinuxHeading}}</h1>
-<script type="text/javascript"><!--
- document.addEventListener('DOMContentLoaded', function() {
- var $list = $('#systemlist');
- function loadSystemList(version) {
- $list.addClass('disabled');
- $list.find('select, input, button').prop('disabled', true);
- $list.find('div, p, span').addClass('text-muted');
- $list.load('{{{listurl}}}', { token: TOKEN, version: version }, function( response, status, xhr ) {
- if ( status === "error" ) {
- var msg = "{{lang_errorGetting}}";
- $list.html( msg + xhr.status + " " + xhr.statusText );
- }
- $list.removeClass('disabled');
- $('#versionbox').change(function() {
- loadSystemList($('#versionbox').val());
- });
- });
- }
- loadSystemList(0);
- });
-// --></script> \ No newline at end of file
+<p>{{lang_introText}}</p> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/sources.html b/modules-available/minilinux/templates/sources.html
new file mode 100644
index 00000000..f2e54745
--- /dev/null
+++ b/modules-available/minilinux/templates/sources.html
@@ -0,0 +1,42 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_sources}}
+ </div>
+ <table class="table table-condensed">
+ <thead>
+ <tr>
+ <th>{{lang_id}}</th>
+ <th>{{lang_title}}</th>
+ <th>{{lang_url}}</th>
+ <th>{{lang_lastUpdate}}</th>
+ <th>{{lang_key}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td class="small">{{sourceid}}</td>
+ <td>{{title}}</td>
+ <td class="small">{{url}}</td>
+ <td class="{{update_class}}">{{lastupdate_s}}</td>
+ <td class="text-center">
+ <button type="button" class="btn btn-default btn-xs" data-confirm="#confirm-{{source}}" data-close="{{lang_close}}">
+ <span class="glyphicon glyphicon-eye-open"></span>
+ </button>
+ <pre id="confirm-{{source}}" class="hidden">{{pubkey}}</pre>
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+ </table>
+ <div class="panel-body text-right">
+ <form method="post" action="?do=minilinux">
+ <input type="hidden" name="token" value="{{token}}">
+ <button type="submit" name="show" value="updatesources" class="btn btn-default"
+ onclick="$(this).find('.glyphicon').addClass('slx-rotation')" {{^show_refresh}}disabled{{/show_refresh}}>
+ <span class="glyphicon glyphicon-refresh"></span>
+ {{lang_updateSourcesButton}}
+ </button>
+ </form>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/versionlist.html b/modules-available/minilinux/templates/versionlist.html
new file mode 100644
index 00000000..1e5c7c96
--- /dev/null
+++ b/modules-available/minilinux/templates/versionlist.html
@@ -0,0 +1,39 @@
+<table class="table table-striped">
+<tr>
+ <th>{{lang_version}}</th>
+ <th>{{lang_releaseDate}}</th>
+ <th>{{lang_title}}</th>
+ <th class="slx-smallcol"></th>
+ <th class="slx-smallcol"></th>
+</tr>
+{{#versions}}
+<tr>
+ <td>
+ <a href="#" class="version-link" data-version="{{versionid}}" {{#downloading}}data-autoclick="true"{{/downloading}}>
+ {{versionid}}
+ <b class="caret"></b>
+ </a>
+ </td>
+ <td>{{dateline_s}}</td>
+ <td>{{title}}</td>
+ <td>
+ {{^installed}}
+ {{^downloading}}
+ <button type="button" class="btn btn-xs btn-success btn-download" data-version="{{versionid}}">
+ <span class="glyphicon glyphicon-download"></span>
+ {{lang_download}}
+ </button>
+ {{/downloading}}
+ {{/installed}}
+ </td>
+ <td>
+ {{#orphan}}
+ {{lang_orphanedVersion}}
+ {{/orphan}}
+ </td>
+</tr>
+<tr>
+ <td colspan="5" class="version-container collapse" data-version="{{versionid}}"></td>
+</tr>
+{{/versions}}
+</table> \ No newline at end of file
diff --git a/style/default.css b/style/default.css
index 6c2beb86..e00037ab 100644
--- a/style/default.css
+++ b/style/default.css
@@ -76,7 +76,7 @@ body {
}
.slx-table td {
- padding-right: 7px;
+ padding-right: 9px;
padding-bottom: 2px;
}