Browse Source

针对nginx+keeplived的vip映射管理方案 (#154)

* fixed environment indicator display overlay & edit not obtain newest data

* finished environment operation sync

* fix: environment indicator display overflow not ellipsis

---------

Co-authored-by: 0xJacky <me@jackyu.cn>
superlollipop 1 year ago
parent
commit
6ede443bbe

+ 3 - 2
frontend/src/api/domain.ts

@@ -1,9 +1,10 @@
 import Curd from '@/api/curd'
 import http from '@/lib/http'
+import {AxiosRequestConfig} from "axios/index";
 
 class Domain extends Curd {
-    enable(name: string) {
-        return http.post(this.baseUrl + '/' + name + '/enable')
+    enable(name: string, config: AxiosRequestConfig) {
+        return http.post(this.baseUrl + '/' + name + '/enable', undefined, config)
     }
 
     disable(name: string) {

+ 3 - 1
frontend/src/components/EnvIndicator/EnvIndicator.vue

@@ -75,9 +75,11 @@ watch(node_id, async () => {
         justify-content: space-between;
 
         .env-name {
-            max-width: 50px;
+            max-width: 85px;
             text-overflow: ellipsis;
             white-space: nowrap;
+            line-height: 1em;
+            overflow: hidden;
         }
 
         .ant-tag {

+ 9 - 1
frontend/src/components/StdDataEntry/StdDataEntry.tsx

@@ -10,7 +10,15 @@ export default defineComponent({
         return () => {
             const template: any = []
             props.dataList.forEach((v: any) => {
-                if (v.edit.type) {
+                let show = true
+                if (v.edit.show) {
+                    if (typeof v.edit.show === "boolean") {
+                        show = v.edit.show
+                    } else if (typeof v.edit.show === "function") {
+                        show = v.edit.show(props.dataSource)
+                    }
+                }
+                if (v.edit.type && show) {
                     template.push(
                         <StdFormItem dataIndex={v.dataIndex} label={v.title()} extra={v.extra} error={props.error}>
                             {v.edit.type(v.edit, props.dataSource, v.dataIndex)}

+ 12 - 2
frontend/src/components/StdDataEntry/index.tsx

@@ -1,6 +1,6 @@
 import StdDataEntry from './StdDataEntry.js'
 import {h} from 'vue'
-import {Input, InputNumber, Textarea} from 'ant-design-vue'
+import {Input, InputNumber, Textarea, Switch} from 'ant-design-vue'
 import StdSelector from './components/StdSelector.vue'
 import StdSelect from './components/StdSelect.vue'
 import StdPassword from './components/StdPassword.vue'
@@ -110,6 +110,15 @@ function selector(edit: IEdit, dataSource: any, dataIndex: any) {
     />
 }
 
+function antSwitch(edit: IEdit, dataSource: any, dataIndex: any) {
+    return h(Switch, {
+        checked: dataSource?.[dataIndex],
+        'onUpdate:checked': value => {
+            dataSource[dataIndex] = value
+        }
+    })
+}
+
 export {
     readonly,
     input,
@@ -117,7 +126,8 @@ export {
     select,
     selector,
     password,
-    inputNumber
+    inputNumber,
+    antSwitch
 }
 
 export default StdDataEntry

+ 32 - 14
frontend/src/language/en/app.po

@@ -19,7 +19,7 @@ msgstr ""
 
 #: src/views/cert/Cert.vue:74 src/views/cert/DNSCredential.vue:31
 #: src/views/config/config.ts:35 src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:61 src/views/user/User.vue:43
+#: src/views/environment/Environment.vue:92 src/views/user/User.vue:43
 msgid "Action"
 msgstr "Action"
 
@@ -604,6 +604,7 @@ msgid "Enabled"
 msgstr "Enabled"
 
 #: src/views/domain/components/RightSettings.vue:26
+#: src/views/domain/components/SiteDuplicate.vue:85
 #: src/views/domain/DomainAdd.vue:49 src/views/domain/DomainList.vue:59
 msgid "Enabled successfully"
 msgstr "Enabled successfully"
@@ -926,7 +927,11 @@ msgstr ""
 msgid "Next"
 msgstr "Next"
 
-#: src/views/preference/NginxLogSettings.vue:3
+#: src/views/preference/Preference.vue:8
+msgid "Nginx"
+msgstr ""
+
+#: src/views/preference/NginxSettings.vue:3
 msgid "Nginx Access Log Path"
 msgstr ""
 
@@ -941,12 +946,11 @@ msgstr "Configuration Name"
 msgid "Nginx Control"
 msgstr ""
 
-#: src/views/preference/NginxLogSettings.vue:6
+#: src/views/preference/NginxSettings.vue:6
 msgid "Nginx Error Log Path"
 msgstr ""
 
 #: src/routes/index.ts:110 src/views/nginx_log/NginxLog.vue:2
-#: src/views/preference/Preference.vue:8
 msgid "Nginx Log"
 msgstr ""
 
@@ -998,7 +1002,7 @@ msgstr ""
 #: src/components/NodeSelector/NodeSelector.vue:11
 #: src/views/dashboard/Environments.vue:15
 #: src/views/dashboard/Environments.vue:16
-#: src/views/environment/Environment.vue:48
+#: src/views/environment/Environment.vue:79
 msgid "Offline"
 msgstr ""
 
@@ -1022,7 +1026,7 @@ msgstr ""
 #: src/components/NodeSelector/NodeSelector.vue:9
 #: src/views/dashboard/Environments.vue:14
 #: src/views/dashboard/Environments.vue:15
-#: src/views/environment/Environment.vue:45
+#: src/views/environment/Environment.vue:76
 msgid "Online"
 msgstr ""
 
@@ -1030,6 +1034,10 @@ msgstr ""
 msgid "OpenAI"
 msgstr ""
 
+#: src/views/environment/Environment.vue:38
+msgid "OperationSync"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:14 src/views/system/Upgrade.vue:15
 #: src/views/system/Upgrade.vue:19 src/views/system/Upgrade.vue:23
 #: src/views/system/Upgrade.vue:27
@@ -1249,7 +1257,7 @@ msgid "Save error %{msg}"
 msgstr "Save error %{msg}"
 
 #: src/components/StdDataDisplay/StdBatchEdit.vue:39
-#: src/views/preference/Preference.vue:60
+#: src/views/preference/Preference.vue:66
 #, fuzzy
 msgid "Save successfully"
 msgstr "Saved successfully"
@@ -1281,7 +1289,7 @@ msgstr "Send"
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
 #: src/views/config/ConfigEdit.vue:41 src/views/domain/DomainList.vue:83
-#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:62
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:68
 #: src/views/system/Upgrade.vue:54
 msgid "Server error"
 msgstr "Server error"
@@ -1348,7 +1356,7 @@ msgstr "Certificate Status"
 msgid "Stable"
 msgstr "Enabled"
 
-#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:38
+#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:69
 msgid "Status"
 msgstr "Status"
 
@@ -1364,10 +1372,20 @@ msgstr "Storage"
 msgid "Subject Name: %{name}"
 msgstr "Subject Name: %{name}"
 
+#: src/views/environment/Environment.vue:67
+msgid ""
+"Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/"
+"nginx/test|/api/config/.+`, please see system api"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:40
 msgid "Swap"
 msgstr "Swap"
 
+#: src/views/environment/Environment.vue:55
+msgid "SyncApiRegex"
+msgstr ""
+
 #: src/routes/index.ts:157
 msgid "System"
 msgstr ""
@@ -1435,10 +1453,6 @@ msgid ""
 "continue?"
 msgstr ""
 
-#: src/views/environment/Environment.vue:30
-msgid "Token"
-msgstr ""
-
 #: src/views/config/config.ts:13
 msgid "Type"
 msgstr ""
@@ -1446,7 +1460,7 @@ msgstr ""
 #: src/views/cert/Cert.vue:68 src/views/cert/DNSCredential.vue:25
 #: src/views/config/config.ts:28 src/views/config/ConfigEdit.vue:31
 #: src/views/domain/components/RightSettings.vue:11
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:55
+#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:86
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "Updated at"
@@ -1520,6 +1534,10 @@ msgid ""
 "Nginx. Are you sure you want to continue?"
 msgstr ""
 
+#: src/views/environment/Environment.vue:45
+msgid "Whether config api regex that will redo on this environment"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:104
 #: src/views/dashboard/ServerAnalytic.vue:29
 msgid "Writes"

+ 36 - 14
frontend/src/language/es/app.po

@@ -22,7 +22,7 @@ msgstr "Registros de acceso"
 
 #: src/views/cert/Cert.vue:74 src/views/cert/DNSCredential.vue:31
 #: src/views/config/config.ts:35 src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:61 src/views/user/User.vue:43
+#: src/views/environment/Environment.vue:92 src/views/user/User.vue:43
 msgid "Action"
 msgstr "Acción"
 
@@ -588,6 +588,7 @@ msgid "Enabled"
 msgstr "Habilitado"
 
 #: src/views/domain/components/RightSettings.vue:26
+#: src/views/domain/components/SiteDuplicate.vue:85
 #: src/views/domain/DomainAdd.vue:49 src/views/domain/DomainList.vue:59
 msgid "Enabled successfully"
 msgstr "Habilitado con éxito"
@@ -901,7 +902,12 @@ msgstr "Se liberó una nueva versión"
 msgid "Next"
 msgstr "Siguiente"
 
-#: src/views/preference/NginxLogSettings.vue:3
+#: src/views/preference/Preference.vue:8
+#, fuzzy
+msgid "Nginx"
+msgstr "Registro Nginx"
+
+#: src/views/preference/NginxSettings.vue:3
 msgid "Nginx Access Log Path"
 msgstr "Ruta de registro de acceso de Nginx"
 
@@ -915,12 +921,11 @@ msgstr "Error de análisis de configuración de Nginx"
 msgid "Nginx Control"
 msgstr "Control de Nginx"
 
-#: src/views/preference/NginxLogSettings.vue:6
+#: src/views/preference/NginxSettings.vue:6
 msgid "Nginx Error Log Path"
 msgstr "Ruta de registro de errores de Nginx"
 
 #: src/routes/index.ts:110 src/views/nginx_log/NginxLog.vue:2
-#: src/views/preference/Preference.vue:8
 msgid "Nginx Log"
 msgstr "Registro Nginx"
 
@@ -969,7 +974,7 @@ msgstr "Obteniendo certificado"
 #: src/components/NodeSelector/NodeSelector.vue:11
 #: src/views/dashboard/Environments.vue:15
 #: src/views/dashboard/Environments.vue:16
-#: src/views/environment/Environment.vue:48
+#: src/views/environment/Environment.vue:79
 msgid "Offline"
 msgstr "Desconectado"
 
@@ -993,7 +998,7 @@ msgstr "OK"
 #: src/components/NodeSelector/NodeSelector.vue:9
 #: src/views/dashboard/Environments.vue:14
 #: src/views/dashboard/Environments.vue:15
-#: src/views/environment/Environment.vue:45
+#: src/views/environment/Environment.vue:76
 msgid "Online"
 msgstr "Conectado"
 
@@ -1001,6 +1006,10 @@ msgstr "Conectado"
 msgid "OpenAI"
 msgstr "OpenAI"
 
+#: src/views/environment/Environment.vue:38
+msgid "OperationSync"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:14 src/views/system/Upgrade.vue:15
 #: src/views/system/Upgrade.vue:19 src/views/system/Upgrade.vue:23
 #: src/views/system/Upgrade.vue:27
@@ -1226,7 +1235,7 @@ msgid "Save error %{msg}"
 msgstr "Error al guardar %{msg}"
 
 #: src/components/StdDataDisplay/StdBatchEdit.vue:39
-#: src/views/preference/Preference.vue:60
+#: src/views/preference/Preference.vue:66
 msgid "Save successfully"
 msgstr "Guardado con éxito"
 
@@ -1256,7 +1265,7 @@ msgstr "Enviado"
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
 #: src/views/config/ConfigEdit.vue:41 src/views/domain/DomainList.vue:83
-#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:62
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:68
 #: src/views/system/Upgrade.vue:54
 msgid "Server error"
 msgstr "Error del servidor"
@@ -1317,7 +1326,7 @@ msgstr "Contenido de la llave del certificado SSL"
 msgid "Stable"
 msgstr "Estable"
 
-#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:38
+#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:69
 msgid "Status"
 msgstr "Estado"
 
@@ -1333,10 +1342,20 @@ msgstr "Almacenamiento"
 msgid "Subject Name: %{name}"
 msgstr "Nombre del asunto: %{name}"
 
+#: src/views/environment/Environment.vue:67
+msgid ""
+"Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/"
+"nginx/test|/api/config/.+`, please see system api"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:40
 msgid "Swap"
 msgstr "Swap"
 
+#: src/views/environment/Environment.vue:55
+msgid "SyncApiRegex"
+msgstr ""
+
 #: src/routes/index.ts:157
 msgid "System"
 msgstr "Sistema"
@@ -1406,10 +1425,6 @@ msgstr ""
 "de la autoridad al backend, y debemos guardar este archivo y volver a cargar "
 "Nginx. ¿Estás seguro de que quieres continuar?"
 
-#: src/views/environment/Environment.vue:30
-msgid "Token"
-msgstr "Token"
-
 #: src/views/config/config.ts:13
 msgid "Type"
 msgstr "Tipo"
@@ -1417,7 +1432,7 @@ msgstr "Tipo"
 #: src/views/cert/Cert.vue:68 src/views/cert/DNSCredential.vue:25
 #: src/views/config/config.ts:28 src/views/config/ConfigEdit.vue:31
 #: src/views/domain/components/RightSettings.vue:11
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:55
+#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:86
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "Actualizado a"
@@ -1490,6 +1505,10 @@ msgstr ""
 "Eliminaremos la configuración de HTTPChallenge de este archivo y "
 "recargaremos Nginx. ¿Estás seguro de que quieres continuar?"
 
+#: src/views/environment/Environment.vue:45
+msgid "Whether config api regex that will redo on this environment"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:104
 #: src/views/dashboard/ServerAnalytic.vue:29
 msgid "Writes"
@@ -1521,3 +1540,6 @@ msgstr "Puede consultar la actualización de Nginx UI en esta página."
 msgctxt "Project"
 msgid "License"
 msgstr "Licencia"
+
+#~ msgid "Token"
+#~ msgstr "Token"

+ 37 - 15
frontend/src/language/fr_FR/app.po

@@ -21,7 +21,7 @@ msgstr "Journaux d'accès"
 
 #: src/views/cert/Cert.vue:74 src/views/cert/DNSCredential.vue:31
 #: src/views/config/config.ts:35 src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:61 src/views/user/User.vue:43
+#: src/views/environment/Environment.vue:92 src/views/user/User.vue:43
 msgid "Action"
 msgstr "Action"
 
@@ -599,6 +599,7 @@ msgid "Enabled"
 msgstr "Activé"
 
 #: src/views/domain/components/RightSettings.vue:26
+#: src/views/domain/components/SiteDuplicate.vue:85
 #: src/views/domain/DomainAdd.vue:49 src/views/domain/DomainList.vue:59
 msgid "Enabled successfully"
 msgstr "Activé avec succès"
@@ -918,7 +919,12 @@ msgstr "Nouvelle version publiée"
 msgid "Next"
 msgstr "Suivant"
 
-#: src/views/preference/NginxLogSettings.vue:3
+#: src/views/preference/Preference.vue:8
+#, fuzzy
+msgid "Nginx"
+msgstr "Journal Nginx"
+
+#: src/views/preference/NginxSettings.vue:3
 msgid "Nginx Access Log Path"
 msgstr "Chemin du journal d'accès Nginx"
 
@@ -932,12 +938,11 @@ msgstr "Erreur d'analyse de configuration Nginx"
 msgid "Nginx Control"
 msgstr "Contrôle Nginx"
 
-#: src/views/preference/NginxLogSettings.vue:6
+#: src/views/preference/NginxSettings.vue:6
 msgid "Nginx Error Log Path"
 msgstr "Chemin du journal des erreurs Nginx"
 
 #: src/routes/index.ts:110 src/views/nginx_log/NginxLog.vue:2
-#: src/views/preference/Preference.vue:8
 msgid "Nginx Log"
 msgstr "Journal Nginx"
 
@@ -987,7 +992,7 @@ msgstr "Obtention du certificat"
 #: src/components/NodeSelector/NodeSelector.vue:11
 #: src/views/dashboard/Environments.vue:15
 #: src/views/dashboard/Environments.vue:16
-#: src/views/environment/Environment.vue:48
+#: src/views/environment/Environment.vue:79
 msgid "Offline"
 msgstr ""
 
@@ -1011,7 +1016,7 @@ msgstr "OK"
 #: src/components/NodeSelector/NodeSelector.vue:9
 #: src/views/dashboard/Environments.vue:14
 #: src/views/dashboard/Environments.vue:15
-#: src/views/environment/Environment.vue:45
+#: src/views/environment/Environment.vue:76
 msgid "Online"
 msgstr ""
 
@@ -1019,6 +1024,10 @@ msgstr ""
 msgid "OpenAI"
 msgstr "OpenAI"
 
+#: src/views/environment/Environment.vue:38
+msgid "OperationSync"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:14 src/views/system/Upgrade.vue:15
 #: src/views/system/Upgrade.vue:19 src/views/system/Upgrade.vue:23
 #: src/views/system/Upgrade.vue:27
@@ -1249,7 +1258,7 @@ msgid "Save error %{msg}"
 msgstr "Enregistrer l'erreur %{msg}"
 
 #: src/components/StdDataDisplay/StdBatchEdit.vue:39
-#: src/views/preference/Preference.vue:60
+#: src/views/preference/Preference.vue:66
 msgid "Save successfully"
 msgstr "Sauvegarde réussie"
 
@@ -1279,7 +1288,7 @@ msgstr "Envoyer"
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
 #: src/views/config/ConfigEdit.vue:41 src/views/domain/DomainList.vue:83
-#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:62
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:68
 #: src/views/system/Upgrade.vue:54
 msgid "Server error"
 msgstr "Erreur du serveur"
@@ -1341,7 +1350,7 @@ msgstr "Contenu de la clé de certification SSL"
 msgid "Stable"
 msgstr "Tableau"
 
-#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:38
+#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:69
 msgid "Status"
 msgstr "Statut"
 
@@ -1357,11 +1366,21 @@ msgstr "Stockage"
 msgid "Subject Name: %{name}"
 msgstr "Nom du sujet : %{name}"
 
+#: src/views/environment/Environment.vue:67
+msgid ""
+"Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/"
+"nginx/test|/api/config/.+`, please see system api"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:40
 #, fuzzy
 msgid "Swap"
 msgstr "Échanger"
 
+#: src/views/environment/Environment.vue:55
+msgid "SyncApiRegex"
+msgstr ""
+
 #: src/routes/index.ts:157
 msgid "System"
 msgstr "Système"
@@ -1434,11 +1453,6 @@ msgstr ""
 "transmettre la demande de l'autorité au backend, et nous devons enregistrer "
 "ce fichier et recharger le Nginx. Êtes-vous sûr de vouloir continuer?"
 
-#: src/views/environment/Environment.vue:30
-#, fuzzy
-msgid "Token"
-msgstr "Jeton d'API"
-
 #: src/views/config/config.ts:13
 msgid "Type"
 msgstr "Type"
@@ -1446,7 +1460,7 @@ msgstr "Type"
 #: src/views/cert/Cert.vue:68 src/views/cert/DNSCredential.vue:25
 #: src/views/config/config.ts:28 src/views/config/ConfigEdit.vue:31
 #: src/views/domain/components/RightSettings.vue:11
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:55
+#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:86
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "Mis à jour le"
@@ -1520,6 +1534,10 @@ msgstr ""
 "Nous allons supprimer la configuration HTTPChallenge de ce fichier et "
 "recharger le Nginx. Êtes-vous sûr de vouloir continuer?"
 
+#: src/views/environment/Environment.vue:45
+msgid "Whether config api regex that will redo on this environment"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:104
 #: src/views/dashboard/ServerAnalytic.vue:29
 msgid "Writes"
@@ -1552,6 +1570,10 @@ msgctxt "Project"
 msgid "License"
 msgstr "Licence"
 
+#, fuzzy
+#~ msgid "Token"
+#~ msgstr "Jeton d'API"
+
 #~ msgid "Git"
 #~ msgstr "Git"
 

+ 30 - 14
frontend/src/language/messages.pot

@@ -15,7 +15,7 @@ msgstr ""
 #: src/views/cert/DNSCredential.vue:31
 #: src/views/config/config.ts:35
 #: src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:61
+#: src/views/environment/Environment.vue:92
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr ""
@@ -616,6 +616,7 @@ msgid "Enabled"
 msgstr ""
 
 #: src/views/domain/components/RightSettings.vue:26
+#: src/views/domain/components/SiteDuplicate.vue:85
 #: src/views/domain/DomainAdd.vue:49
 #: src/views/domain/DomainList.vue:59
 msgid "Enabled successfully"
@@ -942,7 +943,11 @@ msgstr ""
 msgid "Next"
 msgstr ""
 
-#: src/views/preference/NginxLogSettings.vue:3
+#: src/views/preference/Preference.vue:8
+msgid "Nginx"
+msgstr ""
+
+#: src/views/preference/NginxSettings.vue:3
 msgid "Nginx Access Log Path"
 msgstr ""
 
@@ -956,13 +961,12 @@ msgstr ""
 msgid "Nginx Control"
 msgstr ""
 
-#: src/views/preference/NginxLogSettings.vue:6
+#: src/views/preference/NginxSettings.vue:6
 msgid "Nginx Error Log Path"
 msgstr ""
 
 #: src/routes/index.ts:110
 #: src/views/nginx_log/NginxLog.vue:2
-#: src/views/preference/Preference.vue:8
 msgid "Nginx Log"
 msgstr ""
 
@@ -1012,7 +1016,7 @@ msgstr ""
 #: src/components/NodeSelector/NodeSelector.vue:11
 #: src/views/dashboard/Environments.vue:15
 #: src/views/dashboard/Environments.vue:16
-#: src/views/environment/Environment.vue:48
+#: src/views/environment/Environment.vue:79
 msgid "Offline"
 msgstr ""
 
@@ -1036,7 +1040,7 @@ msgstr ""
 #: src/components/NodeSelector/NodeSelector.vue:9
 #: src/views/dashboard/Environments.vue:14
 #: src/views/dashboard/Environments.vue:15
-#: src/views/environment/Environment.vue:45
+#: src/views/environment/Environment.vue:76
 msgid "Online"
 msgstr ""
 
@@ -1044,6 +1048,10 @@ msgstr ""
 msgid "OpenAI"
 msgstr ""
 
+#: src/views/environment/Environment.vue:38
+msgid "OperationSync"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:14
 #: src/views/system/Upgrade.vue:15
 #: src/views/system/Upgrade.vue:19
@@ -1274,7 +1282,7 @@ msgid "Save error %{msg}"
 msgstr ""
 
 #: src/components/StdDataDisplay/StdBatchEdit.vue:39
-#: src/views/preference/Preference.vue:60
+#: src/views/preference/Preference.vue:66
 msgid "Save successfully"
 msgstr ""
 
@@ -1307,7 +1315,7 @@ msgstr ""
 #: src/views/config/ConfigEdit.vue:41
 #: src/views/domain/DomainList.vue:83
 #: src/views/other/Install.vue:71
-#: src/views/preference/Preference.vue:62
+#: src/views/preference/Preference.vue:68
 #: src/views/system/Upgrade.vue:54
 msgid "Server error"
 msgstr ""
@@ -1374,7 +1382,7 @@ msgid "Stable"
 msgstr ""
 
 #: src/views/domain/DomainList.vue:24
-#: src/views/environment/Environment.vue:38
+#: src/views/environment/Environment.vue:69
 msgid "Status"
 msgstr ""
 
@@ -1390,10 +1398,18 @@ msgstr ""
 msgid "Subject Name: %{name}"
 msgstr ""
 
+#: src/views/environment/Environment.vue:67
+msgid "Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/nginx/test|/api/config/.+`, please see system api"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:40
 msgid "Swap"
 msgstr ""
 
+#: src/views/environment/Environment.vue:55
+msgid "SyncApiRegex"
+msgstr ""
+
 #: src/routes/index.ts:157
 msgid "System"
 msgstr ""
@@ -1447,10 +1463,6 @@ msgstr ""
 msgid "To make sure the certification auto-renewal can work normally, we need to add a location which can proxy the request from authority to backend, and we need to save this file and reload the Nginx. Are you sure you want to continue?"
 msgstr ""
 
-#: src/views/environment/Environment.vue:30
-msgid "Token"
-msgstr ""
-
 #: src/views/config/config.ts:13
 msgid "Type"
 msgstr ""
@@ -1461,7 +1473,7 @@ msgstr ""
 #: src/views/config/ConfigEdit.vue:31
 #: src/views/domain/components/RightSettings.vue:11
 #: src/views/domain/DomainList.vue:41
-#: src/views/environment/Environment.vue:55
+#: src/views/environment/Environment.vue:86
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr ""
@@ -1536,6 +1548,10 @@ msgstr ""
 msgid "We will remove the HTTPChallenge configuration from this file and reload the Nginx. Are you sure you want to continue?"
 msgstr ""
 
+#: src/views/environment/Environment.vue:45
+msgid "Whether config api regex that will redo on this environment"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:104
 #: src/views/dashboard/ServerAnalytic.vue:29
 msgid "Writes"

+ 35 - 15
frontend/src/language/ru_RU/app.po

@@ -19,7 +19,7 @@ msgstr "Журнал доступа"
 
 #: src/views/cert/Cert.vue:74 src/views/cert/DNSCredential.vue:31
 #: src/views/config/config.ts:35 src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:61 src/views/user/User.vue:43
+#: src/views/environment/Environment.vue:92 src/views/user/User.vue:43
 msgid "Action"
 msgstr "Действие"
 
@@ -511,7 +511,8 @@ msgstr "Конфигурация домена успешно создана"
 
 #: src/views/cert/Cert.vue:21
 msgid "Domains list is empty, try to reopen auto-cert for %{config}"
-msgstr "Список доменов пуст, попробуйте заново открыть авто-сертификат для %{config}"
+msgstr ""
+"Список доменов пуст, попробуйте заново открыть авто-сертификат для %{config}"
 
 #: src/language/constants.ts:29
 msgid "Download latest release error"
@@ -604,6 +605,7 @@ msgid "Enabled"
 msgstr "Влючено"
 
 #: src/views/domain/components/RightSettings.vue:26
+#: src/views/domain/components/SiteDuplicate.vue:85
 #: src/views/domain/DomainAdd.vue:49 src/views/domain/DomainList.vue:59
 msgid "Enabled successfully"
 msgstr "Активировано успешно"
@@ -926,7 +928,12 @@ msgstr "Вышла новая версия"
 msgid "Next"
 msgstr "Дальше"
 
-#: src/views/preference/NginxLogSettings.vue:3
+#: src/views/preference/Preference.vue:8
+#, fuzzy
+msgid "Nginx"
+msgstr "Журнал"
+
+#: src/views/preference/NginxSettings.vue:3
 msgid "Nginx Access Log Path"
 msgstr "Путь для Nginx Access Log"
 
@@ -941,12 +948,11 @@ msgstr "Ошибка синтаксического анализа конфиг
 msgid "Nginx Control"
 msgstr "Управление Nginx"
 
-#: src/views/preference/NginxLogSettings.vue:6
+#: src/views/preference/NginxSettings.vue:6
 msgid "Nginx Error Log Path"
 msgstr "Путь для Nginx Error Log"
 
 #: src/routes/index.ts:110 src/views/nginx_log/NginxLog.vue:2
-#: src/views/preference/Preference.vue:8
 msgid "Nginx Log"
 msgstr "Журнал"
 
@@ -998,7 +1004,7 @@ msgstr "Получение сертификата"
 #: src/components/NodeSelector/NodeSelector.vue:11
 #: src/views/dashboard/Environments.vue:15
 #: src/views/dashboard/Environments.vue:16
-#: src/views/environment/Environment.vue:48
+#: src/views/environment/Environment.vue:79
 msgid "Offline"
 msgstr ""
 
@@ -1022,7 +1028,7 @@ msgstr ""
 #: src/components/NodeSelector/NodeSelector.vue:9
 #: src/views/dashboard/Environments.vue:14
 #: src/views/dashboard/Environments.vue:15
-#: src/views/environment/Environment.vue:45
+#: src/views/environment/Environment.vue:76
 msgid "Online"
 msgstr ""
 
@@ -1030,6 +1036,10 @@ msgstr ""
 msgid "OpenAI"
 msgstr ""
 
+#: src/views/environment/Environment.vue:38
+msgid "OperationSync"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:14 src/views/system/Upgrade.vue:15
 #: src/views/system/Upgrade.vue:19 src/views/system/Upgrade.vue:23
 #: src/views/system/Upgrade.vue:27
@@ -1251,7 +1261,7 @@ msgid "Save error %{msg}"
 msgstr "Ошибка сохранения %{msg}"
 
 #: src/components/StdDataDisplay/StdBatchEdit.vue:39
-#: src/views/preference/Preference.vue:60
+#: src/views/preference/Preference.vue:66
 #, fuzzy
 msgid "Save successfully"
 msgstr "Успешно сохранено"
@@ -1283,7 +1293,7 @@ msgstr "Отправлено"
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
 #: src/views/config/ConfigEdit.vue:41 src/views/domain/DomainList.vue:83
-#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:62
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:68
 #: src/views/system/Upgrade.vue:54
 msgid "Server error"
 msgstr "Ошибка сервера"
@@ -1350,7 +1360,7 @@ msgstr "Содержание ключа сертификата SSL"
 msgid "Stable"
 msgstr "Таблица"
 
-#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:38
+#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:69
 msgid "Status"
 msgstr "Статус"
 
@@ -1366,10 +1376,20 @@ msgstr "Хранилище"
 msgid "Subject Name: %{name}"
 msgstr "Название темы: %{name}"
 
+#: src/views/environment/Environment.vue:67
+msgid ""
+"Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/"
+"nginx/test|/api/config/.+`, please see system api"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:40
 msgid "Swap"
 msgstr "Своп"
 
+#: src/views/environment/Environment.vue:55
+msgid "SyncApiRegex"
+msgstr ""
+
 #: src/routes/index.ts:157
 msgid "System"
 msgstr "Система"
@@ -1437,10 +1457,6 @@ msgid ""
 "continue?"
 msgstr ""
 
-#: src/views/environment/Environment.vue:30
-msgid "Token"
-msgstr ""
-
 #: src/views/config/config.ts:13
 msgid "Type"
 msgstr "Тип"
@@ -1448,7 +1464,7 @@ msgstr "Тип"
 #: src/views/cert/Cert.vue:68 src/views/cert/DNSCredential.vue:25
 #: src/views/config/config.ts:28 src/views/config/ConfigEdit.vue:31
 #: src/views/domain/components/RightSettings.vue:11
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:55
+#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:86
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "Обновлено в"
@@ -1522,6 +1538,10 @@ msgid ""
 "Nginx. Are you sure you want to continue?"
 msgstr ""
 
+#: src/views/environment/Environment.vue:45
+msgid "Whether config api regex that will redo on this environment"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:104
 #: src/views/dashboard/ServerAnalytic.vue:29
 msgid "Writes"

File diff suppressed because it is too large
+ 0 - 0
frontend/src/language/translations.json


+ 39 - 14
frontend/src/language/zh_CN/app.po

@@ -23,7 +23,7 @@ msgstr "访问日志"
 
 #: src/views/cert/Cert.vue:74 src/views/cert/DNSCredential.vue:31
 #: src/views/config/config.ts:35 src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:61 src/views/user/User.vue:43
+#: src/views/environment/Environment.vue:92 src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
@@ -584,6 +584,7 @@ msgid "Enabled"
 msgstr "启用"
 
 #: src/views/domain/components/RightSettings.vue:26
+#: src/views/domain/components/SiteDuplicate.vue:85
 #: src/views/domain/DomainAdd.vue:49 src/views/domain/DomainList.vue:59
 msgid "Enabled successfully"
 msgstr "启用成功"
@@ -896,7 +897,12 @@ msgstr "新版本发布"
 msgid "Next"
 msgstr "下一步"
 
-#: src/views/preference/NginxLogSettings.vue:3
+#: src/views/preference/Preference.vue:8
+#, fuzzy
+msgid "Nginx"
+msgstr "Nginx 日志"
+
+#: src/views/preference/NginxSettings.vue:3
 msgid "Nginx Access Log Path"
 msgstr "Nginx 访问日志路径"
 
@@ -910,12 +916,11 @@ msgstr "Nginx 配置解析错误"
 msgid "Nginx Control"
 msgstr "控制 Nginx"
 
-#: src/views/preference/NginxLogSettings.vue:6
+#: src/views/preference/NginxSettings.vue:6
 msgid "Nginx Error Log Path"
 msgstr "Nginx 错误日志路径"
 
 #: src/routes/index.ts:110 src/views/nginx_log/NginxLog.vue:2
-#: src/views/preference/Preference.vue:8
 msgid "Nginx Log"
 msgstr "Nginx 日志"
 
@@ -965,7 +970,7 @@ msgstr "正在获取证书"
 #: src/components/NodeSelector/NodeSelector.vue:11
 #: src/views/dashboard/Environments.vue:15
 #: src/views/dashboard/Environments.vue:16
-#: src/views/environment/Environment.vue:48
+#: src/views/environment/Environment.vue:79
 msgid "Offline"
 msgstr "离线"
 
@@ -989,7 +994,7 @@ msgstr "确定"
 #: src/components/NodeSelector/NodeSelector.vue:9
 #: src/views/dashboard/Environments.vue:14
 #: src/views/dashboard/Environments.vue:15
-#: src/views/environment/Environment.vue:45
+#: src/views/environment/Environment.vue:76
 msgid "Online"
 msgstr "在线"
 
@@ -997,6 +1002,10 @@ msgstr "在线"
 msgid "OpenAI"
 msgstr "OpenAI"
 
+#: src/views/environment/Environment.vue:38
+msgid "OperationSync"
+msgstr "操作同步"
+
 #: src/views/system/Upgrade.vue:14 src/views/system/Upgrade.vue:15
 #: src/views/system/Upgrade.vue:19 src/views/system/Upgrade.vue:23
 #: src/views/system/Upgrade.vue:27
@@ -1217,7 +1226,7 @@ msgid "Save error %{msg}"
 msgstr "保存错误 %{msg}"
 
 #: src/components/StdDataDisplay/StdBatchEdit.vue:39
-#: src/views/preference/Preference.vue:60
+#: src/views/preference/Preference.vue:66
 msgid "Save successfully"
 msgstr "保存成功"
 
@@ -1247,7 +1256,7 @@ msgstr "上传"
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
 #: src/views/config/ConfigEdit.vue:41 src/views/domain/DomainList.vue:83
-#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:62
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:68
 #: src/views/system/Upgrade.vue:54
 msgid "Server error"
 msgstr "服务器错误"
@@ -1308,7 +1317,7 @@ msgstr "SSL证书密钥内容"
 msgid "Stable"
 msgstr "稳定"
 
-#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:38
+#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:69
 msgid "Status"
 msgstr "状态"
 
@@ -1324,10 +1333,20 @@ msgstr "存储"
 msgid "Subject Name: %{name}"
 msgstr "主体名称: %{name}"
 
+#: src/views/environment/Environment.vue:67
+msgid ""
+"Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/"
+"nginx/test|/api/config/.+`, please see system api"
+msgstr "`重载`和`配置管理`的操作同步正则可以配置为`/api/nginx/reload|/api/nginx/test|/api/config/.+`,详细请查看系统API"
+
 #: src/views/dashboard/ServerAnalytic.vue:40
 msgid "Swap"
 msgstr "Swap"
 
+#: src/views/environment/Environment.vue:55
+msgid "SyncApiRegex"
+msgstr "Api正则表达式"
+
 #: src/routes/index.ts:157
 msgid "System"
 msgstr "系统"
@@ -1391,10 +1410,6 @@ msgstr ""
 "为了确保认证自动更新能够正常工作,我们需要添加一个能够代理从权威机构到后端的"
 "请求的 Location,并且我们需要保存这个文件并重新加载Nginx。你确定要继续吗?"
 
-#: src/views/environment/Environment.vue:30
-msgid "Token"
-msgstr "Token"
-
 #: src/views/config/config.ts:13
 msgid "Type"
 msgstr "类型"
@@ -1402,7 +1417,7 @@ msgstr "类型"
 #: src/views/cert/Cert.vue:68 src/views/cert/DNSCredential.vue:25
 #: src/views/config/config.ts:28 src/views/config/ConfigEdit.vue:31
 #: src/views/domain/components/RightSettings.vue:11
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:55
+#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:86
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "修改时间"
@@ -1474,6 +1489,10 @@ msgid ""
 msgstr ""
 "我们将从这个文件中删除HTTPChallenge的配置,并重新加载Nginx。你确定要继续吗?"
 
+#: src/views/environment/Environment.vue:45
+msgid "Whether config api regex that will redo on this environment"
+msgstr "是否配置API正则操作同步到该环境"
+
 #: src/views/dashboard/ServerAnalytic.vue:104
 #: src/views/dashboard/ServerAnalytic.vue:29
 msgid "Writes"
@@ -1506,6 +1525,12 @@ msgctxt "Project"
 msgid "License"
 msgstr "开源许可"
 
+#~ msgid "The Operation of Sites, Configs and Certification will redo on this"
+#~ msgstr "网站、配置和证书的操作同步到该环境"
+
+#~ msgid "Token"
+#~ msgstr "Token"
+
 #~ msgid "Git"
 #~ msgstr "Git"
 

+ 41 - 21
frontend/src/language/zh_TW/app.po

@@ -23,7 +23,7 @@ msgstr "存取日誌"
 
 #: src/views/cert/Cert.vue:74 src/views/cert/DNSCredential.vue:31
 #: src/views/config/config.ts:35 src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:61 src/views/user/User.vue:43
+#: src/views/environment/Environment.vue:92 src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
@@ -557,7 +557,6 @@ msgstr "在 %{node_name} 啟用 %{conf_name} 失敗"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "成功在 %{node_name} 啟用 %{conf_name}"
 
-
 #: src/views/domain/cert/components/ObtainCert.vue:55
 msgid "Enable auto-renewal failed for %{name}"
 msgstr "啟用 %{name} 自動續簽失敗"
@@ -586,6 +585,7 @@ msgid "Enabled"
 msgstr "已啟用"
 
 #: src/views/domain/components/RightSettings.vue:26
+#: src/views/domain/components/SiteDuplicate.vue:85
 #: src/views/domain/DomainAdd.vue:49 src/views/domain/DomainList.vue:59
 msgid "Enabled successfully"
 msgstr "成功啟用"
@@ -820,8 +820,7 @@ msgid ""
 "Make sure you have configured a reverse proxy for .well-known directory to "
 "HTTPChallengePort before obtaining the certificate."
 msgstr ""
-"在取得憑證前,請確保您已將 .well-known 目錄反向代理到 "
-"HTTPChallengePort。"
+"在取得憑證前,請確保您已將 .well-known 目錄反向代理到 HTTPChallengePort。"
 
 #: src/routes/index.ts:65
 msgid "Manage Configs"
@@ -898,7 +897,12 @@ msgstr "新版本發布"
 msgid "Next"
 msgstr "下一步"
 
-#: src/views/preference/NginxLogSettings.vue:3
+#: src/views/preference/Preference.vue:8
+#, fuzzy
+msgid "Nginx"
+msgstr "Nginx 日誌"
+
+#: src/views/preference/NginxSettings.vue:3
 msgid "Nginx Access Log Path"
 msgstr "Nginx 存取日誌路徑"
 
@@ -912,12 +916,11 @@ msgstr "Nginx 設定解析錯誤"
 msgid "Nginx Control"
 msgstr "Nginx 控制元件"
 
-#: src/views/preference/NginxLogSettings.vue:6
+#: src/views/preference/NginxSettings.vue:6
 msgid "Nginx Error Log Path"
 msgstr "Nginx 錯誤日誌路徑"
 
 #: src/routes/index.ts:110 src/views/nginx_log/NginxLog.vue:2
-#: src/views/preference/Preference.vue:8
 msgid "Nginx Log"
 msgstr "Nginx 日誌"
 
@@ -966,7 +969,7 @@ msgstr "正在取得憑證"
 #: src/components/NodeSelector/NodeSelector.vue:11
 #: src/views/dashboard/Environments.vue:15
 #: src/views/dashboard/Environments.vue:16
-#: src/views/environment/Environment.vue:48
+#: src/views/environment/Environment.vue:79
 msgid "Offline"
 msgstr "離線"
 
@@ -990,7 +993,7 @@ msgstr "確定"
 #: src/components/NodeSelector/NodeSelector.vue:9
 #: src/views/dashboard/Environments.vue:14
 #: src/views/dashboard/Environments.vue:15
-#: src/views/environment/Environment.vue:45
+#: src/views/environment/Environment.vue:76
 msgid "Online"
 msgstr "線上"
 
@@ -998,6 +1001,10 @@ msgstr "線上"
 msgid "OpenAI"
 msgstr "OpenAI"
 
+#: src/views/environment/Environment.vue:38
+msgid "OperationSync"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:14 src/views/system/Upgrade.vue:15
 #: src/views/system/Upgrade.vue:19 src/views/system/Upgrade.vue:23
 #: src/views/system/Upgrade.vue:27
@@ -1053,8 +1060,8 @@ msgid ""
 "all in seconds."
 msgstr ""
 "請填寫您的 DNS 供應商提供的 API 身份驗證認證。我們會將一個或多個 TXT 記錄新增"
-"到您網域的 DNS 記錄中以進行所有權驗證。驗證完成後,記錄將被刪除。請注意,以下"
-"間設定均以秒為單位。"
+"到您網域的 DNS 記錄中以進行所有權驗證。驗證完成後,記錄將被刪除。請注意,以下"
+"間設定均以秒為單位。"
 
 #: src/views/domain/cert/components/AutoCertStepOne.vue:42
 msgid ""
@@ -1218,7 +1225,7 @@ msgid "Save error %{msg}"
 msgstr "儲存錯誤 %{msg}"
 
 #: src/components/StdDataDisplay/StdBatchEdit.vue:39
-#: src/views/preference/Preference.vue:60
+#: src/views/preference/Preference.vue:66
 msgid "Save successfully"
 msgstr "儲存成功"
 
@@ -1248,7 +1255,7 @@ msgstr "傳送"
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
 #: src/views/config/ConfigEdit.vue:41 src/views/domain/DomainList.vue:83
-#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:62
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:68
 #: src/views/system/Upgrade.vue:54
 msgid "Server error"
 msgstr "伺服器錯誤"
@@ -1309,7 +1316,7 @@ msgstr "SSL 憑證金鑰內容"
 msgid "Stable"
 msgstr "穩定"
 
-#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:38
+#: src/views/domain/DomainList.vue:24 src/views/environment/Environment.vue:69
 msgid "Status"
 msgstr "狀態"
 
@@ -1325,10 +1332,20 @@ msgstr "儲存空間"
 msgid "Subject Name: %{name}"
 msgstr "主體名稱: %{name}"
 
+#: src/views/environment/Environment.vue:67
+msgid ""
+"Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/"
+"nginx/test|/api/config/.+`, please see system api"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:40
 msgid "Swap"
 msgstr "交換空間"
 
+#: src/views/environment/Environment.vue:55
+msgid "SyncApiRegex"
+msgstr ""
+
 #: src/routes/index.ts:157
 msgid "System"
 msgstr "系統"
@@ -1390,12 +1407,8 @@ msgid ""
 "need to save this file and reload the Nginx. Are you sure you want to "
 "continue?"
 msgstr ""
-"為了確保憑證自動續期能夠正常運作,我們需要新增一個 Location 來代理從授權後"
-"端的請求,我們需要儲存這個檔案並重新載入 Nginx。你確定你要繼續嗎?"
-
-#: src/views/environment/Environment.vue:30
-msgid "Token"
-msgstr "Token"
+"為了確保憑證自動續期能夠正常運作,我們需要新增一個 Location 來代理從授權後端"
+"的請求,我們需要儲存這個檔案並重新載入 Nginx。你確定你要繼續嗎?"
 
 #: src/views/config/config.ts:13
 msgid "Type"
@@ -1404,7 +1417,7 @@ msgstr "類型"
 #: src/views/cert/Cert.vue:68 src/views/cert/DNSCredential.vue:25
 #: src/views/config/config.ts:28 src/views/config/ConfigEdit.vue:31
 #: src/views/domain/components/RightSettings.vue:11
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:55
+#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:86
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "更新時間"
@@ -1477,6 +1490,10 @@ msgstr ""
 "我們將從該檔案中刪除 HTTPChallenge 設定並重新載入 Nginx 設定檔案。你確定你要"
 "繼續嗎?"
 
+#: src/views/environment/Environment.vue:45
+msgid "Whether config api regex that will redo on this environment"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:104
 #: src/views/dashboard/ServerAnalytic.vue:29
 msgid "Writes"
@@ -1509,6 +1526,9 @@ msgctxt "Project"
 msgid "License"
 msgstr "授權條款"
 
+#~ msgid "Token"
+#~ msgstr "Token"
+
 #~ msgid "Git"
 #~ msgstr "Git"
 

+ 8 - 1
frontend/src/views/domain/components/SiteDuplicate.vue

@@ -79,6 +79,13 @@ function onSubmit() {
                             description: $gettext(e?.message ?? 'Server error')
                         })
                     })
+                    if (r.enabled) {
+                      domain.enable(modelRef.name, {headers: {'X-Node-ID': id}}).then(() => {
+                        notification.success({
+                          message: $gettext('Enabled successfully')
+                        })
+                      })
+                    }
                 })
             }
         })
@@ -89,7 +96,7 @@ function onSubmit() {
 
 watch(() => props.visible, (v) => {
     if (v) {
-        modelRef.name = ''
+        modelRef.name = props.name  // default with source name
         modelRef.target = [0]
         nextTick(() => clearValidate())
     }

+ 33 - 2
frontend/src/views/environment/Environment.vue

@@ -3,9 +3,9 @@ import {useGettext} from 'vue3-gettext'
 import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
 import environment from '@/api/environment'
 import StdCurd from '@/components/StdDataDisplay/StdCurd.vue'
-import {input} from '@/components/StdDataEntry'
+import {input, antSwitch, textarea} from '@/components/StdDataEntry'
 import {h} from 'vue'
-import {Badge} from 'ant-design-vue'
+import {Badge, Tag} from 'ant-design-vue'
 
 const {$gettext, interpolate} = useGettext()
 
@@ -34,6 +34,37 @@ const columns = [{
     edit: {
         type: input
     }
+}, {
+    title: () => $gettext('OperationSync'),
+    dataIndex: 'operation_sync',
+    sorter: true,
+    pithy: true,
+    edit: {
+        type: antSwitch
+    },
+    extra: $gettext('Whether config api regex that will redo on this environment'),
+    customRender: (args: customRender) => {
+        const {operation_sync} = args.record
+        if (operation_sync) {
+            return h(Tag, {color: 'success'}, {default: ()=> h('span', '是')})
+        } else {
+            return h(Tag, {color: 'default'}, {default: ()=> h('span', '否')})
+        }
+    },
+}, {
+    title: () => $gettext('SyncApiRegex'),
+    dataIndex: 'sync_api_regex',
+    sorter: true,
+    pithy: true,
+    display: false,
+    edit: {
+      type: textarea,
+      show: (data) => {
+        const {operation_sync} = data
+        return operation_sync
+      }
+    },
+    extra: $gettext('Such as Reload and Configs, regex can configure as `/api/nginx/reload|/api/nginx/test|/api/config/.+`, please see system api'),
 }, {
     title: () => $gettext('Status'),
     dataIndex: 'status',

+ 10 - 4
server/api/config.go

@@ -147,8 +147,11 @@ func AddConfig(c *gin.Context) {
 		}
 	}
 
-	output := nginx.Reload()
-
+	output, err := nginx.Reload()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 	if nginx.GetLogLevel(output) >= nginx.Warn {
 		c.JSON(http.StatusInternalServerError, gin.H{
 			"message": output,
@@ -193,8 +196,11 @@ func EditConfig(c *gin.Context) {
 		}
 	}
 
-	output := nginx.Reload()
-
+	output, err := nginx.Reload()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 	if nginx.GetLogLevel(output) >= nginx.Warn {
 		c.JSON(http.StatusInternalServerError, gin.H{
 			"message": output,

+ 25 - 5
server/api/domain.go

@@ -271,7 +271,11 @@ func SaveDomain(c *gin.Context) {
 	enabledConfigFilePath = nginx.GetConfPath("sites-enabled", name)
 	if helper.FileExists(enabledConfigFilePath) {
 		// Test nginx configuration
-		output := nginx.TestConf()
+		output, err := nginx.TestConf()
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
 		if nginx.GetLogLevel(output) > nginx.Warn {
 			c.JSON(http.StatusInternalServerError, gin.H{
 				"message": output,
@@ -280,7 +284,11 @@ func SaveDomain(c *gin.Context) {
 			return
 		}
 
-		output = nginx.Reload()
+		output, err = nginx.Reload()
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
 
 		if nginx.GetLogLevel(output) > nginx.Warn {
 			c.JSON(http.StatusInternalServerError, gin.H{
@@ -314,7 +322,11 @@ func EnableDomain(c *gin.Context) {
 	}
 
 	// Test nginx config, if not pass then disable the site.
-	output := nginx.TestConf()
+	output, err := nginx.TestConf()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		_ = os.Remove(enabledConfigFilePath)
@@ -324,7 +336,11 @@ func EnableDomain(c *gin.Context) {
 		return
 	}
 
-	output = nginx.Reload()
+	output, err = nginx.Reload()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		c.JSON(http.StatusInternalServerError, gin.H{
@@ -363,7 +379,11 @@ func DisableDomain(c *gin.Context) {
 		return
 	}
 
-	output := nginx.Reload()
+	output, err := nginx.Reload()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	if nginx.GetLogLevel(output) > nginx.Warn {
 		c.JSON(http.StatusInternalServerError, gin.H{

+ 32 - 9
server/api/environment.go

@@ -8,6 +8,7 @@ import (
 	"github.com/gin-gonic/gin"
 	"github.com/spf13/cast"
 	"net/http"
+	"regexp"
 )
 
 func GetEnvironment(c *gin.Context) {
@@ -36,9 +37,19 @@ func GetEnvironmentList(c *gin.Context) {
 }
 
 type EnvironmentManageJson struct {
-	Name  string `json:"name" binding:"required"`
-	URL   string `json:"url" binding:"required"`
-	Token string `json:"token"  binding:"required"`
+	Name          string `json:"name" binding:"required"`
+	URL           string `json:"url" binding:"required"`
+	Token         string `json:"token"  binding:"required"`
+	OperationSync bool   `json:"operation_sync"`
+	SyncApiRegex  string `json:"sync_api_regex"`
+}
+
+func validateRegex(data EnvironmentManageJson) error {
+	if data.OperationSync {
+		_, err := regexp.Compile(data.SyncApiRegex)
+		return err
+	}
+	return nil
 }
 
 func AddEnvironment(c *gin.Context) {
@@ -46,11 +57,17 @@ func AddEnvironment(c *gin.Context) {
 	if !BindAndValid(c, &json) {
 		return
 	}
+	if err := validateRegex(json); err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	env := model.Environment{
-		Name:  json.Name,
-		URL:   json.URL,
-		Token: json.Token,
+		Name:          json.Name,
+		URL:           json.URL,
+		Token:         json.Token,
+		OperationSync: &json.OperationSync,
+		SyncApiRegex:  json.SyncApiRegex,
 	}
 
 	envQuery := query.Environment
@@ -73,6 +90,10 @@ func EditEnvironment(c *gin.Context) {
 	if !BindAndValid(c, &json) {
 		return
 	}
+	if err := validateRegex(json); err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	envQuery := query.Environment
 
@@ -83,9 +104,11 @@ func EditEnvironment(c *gin.Context) {
 	}
 
 	_, err = envQuery.Where(envQuery.ID.Eq(env.ID)).Updates(&model.Environment{
-		Name:  json.Name,
-		URL:   json.URL,
-		Token: json.Token,
+		Name:          json.Name,
+		URL:           json.URL,
+		Token:         json.Token,
+		OperationSync: &json.OperationSync,
+		SyncApiRegex:  json.SyncApiRegex,
 	})
 
 	if err != nil {

+ 16 - 4
server/api/ngx.go

@@ -53,7 +53,7 @@ func NginxStatus(c *gin.Context) {
 	pidPath := nginx.GetNginxPIDPath()
 
 	running := true
-	if _, err := os.Stat(pidPath); err != nil {
+	if fileInfo, err := os.Stat(pidPath); err != nil || fileInfo.Size() == 0 { // fileInfo.Size() == 0 no process id
 		running = false
 	}
 
@@ -63,7 +63,11 @@ func NginxStatus(c *gin.Context) {
 }
 
 func ReloadNginx(c *gin.Context) {
-	output := nginx.Reload()
+	output, err := nginx.Reload()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	c.JSON(http.StatusOK, gin.H{
 		"message": output,
@@ -72,7 +76,11 @@ func ReloadNginx(c *gin.Context) {
 }
 
 func TestNginx(c *gin.Context) {
-	output := nginx.TestConf()
+	output, err := nginx.TestConf()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	c.JSON(http.StatusOK, gin.H{
 		"message": output,
@@ -81,7 +89,11 @@ func TestNginx(c *gin.Context) {
 }
 
 func RestartNginx(c *gin.Context) {
-	output := nginx.Restart()
+	output, err := nginx.Restart()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
 	c.JSON(http.StatusOK, gin.H{
 		"message": output,

+ 13 - 8
server/internal/nginx/nginx.go

@@ -14,55 +14,60 @@ func execShell(cmd string) (out string, err error) {
 	return
 }
 
-func TestConf() string {
+func TestConf() (string, error) {
 	out, err := exec.Command("nginx", "-t").CombinedOutput()
 	if err != nil {
 		logger.Error(err)
+		return string(out), err
 	}
 
-	return string(out)
+	return string(out), nil
 }
 
-func Reload() string {
+func Reload() (string, error) {
 	if settings.NginxSettings.ReloadCmd != "" {
 		out, err := execShell(settings.NginxSettings.ReloadCmd)
 
 		if err != nil {
 			logger.Error(err)
+			return out, err
 		}
 
-		return out
+		return out, nil
 
 	} else {
 		out, err := exec.Command("nginx", "-s", "reload").CombinedOutput()
 
 		if err != nil {
 			logger.Error(err)
+			return string(out), err
 		}
 
-		return string(out)
+		return string(out), nil
 	}
 
 }
 
-func Restart() string {
+func Restart() (string, error) {
 	if settings.NginxSettings.RestartCmd != "" {
 		out, err := execShell(settings.NginxSettings.RestartCmd)
 
 		if err != nil {
 			logger.Error(err)
+			return "", err
 		}
 
-		return out
+		return out, nil
 	} else {
 
 		out, err := exec.Command("nginx", "-s", "reopen").CombinedOutput()
 
 		if err != nil {
 			logger.Error(err)
+			return "", err
 		}
 
-		return string(out)
+		return string(out), nil
 	}
 
 }

+ 5 - 3
server/model/environment.go

@@ -7,9 +7,11 @@ import (
 
 type Environment struct {
 	Model
-	Name  string `json:"name"`
-	URL   string `json:"url"`
-	Token string `json:"token"`
+	Name          string `json:"name"`
+	URL           string `json:"url"`
+	Token         string `json:"token"`
+	OperationSync *bool  `json:"operation_sync"`
+	SyncApiRegex  string `json:"sync_api_regex"`
 }
 
 func (e *Environment) GetWebSocketURL(uri string) (decodedUri string, err error) {

+ 17 - 9
server/query/environments.gen.go

@@ -35,6 +35,8 @@ func newEnvironment(db *gorm.DB, opts ...gen.DOOption) environment {
 	_environment.Name = field.NewString(tableName, "name")
 	_environment.URL = field.NewString(tableName, "url")
 	_environment.Token = field.NewString(tableName, "token")
+	_environment.OperationSync = field.NewBool(tableName, "operation_sync")
+	_environment.SyncApiRegex = field.NewString(tableName, "sync_api_regex")
 
 	_environment.fillFieldMap()
 
@@ -44,14 +46,16 @@ func newEnvironment(db *gorm.DB, opts ...gen.DOOption) environment {
 type environment struct {
 	environmentDo
 
-	ALL       field.Asterisk
-	ID        field.Int
-	CreatedAt field.Time
-	UpdatedAt field.Time
-	DeletedAt field.Field
-	Name      field.String
-	URL       field.String
-	Token     field.String
+	ALL           field.Asterisk
+	ID            field.Int
+	CreatedAt     field.Time
+	UpdatedAt     field.Time
+	DeletedAt     field.Field
+	Name          field.String
+	URL           field.String
+	Token         field.String
+	OperationSync field.Bool
+	SyncApiRegex  field.String
 
 	fieldMap map[string]field.Expr
 }
@@ -75,6 +79,8 @@ func (e *environment) updateTableName(table string) *environment {
 	e.Name = field.NewString(table, "name")
 	e.URL = field.NewString(table, "url")
 	e.Token = field.NewString(table, "token")
+	e.OperationSync = field.NewBool(table, "operation_sync")
+	e.SyncApiRegex = field.NewString(table, "sync_api_regex")
 
 	e.fillFieldMap()
 
@@ -91,7 +97,7 @@ func (e *environment) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
 }
 
 func (e *environment) fillFieldMap() {
-	e.fieldMap = make(map[string]field.Expr, 7)
+	e.fieldMap = make(map[string]field.Expr, 9)
 	e.fieldMap["id"] = e.ID
 	e.fieldMap["created_at"] = e.CreatedAt
 	e.fieldMap["updated_at"] = e.UpdatedAt
@@ -99,6 +105,8 @@ func (e *environment) fillFieldMap() {
 	e.fieldMap["name"] = e.Name
 	e.fieldMap["url"] = e.URL
 	e.fieldMap["token"] = e.Token
+	e.fieldMap["operation_sync"] = e.OperationSync
+	e.fieldMap["sync_api_regex"] = e.SyncApiRegex
 }
 
 func (e environment) clone(db *gorm.DB) environment {

+ 147 - 0
server/router/operation_sync.go

@@ -0,0 +1,147 @@
+package router
+
+import (
+	"bytes"
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	"github.com/0xJacky/Nginx-UI/server/internal/analytic"
+	"github.com/0xJacky/Nginx-UI/server/internal/logger"
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	"io"
+	"net/http"
+	"net/url"
+	"regexp"
+	"sync"
+)
+
+type ErrorRes struct {
+	Message string `json:"message"`
+}
+
+type toolBodyWriter struct {
+	gin.ResponseWriter
+	body *bytes.Buffer
+}
+
+func (r toolBodyWriter) Write(b []byte) (int, error) {
+	return r.body.Write(b)
+}
+
+// OperationSync 针对配置了vip的环境操作进行同步
+func OperationSync() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		bodyBytes, _ := PeekRequest(c.Request)
+		wb := &toolBodyWriter{
+			body:           &bytes.Buffer{},
+			ResponseWriter: c.Writer,
+		}
+		c.Writer = wb
+
+		c.Next()
+		if c.Request.Method == "GET" || !statusValid(c.Writer.Status()) { // 请求有问题,无需执行同步操作
+			wb.ResponseWriter.Write(wb.body.Bytes())
+			return
+		}
+
+		totalCount := 0
+		successCount := 0
+		detailMsg := ""
+		// 后置处理操作同步
+		wg := sync.WaitGroup{}
+		for _, node := range analytic.NodeMap {
+			wg.Add(1)
+			go func(data analytic.Node) {
+				defer wg.Done()
+				if *node.OperationSync && node.Status && requestUrlMatch(c.Request.URL.Path, data) { // 开启操作同步且当前状态正常
+					totalCount++
+					if err := syncNodeOperation(c, data, bodyBytes); err != nil {
+						detailMsg += fmt.Sprintf("node_name: %s, err_msg: %s; ", data.Name, err)
+						return
+					}
+					successCount++
+				}
+			}(*node)
+		}
+		wg.Wait()
+		if successCount < totalCount { // 如果有错误,替换原来的消息内容
+			originBytes := wb.body
+			logger.Infof("origin response body: %s", originBytes)
+			// clear Origin Buffer
+			wb.body = &bytes.Buffer{}
+			wb.ResponseWriter.WriteHeader(http.StatusInternalServerError)
+
+			errorRes := ErrorRes{
+				Message: fmt.Sprintf("operation sync failed, total: %d, success: %d, fail: %d, detail: %s", totalCount, successCount, totalCount-successCount, detailMsg),
+			}
+			byts, _ := json.Marshal(errorRes)
+			wb.Write(byts)
+		}
+		wb.ResponseWriter.Write(wb.body.Bytes())
+	}
+}
+
+func PeekRequest(request *http.Request) ([]byte, error) {
+	if request.Body != nil {
+		byts, err := io.ReadAll(request.Body) // io.ReadAll as Go 1.16, below please use ioutil.ReadAll
+		if err != nil {
+			return nil, err
+		}
+		request.Body = io.NopCloser(bytes.NewReader(byts))
+		return byts, nil
+	}
+	return make([]byte, 0), nil
+}
+
+func requestUrlMatch(url string, node analytic.Node) bool {
+	p, _ := regexp.Compile(node.SyncApiRegex)
+	result := p.FindAllString(url, -1)
+	if len(result) > 0 && result[0] == url {
+		return true
+	}
+	return false
+}
+
+func statusValid(code int) bool {
+	return code < http.StatusMultipleChoices
+}
+
+func syncNodeOperation(c *gin.Context, node analytic.Node, bodyBytes []byte) error {
+	u, err := url.JoinPath(node.URL, c.Request.RequestURI)
+	if err != nil {
+		return err
+	}
+	decodedUri, err := url.QueryUnescape(u)
+	if err != nil {
+		return err
+	}
+	logger.Debugf("syncNodeOperation request: %s, node_id: %d, node_name: %s", decodedUri, node.ID, node.Name)
+	client := http.Client{
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+		},
+	}
+
+	req, err := http.NewRequest(c.Request.Method, decodedUri, bytes.NewReader(bodyBytes))
+	req.Header.Set("X-Node-Secret", node.Token)
+
+	res, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer res.Body.Close()
+	byts, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	if !statusValid(res.StatusCode) {
+		errRes := ErrorRes{}
+		if err = json.Unmarshal(byts, &errRes); err != nil {
+			return err
+		}
+		return errors.New(errRes.Message)
+	}
+	logger.Debug("syncNodeOperation result: ", string(byts))
+	return nil
+}

+ 2 - 0
server/router/routers.go

@@ -15,6 +15,8 @@ func InitRouter() *gin.Engine {
 
 	r.Use(cacheJs())
 
+	r.Use(OperationSync())
+
 	r.Use(static.Serve("/", mustFS("")))
 
 	r.NoRoute(func(c *gin.Context) {

Some files were not shown because too many files changed in this diff