From 23e4183becc663ef8c63495500bebc24d88bbc24 Mon Sep 17 00:00:00 2001
From: Shizun Ge <shizunge@gmail.com>
Date: Tue, 29 Dec 2020 19:43:37 -0800
Subject: [PATCH] Show global options in the about page

Only shows the options when current user is admin.
---
 config/options.go              | 132 ++++++++++++++++++++-------------
 locale/translations.go         |  33 ++++++---
 locale/translations/de_DE.json |   1 +
 locale/translations/en_US.json |   1 +
 locale/translations/es_ES.json |   1 +
 locale/translations/fr_FR.json |   1 +
 locale/translations/it_IT.json |   1 +
 locale/translations/ja_JP.json |   1 +
 locale/translations/nl_NL.json |   1 +
 locale/translations/pl_PL.json |   1 +
 locale/translations/pt_BR.json |   1 +
 locale/translations/ru_RU.json |   1 +
 locale/translations/zh_CN.json |   1 +
 template/html/about.html       |  11 +++
 template/views.go              |  13 +++-
 ui/about.go                    |   2 +
 16 files changed, 139 insertions(+), 63 deletions(-)

diff --git a/config/options.go b/config/options.go
index 55316741..81b62bc2 100644
--- a/config/options.go
+++ b/config/options.go
@@ -6,6 +6,7 @@ package config // import "miniflux.app/config"
 
 import (
 	"fmt"
+	"sort"
 	"strings"
 
 	"miniflux.app/version"
@@ -66,6 +67,12 @@ const (
 
 var defaultHTTPClientUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)"
 
+// Option contains a key to value map of a single option. It may be used to output debug strings.
+type Option struct {
+	Key   string
+	Value interface{}
+}
+
 // Options contains configuration options.
 type Options struct {
 	HTTPS                              bool
@@ -441,58 +448,81 @@ func (o *Options) HTTPClientUserAgent() string {
 	return o.httpClientUserAgent
 }
 
+// SortedOptions returns options as a list of key value pairs, sorted by keys.
+func (o *Options) SortedOptions() []*Option {
+	var keyValues = map[string]interface{}{
+		"ADMIN_PASSWORD":                         o.adminPassword,
+		"ADMIN_USERNAME":                         o.adminUsername,
+		"AUTH_PROXY_HEADER":                      o.authProxyHeader,
+		"AUTH_PROXY_USER_CREATION":               o.authProxyUserCreation,
+		"BASE_PATH":                              o.basePath,
+		"BASE_URL":                               o.baseURL,
+		"BATCH_SIZE":                             o.batchSize,
+		"CERT_CACHE":                             o.certCache,
+		"CERT_DOMAIN":                            o.certDomain,
+		"CERT_FILE":                              o.certFile,
+		"CLEANUP_ARCHIVE_READ_DAYS":              o.cleanupArchiveReadDays,
+		"CLEANUP_ARCHIVE_UNREAD_DAYS":            o.cleanupArchiveUnreadDays,
+		"CLEANUP_FREQUENCY_HOURS":                o.cleanupFrequencyHours,
+		"CLEANUP_REMOVE_SESSIONS_DAYS":           o.cleanupRemoveSessionsDays,
+		"CREATE_ADMIN":                           o.createAdmin,
+		"DATABASE_MAX_CONNS":                     o.databaseMaxConns,
+		"DATABASE_MIN_CONNS":                     o.databaseMinConns,
+		"DATABASE_URL":                           o.databaseURL,
+		"DEBUG":                                  o.debug,
+		"HSTS":                                   o.hsts,
+		"HTTPS":                                  o.HTTPS,
+		"HTTP_CLIENT_MAX_BODY_SIZE":              o.httpClientMaxBodySize,
+		"HTTP_CLIENT_PROXY":                      o.httpClientProxy,
+		"HTTP_CLIENT_TIMEOUT":                    o.httpClientTimeout,
+		"HTTP_CLIENT_USER_AGENT":                 o.httpClientUserAgent,
+		"HTTP_SERVICE":                           o.httpService,
+		"KEY_FILE":                               o.certKeyFile,
+		"LISTEN_ADDR":                            o.listenAddr,
+		"LOG_DATE_TIME":                          o.logDateTime,
+		"MAINTENANCE_MESSAGE":                    o.maintenanceMessage,
+		"MAINTENANCE_MODE":                       o.maintenanceMode,
+		"METRICS_ALLOWED_NETWORKS":               o.metricsAllowedNetworks,
+		"METRICS_COLLECTOR":                      o.metricsCollector,
+		"METRICS_REFRESH_INTERVAL":               o.metricsRefreshInterval,
+		"OAUTH2_CLIENT_ID":                       o.oauth2ClientID,
+		"OAUTH2_CLIENT_SECRET":                   o.oauth2ClientSecret,
+		"OAUTH2_OIDC_DISCOVERY_ENDPOINT":         o.oauth2OidcDiscoveryEndpoint,
+		"OAUTH2_PROVIDER":                        o.oauth2Provider,
+		"OAUTH2_REDIRECT_URL":                    o.oauth2RedirectURL,
+		"OAUTH2_USER_CREATION":                   o.oauth2UserCreationAllowed,
+		"POCKET_CONSUMER_KEY":                    o.pocketConsumerKey,
+		"POLLING_FREQUENCY":                      o.pollingFrequency,
+		"POLLING_SCHEDULER":                      o.pollingScheduler,
+		"PROXY_IMAGES":                           o.proxyImages,
+		"ROOT_URL":                               o.rootURL,
+		"RUN_MIGRATIONS":                         o.runMigrations,
+		"SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL": o.schedulerEntryFrequencyMaxInterval,
+		"SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": o.schedulerEntryFrequencyMinInterval,
+		"SCHEDULER_SERVICE":                      o.schedulerService,
+		"SERVER_TIMING_HEADER":                   o.serverTimingHeader,
+		"WORKER_POOL_SIZE":                       o.workerPoolSize,
+	}
+
+	keys := make([]string, 0, len(keyValues))
+	for key := range keyValues {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+
+	var sortedOptions []*Option
+	for _, key := range keys {
+		sortedOptions = append(sortedOptions, &Option{Key: key, Value: keyValues[key]})
+	}
+	return sortedOptions
+}
+
 func (o *Options) String() string {
 	var builder strings.Builder
-	builder.WriteString(fmt.Sprintf("LOG_DATE_TIME: %v\n", o.logDateTime))
-	builder.WriteString(fmt.Sprintf("DEBUG: %v\n", o.debug))
-	builder.WriteString(fmt.Sprintf("SERVER_TIMING_HEADER: %v\n", o.serverTimingHeader))
-	builder.WriteString(fmt.Sprintf("HTTP_SERVICE: %v\n", o.httpService))
-	builder.WriteString(fmt.Sprintf("SCHEDULER_SERVICE: %v\n", o.schedulerService))
-	builder.WriteString(fmt.Sprintf("HTTPS: %v\n", o.HTTPS))
-	builder.WriteString(fmt.Sprintf("HSTS: %v\n", o.hsts))
-	builder.WriteString(fmt.Sprintf("BASE_URL: %v\n", o.baseURL))
-	builder.WriteString(fmt.Sprintf("ROOT_URL: %v\n", o.rootURL))
-	builder.WriteString(fmt.Sprintf("BASE_PATH: %v\n", o.basePath))
-	builder.WriteString(fmt.Sprintf("LISTEN_ADDR: %v\n", o.listenAddr))
-	builder.WriteString(fmt.Sprintf("DATABASE_URL: %v\n", o.databaseURL))
-	builder.WriteString(fmt.Sprintf("DATABASE_MAX_CONNS: %v\n", o.databaseMaxConns))
-	builder.WriteString(fmt.Sprintf("DATABASE_MIN_CONNS: %v\n", o.databaseMinConns))
-	builder.WriteString(fmt.Sprintf("RUN_MIGRATIONS: %v\n", o.runMigrations))
-	builder.WriteString(fmt.Sprintf("CERT_FILE: %v\n", o.certFile))
-	builder.WriteString(fmt.Sprintf("KEY_FILE: %v\n", o.certKeyFile))
-	builder.WriteString(fmt.Sprintf("CERT_DOMAIN: %v\n", o.certDomain))
-	builder.WriteString(fmt.Sprintf("CERT_CACHE: %v\n", o.certCache))
-	builder.WriteString(fmt.Sprintf("CLEANUP_FREQUENCY_HOURS: %v\n", o.cleanupFrequencyHours))
-	builder.WriteString(fmt.Sprintf("CLEANUP_ARCHIVE_READ_DAYS: %v\n", o.cleanupArchiveReadDays))
-	builder.WriteString(fmt.Sprintf("CLEANUP_ARCHIVE_UNREAD_DAYS: %v\n", o.cleanupArchiveUnreadDays))
-	builder.WriteString(fmt.Sprintf("CLEANUP_REMOVE_SESSIONS_DAYS: %v\n", o.cleanupRemoveSessionsDays))
-	builder.WriteString(fmt.Sprintf("WORKER_POOL_SIZE: %v\n", o.workerPoolSize))
-	builder.WriteString(fmt.Sprintf("POLLING_FREQUENCY: %v\n", o.pollingFrequency))
-	builder.WriteString(fmt.Sprintf("BATCH_SIZE: %v\n", o.batchSize))
-	builder.WriteString(fmt.Sprintf("POLLING_SCHEDULER: %v\n", o.pollingScheduler))
-	builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL: %v\n", o.schedulerEntryFrequencyMaxInterval))
-	builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL: %v\n", o.schedulerEntryFrequencyMinInterval))
-	builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages))
-	builder.WriteString(fmt.Sprintf("CREATE_ADMIN: %v\n", o.createAdmin))
-	builder.WriteString(fmt.Sprintf("ADMIN_USERNAME: %v\n", o.adminUsername))
-	builder.WriteString(fmt.Sprintf("ADMIN_PASSWORD: %v\n", o.adminPassword))
-	builder.WriteString(fmt.Sprintf("POCKET_CONSUMER_KEY: %v\n", o.pocketConsumerKey))
-	builder.WriteString(fmt.Sprintf("OAUTH2_USER_CREATION: %v\n", o.oauth2UserCreationAllowed))
-	builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID))
-	builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_SECRET: %v\n", o.oauth2ClientSecret))
-	builder.WriteString(fmt.Sprintf("OAUTH2_REDIRECT_URL: %v\n", o.oauth2RedirectURL))
-	builder.WriteString(fmt.Sprintf("OAUTH2_OIDC_DISCOVERY_ENDPOINT: %v\n", o.oauth2OidcDiscoveryEndpoint))
-	builder.WriteString(fmt.Sprintf("OAUTH2_PROVIDER: %v\n", o.oauth2Provider))
-	builder.WriteString(fmt.Sprintf("HTTP_CLIENT_TIMEOUT: %v\n", o.httpClientTimeout))
-	builder.WriteString(fmt.Sprintf("HTTP_CLIENT_MAX_BODY_SIZE: %v\n", o.httpClientMaxBodySize))
-	builder.WriteString(fmt.Sprintf("HTTP_CLIENT_PROXY: %v\n", o.httpClientProxy))
-	builder.WriteString(fmt.Sprintf("HTTP_CLIENT_USER_AGENT: %v\n", o.httpClientUserAgent))
-	builder.WriteString(fmt.Sprintf("AUTH_PROXY_HEADER: %v\n", o.authProxyHeader))
-	builder.WriteString(fmt.Sprintf("AUTH_PROXY_USER_CREATION: %v\n", o.authProxyUserCreation))
-	builder.WriteString(fmt.Sprintf("MAINTENANCE_MODE: %v\n", o.maintenanceMode))
-	builder.WriteString(fmt.Sprintf("MAINTENANCE_MESSAGE: %v\n", o.maintenanceMessage))
-	builder.WriteString(fmt.Sprintf("METRICS_COLLECTOR: %v\n", o.metricsCollector))
-	builder.WriteString(fmt.Sprintf("METRICS_REFRESH_INTERVAL: %v\n", o.metricsRefreshInterval))
-	builder.WriteString(fmt.Sprintf("METRICS_ALLOWED_NETWORKS: %v\n", o.metricsAllowedNetworks))
+
+	for _, option := range o.SortedOptions() {
+		builder.WriteString(fmt.Sprintf("%s: %v\n", option.Key, option.Value))
+	}
+
 	return builder.String()
 }
