فهرست منبع

Improved: update page (#5420)

* prependTitle()

* do not need the "damn" in the alert

* update page layout improved

* release channel

* i18n labels

* add log messages while updating

* Delete updatee.php

* Update updateController.php

* Update updateController.php

* Update updateController.php

* Update updateController.php

* add getCurrentGitBranch()

* Update updateController.php

* state2 buttons

* i18n

* loading

* Update feedback.php

* Update feedback.php

* Update feedback.php

* Update extra.js

* Apply suggestions from code review

Co-authored-by: Luc SANCHEZ <4697568+ColonelMoutarde@users.noreply.github.com>

* Update updateController.php

* Update terminology

* update button is now armed

---------

Co-authored-by: Luc SANCHEZ <4697568+ColonelMoutarde@users.noreply.github.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
maTh 2 سال پیش
والد
کامیت
3d9e0c47ec

+ 29 - 5
app/Controllers/updateController.php

@@ -44,6 +44,16 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 		return true;
 	}
 
+	public static function getCurrentGitBranch(): string {
+		$output = [];
+		exec('git branch --show-current', $output, $return);
+		if ($return === 0) {
+			return 'git branch: ' . $output[0];
+		} else {
+			return 'git';
+		}
+	}
+
 	public static function hasGitUpdate(): bool {
 		$cwd = getcwd();
 		if ($cwd === false) {
@@ -72,6 +82,7 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 
 	/** @return string|true */
 	public static function gitPull() {
+		Minz_Log::notice(_t('admin.update.viaGit'));
 		$cwd = getcwd();
 		if ($cwd === false) {
 			Minz_Log::warning('getcwd() failed');
@@ -110,6 +121,8 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 
 		invalidateHttpCache();
 
+		$this->view->is_release_channel_stable = $this->is_release_channel_stable(FRESHRSS_VERSION);
+
 		$this->view->update_to_apply = false;
 		$this->view->last_update_time = 'unknown';
 		$timestamp = @filemtime(join_path(DATA_PATH, self::LASTUPDATEFILE));
@@ -144,7 +157,17 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 		}
 	}
 
+	private function is_release_channel_stable(string $currentVersion): bool {
+		return strpos($currentVersion, 'dev') === false &&
+			strpos($currentVersion, 'edge') === false;
+	}
+
+	/*  Check installation if there is a newer version.
+		via Git, if available.
+		Else via system configuration  auto_update_url
+	*/
 	public function checkAction(): void {
+		FreshRSS_View::prependTitle(_t('admin.update.title') . ' · ');
 		$this->view->_path('update/index.phtml');
 
 		if (file_exists(UPDATE_FILENAME)) {
@@ -160,11 +183,10 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 
 		if (self::isGit()) {
 			if (self::hasGitUpdate()) {
-				$version = 'git';
+				$version = self::getCurrentGitBranch();
 			} else {
 				$this->view->message = array(
 					'status' => 'latest',
-					'title' => _t('gen.short.damn'),
 					'body' => _t('feedback.update.none')
 				);
 				@touch(join_path(DATA_PATH, self::LASTUPDATEFILE));
@@ -199,7 +221,6 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 
 				$this->view->message = array(
 					'status' => 'bad',
-					'title' => _t('gen.short.damn'),
 					'body' => _t('feedback.update.server_not_found', $auto_update_url)
 				);
 				return;
@@ -210,7 +231,6 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 			if (strpos($status, 'UPDATE') !== 0) {
 				$this->view->message = array(
 					'status' => 'latest',
-					'title' => _t('gen.short.damn'),
 					'body' => _t('feedback.update.none')
 				);
 				@touch(join_path(DATA_PATH, self::LASTUPDATEFILE));
@@ -220,6 +240,8 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 			$script = $res_array[1];
 			$version = explode(' ', $status, 2);
 			$version = $version[1];
+
+			Minz_Log::notice(_t('admin.update.copiedFromURL', $auto_update_url));
 		}
 
 		if (file_put_contents(UPDATE_FILENAME, $script) !== false) {
@@ -228,7 +250,6 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 		} else {
 			$this->view->message = array(
 				'status' => 'bad',
-				'title' => _t('gen.short.damn'),
 				'body' => _t('feedback.update.error', 'Cannot save the update script')
 			);
 		}
@@ -253,8 +274,10 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 			if ($res === true) {
 				@unlink(UPDATE_FILENAME);
 				@file_put_contents(join_path(DATA_PATH, self::LASTUPDATEFILE), '');
+				Minz_Log::notice(_t('feedback.update.finished'));
 				Minz_Request::good(_t('feedback.update.finished'));
 			} else {
+				Minz_Log::error(_t('feedback.update.error', $res));
 				Minz_Request::bad(_t('feedback.update.error', $res), [ 'c' => 'update', 'a' => 'index' ]);
 			}
 		} else {
@@ -288,6 +311,7 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 					'params' => array('post_conf' => '1')
 				), true);
 			} else {
+				Minz_Log::error(_t('feedback.update.error', $res));
 				Minz_Request::bad(_t('feedback.update.error', $res), [ 'c' => 'update', 'a' => 'index' ]);
 			}
 		}

+ 2 - 0
app/Models/View.php

@@ -71,6 +71,8 @@ class FreshRSS_View extends Minz_View {
 	public $update_to_apply;
 	/** @var array<string,bool> */
 	public $status_database;
+	/** @var bool */
+	public $is_release_channel_stable;
 
 	// Archiving
 	/** @var int */

+ 9 - 0
app/i18n/cz/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Aktualizace systému',
 		'apply' => 'Použít',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Zkontrolovat aktualizace',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Vaše aktuální verze FreshRSS je %s.',
 		'last' => 'Poslední kontrola: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Žádné nové aktualizace',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Aktualizovat systém',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrátor',

+ 9 - 0
app/i18n/de/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'System aktualisieren',
 		'apply' => 'Anwenden',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Auf neue Aktualisierungen prüfen',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Ihre aktuelle Version von FreshRSS ist %s.',
 		'last' => 'Letzte Überprüfung: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Keine ausstehende Aktualisierung',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'System aktualisieren',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrator',	// IGNORE

+ 9 - 0
app/i18n/el/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Ενημέρωση συστήματος',
 		'apply' => 'Εφαρμογή',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Έλεγχος για νέες ενημερώσεις',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Η τρέχουσα έκδοση του FreshRSS είναι %s.',
 		'last' => 'Τελευταία επαλήθευση: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Δεν υπάρχουν ενημερώσεις',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Ενημέρωση συστήματος',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Διαχειριστής',

+ 3 - 3
app/i18n/el/feedback.php

@@ -120,11 +120,11 @@ return array(
 		'renamed' => 'Label “%s” has been renamed to “%s”.',	// TODO
 	),
 	'update' => array(
-		'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',	// TODO
+		'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',	// DIRTY
 		'error' => 'The update process has encountered an error: %s',	// TODO
-		'file_is_nok' => 'New <strong>version %s</strong> available, but check permissions on <em>%s</em> directory. HTTP server must have have write permission',	// TODO
+		'file_is_nok' => 'New <strong>version %s</strong> available, but check permissions on <em>%s</em> directory. HTTP server must have have write permission',	// DIRTY
 		'finished' => 'Update complete!',	// TODO
-		'none' => 'No update to apply',	// TODO
+		'none' => 'No update to apply',	// DIRTY
 		'server_not_found' => 'Update server cannot be found. [%s]',	// TODO
 	),
 	'user' => array(

+ 9 - 0
app/i18n/en-us/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Update system',	// IGNORE
 		'apply' => 'Apply',	// IGNORE
+		'changelog' => 'Changelog',	// IGNORE
 		'check' => 'Check for new updates',	// IGNORE
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// IGNORE
 		'current_version' => 'Your current version of FreshRSS is %s.',	// IGNORE
 		'last' => 'Last verification: %s',	// IGNORE
+		'loading' => 'Updating…',	// IGNORE
 		'none' => 'No update to apply',	// IGNORE
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// IGNORE
+			'edge' => 'Rolling release (“edge”)',	// IGNORE
+			'latest' => 'Stable release (“latest”)',	// IGNORE
+		),
 		'title' => 'Update system',	// IGNORE
+		'viaGit' => 'Update via git and Github.com started',	// IGNORE
 	),
 	'user' => array(
 		'admin' => 'Administrator',	// IGNORE

+ 1 - 1
app/i18n/en-us/gen.php

@@ -200,7 +200,7 @@ return array(
 		'clipboard' => 'Clipboard',	// IGNORE
 		'diaspora' => 'Diaspora*',	// IGNORE
 		'email' => 'Email',	// IGNORE
-		'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)',	// TODO
+		'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)',	// IGNORE
 		'facebook' => 'Facebook',	// IGNORE
 		'gnusocial' => 'GNU social',	// IGNORE
 		'jdh' => 'Journal du hacker',	// IGNORE

+ 18 - 9
app/i18n/en/admin.php

@@ -184,19 +184,28 @@ return array(
 			'title' => 'User registration form',
 		),
 		'tos' => array(
-			'disabled' => 'is not given',	// TODO
-			'enabled' => '<a href="./?a=tos">is enabled</a>',	// TODO
-			'help' => 'How to <a href="https://freshrss.github.io/FreshRSS/en/admins/12_User_management.html#enable-terms-of-service-tos" target="_blank">enable the Terms of Service</a>',	// TODO
+			'disabled' => 'is not given',
+			'enabled' => '<a href="./?a=tos">is enabled</a>',
+			'help' => 'How to <a href="https://freshrss.github.io/FreshRSS/en/admins/12_User_management.html#enable-terms-of-service-tos" target="_blank">enable the Terms of Service</a>',
 		),
 	),
 	'update' => array(
-		'_' => 'Update system',
-		'apply' => 'Apply',
+		'_' => 'Update FreshRSS',
+		'apply' => 'Start update',
+		'changelog' => 'Changelog',
 		'check' => 'Check for new updates',
-		'current_version' => 'Your current version of FreshRSS is %s.',
-		'last' => 'Last verification: %s',
-		'none' => 'No update to apply',
-		'title' => 'Update system',
+		'copiedFromURL' => 'update.php copied from %s to ./data',
+		'current_version' => 'Current installed version',
+		'last' => 'Last check',
+		'loading' => 'Updating…',
+		'none' => 'No update available',
+		'releaseChannel' => array(
+			'_' => 'Release channel',
+			'edge' => 'Rolling release (“edge”)',
+			'latest' => 'Stable release (“latest”)',
+		),
+		'title' => 'Update FreshRSS',
+		'viaGit' => 'Update via git and Github.com started',
 	),
 	'user' => array(
 		'admin' => 'Administrator',

+ 3 - 3
app/i18n/en/conf.php

@@ -33,9 +33,9 @@ return array(
 	'display' => array(
 		'_' => 'Display',
 		'darkMode' => array(
-			'_' => 'Automatic dark mode (beta)',	// TODO
-			'auto' => 'Auto',	// TODO
-			'no' => 'No',	// TODO
+			'_' => 'Automatic dark mode (beta)',
+			'auto' => 'Auto',
+			'no' => 'No',
 		),
 		'icon' => array(
 			'bottom_line' => 'Bottom line',

+ 3 - 3
app/i18n/en/feedback.php

@@ -120,11 +120,11 @@ return array(
 		'renamed' => 'Label “%s” has been renamed to “%s”.',
 	),
 	'update' => array(
-		'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',
+		'can_apply' => 'An update of FreshRSS is available: <strong>Version %s</strong>.',
 		'error' => 'The update process has encountered an error: %s',
-		'file_is_nok' => 'New <strong>version %s</strong> available, but check permissions on <em>%s</em> directory. HTTP server must have have write permission',
+		'file_is_nok' => 'An update of FreshRSS is available (<strong>Version %s</strong>), but check permissions on <em>%s</em> directory. HTTP server must have have write permission',
 		'finished' => 'Update complete!',
-		'none' => 'No update to apply',
+		'none' => 'No update available',
 		'server_not_found' => 'Update server cannot be found. [%s]',
 	),
 	'user' => array(

+ 3 - 3
app/i18n/en/gen.php

@@ -175,7 +175,7 @@ return array(
 		'queries' => 'User queries',
 		'reading' => 'Reading',
 		'search' => 'Search words or #tags',
-		'search_help' => 'See documentation for advanced <a href="https://freshrss.github.io/FreshRSS/en/users/10_filter.html#with-the-search-field" target="_blank">search parameters</a>',	// TODO
+		'search_help' => 'See documentation for advanced <a href="https://freshrss.github.io/FreshRSS/en/users/10_filter.html#with-the-search-field" target="_blank">search parameters</a>',
 		'sharing' => 'Sharing',
 		'shortcuts' => 'Shortcuts',
 		'stats' => 'Statistics',
@@ -196,11 +196,11 @@ return array(
 		'archiveORG' => 'archive.org',
 		'archivePH' => 'archive.ph',
 		'blogotext' => 'Blogotext',
-		'buffer' => 'Buffer',	// IGNORE
+		'buffer' => 'Buffer',
 		'clipboard' => 'Clipboard',
 		'diaspora' => 'Diaspora*',
 		'email' => 'Email',
-		'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)',	// TODO
+		'email-webmail-firefox-fix' => 'Email (webmail - fix for Firefox)',
 		'facebook' => 'Facebook',
 		'gnusocial' => 'GNU social',
 		'jdh' => 'Journal du hacker',

+ 1 - 1
app/i18n/en/sub.php

@@ -122,7 +122,7 @@ return array(
 				'xpath' => 'XPath for:',
 			),
 			'rss' => 'RSS / Atom (default)',
-			'xml_xpath' => 'XML + XPath',	// TODO
+			'xml_xpath' => 'XML + XPath',
 		),
 		'maintenance' => array(
 			'clear_cache' => 'Clear cache',

+ 9 - 0
app/i18n/es/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Actualizar sistema',
 		'apply' => 'Aplicar',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Buscar actualizaciones',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Dispones de la versión %s de FreshRSS.',
 		'last' => 'Última comprobación: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'No hay actualizaciones disponibles',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Actualizar sistema',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrador',

+ 9 - 0
app/i18n/fr/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Système de mise à jour',
 		'apply' => 'Appliquer la mise à jour',
+		'changelog' => 'Journal des modifications',
 		'check' => 'Vérifier les mises à jour',
+		'copiedFromURL' => 'update.php copié depuis %s vers ./data',
 		'current_version' => 'Votre version actuelle de FreshRSS est la %s.',
 		'last' => 'Dernière vérification : %s',
+		'loading' => 'Mise à jour en cours…',
 		'none' => 'Aucune mise à jour à appliquer',
+		'releaseChannel' => array(
+			'_' => 'Canal de publication',
+			'edge' => 'Publication continue (“edge”)',
+			'latest' => 'Publication stable (“latest”)',
+		),
 		'title' => 'Système de mise à jour',
+		'viaGit' => 'Mise à jour via git et Github.com démarrée',
 	),
 	'user' => array(
 		'admin' => 'Administrateur',

+ 9 - 0
app/i18n/he/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'מערכת העדכון',
 		'apply' => 'החלת העדכון',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'בדיקת עדכונים חדשים',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Your current version of FreshRSS is the %s.',
 		'last' => 'תאריך בדיקה אחרון: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'אין עדכון להחלה',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'מערכת העדכון',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrator',	// TODO

+ 11 - 2
app/i18n/id/admin.php

@@ -190,13 +190,22 @@ return array(
 		),
 	),
 	'update' => array(
-		'_' => 'Update system',	// TODO
-		'apply' => 'Apply',	// TODO
+		'_' => 'Update system',	// DIRTY
+		'apply' => 'Apply',	// DIRTY
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Periksa pembaruan baru',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Versi FreshRSS saat ini adalah %s.',
 		'last' => 'Verifikasi terakhir: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Tidak ada pembaruan untuk diterapkan',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Perbarui Sistem',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrator',	// TODO

+ 3 - 3
app/i18n/id/feedback.php

@@ -120,11 +120,11 @@ return array(
 		'renamed' => 'Label “%s”has been renamed to “%s”.',	// DIRTY
 	),
 	'update' => array(
-		'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',	// TODO
+		'can_apply' => 'An update of FreshRSS is available: <strong>Version %s</strong>.',	// TODO
 		'error' => 'The update process has encountered an error: %s',	// TODO
-		'file_is_nok' => 'New <strong>version %s</strong> available, but check permissions on <em>%s</em> directory. HTTP server must have have write permission',	// TODO
+		'file_is_nok' => 'An update of FreshRSS is available (<strong>Version %s</strong>), but check permissions on <em>%s</em> directory. HTTP server must have have write permission',	// TODO
 		'finished' => 'Update complete!',	// TODO
-		'none' => 'No update to apply',	// TODO
+		'none' => 'No update available',	// TODO
 		'server_not_found' => 'Update server cannot be found. [%s]',	// TODO
 	),
 	'user' => array(

+ 9 - 0
app/i18n/it/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Aggiornamento sistema',
 		'apply' => 'Applica',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Controlla la presenza di nuovi aggiornamenti',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'FreshRSS versione %s.',
 		'last' => 'Ultima verifica: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Nessun aggiornamento da applicare',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Aggiorna sistema',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Amministratore',

+ 9 - 0
app/i18n/ja/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'システムアップデート',
 		'apply' => '適用',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'アップデートを確認する',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'FreshRSS の現在のバージョンは %s です。',
 		'last' => '最近の検証: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => '更新を適用できません',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'アップデートシステム',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => '管理者',

+ 9 - 0
app/i18n/ko/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => '업데이트',
 		'apply' => '업데이트 적용하기',
+		'changelog' => 'Changelog',	// TODO
 		'check' => '새 업데이트 확인하기',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => '현재 FreshRSS 버전은 %s 입니다.',
 		'last' => '마지막 확인: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => '적용 가능한 업데이트가 없습니다',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => '업데이트',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => '관리자',

+ 9 - 0
app/i18n/lv/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Atjaunināt sistēmu',
 		'apply' => 'Pieteikties',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Jaunu atjauninājumu pārbaude',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Jūsu pašreizējā FreshRSS versija ir %s.',
 		'last' => 'Pēdējā verifikācija: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Nav jāpiemēro atjauninājums',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Atjaunināt sistēmu',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrators',

+ 1 - 1
app/i18n/lv/conf.php

@@ -208,7 +208,7 @@ return array(
 			'scroll' => 'ritināšanas laikā',
 			'upon_gone' => 'kad tas vairs nav augšupējā ziņu barotnē',
 			'upon_reception' => 'pēc raksta saņemšanas',
-			'when' => 'Atzīmēt rakstu kā izlasītu...',
+			'when' => 'Atzīmēt rakstu kā izlasītu',
 			'when_same_title' => 'ja identisks virsraksts jau ir jaunākajos <i>n</i> rakstos',
 		),
 		'show' => array(

+ 9 - 0
app/i18n/nl/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Versie controle',
 		'apply' => 'Toepassen',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Controleer op nieuwe versies',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Uw huidige versie van FreshRSS is %s.',
 		'last' => 'Laatste controle: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Geen nieuwe versie om toe te passen',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Vernieuw systeem',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Beheerder',

+ 9 - 0
app/i18n/oc/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Sistèma de mesa a jorn',
 		'apply' => 'Aplicar',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Verificar las mesas a jorn',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Vòstra version actuala de FreshRSS es %s.',
 		'last' => 'Darrièra verificacion : %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Cap d’actualizacion d’aplicar',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Sistèma de mesa a jorn',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrator',	// IGNORE

+ 9 - 0
app/i18n/pl/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Aktualizacja',
 		'apply' => 'Zastosuj',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Szukaj uaktualnień',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Używana wersja FreshRSS to %s.',
 		'last' => 'Ostatnie sprawdzenie: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Brak nowych aktualizacji',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Aktualizacja',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrator',	// IGNORE

+ 9 - 0
app/i18n/pt-br/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Atualização do sistema',
 		'apply' => 'Aplicar',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Buscar por novas atualizações',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Sua versão do FreshRSS é %s.',
 		'last' => 'Última verificação: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Nenhuma atualização para se aplicar',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Sistema de atualização',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrador',

+ 9 - 0
app/i18n/ru/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Обновление системы',
 		'apply' => 'Применить',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Проверить обновления',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Ваша текущая версия FreshRSS: %s.',
 		'last' => 'Последняя проверка: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Нет обновлений',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Обновить систему',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Администратор',

+ 9 - 0
app/i18n/sk/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Aktualizácia systému',
 		'apply' => 'Použiť',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Skontrolovať aktualizácie',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Vaša aktuálna verzia FreshRSS: %s',
 		'last' => 'Posledná kontrola: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Žiadna nová aktualizácia',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Aktualizácia systému',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Administrátor',

+ 9 - 0
app/i18n/tr/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => 'Sistem güncelleme',
 		'apply' => 'Uygula',
+		'changelog' => 'Changelog',	// TODO
 		'check' => 'Güncelleme kontrolü',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => 'Mevcut FreshRSS sürümünüz %s.',
 		'last' => 'Son kontrol: %s',
+		'loading' => 'Updating…',	// TODO
 		'none' => 'Yeni güncelleme yok',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => 'Sistem güncelleme',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => 'Yönetici',

+ 9 - 0
app/i18n/zh-cn/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => '更新系统',
 		'apply' => '应用',
+		'changelog' => 'Changelog',	// TODO
 		'check' => '检查更新',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => '当前 FreshRSS 版本为 %s。',
 		'last' => '上次检查:%s',
+		'loading' => 'Updating…',	// TODO
 		'none' => '没有可用更新',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => '更新系统',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => '管理员',

+ 9 - 0
app/i18n/zh-tw/admin.php

@@ -192,11 +192,20 @@ return array(
 	'update' => array(
 		'_' => '更新系統',
 		'apply' => '應用',
+		'changelog' => 'Changelog',	// TODO
 		'check' => '檢查更新',
+		'copiedFromURL' => 'update.php copied from %s to ./data',	// TODO
 		'current_version' => '當前 FreshRSS 版本為 %s。',
 		'last' => '上次檢查:%s',
+		'loading' => 'Updating…',	// TODO
 		'none' => '沒有可用更新',
+		'releaseChannel' => array(
+			'_' => 'Release channel',	// TODO
+			'edge' => 'Rolling release (“edge”)',	// TODO
+			'latest' => 'Stable release (“latest”)',	// TODO
+		),
 		'title' => '系統更新',
+		'viaGit' => 'Update via git and Github.com started',	// TODO
 	),
 	'user' => array(
 		'admin' => '管理員',

+ 40 - 13
app/views/update/index.phtml

@@ -10,14 +10,6 @@
 
 	<h1><?= _t('admin.update') ?></h1>
 
-	<p>
-		<?= _i('help') ?> <?= _t('admin.update.current_version', FRESHRSS_VERSION) ?>
-	</p>
-
-	<p>
-		<?= _t('admin.update.last', $this->last_update_time) ?>
-	</p>
-
 	<?php if (!empty($this->message)) { ?>
 	<?php
 		$class = 'alert-warn';
@@ -34,20 +26,55 @@
 		}
 	?>
 	<p class="alert <?= $class ?>">
-		<span class="alert-head"><?= $this->message['title'] ?></span>
 		<?= $this->message['body'] ?>
 	</p>
 	<?php } ?>
 
+	<div class="form-group">
+		<label class="group-name"><?= _t('admin.update.current_version') ?></label>
+		<div class="group-controls">
+			<?= FRESHRSS_VERSION ?> (<a href="https://github.com/FreshRSS/FreshRSS/releases" target="_blank"><?= _t('admin.update.changelog') ?></a>)
+		</div>
+	</div>
+
+	<div class="form-group">
+		<label class="group-name"><?= _t('admin.update.releaseChannel') ?></label>
+		<div class="group-controls">
+			<?php if($this->is_release_channel_stable) { ?>
+				<a href="https://github.com/FreshRSS/FreshRSS/releases/latest" target="_blank">
+					<?= _t('admin.update.releaseChannel.latest') ?>
+				</a>
+			<?php } else { ?>
+				<a href="https://github.com/FreshRSS/FreshRSS/tree/edge" target="_blank">
+					<?= _t('admin.update.releaseChannel.edge') ?>
+				</a>
+			<?php } ?>
+		</div>
+	</div>
+
+	<div class="form-group">
+		<label class="group-name"><?= _t('admin.update.last') ?></label>
+		<div class="group-controls">
+		<?= $this->last_update_time ?>
+		</div>
+	</div>
+
 	<?php
 		if (empty($this->message) || $this->message['status'] !== 'good') {
 	?>
-	<p>
-		<a href="<?= _url('update', 'check') ?>" class="btn"><?= _t('admin.update.check') ?></a>
-	</p>
+	<div class="form-group form-actions">
+		<div class="group-controls">
+			<a href="<?= _url('update', 'check') ?>" class="btn btn-important"><?= _t('admin.update.check') ?></a>
+		</div>
+	</div>
 	<?php } ?>
 
 	<?php if ($this->update_to_apply) { ?>
-	<a class="btn btn-important" href="<?= _url('update', 'apply') ?>"><?= _t('admin.update.apply') ?></a>
+	<div class="form-group form-actions">
+		<div class="group-controls">
+			<a class="btn btn-important btn-state1" href="<?= _url('update', 'apply') ?>" data-state2-id="button-update-loading"><?= _t('admin.update.apply') ?></a>
+			<span class="btn btn-state2" id="button-update-loading"><?= _t('admin.update.loading') ?></span>
+		</div>
+	</div>
 	<?php } ?>
 </main>

+ 12 - 0
p/scripts/extra.js

@@ -261,6 +261,17 @@ function data_leave_validation(parent, excludeForm = null) {
 	return true;
 }
 
+function init_2stateButton() {
+	const btns = document.getElementsByClassName('btn-state1');
+	Array.prototype.forEach.call(btns, function (el) {
+		el.addEventListener('click', function () {
+			const btnState2 = document.getElementById(el.dataset.state2Id);
+			btnState2.classList.add('show');
+			this.classList.add('hide');
+		});
+	});
+}
+
 function init_configuration_alert() {
 	window.onsubmit = function (e) {
 		window.hasSubmit = data_leave_validation(document.body, e.submitter ? e.submitter.form : null);
@@ -288,6 +299,7 @@ function init_extra_afterDOM() {
 		init_password_observers(document.body);
 		init_select_observers();
 		init_configuration_alert();
+		init_2stateButton();
 
 		const slider = document.getElementById('slider');
 		if (slider) {

+ 18 - 0
p/themes/Origine/origine.css

@@ -283,6 +283,24 @@ form th {
 	text-decoration: none;
 }
 
+.btn-state1.hide {
+	display: none;
+}
+
+.btn-state2 {
+	display: none;
+}
+
+.btn-state2.show {
+	display: inline-block;
+}
+
+#button-update-loading {
+	background: var(--frss-loading-image) 0.5rem center no-repeat;
+	background-size: 1rem;
+	padding-left: 2rem;
+}
+
 a:hover .icon {
 	filter: brightness(1.5);
 	transition: 0.1s linear;

+ 18 - 0
p/themes/Origine/origine.rtl.css

@@ -283,6 +283,24 @@ form th {
 	text-decoration: none;
 }
 
+.btn-state1.hide {
+	display: none;
+}
+
+.btn-state2 {
+	display: none;
+}
+
+.btn-state2.show {
+	display: inline-block;
+}
+
+#button-update-loading {
+	background: var(--frss-loading-image) 0.5rem center no-repeat;
+	background-size: 1rem;
+	padding-right: 2rem;
+}
+
 a:hover .icon {
 	filter: brightness(1.5);
 	transition: 0.1s linear;