diff --git a/locale/translations.go b/locale/translations.go
index 94316a23..815e0bbe 100644
--- a/locale/translations.go
+++ b/locale/translations.go
@@ -118,6 +118,7 @@ var translations = map[string]string{
     "page.about.build_date": "Datum der Kompilierung:",
     "page.about.author": "Autor:",
     "page.about.license": "Lizenz:",
+    "page.about.global_config_options": "Globale Konfigurationsoptionen",
     "page.add_feed.title": "Neues Abonnement",
     "page.add_feed.no_category": "Es ist keine Kategorie vorhanden. Wenigstens eine Kategorie muss angelegt sein.",
     "page.add_feed.label.url": "URL",
@@ -479,6 +480,7 @@ var translations = map[string]string{
     "page.about.build_date": "Build Date:",
     "page.about.author": "Author:",
     "page.about.license": "License:",
+    "page.about.global_config_options": "Global configuration options",
     "page.add_feed.title": "New Subscription",
     "page.add_feed.no_category": "There is no category. You must have at least one category.",
     "page.add_feed.label.url": "URL",
@@ -820,6 +822,7 @@ var translations = map[string]string{
     "page.about.build_date": "Fecha de construcción:",
     "page.about.author": "Autor:",
     "page.about.license": "Licencia:",
+    "page.about.global_config_options": "Opciones de configuración global",
     "page.add_feed.title": "Nueva suscripción",
     "page.add_feed.no_category": "No hay categoría. Debe tener al menos una categoría.",
     "page.add_feed.label.url": "URL",
@@ -1161,6 +1164,7 @@ var translations = map[string]string{
     "page.about.build_date": "Date de la compilation :",
     "page.about.author": "Auteur :",
     "page.about.license": "Licence :",
+    "page.about.global_config_options": "Options de configuration globales",
     "page.add_feed.title": "Nouvel Abonnement",
     "page.add_feed.no_category": "Il n'y a aucune catégorie. Vous devez avoir au moins une catégorie.",
     "page.add_feed.label.url": "Lien",
@@ -1522,6 +1526,7 @@ var translations = map[string]string{
     "page.about.build_date": "Data della build:",
     "page.about.author": "Autore:",
     "page.about.license": "Licenza:",
+    "page.about.global_config_options": "Opzioni di configurazione globali",
     "page.add_feed.title": "Nuovo feed",
     "page.add_feed.no_category": "Nessuna categoria selezionata. Devi scegliere almeno una categoria.",
     "page.add_feed.label.url": "URL",
@@ -1863,6 +1868,7 @@ var translations = map[string]string{
     "page.about.build_date": "ビルド日時:",
     "page.about.author": "作者:",
     "page.about.license": "ライセンス:",
+    "page.about.global_config_options": "グローバル構成オプション",
     "page.add_feed.title": "新規購読",
     "page.add_feed.no_category": "カテゴリが存在しません。 少なくとも1つのカテゴリが必要です。",
     "page.add_feed.label.url": "URL",
@@ -2205,6 +2211,7 @@ var translations = map[string]string{
     "page.about.build_date": "Datum build:",
     "page.about.author": "Auteur:",
     "page.about.license": "Licentie:",
+    "page.about.global_config_options": "globale configuratie-opties",
     "page.add_feed.title": "Nieuwe feed",
     "page.add_feed.no_category": "Er zijn geen categorieën. Je moet op zijn minst één caterogie hebben.",
     "page.add_feed.label.url": "URL",
@@ -2565,6 +2572,7 @@ var translations = map[string]string{
     "page.about.build_date": "Data opracowania:",
     "page.about.author": "Autor:",
     "page.about.license": "Licencja:",
+    "page.about.global_config_options": "globalne opcje konfiguracji",
     "page.add_feed.title": "Nowa subskrypcja",
     "page.add_feed.no_category": "Nie ma żadnej kategorii. Musisz mieć co najmniej jedną kategorię.",
     "page.add_feed.label.url": "URL",
@@ -2930,6 +2938,7 @@ var translations = map[string]string{
     "page.about.build_date": "Compilado em:",
     "page.about.author": "Autor:",
     "page.about.license": "Licença:",
+    "page.about.global_config_options": "opções de configuração global",
     "page.add_feed.title": "Nova inscrição",
     "page.add_feed.no_category": "Não existe uma categoria. Deve existir pelo menos uma categoria.",
     "page.add_feed.label.url": "URL",
@@ -3273,6 +3282,7 @@ var translations = map[string]string{
     "page.about.build_date": "Дата сборки:",
     "page.about.author": "Автор:",
     "page.about.license": "Лицензия:",
+    "page.about.global_config_options": "глобальные параметры конфигурации",
     "page.add_feed.title": "Новая подписка",
     "page.add_feed.no_category": "Категории отсутствуют. У вас должна быть хотя бы одна категория.",
     "page.add_feed.label.url": "URL",
@@ -3618,6 +3628,7 @@ var translations = map[string]string{
     "page.about.build_date": "构建日期:",
     "page.about.author": "作者:",
     "page.about.license": "协议:",
+    "page.about.global_config_options": "全局配置选项",
     "page.add_feed.title": "新增订阅",
     "page.add_feed.no_category": "没有类别,您必须至少有一个类别",
     "page.add_feed.label.url": "网址",
@@ -3860,15 +3871,15 @@ var translations = map[string]string{
 }
 
 var translationsChecksums = map[string]string{
-	"de_DE": "9db01e4337375c37edae008d93ccf8f1ab50f6b30a468cefe75257e5b1364513",
-	"en_US": "506dc83a66e38147328f924b15f1280a71fb3cc5f83c1f690c6029d137f8baee",
-	"es_ES": "37c7d271dcae76f524a8e52b86bfaa9dc680954ba75ed53e7945b95ffe2ae9e9",
-	"fr_FR": "ed626b9752239c0f89a17ff28c5003a5ab930a3f0d1df5628b23e8de3587c0f5",
-	"it_IT": "015892b01bc85407a0813eb60deb1a1bbbfaf2b72bb9194e13378083f6f54b84",
-	"ja_JP": "ea1843af4638ce58cfe4ca730133e7ef178c6242b6bd253c714b179b45efde9f",
-	"nl_NL": "fe3d9e519d3326d0ff51590011ac6cb344e26e0aa241a8295fb38ca7a7c2191c",
-	"pl_PL": "5af4497ab4420ff8cec45b86dc65ddc685cd9cca0fb750e238d57f1a8d43c32f",
-	"pt_BR": "052cfe35211165ed7de9e99109a819d362b5a69b490bb58cc6d884e8fbbf4469",
-	"ru_RU": "c7216ede62f1c1d18b2ad05bb20a2dfdab04e7bb968773a705da8c26cd5bdcd8",
-	"zh_CN": "8e02550d068e3d8020bd7923a00e5a045dd09db1cc0dfdaa2294417175068743",
+	"de_DE": "96616242d64bfca6bcc27762260aac38a3014ae854dfe4d85711491f7e0e52a2",
+	"en_US": "0718463b2edc0157dba7b26c033bcfa36181e57addd613a19ce60e6c6b34cb19",
+	"es_ES": "fc767762d1eb5cd44d89aab99a58a0c28b910ba06457d7ffe1b21092d473bb8e",
+	"fr_FR": "3abdb817b699ffb2c9f3c2316879c65b96510ff3e6a875860fcccdd063b29e63",
+	"it_IT": "2d2aa46e81f48494c08b78963d88d6522395856bd25f4b1504403da1f8d531e8",
+	"ja_JP": "7c1098e38962f4d285b957f7db428b28d562d1fe3b16117c0ac0340870cd43b0",
+	"nl_NL": "43093afdfa7d692d83455f5e4f3d36d4e725c704796961b749a84ba27ddbbd0b",
+	"pl_PL": "eb0baf99b46f5440a32084453091272f57c5f676b595640016f168fb65e439b7",
+	"pt_BR": "9ed1a472f60e65c4f8309417a3af17572299cd23c25ae6e1d368996578963b25",
+	"ru_RU": "de2cda1638754b7c3376749fa7d62d75d3594543465c1f78f801fe6c08d4eb2f",
+	"zh_CN": "07f1fb9efeb93f2d45b71c7028b50fe5bca378c307d1e2dadd0821fabd074918",
 }
diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json
index b95f264a..3badbc5c 100644
--- a/locale/translations/de_DE.json
+++ b/locale/translations/de_DE.json
@@ -113,6 +113,7 @@
     "page.about.build_date": "Datum der Kompilierung:",
     "page.about.author": "Autor:",
     "page.about.license": "Lizenz:",
+    "page.about.global_config_options": "Globale Konfigurationsoptionen",
     "page.add_feed.title": "Neues Abonnement",
     "page.add_feed.no_category": "Es ist keine Kategorie vorhanden. Wenigstens eine Kategorie muss angelegt sein.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json
index 8df2b40c..4fddc568 100644
--- a/locale/translations/en_US.json
+++ b/locale/translations/en_US.json
@@ -113,6 +113,7 @@
     "page.about.build_date": "Build Date:",
     "page.about.author": "Author:",
     "page.about.license": "License:",
+    "page.about.global_config_options": "Global configuration options",
     "page.add_feed.title": "New Subscription",
     "page.add_feed.no_category": "There is no category. You must have at least one category.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json
index 11eb6f15..cd2cbc0d 100644
--- a/locale/translations/es_ES.json
+++ b/locale/translations/es_ES.json
@@ -113,6 +113,7 @@
     "page.about.build_date": "Fecha de construcción:",
     "page.about.author": "Autor:",
     "page.about.license": "Licencia:",
+    "page.about.global_config_options": "Opciones de configuración global",
     "page.add_feed.title": "Nueva suscripción",
     "page.add_feed.no_category": "No hay categoría. Debe tener al menos una categoría.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json
index b240b6a9..5c03805f 100644
--- a/locale/translations/fr_FR.json
+++ b/locale/translations/fr_FR.json
@@ -113,6 +113,7 @@
     "page.about.build_date": "Date de la compilation :",
     "page.about.author": "Auteur :",
     "page.about.license": "Licence :",
+    "page.about.global_config_options": "Options de configuration globales",
     "page.add_feed.title": "Nouvel Abonnement",
     "page.add_feed.no_category": "Il n'y a aucune catégorie. Vous devez avoir au moins une catégorie.",
     "page.add_feed.label.url": "Lien",
diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json
index 113d78c0..91df1ae1 100644
--- a/locale/translations/it_IT.json
+++ b/locale/translations/it_IT.json
@@ -113,6 +113,7 @@
     "page.about.build_date": "Data della build:",
     "page.about.author": "Autore:",
     "page.about.license": "Licenza:",
+    "page.about.global_config_options": "Opzioni di configurazione globali",
     "page.add_feed.title": "Nuovo feed",
     "page.add_feed.no_category": "Nessuna categoria selezionata. Devi scegliere almeno una categoria.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json
index feebc55f..88512725 100644
--- a/locale/translations/ja_JP.json
+++ b/locale/translations/ja_JP.json
@@ -113,6 +113,7 @@
     "page.about.build_date": "ビルド日時:",
     "page.about.author": "作者:",
     "page.about.license": "ライセンス:",
+    "page.about.global_config_options": "グローバル構成オプション",
     "page.add_feed.title": "新規購読",
     "page.add_feed.no_category": "カテゴリが存在しません。 少なくとも1つのカテゴリが必要です。",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json
index 0b8bf818..72cef0a0 100644
--- a/locale/translations/nl_NL.json
+++ b/locale/translations/nl_NL.json
@@ -114,6 +114,7 @@
     "page.about.build_date": "Datum build:",
     "page.about.author": "Auteur:",
     "page.about.license": "Licentie:",
+    "page.about.global_config_options": "globale configuratie-opties",
     "page.add_feed.title": "Nieuwe feed",
     "page.add_feed.no_category": "Er zijn geen categorieën. Je moet op zijn minst één caterogie hebben.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json
index 319a0804..3c90a1e3 100644
--- a/locale/translations/pl_PL.json
+++ b/locale/translations/pl_PL.json
@@ -115,6 +115,7 @@
     "page.about.build_date": "Data opracowania:",
     "page.about.author": "Autor:",
     "page.about.license": "Licencja:",
+    "page.about.global_config_options": "globalne opcje konfiguracji",
     "page.add_feed.title": "Nowa subskrypcja",
     "page.add_feed.no_category": "Nie ma żadnej kategorii. Musisz mieć co najmniej jedną kategorię.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/pt_BR.json b/locale/translations/pt_BR.json
index 266a444d..9c44a309 100644
--- a/locale/translations/pt_BR.json
+++ b/locale/translations/pt_BR.json
@@ -113,6 +113,7 @@
     "page.about.build_date": "Compilado em:",
     "page.about.author": "Autor:",
     "page.about.license": "Licença:",
+    "page.about.global_config_options": "opções de configuração global",
     "page.add_feed.title": "Nova inscrição",
     "page.add_feed.no_category": "Não existe uma categoria. Deve existir pelo menos uma categoria.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json
index 715f36bc..a75e995d 100644
--- a/locale/translations/ru_RU.json
+++ b/locale/translations/ru_RU.json
@@ -115,6 +115,7 @@
     "page.about.build_date": "Дата сборки:",
     "page.about.author": "Автор:",
     "page.about.license": "Лицензия:",
+    "page.about.global_config_options": "глобальные параметры конфигурации",
     "page.add_feed.title": "Новая подписка",
     "page.add_feed.no_category": "Категории отсутствуют. У вас должна быть хотя бы одна категория.",
     "page.add_feed.label.url": "URL",
diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json
index 9de8c1b3..39cacd41 100644
--- a/locale/translations/zh_CN.json
+++ b/locale/translations/zh_CN.json
@@ -111,6 +111,7 @@
     "page.about.build_date": "构建日期:",
     "page.about.author": "作者:",
     "page.about.license": "协议:",
+    "page.about.global_config_options": "全局配置选项",
     "page.add_feed.title": "新增订阅",
     "page.add_feed.no_category": "没有类别,您必须至少有一个类别",
     "page.add_feed.label.url": "网址",
diff --git a/template/html/about.html b/template/html/about.html
index ac96e729..a48d4c10 100644
--- a/template/html/about.html
+++ b/template/html/about.html
@@ -23,4 +23,15 @@
     </ul>
 </div>
 
+{{ if .user.IsAdmin }}
+<div class="panel">
+    <h3>{{ t "page.about.global_config_options" }}</h3>
+    <ul>
+    {{ range .globalConfigOptions }}
+    <li><code><strong>{{ .Key }}</strong>={{ .Value }}</code></li>
+    {{ end }}
+    </ul>
+</div>
+{{ end }}
+
 {{ end }}
diff --git a/template/views.go b/template/views.go
index 3c32249d..3a6dfb69 100644
--- a/template/views.go
+++ b/template/views.go
@@ -28,6 +28,17 @@ var templateViewsMap = map[string]string{
     </ul>
 </div>
 
+{{ if .user.IsAdmin }}
+<div class="panel">
+    <h3>{{ t "page.about.global_config_options" }}</h3>
+    <ul>
+    {{ range .globalConfigOptions }}
+    <li><code><strong>{{ .Key }}</strong>={{ .Value }}</code></li>
+    {{ end }}
+    </ul>
+</div>
+{{ end }}
+
 {{ end }}
 `,
 	"add_subscription": `{{ define "title"}}{{ t "page.add_feed.title" }}{{ end }}
@@ -1601,7 +1612,7 @@ var templateViewsMap = map[string]string{
 }
 
 var templateViewsMapChecksums = map[string]string{
-	"about":               "504b3635a7f898c12a1120c2270a2911aa8378c0fa272ea0980feca1fa3161f2",
+	"about":               "ed362f506b931186b2273655e3264110225154e7756e29d49ba4ede442caffc9",
 	"add_subscription":    "bc0f878b37692a00d51e834536f211843a59703991d2a743ef204b9d6ae38549",
 	"api_keys":            "27d401b31a72881d5232486ba17eb47edaf5246eaedce81de88698c15ebb2284",
 	"bookmark_entries":    "eacbbdce7fa85ec66c4c12f02879daab562a17ff79f1aac1805617e83e3a3a42",
diff --git a/ui/about.go b/ui/about.go
index 69e3f285..fa58fe5c 100644
--- a/ui/about.go
+++ b/ui/about.go
@@ -7,6 +7,7 @@ package ui // import "miniflux.app/ui"
 import (
 	"net/http"
 
+	"miniflux.app/config"
 	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
 	"miniflux.app/ui/session"
@@ -30,6 +31,7 @@ func (h *handler) showAboutPage(w http.ResponseWriter, r *http.Request) {
 	view.Set("user", user)
 	view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
 	view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
+	view.Set("globalConfigOptions", config.Opts.SortedOptions())
 
 	html.OK(w, r, view.Render("about"))
 }