Prechádzať zdrojové kódy

feat: batch upgrade nginx-ui on remote nodes #424.

Jacky 9 mesiacov pred
rodič
commit
d389bb07ae

+ 1 - 0
app/components.d.ts

@@ -54,6 +54,7 @@ declare module 'vue' {
     ASelect: typeof import('ant-design-vue/es')['Select']
     ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
     ASpace: typeof import('ant-design-vue/es')['Space']
+    ASpin: typeof import('ant-design-vue/es')['Spin']
     AStatistic: typeof import('ant-design-vue/es')['Statistic']
     AStep: typeof import('ant-design-vue/es')['Step']
     ASteps: typeof import('ant-design-vue/es')['Steps']

+ 3 - 1
app/src/App.vue

@@ -12,6 +12,8 @@ import { useSettingsStore } from '@/pinia'
 import gettext from '@/gettext'
 import loadTranslations from '@/api/translations'
 
+const route = useRoute()
+
 const media = window.matchMedia('(prefers-color-scheme: dark)')
 
 const callback = () => {
@@ -51,7 +53,7 @@ const lang = computed(() => {
 const settings = useSettingsStore()
 const is_theme_dark = computed(() => settings.theme === 'dark')
 
-loadTranslations()
+loadTranslations(route)
 </script>
 
 <template>

+ 3 - 4
app/src/api/translations.ts

@@ -1,8 +1,7 @@
+import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
 import gettext from '@/gettext'
 
-export default async function loadTranslations() {
-  const route = useRoute()
-
+export default async function loadTranslations(route: RouteLocationNormalizedLoadedGeneric) {
   if (gettext.current !== 'en') {
     await fetch(`${import.meta.env.VITE_API_ROOT}/translation/${gettext.current}`).then(async r => {
       gettext.translations[gettext.current] = await r.json()
@@ -12,7 +11,7 @@ export default async function loadTranslations() {
       document.title = `${route.meta.name?.()} | Nginx UI`
   }
 
-  watch(route, () => {
+  watch(() => route, () => {
     if (route?.meta?.name)
       document.title = `${route.meta.name?.()} | Nginx UI`
   })

+ 1 - 1
app/src/components/SetLanguage/SetLanguage.vue

@@ -21,7 +21,7 @@ const current = computed({
 const languageAvailable = gettext.available
 
 watch(current, v => {
-  loadTranslations()
+  loadTranslations(route)
   settings.set_language(v)
   gettext.current = v
 

+ 47 - 58
app/src/components/StdDesign/StdDataDisplay/StdCurd.vue

@@ -1,4 +1,4 @@
-<script setup lang="ts">
+<script setup lang="ts" generic="T=any">
 import { message } from 'ant-design-vue'
 import type { ComputedRef } from 'vue'
 import type { StdTableProps } from './StdTable.vue'
@@ -7,7 +7,7 @@ import StdDataEntry from '@/components/StdDesign/StdDataEntry'
 import type { Column } from '@/components/StdDesign/types'
 import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
 
-export interface StdCurdProps {
+export interface StdCurdProps<T> extends StdTableProps<T> {
   cardTitleKey?: string
   modalMaxWidth?: string | number
   modalMask?: boolean
@@ -16,28 +16,28 @@ export interface StdCurdProps {
 
   disableAdd?: boolean
   onClickAdd?: () => void
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  onClickEdit?: (id: number | string, record: any, index: number) => void
+
+  onClickEdit?: (id: number | string, record: T, index: number) => void
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   beforeSave?: (data: any) => Promise<void>
 }
 
-const props = defineProps<StdTableProps & StdCurdProps>()
-const route = useRoute()
-const router = useRouter()
+const props = defineProps<StdTableProps<T> & StdCurdProps<T>>()
 const visible = ref(false)
-const update = ref(0)
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const data: any = reactive({ id: null })
 const modifyMode = ref(true)
 const editMode = ref<string>()
+const shouldRefetchList = ref(false)
 
 provide('data', data)
 provide('editMode', editMode)
+provide('shouldRefetchList', shouldRefetchList)
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const error: any = reactive({})
 const selected = ref([])
+
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 function onSelect(keys: any) {
   selected.value = keys
@@ -49,19 +49,35 @@ const editableColumns = computed(() => {
   })
 }) as ComputedRef<Column[]>
 
-function add() {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function add(preset: any = undefined) {
+  if (props.onClickAdd)
+    return
   Object.keys(data).forEach(v => {
     delete data[v]
   })
 
+  if (preset)
+    Object.assign(data, preset)
+
   clear_error()
   visible.value = true
   editMode.value = 'create'
   modifyMode.value = true
 }
+
 const table = ref()
 
-const selectedRowKeys = ref([])
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
+  default: () => [],
+})
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const selectedRows = defineModel<any[]>('selectedRows', {
+  type: Array,
+  default: () => [],
+})
 
 const getParams = reactive({
   trash: false,
@@ -91,13 +107,14 @@ function clear_error() {
 }
 
 const stdEntryRef = ref()
+
 async function ok() {
   const { formRef } = stdEntryRef.value
 
   clear_error()
   try {
     await formRef.validateFields()
-    await props?.beforeSave?.(data)
+    props?.beforeSave?.(data)
     props
       .api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } })
       .then(r => {
@@ -111,37 +128,26 @@ async function ok() {
         Object.assign(error, e.errors)
       })
   }
-  catch { }
+  catch {
+    message.error($gettext('Please fill in the required fields'))
+  }
 }
 
 function cancel() {
   visible.value = false
 
   clear_error()
-}
 
-watch(visible, v => {
-  if (!v) {
-    router.push({
-      query: {
-        ...route.query,
-        [`open.${props.rowKey || 'id'}`]: undefined,
-      },
-    })
+  if (shouldRefetchList.value) {
+    get_list()
+    shouldRefetchList.value = false
   }
-})
-
-watch(modifyMode, v => {
-  router.push({
-    query: {
-      ...route.query,
-      modify_mode: v.toString(),
-    },
-  })
-})
+}
 
 function edit(id: number | string) {
-  get(id, true).then(() => {
+  if (props.onClickEdit)
+    return
+  get(id).then(() => {
     visible.value = true
     modifyMode.value = true
     editMode.value = 'modify'
@@ -151,7 +157,7 @@ function edit(id: number | string) {
 }
 
 function view(id: number | string) {
-  get(id, false).then(() => {
+  get(id).then(() => {
     visible.value = true
     modifyMode.value = false
   }).catch(e => {
@@ -159,7 +165,7 @@ function view(id: number | string) {
   })
 }
 
-async function get(id: number | string, _modifyMode: boolean) {
+async function get(id: number | string) {
   return props
     .api!.get(id, { ...props.overwriteParams })
     .then(async r => {
@@ -168,32 +174,14 @@ async function get(id: number | string, _modifyMode: boolean) {
       })
       data.id = null
       Object.assign(data, r)
-      if (!props.disabledModify) {
-        await router.push({
-          query: {
-            ...route.query,
-            [`open.${props.rowKey || 'id'}`]: id,
-            modify_mode: _modifyMode.toString(),
-          },
-        })
-      }
     })
 }
 
-onMounted(async () => {
-  const id = route.query[`open.${props.rowKey || 'id'}`] as string
-  const _modifyMode = (route.query.modify_mode as string) === 'true'
-  if (id && !props.disabledModify && _modifyMode)
-    edit(id)
-
-  if (id && !props.disabledView && !_modifyMode)
-    view(id)
-})
-
 const modalTitle = computed(() => {
   return data.id ? modifyMode.value ? $gettext('Modify') : $gettext('View Details') : $gettext('Add')
 })
 
+const localOverwriteParams = reactive(props.overwriteParams ?? {})
 </script>
 
 <template>
@@ -209,7 +197,7 @@ const modalTitle = computed(() => {
         <ASpace>
           <slot name="beforeAdd" />
           <a
-            v-if="!disableAdd"
+            v-if="!disableAdd && !getParams.trash"
             @click="add"
           >{{ $gettext('Add') }}</a>
           <slot name="extra" />
@@ -224,7 +212,7 @@ const modalTitle = computed(() => {
               v-else
               @click="getParams.trash = false"
             >
-              {{ $gettext('List') }}
+              {{ $gettext('Back to list') }}
             </a>
           </template>
         </ASpace>
@@ -232,11 +220,12 @@ const modalTitle = computed(() => {
 
       <StdTable
         ref="table"
-        :key="update"
         v-model:selected-row-keys="selectedRowKeys"
+        v-model:selected-rows="selectedRows"
         v-bind="{
           ...props,
           getParams,
+          overwriteParams: localOverwriteParams,
         }"
         @click-edit="edit"
         @click-view="view"
@@ -269,7 +258,7 @@ const modalTitle = computed(() => {
       @ok="ok"
     >
       <div
-        v-if="!disabledModify && !disabledView"
+        v-if="!disableModify && !disableView && editMode === 'modify'"
         class="m-2 flex justify-end"
       >
         <ASwitch
@@ -294,7 +283,7 @@ const modalTitle = computed(() => {
           ref="stdEntryRef"
           :data-list="editableColumns"
           :data-source="data"
-          :error="error"
+          :errors="error"
         />
 
         <slot

+ 71 - 44
app/src/components/StdDesign/StdDataDisplay/StdTable.vue

@@ -1,4 +1,5 @@
-<script setup lang="ts">
+<script setup lang="ts" generic="T=any">
+import type { TableProps } from 'ant-design-vue'
 import { message } from 'ant-design-vue'
 import { HolderOutlined } from '@ant-design/icons-vue'
 import type { ComputedRef, Ref } from 'vue'
@@ -14,12 +15,13 @@ import type { Column } from '@/components/StdDesign/types'
 import useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
 import type Curd from '@/api/curd'
 
-export interface StdTableProps {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export interface StdTableProps<T = any> {
   title?: string
   mode?: string
   rowKey?: string
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  api: Curd<any>
+
+  api: Curd<T>
   columns: Column[]
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   getParams?: Record<string, any>
@@ -31,25 +33,45 @@ export interface StdTableProps {
   exportMaterial?: boolean
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   overwriteParams?: Record<string, any>
-  disabledView?: boolean
-  disabledModify?: boolean
+  disableView?: boolean
+  disableModify?: boolean
   selectionType?: string
   sortable?: boolean
   disableDelete?: boolean
   disablePagination?: boolean
   sortableMoveHook?: (oldRow: number[], newRow: number[]) => boolean
   scrollX?: string | number
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  getCheckboxProps?: (record: any) => any
 }
 
-const props = withDefaults(defineProps<StdTableProps>(), {
+const props = withDefaults(defineProps<StdTableProps<T>>(), {
   rowKey: 'id',
 })
 
 const emit = defineEmits(['clickEdit', 'clickView', 'clickBatchModify', 'update:selectedRowKeys'])
 const route = useRoute()
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const dataSource: Ref<any[]> = ref([])
+
+const dataSource: Ref<T[]> = ref([])
 const expandKeysList: Ref<Key[]> = ref([])
+
+watch(dataSource, () => {
+  const res: Key[] = []
+
+  function buildKeysList(record) {
+    record.children?.forEach(v => {
+      buildKeysList(v)
+    })
+    res.push(record[props.rowKey])
+  }
+
+  dataSource.value.forEach(v => {
+    buildKeysList(v)
+  })
+
+  expandKeysList.value = res
+})
+
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const rowsKeyIndexMap: Ref<Record<number, any>> = ref({})
 const loading = ref(true)
@@ -72,11 +94,6 @@ const params = reactive({
   ...props.getParams,
 })
 
-const get_list = _.debounce(_get_list, 200, {
-  leading: true,
-  trailing: false,
-})
-
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
   default: () => [],
@@ -105,9 +122,8 @@ const searchColumns = computed(() => {
           edit: column.search,
         })
       }
-      else {
-        _searchColumns.push({ ...column })
-      }
+
+      else { _searchColumns.push({ ...column }) }
     }
   })
 
@@ -137,8 +153,9 @@ const batchColumns = computed(() => {
   return batch
 })
 
-watch(route, () => {
-  params.trash = route.query.trash === 'true'
+const get_list = _.debounce(_get_list, 100, {
+  leading: true,
+  trailing: false,
 })
 
 const filterParams = reactive({})
@@ -214,12 +231,13 @@ function buildIndexMap(data: any, level: number = 0, index: number = 0, total: n
 }
 
 async function _get_list(page_num = null, page_size = 20) {
+  dataSource.value = []
   loading.value = true
   if (page_num) {
     params.page = page_num
     params.page_size = page_size
   }
-  props.api?.get_list({ ...route.query, ...params, ...props.overwriteParams }).then(async r => {
+  props.api?.get_list({ ...params, ...props.overwriteParams }).then(async r => {
     dataSource.value = r.data
     rowsKeyIndexMap.value = {}
     if (props.sortable)
@@ -260,6 +278,7 @@ function onTableChange(_pagination: TablePaginationConfig, filters: Record<strin
       params[v] = filters[v]
     })
   }
+
   if (_pagination)
     selectedRowKeys.value = []
 }
@@ -297,15 +316,13 @@ async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
       selectedRows.value = filteredRows
     })
   }
+  else if (selected) {
+    selectedRowKeys.value = record[props.rowKey]
+    selectedRows.value = [record]
+  }
   else {
-    if (selected) {
-      selectedRowKeys.value = record[props.rowKey]
-      selectedRows.value = [record]
-    }
-    else {
-      selectedRowKeys.value = []
-      selectedRows.value = []
-    }
+    selectedRowKeys.value = []
+    selectedRows.value = []
   }
 }
 
@@ -358,18 +375,29 @@ const resetSearch = async () => {
   router.push({ query: {} }).catch(() => {
   })
 
+  Object.keys(filterParams).forEach(v => {
+    delete filterParams[v]
+  })
+
   updateFilter.value++
 }
 
-watch(params, async v => {
-  if (init.value) {
-    await nextTick(() => {
-      get_list()
-    })
+watch(params, v => {
+  if (!init.value)
+    return
 
-    if (!props.disableQueryParams)
-      await router.push({ query: v as RouteParams })
-  }
+  if (!props.disableQueryParams)
+    router.push({ query: { ...v as RouteParams } })
+  else
+    get_list()
+})
+
+watch(() => route.query, async () => {
+  params.trash = route.query.trash === 'true'
+  params.team_id = route.query.team_id
+
+  if (init.value)
+    await get_list()
 })
 
 if (props.getParams) {
@@ -401,14 +429,12 @@ const rowSelection = computed(() => {
       selectedRowKeys: selectedRowKeys.value,
       onSelect,
       onSelectAll,
+      getCheckboxProps: props?.getCheckboxProps,
       type: (batchColumns.value.length > 0 || props.exportExcel) ? 'checkbox' : props.selectionType,
     }
   }
-  else {
-    return null
-  }
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-}) as ComputedRef<any>
+  else { return null }
+}) as ComputedRef<TableProps['rowSelection']>
 
 const hasSelectedRow = computed(() => {
   return batchColumns.value.length > 0 && selectedRowKeys.value.length > 0
@@ -435,6 +461,7 @@ const paginationSize = computed(() => {
   else
     return 'default'
 })
+
 </script>
 
 <template>
@@ -482,7 +509,7 @@ const paginationSize = computed(() => {
           {{ text }}
         </template>
         <template v-if="column.dataIndex === 'action'">
-          <template v-if="!props.disabledView && !params.trash">
+          <template v-if="!props.disableView && !params.trash">
             <AButton
               type="link"
               size="small"
@@ -491,12 +518,12 @@ const paginationSize = computed(() => {
               {{ $gettext('View') }}
             </AButton>
             <ADivider
-              v-if="!props.disabledModify"
+              v-if="!props.disableModify"
               type="vertical"
             />
           </template>
 
-          <template v-if="!props.disabledModify && !params.trash">
+          <template v-if="!props.disableModify && !params.trash">
             <AButton
               type="link"
               size="small"

+ 127 - 83
app/src/language/en/app.po

@@ -26,15 +26,15 @@ msgstr "Username"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "Action"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr ""
 msgid "API Token"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr ""
 
@@ -106,17 +106,17 @@ msgstr "Are you sure you want to remove this directive?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "Are you sure you want to remove this directive?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Are you sure you want to remove this directive?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Are you sure you want to remove this directive?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgstr "Are you sure you want to remove this directive?"
@@ -126,7 +126,7 @@ msgstr "Are you sure you want to remove this directive?"
 msgid "Are you sure you want to delete?"
 msgstr "Are you sure you want to remove this directive?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgstr "Are you sure you want to remove this directive?"
@@ -145,11 +145,11 @@ msgstr "Are you sure you want to remove this directive?"
 msgid "Are you sure you want to remove this location?"
 msgstr "Are you sure you want to remove this directive?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr ""
 
@@ -194,6 +194,10 @@ msgstr "Back"
 msgid "Back Home"
 msgstr "Back"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -223,11 +227,15 @@ msgid "Basic Mode"
 msgstr "Basic Mode"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 msgid "Batch Modify"
 msgstr "Modify Config"
 
+#: src/views/environment/BatchUpgrader.vue:154
+msgid "Batch Upgrade"
+msgstr ""
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "Build with"
@@ -240,9 +248,9 @@ msgstr ""
 msgid "CADir"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -293,11 +301,11 @@ msgstr ""
 msgid "Change Certificate"
 msgstr "Certificate is valid"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr ""
 
@@ -305,7 +313,7 @@ msgstr ""
 msgid "Cleaning environment variables"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -350,7 +358,7 @@ msgstr "Configurations"
 msgid "Configure SSL"
 msgstr "Configure SSL"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr ""
 
@@ -360,7 +368,7 @@ msgstr ""
 msgid "Content"
 msgstr "Content"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr ""
 
@@ -397,7 +405,7 @@ msgstr ""
 msgid "Credentials"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr ""
 
@@ -424,7 +432,7 @@ msgstr "Database (Optional, default: database)"
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -432,7 +440,7 @@ msgstr ""
 msgid "Delete"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -444,7 +452,7 @@ msgstr ""
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 msgid "Deleted successfully"
 msgstr "Disabled successfully"
@@ -508,8 +516,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "Disable auto-renewal failed for %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "Disabled"
 
@@ -604,7 +613,7 @@ msgstr ""
 msgid "Downloading latest release"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr ""
 
@@ -704,7 +713,8 @@ msgstr "Enable TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -724,7 +734,7 @@ msgstr "Enabled successfully"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Encrypt website with Let's Encrypt"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr ""
 
@@ -732,7 +742,7 @@ msgstr ""
 msgid "Environment variables cleaned"
 msgstr ""
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 msgid "Environments"
 msgstr "Comments"
@@ -745,7 +755,7 @@ msgstr ""
 msgid "Error Logs"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr ""
 
@@ -846,7 +856,7 @@ msgstr ""
 msgid "Generating private key for registering account"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 #, fuzzy
 msgid "Get release information error"
 msgstr "Base information"
@@ -965,7 +975,7 @@ msgstr ""
 msgid "Key Type"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr ""
 
@@ -988,12 +998,12 @@ msgstr "Leave blank for no change"
 msgid "License"
 msgstr "License"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -1002,11 +1012,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr "Load Averages:"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 msgid "Load successfully"
 msgstr "Saved successfully"
@@ -1109,9 +1119,9 @@ msgstr ""
 msgid "Model"
 msgstr "Advance Mode"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 #, fuzzy
 msgid "Modify"
 msgstr "Modify Config"
@@ -1125,7 +1135,7 @@ msgstr "Certificate Status"
 msgid "Modify Config"
 msgstr "Modify Config"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "Modify Mode"
 msgstr "Modify Config"
@@ -1144,7 +1154,7 @@ msgstr "Single Directive"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1167,7 +1177,7 @@ msgstr "Network Total Receive"
 msgid "Network Total Send"
 msgstr "Network Total Send"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr ""
 
@@ -1212,11 +1222,11 @@ msgstr "Saved successfully"
 msgid "Nginx restarted successfully"
 msgstr "Saved successfully"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1268,22 +1278,22 @@ msgid "Obtaining certificate"
 msgstr ""
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1304,8 +1314,8 @@ msgstr ""
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr ""
 
@@ -1313,7 +1323,7 @@ msgstr ""
 msgid "OpenAI"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 msgid "OS"
 msgstr "OS:"
@@ -1350,6 +1360,10 @@ msgstr "Password (*)"
 msgid "Path"
 msgstr "Path"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr ""
@@ -1364,6 +1378,10 @@ msgid ""
 "provider."
 msgstr ""
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1399,7 +1417,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr ""
 
@@ -1438,11 +1458,11 @@ msgstr "Reads"
 msgid "Receive"
 msgstr "Receive"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 msgid "Recovered Successfully"
 msgstr "Saved successfully"
@@ -1451,7 +1471,7 @@ msgstr "Saved successfully"
 msgid "Recursive Nameservers"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr ""
 
@@ -1477,16 +1497,16 @@ msgstr ""
 msgid "Registration Status"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 msgid "Reinstall"
 msgstr "Install"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr ""
@@ -1544,7 +1564,7 @@ msgstr "Enabled successfully"
 msgid "Requested with wrong parameters"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr ""
 
@@ -1565,7 +1585,7 @@ msgstr "Advance Mode"
 msgid "Running"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1583,7 +1603,7 @@ msgid "Save error %{msg}"
 msgstr "Save error %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #, fuzzy
@@ -1615,10 +1635,11 @@ msgstr "Send"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1701,14 +1722,16 @@ msgstr "Certificate Status"
 msgid "SSO Login"
 msgstr "Login"
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 #, fuzzy
 msgid "Stable"
 msgstr "Enabled"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "Status"
 
@@ -1833,6 +1856,13 @@ msgstr "Certificate Status"
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1880,6 +1910,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1905,7 +1940,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1918,7 +1953,7 @@ msgstr ""
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1929,17 +1964,22 @@ msgstr "Updated at"
 msgid "Updated successfully"
 msgstr "Saved successfully"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "Saved successfully"
+
 #: src/language/constants.ts:29
 #, fuzzy
 msgid "Upgraded successfully"
 msgstr "Saved successfully"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr ""
 
@@ -1951,11 +1991,11 @@ msgstr ""
 msgid "Uptime:"
 msgstr "Uptime:"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 msgid "User"
 msgstr "Username"
@@ -1977,7 +2017,11 @@ msgstr "Username (*)"
 msgid "Valid"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+msgid "Version"
+msgstr ""
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr ""
@@ -1987,11 +2031,11 @@ msgstr ""
 msgid "View all notifications"
 msgstr "Certificate is valid"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "View Mode"
 msgstr "Basic Mode"
@@ -2034,11 +2078,11 @@ msgstr ""
 msgid "Yes"
 msgstr "Yes"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr ""
 

+ 129 - 83
app/src/language/es/app.po

@@ -31,15 +31,15 @@ msgstr "Usuario"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "Acción"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -94,7 +94,7 @@ msgstr "Proxy de la API"
 msgid "API Token"
 msgstr "Token de la API"
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr "Arquitectura"
 
@@ -108,16 +108,16 @@ msgstr "¿Está seguro de que quiere borrar?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "¿Está seguro de que desea borrar todas las notificaciones?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "¿Está seguro de que desea borrar el registro del chat?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "¿Está seguro de que quiere borrar?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgstr "¿Está seguro de que quiere borrar?"
@@ -126,7 +126,7 @@ msgstr "¿Está seguro de que quiere borrar?"
 msgid "Are you sure you want to delete?"
 msgstr "¿Está seguro de que quiere borrar?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgstr "¿Está seguro de que quiere borrar esta directiva?"
@@ -144,11 +144,11 @@ msgstr "¿Está seguro de que quiere borrar esta directiva?"
 msgid "Are you sure you want to remove this location?"
 msgstr "¿Está seguro de que quiere borrar esta ubicación?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr "Preguntar por ayuda a ChatGPT"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr "Asistente"
 
@@ -193,6 +193,10 @@ msgstr "Volver"
 msgid "Back Home"
 msgstr "Volver al Inicio"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -221,10 +225,15 @@ msgid "Basic Mode"
 msgstr "Modo Básico"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgstr "Modificar por lotes"
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "Actualizar"
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "Desarrollado con"
@@ -237,9 +246,9 @@ msgstr ""
 msgid "CADir"
 msgstr "Directorio CA"
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -287,11 +296,11 @@ msgstr "Método de desafío"
 msgid "Change Certificate"
 msgstr "Cambiar Certificado"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr "Canal"
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr "Intentar nuevamente"
 
@@ -299,7 +308,7 @@ msgstr "Intentar nuevamente"
 msgid "Cleaning environment variables"
 msgstr "Borrar las variables de entorno"
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -342,7 +351,7 @@ msgstr "Configuraciones"
 msgid "Configure SSL"
 msgstr "Configurar SSL"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr "Conectado"
 
@@ -352,7 +361,7 @@ msgstr "Conectado"
 msgid "Content"
 msgstr "Contenido"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr "Actualización del kernel"
 
@@ -388,7 +397,7 @@ msgstr "Credencial"
 msgid "Credentials"
 msgstr "Credenciales"
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr "Versión actual"
 
@@ -415,7 +424,7 @@ msgstr "Base de datos (Opcional, default: database)"
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -423,7 +432,7 @@ msgstr ""
 msgid "Delete"
 msgstr "Eliminar"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -435,7 +444,7 @@ msgstr "Eliminar sitio: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Eliminar stream: %{site_name}"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 msgid "Deleted successfully"
 msgstr "Borrado exitoso"
 
@@ -495,8 +504,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "No se pudo desactivar la renovación automática por %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "Desactivado"
 
@@ -585,7 +595,7 @@ msgstr "Error al descargar la última versión"
 msgid "Downloading latest release"
 msgstr "Descargando la última versión"
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr "Modo de ejecución de prueba habilitado"
 
@@ -678,7 +688,8 @@ msgstr "Habilitar TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -698,7 +709,7 @@ msgstr "Habilitado con éxito"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Encriptar sitio web con Let's Encrypt"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr "Entorno"
 
@@ -707,7 +718,7 @@ msgstr "Entorno"
 msgid "Environment variables cleaned"
 msgstr "Configuración de variables de entorno"
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgstr "Entornos"
 
@@ -719,7 +730,7 @@ msgstr "Error"
 msgid "Error Logs"
 msgstr "Registros de acceso"
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr "Ruta ejecutable"
 
@@ -816,7 +827,7 @@ msgstr "Generar"
 msgid "Generating private key for registering account"
 msgstr "Generando clave privada para registrar cuenta"
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 msgid "Get release information error"
 msgstr "Obtener error de información de versión"
 
@@ -931,7 +942,7 @@ msgstr "Secreto Jwt"
 msgid "Key Type"
 msgstr "Tipo"
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr "Comprobado por última vez el"
 
@@ -952,12 +963,12 @@ msgstr "Dejarlo en blanco no cambiará nada"
 msgid "License"
 msgstr "Licencia"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr "Iniciar conexión"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -965,11 +976,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr "Promedios de carga:"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 msgid "Load successfully"
 msgstr "Guardado con éxito"
@@ -1067,9 +1078,9 @@ msgstr ""
 msgid "Model"
 msgstr "Modo de ejecución"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 msgid "Modify"
 msgstr "Modificar"
 
@@ -1081,7 +1092,7 @@ msgstr "Modificar Certificado"
 msgid "Modify Config"
 msgstr "Modificar configuración"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "Modify Mode"
 msgstr "Modificar"
@@ -1099,7 +1110,7 @@ msgstr "Directiva multilínea"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1122,7 +1133,7 @@ msgstr "Total recibido por la red"
 msgid "Network Total Send"
 msgstr "Total enviado por la red"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr "Se liberó una nueva versión"
 
@@ -1164,11 +1175,11 @@ msgstr "Nginx recargado con éxito"
 msgid "Nginx restarted successfully"
 msgstr "Nginx reiniciado con éxito"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1217,22 +1228,22 @@ msgid "Obtaining certificate"
 msgstr "Obteniendo certificado"
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr "Desconectado"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1253,8 +1264,8 @@ msgstr "Una vez que se complete la verificación, los registros se eliminarán."
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr "En línea"
 
@@ -1262,7 +1273,7 @@ msgstr "En línea"
 msgid "OpenAI"
 msgstr "OpenAI"
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgstr "SO"
 
@@ -1298,6 +1309,10 @@ msgstr "Contraseña (*)"
 msgid "Path"
 msgstr "Ruta"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr "Error al ejecutar la actualización del kernel"
@@ -1314,6 +1329,10 @@ msgstr ""
 "Por favor, complete las credenciales de autenticación API proporcionadas por "
 "su proveedor de DNS."
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1356,7 +1375,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgstr "¡Seleccione al menos un nodo!"
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr "Prelanzamiento"
 
@@ -1394,11 +1415,11 @@ msgstr "Lecturas"
 msgid "Receive"
 msgstr "Recibido"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 msgid "Recovered Successfully"
 msgstr "Eliminado con éxito"
@@ -1407,7 +1428,7 @@ msgstr "Eliminado con éxito"
 msgid "Recursive Nameservers"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr "Regenerar respuesta"
 
@@ -1435,15 +1456,15 @@ msgstr "Registrando Usuario"
 msgid "Registration Status"
 msgstr "Registrando Usuario"
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgstr "Reinstalar"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr "Nota de versión"
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr "Recargar"
@@ -1495,7 +1516,7 @@ msgstr "Renovado con éxito"
 msgid "Requested with wrong parameters"
 msgstr "Pedido con parámetros incorrectos"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr "Limpiar"
 
@@ -1515,7 +1536,7 @@ msgstr "Modo de ejecución"
 msgid "Running"
 msgstr "Corriendo"
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1533,7 +1554,7 @@ msgid "Save error %{msg}"
 msgstr "Error al guardar %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
@@ -1564,10 +1585,11 @@ msgstr "Enviado"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1644,13 +1666,15 @@ msgstr "Ruta del certificado SSL"
 msgid "SSO Login"
 msgstr "Acceso SSO"
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 msgid "Stable"
 msgstr "Estable"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "Estado"
 
@@ -1773,6 +1797,13 @@ msgstr "La ruta existe, pero el archivo no es una clave privada"
 msgid "The path exists, but the file is not a private key"
 msgstr "La ruta existe, pero el archivo no es una clave privada"
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1821,6 +1852,11 @@ msgstr "Este campo es obligatorio"
 msgid "This field should not be empty"
 msgstr "Este campo no debe estar vacío"
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1850,7 +1886,7 @@ msgstr "El token no es válido"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1863,7 +1899,7 @@ msgstr "Tipo"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1873,16 +1909,21 @@ msgstr "Actualizado a"
 msgid "Updated successfully"
 msgstr "Actualización exitosa"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Actualizar"
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "Actualización exitosa"
+
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgstr "Actualización exitosa"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Actualizando Nginx UI, por favor espere..."
 
@@ -1894,11 +1935,11 @@ msgstr "Nombre de la Transmisión"
 msgid "Uptime:"
 msgstr "Tiempo encendido:"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr "URL"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "User"
 msgstr "Usuario"
 
@@ -1919,7 +1960,12 @@ msgstr "Nombre de usuario (*)"
 msgid "Valid"
 msgstr "Válido"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+#, fuzzy
+msgid "Version"
+msgstr "Versión actual"
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Ver"
@@ -1928,12 +1974,12 @@ msgstr "Ver"
 msgid "View all notifications"
 msgstr "Ver todas las notificaciones"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 #, fuzzy
 msgid "View Details"
 msgstr "Detalles"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "View Mode"
 msgstr "Modo Básico"
@@ -1980,11 +2026,11 @@ msgstr "Escribir certificado a disco"
 msgid "Yes"
 msgstr "Si"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr "Estás usando la última versión"
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "Puede consultar la actualización de Nginx UI en esta página."
 

+ 129 - 83
app/src/language/fr_FR/app.po

@@ -28,15 +28,15 @@ msgstr "Nom d'utilisateur"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "Action"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -94,7 +94,7 @@ msgstr "Proxy d'API"
 msgid "API Token"
 msgstr "Jeton d'API"
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 #, fuzzy
 msgid "Arch"
 msgstr "Arch"
@@ -110,16 +110,16 @@ msgstr "Etes-vous sûr que vous voulez supprimer ?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "Voulez-vous vraiment effacer l'historique du chat ?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Voulez-vous vraiment effacer l'historique du chat ?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
@@ -128,7 +128,7 @@ msgstr "Etes-vous sûr que vous voulez supprimer ?"
 msgid "Are you sure you want to delete?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgstr "Voulez-vous vraiment supprimer cette directive ?"
@@ -146,12 +146,12 @@ msgstr "Voulez-vous vraiment supprimer cette directive ?"
 msgid "Are you sure you want to remove this location?"
 msgstr "Voulez-vous vraiment supprimer cette localisation ?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 #, fuzzy
 msgid "Ask ChatGPT for Help"
 msgstr "Modèle ChatGPT"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr ""
 
@@ -196,6 +196,10 @@ msgstr "Retour"
 msgid "Back Home"
 msgstr "Retour au menu principal"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -224,11 +228,16 @@ msgid "Basic Mode"
 msgstr "Mode simple"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 msgid "Batch Modify"
 msgstr "Batch Modify"
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "Mettre à niveau"
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "Build avec"
@@ -241,9 +250,9 @@ msgstr ""
 msgid "CADir"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -293,11 +302,11 @@ msgstr "Méthode de challenge"
 msgid "Change Certificate"
 msgstr "Changer de certificat"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr "Revérifier"
 
@@ -305,7 +314,7 @@ msgstr "Revérifier"
 msgid "Cleaning environment variables"
 msgstr "Nettoyage des variables d'environnement"
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -349,7 +358,7 @@ msgstr "Configurations"
 msgid "Configure SSL"
 msgstr "Configurer SSL"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr ""
 
@@ -359,7 +368,7 @@ msgstr ""
 msgid "Content"
 msgstr "Contenu"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr "Mise à jour du core"
 
@@ -396,7 +405,7 @@ msgstr "Identifiant"
 msgid "Credentials"
 msgstr "Identifiants"
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr "Version actuelle"
 
@@ -423,7 +432,7 @@ msgstr "Base de données (Facultatif, par défaut : database)"
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -431,7 +440,7 @@ msgstr ""
 msgid "Delete"
 msgstr "Supprimer"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -444,7 +453,7 @@ msgstr "Supprimer le site : %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Supprimer le site : %{site_name}"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 msgid "Deleted successfully"
 msgstr "Désactivé avec succès"
@@ -508,8 +517,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "La désactivation du renouvellement automatique a échoué pour %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "Désactivé"
 
@@ -603,7 +613,7 @@ msgstr "Erreur de téléchargement de la dernière version"
 msgid "Downloading latest release"
 msgstr "Téléchargement de la dernière version"
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr ""
 
@@ -703,7 +713,8 @@ msgstr "Activer TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -723,7 +734,7 @@ msgstr "Activé avec succès"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Crypter le site Web avec Let's Encrypt"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr ""
 
@@ -732,7 +743,7 @@ msgstr ""
 msgid "Environment variables cleaned"
 msgstr "Définition des variables d'environnement"
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 msgid "Environments"
 msgstr "Commentaires"
@@ -745,7 +756,7 @@ msgstr "Erreur"
 msgid "Error Logs"
 msgstr "Journaux d'erreurs"
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr "Chemin exécutable"
 
@@ -847,7 +858,7 @@ msgstr "Générer"
 msgid "Generating private key for registering account"
 msgstr "Génération de clé privée pour l'enregistrement du compte"
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 msgid "Get release information error"
 msgstr "Erreur d'obtention des informations sur la version"
 
@@ -965,7 +976,7 @@ msgstr "Secret Jwt"
 msgid "Key Type"
 msgstr "Type"
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr "Dernière vérification le"
 
@@ -988,12 +999,12 @@ msgstr "Laisser vide pour aucun changement"
 msgid "License"
 msgstr "Licence"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -1002,11 +1013,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr "Charges moyennes :"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 msgid "Load successfully"
 msgstr "Enregistré avec succès"
@@ -1111,9 +1122,9 @@ msgstr ""
 msgid "Model"
 msgstr "Mode d'exécution"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 msgid "Modify"
 msgstr "Modifier"
 
@@ -1126,7 +1137,7 @@ msgstr "État du certificat"
 msgid "Modify Config"
 msgstr "Modifier la configuration"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "Modify Mode"
 msgstr "Modifier"
@@ -1144,7 +1155,7 @@ msgstr "Directive multiligne"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1167,7 +1178,7 @@ msgstr "Réception totale du réseau"
 msgid "Network Total Send"
 msgstr "Envoi total réseau"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr "Nouvelle version publiée"
 
@@ -1210,11 +1221,11 @@ msgstr "Nginx a été rechargé avec succès"
 msgid "Nginx restarted successfully"
 msgstr "Nginx a redémarré avec succès"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1266,22 +1277,22 @@ msgid "Obtaining certificate"
 msgstr "Obtention du certificat"
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1302,8 +1313,8 @@ msgstr ""
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr ""
 
@@ -1311,7 +1322,7 @@ msgstr ""
 msgid "OpenAI"
 msgstr "OpenAI"
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgstr "OS"
 
@@ -1347,6 +1358,10 @@ msgstr "Mot de passe (*)"
 msgid "Path"
 msgstr "Chemin"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr "Erreur lors de la mise a niveau du core"
@@ -1361,6 +1376,10 @@ msgid ""
 "provider."
 msgstr ""
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #, fuzzy
 msgid ""
@@ -1403,7 +1422,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr ""
 
@@ -1444,11 +1465,11 @@ msgstr "Lectures"
 msgid "Receive"
 msgstr "Recevoir"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 msgid "Recovered Successfully"
 msgstr "Enregistré avec succès"
@@ -1457,7 +1478,7 @@ msgstr "Enregistré avec succès"
 msgid "Recursive Nameservers"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr "Régénérer la réponse"
 
@@ -1485,15 +1506,15 @@ msgstr "Enregistrement de l'utilisateur"
 msgid "Registration Status"
 msgstr "Enregistrement de l'utilisateur"
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgstr "Réinstaller"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr "Note de version"
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr "Recharger"
@@ -1551,7 +1572,7 @@ msgstr "Activé avec succès"
 msgid "Requested with wrong parameters"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr "Réinitialiser"
 
@@ -1571,7 +1592,7 @@ msgstr "Mode d'exécution"
 msgid "Running"
 msgstr "En cours d'éxécution"
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1589,7 +1610,7 @@ msgid "Save error %{msg}"
 msgstr "Enregistrer l'erreur %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
@@ -1620,10 +1641,11 @@ msgstr "Envoyer"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1705,14 +1727,16 @@ msgstr "Chemin du certificat SSL"
 msgid "SSO Login"
 msgstr "Connexion"
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 #, fuzzy
 msgid "Stable"
 msgstr "Tableau"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "Statut"
 
@@ -1838,6 +1862,13 @@ msgstr "Chemin de la clé du certificat SSL"
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1889,6 +1920,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1918,7 +1954,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1931,7 +1967,7 @@ msgstr "Type"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1941,16 +1977,21 @@ msgstr "Mis à jour le"
 msgid "Updated successfully"
 msgstr "Mis à jour avec succés"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Mettre à niveau"
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "Mise à niveau réussie"
+
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgstr "Mise à niveau réussie"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Mise à jour de Nginx UI, veuillez patienter..."
 
@@ -1962,11 +2003,11 @@ msgstr ""
 msgid "Uptime:"
 msgstr "Disponibilité :"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 msgid "User"
 msgstr "Nom d'utilisateur"
@@ -1988,7 +2029,12 @@ msgstr "Nom d'utilisateur (*)"
 msgid "Valid"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+#, fuzzy
+msgid "Version"
+msgstr "Version actuelle"
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Voir"
@@ -1998,11 +2044,11 @@ msgstr "Voir"
 msgid "View all notifications"
 msgstr "Certification"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "View Mode"
 msgstr "Mode simple"
@@ -2047,11 +2093,11 @@ msgstr "Écriture du certificat sur le disque"
 msgid "Yes"
 msgstr "Oui"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr "Vous utilisez la dernière version"
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "Vous pouvez vérifier la mise à niveau de Nginx UI sur cette page."
 

+ 129 - 83
app/src/language/ko_KR/app.po

@@ -30,15 +30,15 @@ msgstr "사용자 이름"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "작업"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -93,7 +93,7 @@ msgstr "API 프록시"
 msgid "API Token"
 msgstr "API 토큰"
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr "아키텍처"
 
@@ -107,16 +107,16 @@ msgstr "정말 삭제하시겠습니까?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "모든 알림을 지우시겠습니까?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "기록을 지우시겠습니까?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "정말 삭제하시겠습니까?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgstr "정말 삭제하시겠습니까?"
@@ -125,7 +125,7 @@ msgstr "정말 삭제하시겠습니까?"
 msgid "Are you sure you want to delete?"
 msgstr "정말 삭제하시겠습니까?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgstr "이 지시문을 정말로 제거하시겠습니까?"
@@ -143,11 +143,11 @@ msgstr "이 지시문을 정말로 제거하시겠습니까?"
 msgid "Are you sure you want to remove this location?"
 msgstr "이 위치를 제거하시겠습니까?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr "ChatGPT에게 도움 요청"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr "조수"
 
@@ -192,6 +192,10 @@ msgstr "뒤로"
 msgid "Back Home"
 msgstr "홈으로"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -220,10 +224,15 @@ msgid "Basic Mode"
 msgstr "기본 모드"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgstr "일괄 수정"
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "업그레이드"
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "빌드 환경"
@@ -236,9 +245,9 @@ msgstr ""
 msgid "CADir"
 msgstr "CA 디렉토리"
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -286,11 +295,11 @@ msgstr "인증 방법"
 msgid "Change Certificate"
 msgstr "인증서 변경"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr "채널"
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr "다시 확인"
 
@@ -298,7 +307,7 @@ msgstr "다시 확인"
 msgid "Cleaning environment variables"
 msgstr "환경 변수 정리"
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -340,7 +349,7 @@ msgstr "구성들"
 msgid "Configure SSL"
 msgstr "SSL 구성하기"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr "연결됨"
 
@@ -350,7 +359,7 @@ msgstr "연결됨"
 msgid "Content"
 msgstr "내용"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr "코어 업그레이드"
 
@@ -386,7 +395,7 @@ msgstr "인증 정보"
 msgid "Credentials"
 msgstr "인증 정보들"
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr "현재 버전"
 
@@ -413,7 +422,7 @@ msgstr "데이터베이스 (선택사항, 기본값: database)"
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -421,7 +430,7 @@ msgstr ""
 msgid "Delete"
 msgstr "삭제"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -433,7 +442,7 @@ msgstr "사이트 삭제: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "스트림 삭제: %{stream_name}"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 msgid "Deleted successfully"
 msgstr "성공적으로 삭제됨"
 
@@ -493,8 +502,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "%{name}의 자동 갱신 비활성화 실패"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "비활성화됨"
 
@@ -582,7 +592,7 @@ msgstr "최신 릴리스 다운로드 오류"
 msgid "Downloading latest release"
 msgstr "최신 릴리스 다운로드 중"
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr "드라이런 모드 활성화됨"
 
@@ -676,7 +686,8 @@ msgstr "TLS 활성화"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -696,7 +707,7 @@ msgstr "성공적으로 활성화됨"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Let's Encrypt로 웹사이트 암호화"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr "환경"
 
@@ -705,7 +716,7 @@ msgstr "환경"
 msgid "Environment variables cleaned"
 msgstr "환경 변수 설정"
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgstr "환경"
 
@@ -717,7 +728,7 @@ msgstr "오류"
 msgid "Error Logs"
 msgstr "오류 로그"
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr "실행 가능 경로"
 
@@ -818,7 +829,7 @@ msgstr "생성"
 msgid "Generating private key for registering account"
 msgstr "계정 등록을 위한 개인 키 생성 중"
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 #, fuzzy
 msgid "Get release information error"
 msgstr "릴리스 정보 가져오기 오류"
@@ -938,7 +949,7 @@ msgstr "Jwt 토큰"
 msgid "Key Type"
 msgstr "키 유형"
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr "마지막 확인 시간"
 
@@ -961,12 +972,12 @@ msgstr "변경사항이 없으면 비워두세요"
 msgid "License"
 msgstr "라이센스"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr "링크 시작"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -975,11 +986,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr "부하 평균:"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 msgid "Load successfully"
 msgstr "성공적으로 저장됨"
@@ -1087,9 +1098,9 @@ msgstr "분"
 msgid "Model"
 msgstr "실행 모드"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 #, fuzzy
 msgid "Modify"
 msgstr "설정 수정"
@@ -1103,7 +1114,7 @@ msgstr "인증서 상태"
 msgid "Modify Config"
 msgstr "설정 수정"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "Modify Mode"
 msgstr "설정 수정"
@@ -1122,7 +1133,7 @@ msgstr "단일 지시문"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1145,7 +1156,7 @@ msgstr "네트워크 총 수신"
 msgid "Network Total Send"
 msgstr "네트워크 총 송신"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr "새 버전 출시"
 
@@ -1190,11 +1201,11 @@ msgstr "Nginx가 성공적으로 리로드됨"
 msgid "Nginx restarted successfully"
 msgstr "Nginx가 성공적으로 재시작됨"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1246,22 +1257,22 @@ msgid "Obtaining certificate"
 msgstr "인증서 획득 중"
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr "오프라인"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1282,8 +1293,8 @@ msgstr "검증이 완료되면, 레코드는 제거됩니다."
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr "온라인"
 
@@ -1291,7 +1302,7 @@ msgstr "온라인"
 msgid "OpenAI"
 msgstr "오픈AI"
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 msgid "OS"
 msgstr "OS"
@@ -1328,6 +1339,10 @@ msgstr "비밀번호 (*)"
 msgid "Path"
 msgstr "경로"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr "핵심 업그레이드 오류 수행"
@@ -1342,6 +1357,10 @@ msgid ""
 "provider."
 msgstr "DNS 제공자가 제공한 API 인증 자격 증명을 입력해주세요."
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1379,7 +1398,9 @@ msgstr "아래의 시간 설정 단위는 모두 초 단위임을 유의해주
 msgid "Please select at least one node!"
 msgstr "적어도 하나의 노드를 선택해주세요!"
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr "사전 출시"
 
@@ -1418,11 +1439,11 @@ msgstr "읽기"
 msgid "Receive"
 msgstr "수신"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 msgid "Recovered Successfully"
 msgstr "성공적으로 제거됨"
@@ -1431,7 +1452,7 @@ msgstr "성공적으로 제거됨"
 msgid "Recursive Nameservers"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr "응답 재생성"
 
@@ -1459,16 +1480,16 @@ msgstr "사용자 등록 중"
 msgid "Registration Status"
 msgstr "사용자 등록 중"
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 msgid "Reinstall"
 msgstr "재설치"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr "릴리스 노트"
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr "리로드"
@@ -1526,7 +1547,7 @@ msgstr "성공적으로 갱신됨"
 msgid "Requested with wrong parameters"
 msgstr "잘못된 매개변수로 요청됨"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr "재설정"
 
@@ -1547,7 +1568,7 @@ msgstr "실행 모드"
 msgid "Running"
 msgstr "실행 중"
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1565,7 +1586,7 @@ msgid "Save error %{msg}"
 msgstr "저장 오류 %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #, fuzzy
@@ -1597,10 +1618,11 @@ msgstr "보내기"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1682,14 +1704,16 @@ msgstr "SSL 인증서 경로"
 msgid "SSO Login"
 msgstr "SSO 로그인"
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 #, fuzzy
 msgid "Stable"
 msgstr "활성화됨"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "상태"
 
@@ -1814,6 +1838,13 @@ msgstr "Certificate Status"
 msgid "The path exists, but the file is not a private key"
 msgstr "경로는 존재하지만 파일은 개인 키가 아닙니다"
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1863,6 +1894,11 @@ msgstr "이 필드는 필수입니다"
 msgid "This field should not be empty"
 msgstr "이 필드는 비워둘 수 없습니다"
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1891,7 +1927,7 @@ msgstr "토큰이 유효하지 않습니다"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1904,7 +1940,7 @@ msgstr "유형"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1915,17 +1951,22 @@ msgstr "업데이트됨"
 msgid "Updated successfully"
 msgstr "성공적으로 저장되었습니다"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "업그레이드"
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "성공적으로 저장되었습니다"
+
 #: src/language/constants.ts:29
 #, fuzzy
 msgid "Upgraded successfully"
 msgstr "성공적으로 저장되었습니다"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Nginx UI를 업그레이드하는 중입니다. 잠시 기다려주세요..."
 
@@ -1937,11 +1978,11 @@ msgstr "업스트림 이름"
 msgid "Uptime:"
 msgstr "가동 시간:"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr "URL"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 msgid "User"
 msgstr "사용자 이름"
@@ -1963,7 +2004,12 @@ msgstr "사용자 이름 (*)"
 msgid "Valid"
 msgstr "유효함"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+#, fuzzy
+msgid "Version"
+msgstr "현재 버전"
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "보기"
@@ -1973,12 +2019,12 @@ msgstr "보기"
 msgid "View all notifications"
 msgstr "Certificate is valid"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 #, fuzzy
 msgid "View Details"
 msgstr "세부 사항"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "View Mode"
 msgstr "기본 모드"
@@ -2025,11 +2071,11 @@ msgstr "인증서를 디스크에 쓰기"
 msgid "Yes"
 msgstr "예"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr "최신 버전을 사용하고 있습니다"
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "이 페이지에서 Nginx UI 업그레이드를 확인할 수 있습니다."
 

+ 127 - 83
app/src/language/messages.pot

@@ -22,7 +22,7 @@ msgstr ""
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/config.ts:34
 #: src/views/domain/DomainList.vue:47
-#: src/views/environment/Environment.vue:129
+#: src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26
 #: src/views/stream/StreamList.vue:47
@@ -30,8 +30,8 @@ msgstr ""
 msgid "Action"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -87,7 +87,7 @@ msgstr ""
 msgid "API Token"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr ""
 
@@ -100,15 +100,15 @@ msgstr ""
 msgid "Are you sure you want to clear all notifications?"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 msgid "Are you sure you want to clear the record of chat?"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 msgid "Are you sure you want to delete this item permanently?"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 msgid "Are you sure you want to delete this item?"
 msgstr ""
 
@@ -117,7 +117,7 @@ msgstr ""
 msgid "Are you sure you want to delete?"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 msgid "Are you sure you want to recover this item?"
 msgstr ""
 
@@ -133,11 +133,11 @@ msgstr ""
 msgid "Are you sure you want to remove this location?"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr ""
 
@@ -183,6 +183,10 @@ msgstr ""
 msgid "Back Home"
 msgstr ""
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -212,10 +216,14 @@ msgid "Basic Mode"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:154
+msgid "Batch Upgrade"
+msgstr ""
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr ""
@@ -228,9 +236,9 @@ msgstr ""
 msgid "CADir"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -278,11 +286,12 @@ msgstr ""
 msgid "Change Certificate"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161
+#: src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr ""
 
@@ -290,7 +299,7 @@ msgstr ""
 msgid "Cleaning environment variables"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -332,7 +341,7 @@ msgstr ""
 msgid "Configure SSL"
 msgstr ""
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr ""
 
@@ -342,7 +351,7 @@ msgstr ""
 msgid "Content"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr ""
 
@@ -379,7 +388,7 @@ msgstr ""
 msgid "Credentials"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr ""
 
@@ -404,7 +413,7 @@ msgstr ""
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -412,7 +421,7 @@ msgstr ""
 msgid "Delete"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -424,7 +433,7 @@ msgstr ""
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 msgid "Deleted successfully"
 msgstr ""
 
@@ -487,7 +496,8 @@ msgstr ""
 #: src/views/domain/cert/ChangeCert.vue:44
 #: src/views/domain/DomainEdit.vue:183
 #: src/views/domain/DomainList.vue:33
-#: src/views/environment/Environment.vue:93
+#: src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95
 #: src/views/stream/StreamEdit.vue:175
 #: src/views/stream/StreamList.vue:33
 msgid "Disabled"
@@ -577,7 +587,8 @@ msgstr ""
 msgid "Downloading latest release"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190
+#: src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr ""
 
@@ -672,7 +683,8 @@ msgstr ""
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177
 #: src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169
@@ -695,7 +707,7 @@ msgid "Encrypt website with Let's Encrypt"
 msgstr ""
 
 #: src/routes/index.ts:212
-#: src/views/environment/Environment.vue:147
+#: src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr ""
 
@@ -703,7 +715,7 @@ msgstr ""
 msgid "Environment variables cleaned"
 msgstr ""
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgstr ""
 
@@ -717,7 +729,7 @@ msgstr ""
 msgid "Error Logs"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr ""
 
@@ -814,7 +826,8 @@ msgstr ""
 msgid "Generating private key for registering account"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179
+#: src/views/system/Upgrade.vue:171
 msgid "Get release information error"
 msgstr ""
 
@@ -926,7 +939,7 @@ msgstr ""
 msgid "Key Type"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr ""
 
@@ -947,12 +960,12 @@ msgstr ""
 msgid "License"
 msgstr ""
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -960,11 +973,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr ""
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 msgid "Load successfully"
 msgstr ""
 
@@ -1055,9 +1068,9 @@ msgstr ""
 msgid "Model"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 msgid "Modify"
 msgstr ""
 
@@ -1070,7 +1083,7 @@ msgstr ""
 msgid "Modify Config"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 msgid "Modify Mode"
 msgstr ""
 
@@ -1088,7 +1101,7 @@ msgstr ""
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13
@@ -1112,7 +1125,7 @@ msgstr ""
 msgid "Network Total Send"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr ""
 
@@ -1156,11 +1169,11 @@ msgstr ""
 msgid "Nginx restarted successfully"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1210,22 +1223,22 @@ msgid "Obtaining certificate"
 msgstr ""
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1246,8 +1259,8 @@ msgstr ""
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr ""
 
@@ -1255,7 +1268,7 @@ msgstr ""
 msgid "OpenAI"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgstr ""
 
@@ -1292,6 +1305,10 @@ msgstr ""
 msgid "Path"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr ""
@@ -1304,6 +1321,10 @@ msgstr ""
 msgid "Please fill in the API authentication credentials provided by your DNS provider."
 msgstr ""
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid "Please first add credentials in Certification > DNS Credentials, and then select one of the credentialsbelow to request the API of the DNS provider."
 msgstr ""
@@ -1336,8 +1357,10 @@ msgstr ""
 msgid "Please select at least one node!"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:191
-#: src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222
+#: src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr ""
 
@@ -1376,11 +1399,11 @@ msgstr ""
 msgid "Receive"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 msgid "Recovered Successfully"
 msgstr ""
 
@@ -1388,7 +1411,7 @@ msgstr ""
 msgid "Recursive Nameservers"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr ""
 
@@ -1412,15 +1435,15 @@ msgstr ""
 msgid "Registration Status"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr ""
@@ -1471,7 +1494,7 @@ msgstr ""
 msgid "Requested with wrong parameters"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr ""
 
@@ -1491,7 +1514,7 @@ msgstr ""
 msgid "Running"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96
 #: src/views/domain/DomainEdit.vue:261
@@ -1512,7 +1535,7 @@ msgid "Save error %{msg}"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
@@ -1544,11 +1567,12 @@ msgstr ""
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40
 #: src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15
 #: src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78
@@ -1625,15 +1649,17 @@ msgstr ""
 msgid "SSO Login"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:188
-#: src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216
+#: src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 msgid "Stable"
 msgstr ""
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88
 #: src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76
+#: src/views/environment/envColumns.tsx:78
 #: src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr ""
@@ -1742,6 +1768,10 @@ msgstr ""
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 
+#: src/views/dashboard/Environments.vue:148
+msgid "The remote Nginx UI version is not compatible with the local Nginx UI version. To avoid potential errors, please upgrade the remote Nginx UI to match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid "The server name should only contain letters, unicode, numbers, hyphens, dashes, and dots."
 msgstr ""
@@ -1782,6 +1812,10 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1803,7 +1837,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1819,7 +1853,7 @@ msgstr ""
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: src/views/domain/DomainList.vue:41
-#: src/views/environment/Environment.vue:122
+#: src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41
 #: src/views/user/User.vue:37
@@ -1831,15 +1865,21 @@ msgid "Updated successfully"
 msgstr ""
 
 #: src/routes/index.ts:263
-#: src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145
+#: src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:139
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr ""
+
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgstr ""
 
+#: src/views/environment/BatchUpgrader.vue:90
 #: src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr ""
@@ -1852,11 +1892,11 @@ msgstr ""
 msgid "Uptime:"
 msgstr ""
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "User"
 msgstr ""
 
@@ -1878,7 +1918,11 @@ msgstr ""
 msgid "Valid"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+msgid "Version"
+msgstr ""
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr ""
@@ -1887,11 +1931,11 @@ msgstr ""
 msgid "View all notifications"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 msgid "View Mode"
 msgstr ""
 
@@ -1930,10 +1974,10 @@ msgstr ""
 msgid "Yes"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr ""

+ 129 - 83
app/src/language/ru_RU/app.po

@@ -26,15 +26,15 @@ msgstr "Пользователь"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "Действие"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr ""
 msgid "API Token"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr ""
 
@@ -106,17 +106,17 @@ msgstr "Вы уверены, что хотите удалить?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "Вы уверены, что хотите удалить все уведомления?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Вы уверены, что хотите очистить сообщения чата?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Вы уверены, что хотите удалить?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgstr "Вы уверены, что хотите удалить?"
@@ -126,7 +126,7 @@ msgstr "Вы уверены, что хотите удалить?"
 msgid "Are you sure you want to delete?"
 msgstr "Вы уверены, что хотите удалить?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
@@ -145,11 +145,11 @@ msgstr "Вы уверены, что хотите удалить эту дире
 msgid "Are you sure you want to remove this location?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr "Обратитесь за помощью к ChatGPT"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr "Ассистент"
 
@@ -195,6 +195,10 @@ msgstr "Назад"
 msgid "Back Home"
 msgstr "Вернутся"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -224,11 +228,16 @@ msgid "Basic Mode"
 msgstr "Простой режим"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 msgid "Batch Modify"
 msgstr "Изменение конфигурации"
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "Обновление"
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "Собрать с"
@@ -241,9 +250,9 @@ msgstr ""
 msgid "CADir"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -294,11 +303,11 @@ msgstr "Метод Challenge"
 msgid "Change Certificate"
 msgstr "Сертификат действителен"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr "Канал"
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr "Проверить повторно"
 
@@ -306,7 +315,7 @@ msgstr "Проверить повторно"
 msgid "Cleaning environment variables"
 msgstr "Очистка переменных среды"
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -351,7 +360,7 @@ msgstr "Конфигурации"
 msgid "Configure SSL"
 msgstr "Настроить SSL"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr "Подключено"
 
@@ -361,7 +370,7 @@ msgstr "Подключено"
 msgid "Content"
 msgstr "Содержание"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr "Обновление ядра"
 
@@ -398,7 +407,7 @@ msgstr "Учетные данные"
 msgid "Credentials"
 msgstr "Учетные данные"
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr "Текущяя версия"
 
@@ -425,7 +434,7 @@ msgstr "База данных (Опционально, по умолчанию:
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -433,7 +442,7 @@ msgstr ""
 msgid "Delete"
 msgstr "Удалить"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -445,7 +454,7 @@ msgstr ""
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 msgid "Deleted successfully"
 msgstr "Отключено успешно"
@@ -509,8 +518,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "Не удалось отключить автоматическое продление для %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "Отключено"
 
@@ -607,7 +617,7 @@ msgstr "Ошибка загрузки последней версии"
 msgid "Downloading latest release"
 msgstr "Загрузка последней версии"
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr "Включен пробный режим"
 
@@ -707,7 +717,8 @@ msgstr "Включить TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -727,7 +738,7 @@ msgstr "Активировано успешно"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Использовать для сайта Let's Encrypt"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr "Окружение"
 
@@ -736,7 +747,7 @@ msgstr "Окружение"
 msgid "Environment variables cleaned"
 msgstr "Настройка переменных сред"
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 msgid "Environments"
 msgstr "Комментарии"
@@ -749,7 +760,7 @@ msgstr "Ошибка"
 msgid "Error Logs"
 msgstr "Ошибка логирования"
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr "Исполняемый путь"
 
@@ -850,7 +861,7 @@ msgstr "Сгенерировать"
 msgid "Generating private key for registering account"
 msgstr "Генерация приватного ключа для регистрации учетной записи"
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 #, fuzzy
 msgid "Get release information error"
 msgstr "Ошибка получения информации о релизе"
@@ -972,7 +983,7 @@ msgstr ""
 msgid "Key Type"
 msgstr "Тип"
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr "Последняя проверка в"
 
@@ -995,12 +1006,12 @@ msgstr "Оставьте пустым без изменений"
 msgid "License"
 msgstr "Лицензия"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -1009,11 +1020,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr "Средняя нагрузка:"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 msgid "Load successfully"
 msgstr "Успешно сохранено"
@@ -1116,9 +1127,9 @@ msgstr ""
 msgid "Model"
 msgstr "Расширенный режим"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 #, fuzzy
 msgid "Modify"
 msgstr "Изменить"
@@ -1132,7 +1143,7 @@ msgstr "Статус сертификата"
 msgid "Modify Config"
 msgstr "Изменить конфигурацию"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "Modify Mode"
 msgstr "Изменить"
@@ -1151,7 +1162,7 @@ msgstr "Одиночная директива"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1174,7 +1185,7 @@ msgstr "Всего получено"
 msgid "Network Total Send"
 msgstr "Всего отправлено"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr "Вышла новая версия"
 
@@ -1220,11 +1231,11 @@ msgstr "Nginx перезагружен успешно"
 msgid "Nginx restarted successfully"
 msgstr "Nginx успешно перезапущен"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1276,22 +1287,22 @@ msgid "Obtaining certificate"
 msgstr "Получение сертификата"
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1312,8 +1323,8 @@ msgstr ""
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr ""
 
@@ -1321,7 +1332,7 @@ msgstr ""
 msgid "OpenAI"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 msgid "OS"
 msgstr "OS:"
@@ -1358,6 +1369,10 @@ msgstr "Пароль (*)"
 msgid "Path"
 msgstr "Путь"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr ""
@@ -1372,6 +1387,10 @@ msgid ""
 "provider."
 msgstr ""
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1409,7 +1428,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr ""
 
@@ -1448,11 +1469,11 @@ msgstr "Чтение"
 msgid "Receive"
 msgstr "Принято"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 msgid "Recovered Successfully"
 msgstr "Успешно сохранено"
@@ -1461,7 +1482,7 @@ msgstr "Успешно сохранено"
 msgid "Recursive Nameservers"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr "Восстановить ответ"
 
@@ -1489,16 +1510,16 @@ msgstr "Регистрация пользователя"
 msgid "Registration Status"
 msgstr "Регистрация пользователя"
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 msgid "Reinstall"
 msgstr "Переустановить"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr "Что нового"
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr "Перегрузить"
@@ -1556,7 +1577,7 @@ msgstr "Активировано успешно"
 msgid "Requested with wrong parameters"
 msgstr "Запрос с неправильными параметрами"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr "Сброс"
 
@@ -1577,7 +1598,7 @@ msgstr "Расширенный режим"
 msgid "Running"
 msgstr "Выполняется"
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1595,7 +1616,7 @@ msgid "Save error %{msg}"
 msgstr "Ошибка сохранения %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #, fuzzy
@@ -1627,10 +1648,11 @@ msgstr "Отправлено"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1713,14 +1735,16 @@ msgstr "Путь к сертификату SSL"
 msgid "SSO Login"
 msgstr "Логин"
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 #, fuzzy
 msgid "Stable"
 msgstr "Таблица"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "Статус"
 
@@ -1845,6 +1869,13 @@ msgstr "Путь к ключу сертификата SSL"
 msgid "The path exists, but the file is not a private key"
 msgstr "Путь существует, но файл не является приватным ключом"
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1895,6 +1926,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr "Это поле обязательно к заполнению"
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1920,7 +1956,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1933,7 +1969,7 @@ msgstr "Тип"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1944,17 +1980,22 @@ msgstr "Обновлено в"
 msgid "Updated successfully"
 msgstr "Обновлено успешно"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Обновление"
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "Обновление успешно выполнено"
+
 #: src/language/constants.ts:29
 #, fuzzy
 msgid "Upgraded successfully"
 msgstr "Обновление успешно выполнено"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Обновление Nginx UI, подождите..."
 
@@ -1966,11 +2007,11 @@ msgstr ""
 msgid "Uptime:"
 msgstr "Аптайм:"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 msgid "User"
 msgstr "Пользователь"
@@ -1992,7 +2033,12 @@ msgstr "Имя пользователя (*)"
 msgid "Valid"
 msgstr "Действительный"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+#, fuzzy
+msgid "Version"
+msgstr "Текущяя версия"
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Просмотр"
@@ -2002,11 +2048,11 @@ msgstr "Просмотр"
 msgid "View all notifications"
 msgstr "Просмотреть все уведомления"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "View Mode"
 msgstr "Простой режим"
@@ -2051,11 +2097,11 @@ msgstr "Запись сертификата на диск"
 msgid "Yes"
 msgstr "Да"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr "Вы используете последнюю версию"
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "Вы можете проверить обновление Nginx UI на этой странице."
 

+ 129 - 83
app/src/language/vi_VN/app.po

@@ -26,15 +26,15 @@ msgstr "Người dùng"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "Hành động"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr ""
 msgid "API Token"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr ""
 
@@ -106,17 +106,17 @@ msgstr "Bạn chắc chắn muốn xóa nó "
 msgid "Are you sure you want to clear all notifications?"
 msgstr "Bạn có chắc chắn muốn xóa tất cả thông báo không ?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Bạn có chắc chắn muốn xóa lịch sử trò chuyện không ?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Bạn chắc chắn muốn xóa nó "
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgstr "Bạn chắc chắn muốn xóa nó "
@@ -126,7 +126,7 @@ msgstr "Bạn chắc chắn muốn xóa nó "
 msgid "Are you sure you want to delete?"
 msgstr "Bạn chắc chắn muốn xóa nó "
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgstr "Bạn chắc chắn muốn xoá directive này ?"
@@ -145,11 +145,11 @@ msgstr "Bạn chắc chắn muốn xoá directive này ?"
 msgid "Are you sure you want to remove this location?"
 msgstr "Bạn chắc chắn muốn xoá location này ?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr "Hỏi ChatGPT"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr "Trợ lý"
 
@@ -195,6 +195,10 @@ msgstr "Quay lại"
 msgid "Back Home"
 msgstr "Quay lại"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -224,11 +228,16 @@ msgid "Basic Mode"
 msgstr "Cơ bản"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 msgid "Batch Modify"
 msgstr "Sửa đổi cấu hình"
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "Cập nhật"
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "Xây dựng với"
@@ -241,9 +250,9 @@ msgstr ""
 msgid "CADir"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -294,11 +303,11 @@ msgstr "Phương pháp xác thực"
 msgid "Change Certificate"
 msgstr "Thay đổi chứng chỉ"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr "Kênh"
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr "Kiểm tra lại"
 
@@ -306,7 +315,7 @@ msgstr "Kiểm tra lại"
 msgid "Cleaning environment variables"
 msgstr "Xoá các biến môi trường"
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -351,7 +360,7 @@ msgstr "Cấu hình"
 msgid "Configure SSL"
 msgstr "Cấu hình SSL"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr "Đã kết nối"
 
@@ -361,7 +370,7 @@ msgstr "Đã kết nối"
 msgid "Content"
 msgstr "Nội dung"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr "Cập nhật core"
 
@@ -398,7 +407,7 @@ msgstr "Chứng chỉ"
 msgid "Credentials"
 msgstr "Chứng chỉ"
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr "Phiên bản hiện tại"
 
@@ -425,7 +434,7 @@ msgstr "Tên cơ sở dữ liệu (Tuỳ chọn, Mặc định là: database)"
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -433,7 +442,7 @@ msgstr ""
 msgid "Delete"
 msgstr "Xoá"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -446,7 +455,7 @@ msgstr "Xoá trang web: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Xoá trang web: %{site_name}"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 msgid "Deleted successfully"
 msgstr "Đã xoá thành công"
@@ -510,8 +519,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "Tắt tự động gia hạn SSL cho %{name} thất bại"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "Đã tắt"
 
@@ -608,7 +618,7 @@ msgstr "Đã có lỗi xảy ra khi tải về phiên bản mới nhất"
 msgid "Downloading latest release"
 msgstr "Đang tải phiên bản mới nhất"
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr "Đã bật chế độ Dry run"
 
@@ -708,7 +718,8 @@ msgstr "Bật TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -728,7 +739,7 @@ msgstr "Đã bật"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Bảo mật trang web với Let's Encrypt"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr "Environment"
 
@@ -737,7 +748,7 @@ msgstr "Environment"
 msgid "Environment variables cleaned"
 msgstr "Đặt biến môi trường"
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 msgid "Environments"
 msgstr "Environments"
@@ -750,7 +761,7 @@ msgstr "Lỗi"
 msgid "Error Logs"
 msgstr "Log lỗi"
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr "Đường dẫn thực thi"
 
@@ -852,7 +863,7 @@ msgstr "Tạo"
 msgid "Generating private key for registering account"
 msgstr "Tạo khóa riêng để đăng ký tài khoản"
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 #, fuzzy
 msgid "Get release information error"
 msgstr "Nhận lỗi thông tin phát hành"
@@ -974,7 +985,7 @@ msgstr ""
 msgid "Key Type"
 msgstr "Loại"
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr "Kiểm tra lần cuối lúc"
 
@@ -997,12 +1008,12 @@ msgstr "Bỏ trống nếu không thay đổi"
 msgid "License"
 msgstr "Giấy phép"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr "Liên kết bắt đầu"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -1011,11 +1022,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr "Tải trung bình:"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 msgid "Load successfully"
 msgstr "Lưu thành công"
@@ -1117,9 +1128,9 @@ msgstr ""
 msgid "Model"
 msgstr "Run Mode"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 #, fuzzy
 msgid "Modify"
 msgstr "Sửa"
@@ -1133,7 +1144,7 @@ msgstr "Sửa chứng chỉ"
 msgid "Modify Config"
 msgstr "Sửa cấu hình"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "Modify Mode"
 msgstr "Sửa"
@@ -1152,7 +1163,7 @@ msgstr "Single Directive"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1175,7 +1186,7 @@ msgstr "Tổng lưu lượng mạng đã nhận"
 msgid "Network Total Send"
 msgstr "Tổng lưu lượng mạng đã gửi"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr "Đã có phiên bản mới"
 
@@ -1220,11 +1231,11 @@ msgstr "Reload Nginx thành công"
 msgid "Nginx restarted successfully"
 msgstr "Restart Nginx thành công"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1276,22 +1287,22 @@ msgid "Obtaining certificate"
 msgstr "Đang nhận chứng chỉ"
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr "Ngoại tuyến"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1312,8 +1323,8 @@ msgstr "Sau khi quá trình xác minh hoàn tất, bản ghi sẽ bị xóa."
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr "Trực tuyến"
 
@@ -1321,7 +1332,7 @@ msgstr "Trực tuyến"
 msgid "OpenAI"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 msgid "OS"
 msgstr "Hệ điều hành"
@@ -1358,6 +1369,10 @@ msgstr "Mật khẩu (*)"
 msgid "Path"
 msgstr "Đường dẫn"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr "Nâng cấp core không thành công"
@@ -1373,6 +1388,10 @@ msgid ""
 msgstr ""
 "Vui lòng điền thông tin xác thực API do nhà cung cấp DNS của bạn cung cấp"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1411,7 +1430,9 @@ msgstr "Lưu ý đơn vị cấu hình thời gian bên dưới được tính b
 msgid "Please select at least one node!"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr ""
 
@@ -1450,11 +1471,11 @@ msgstr "Đọc"
 msgid "Receive"
 msgstr "Nhận"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 msgid "Recovered Successfully"
 msgstr "Xoá thành công"
@@ -1463,7 +1484,7 @@ msgstr "Xoá thành công"
 msgid "Recursive Nameservers"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr "Tạo lại câu trả lời"
 
@@ -1491,16 +1512,16 @@ msgstr "Đăng ký người dùng"
 msgid "Registration Status"
 msgstr "Đăng ký người dùng"
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 msgid "Reinstall"
 msgstr "Cài lại"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr "Ghi chú phát hành"
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr "Tải lại"
@@ -1558,7 +1579,7 @@ msgstr "Gia hạn chứng chỉ SSL"
 msgid "Requested with wrong parameters"
 msgstr "Yêu cầu có chứa tham số sai"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr "Đặt lại"
 
@@ -1579,7 +1600,7 @@ msgstr "Run Mode"
 msgid "Running"
 msgstr "Running"
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1597,7 +1618,7 @@ msgid "Save error %{msg}"
 msgstr "Đã xảy ra lỗi khi lưu %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #, fuzzy
@@ -1629,10 +1650,11 @@ msgstr "Gửi"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1711,14 +1733,16 @@ msgstr ""
 msgid "SSO Login"
 msgstr ""
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 #, fuzzy
 msgid "Stable"
 msgstr "Ổn định"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "Trạng thái"
 
@@ -1841,6 +1865,13 @@ msgstr ""
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1887,6 +1918,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr "Trường này không được để trống"
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1916,7 +1952,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1929,7 +1965,7 @@ msgstr "Loại"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1940,17 +1976,22 @@ msgstr "Ngày cập nhật"
 msgid "Updated successfully"
 msgstr "Cập nhật thành công"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "Cập nhật"
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "Cập nhật thành công"
+
 #: src/language/constants.ts:29
 #, fuzzy
 msgid "Upgraded successfully"
 msgstr "Cập nhật thành công"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Đang cập nhật Nginx UI, vui lòng đợi..."
 
@@ -1962,11 +2003,11 @@ msgstr ""
 msgid "Uptime:"
 msgstr "Thời gian hoạt động:"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 msgid "User"
 msgstr "Người dùng"
@@ -1988,7 +2029,12 @@ msgstr "Username (*)"
 msgid "Valid"
 msgstr "Hợp lệ"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+#, fuzzy
+msgid "Version"
+msgstr "Phiên bản hiện tại"
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Xem"
@@ -1998,12 +2044,12 @@ msgstr "Xem"
 msgid "View all notifications"
 msgstr "Xem tất cả thông báo"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 #, fuzzy
 msgid "View Details"
 msgstr "Chi tiết"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "View Mode"
 msgstr "Cơ bản"
@@ -2050,11 +2096,11 @@ msgstr "Ghi chứng chỉ vào disk"
 msgid "Yes"
 msgstr "Có"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr "Bạn đang sử dụng phiên bản mới nhất"
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "Bạn có thể kiểm tra nâng cấp Nginx UI tại trang này"
 

BIN
app/src/language/zh_CN/app.mo


+ 129 - 84
app/src/language/zh_CN/app.po

@@ -29,15 +29,15 @@ msgstr "ACME 用户"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr "API 代理"
 msgid "API Token"
 msgstr "API Token"
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr "架构"
 
@@ -104,15 +104,15 @@ msgstr "您确定要立即删除这个被禁用的 IP 吗?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "您确定要清除所有通知吗?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "你确定你要清除聊天记录吗?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "您确定要永久删除此项目吗?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 msgid "Are you sure you want to delete this item?"
 msgstr "你确定要删除这个项目吗?"
 
@@ -120,7 +120,7 @@ msgstr "你确定要删除这个项目吗?"
 msgid "Are you sure you want to delete?"
 msgstr "您确定要删除吗?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 msgid "Are you sure you want to recover this item?"
 msgstr "您确定要恢复这个项目吗?"
 
@@ -136,11 +136,11 @@ msgstr "您确定要删除这个项目吗?"
 msgid "Are you sure you want to remove this location?"
 msgstr "您确定要删除这个 Location?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr "与ChatGPT聊天"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr "助手"
 
@@ -184,6 +184,10 @@ msgstr "返回"
 msgid "Back Home"
 msgstr "返回首页"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr "返回列表"
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr "禁止阈值(分钟)"
@@ -212,10 +216,14 @@ msgid "Basic Mode"
 msgstr "基本模式"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgstr "批量修改"
 
+#: src/views/environment/BatchUpgrader.vue:154
+msgid "Batch Upgrade"
+msgstr "批量升级"
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "构建基于"
@@ -228,9 +236,9 @@ msgstr "CA Dir"
 msgid "CADir"
 msgstr "CADir"
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -277,11 +285,11 @@ msgstr "挑战方法"
 msgid "Change Certificate"
 msgstr "更改证书"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr "通道"
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr "重新检查"
 
@@ -289,7 +297,7 @@ msgstr "重新检查"
 msgid "Cleaning environment variables"
 msgstr "正在清理环境变量"
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -331,7 +339,7 @@ msgstr "配置"
 msgid "Configure SSL"
 msgstr "配置 SSL"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr "已连接"
 
@@ -341,7 +349,7 @@ msgstr "已连接"
 msgid "Content"
 msgstr "内容"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr "核心升级"
 
@@ -377,7 +385,7 @@ msgstr "DNS 凭证"
 msgid "Credentials"
 msgstr "凭证"
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr "当前版本"
 
@@ -404,7 +412,7 @@ msgstr "数据库 (可选,默认: database)"
 msgid "Days"
 msgstr "天"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -412,7 +420,7 @@ msgstr "天"
 msgid "Delete"
 msgstr "删除"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr "彻底删除"
 
@@ -424,7 +432,7 @@ msgstr "删除站点: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "删除 Stream: %{stream_name}"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 msgid "Deleted successfully"
 msgstr "删除成功"
 
@@ -484,8 +492,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "关闭 %{name} 自动续签失败"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "禁用"
 
@@ -571,7 +580,7 @@ msgstr "下载最新版本错误"
 msgid "Downloading latest release"
 msgstr "下载最新版本"
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr "试运行模式已启动"
 
@@ -663,7 +672,8 @@ msgstr "启用 TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -683,7 +693,7 @@ msgstr "启用成功"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 Let's Encrypt 对网站进行加密"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr "环境"
 
@@ -691,7 +701,7 @@ msgstr "环境"
 msgid "Environment variables cleaned"
 msgstr "环境变量已清理"
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgstr "环境"
 
@@ -703,7 +713,7 @@ msgstr "错误"
 msgid "Error Logs"
 msgstr "错误日志"
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr "可执行文件路径"
 
@@ -798,7 +808,7 @@ msgstr "生成"
 msgid "Generating private key for registering account"
 msgstr "正在生成私钥用于注册账户"
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 msgid "Get release information error"
 msgstr "获取发布信息错误"
 
@@ -912,7 +922,7 @@ msgstr "Jwt 密钥"
 msgid "Key Type"
 msgstr "密钥类型"
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr "最后检查时间"
 
@@ -933,12 +943,12 @@ msgstr "留空不做任何更改"
 msgid "License"
 msgstr "开源许可"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr "链接"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr "列表"
 
@@ -946,11 +956,11 @@ msgstr "列表"
 msgid "Load Average:"
 msgstr "系统负载:"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr "从设置中加载"
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 msgid "Load successfully"
 msgstr "加载成功"
 
@@ -1050,9 +1060,9 @@ msgstr "分钟"
 msgid "Model"
 msgstr "模型"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 msgid "Modify"
 msgstr "修改"
 
@@ -1064,7 +1074,7 @@ msgstr "修改证书"
 msgid "Modify Config"
 msgstr "修改配置文件"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 msgid "Modify Mode"
 msgstr "修改模式"
 
@@ -1081,7 +1091,7 @@ msgstr "多行指令"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1104,7 +1114,7 @@ msgstr "下载流量"
 msgid "Network Total Send"
 msgstr "上传流量"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr "新版本发布"
 
@@ -1146,11 +1156,11 @@ msgstr "Nginx 重载成功"
 msgid "Nginx restarted successfully"
 msgstr "Nginx 重启成功"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1199,22 +1209,22 @@ msgid "Obtaining certificate"
 msgstr "正在获取证书"
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr "离线"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr "确定"
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1235,8 +1245,8 @@ msgstr "一旦验证完成,这些记录将被删除。"
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr "在线"
 
@@ -1244,7 +1254,7 @@ msgstr "在线"
 msgid "OpenAI"
 msgstr "OpenAI"
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgstr "OS"
 
@@ -1280,6 +1290,10 @@ msgstr "密码 (*)"
 msgid "Path"
 msgstr "路径"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr "执行"
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr "执行核心升级错误"
@@ -1294,6 +1308,10 @@ msgid ""
 "provider."
 msgstr "请填写 DNS 提供商提供的 API 验证凭据。"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr "请填写必填字段"
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1331,7 +1349,9 @@ msgstr "请注意,下面的时间单位配置均以秒为单位。"
 msgid "Please select at least one node!"
 msgstr "请至少选择一个节点!"
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr "预发布"
 
@@ -1369,11 +1389,11 @@ msgstr "读"
 msgid "Receive"
 msgstr "下载"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr "恢复"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 msgid "Recovered Successfully"
 msgstr "恢复成功"
 
@@ -1381,7 +1401,7 @@ msgstr "恢复成功"
 msgid "Recursive Nameservers"
 msgstr "递归域名服务器"
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr "重新生成响应"
 
@@ -1405,15 +1425,15 @@ msgstr "正在注册用户"
 msgid "Registration Status"
 msgstr "注册状态"
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgstr "重新安装"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr "发行日志"
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr "重载"
@@ -1464,7 +1484,7 @@ msgstr "更新成功"
 msgid "Requested with wrong parameters"
 msgstr "请求参数错误"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr "重置"
 
@@ -1484,7 +1504,7 @@ msgstr "运行模式"
 msgid "Running"
 msgstr "运行中"
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1502,7 +1522,7 @@ msgid "Save error %{msg}"
 msgstr "保存错误 %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
@@ -1533,10 +1553,11 @@ msgstr "上传"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1612,13 +1633,15 @@ msgstr "SSL证书路径"
 msgid "SSO Login"
 msgstr "SSO 登录"
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 msgid "Stable"
 msgstr "稳定"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "状态"
 
@@ -1735,6 +1758,15 @@ msgstr "路径存在,但文件不是证书"
 msgid "The path exists, but the file is not a private key"
 msgstr "路径存在,但文件不是私钥"
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+"远程 Nginx UI 版本与本地 Nginx UI版本不兼容。为避免意料之外的错误,请升级远"
+"程 Nginx UI,使其与本地版本一致。"
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1779,6 +1811,11 @@ msgstr "此字段必填"
 msgid "This field should not be empty"
 msgstr "该字段不能为空"
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr "将 %{nodeNames} 上的 Nginx UI 升级或重新安装到 %{version} 版本。"
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1806,7 +1843,7 @@ msgstr "Token 无效"
 msgid "Too many login failed attempts, please try again later"
 msgstr "登录失败次数过多,请稍后再试"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr "回收站"
 
@@ -1819,7 +1856,7 @@ msgstr "类型"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1829,18 +1866,22 @@ msgstr "修改时间"
 msgid "Updated successfully"
 msgstr "更新成功"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "升级"
 
+#: src/views/environment/BatchUpgrader.vue:139
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "成功升级 %{node} 上的 Nginx UI 🎉"
+
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgstr "升级成功"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
-msgstr "正在升级Nginx UI,请等待..."
+msgstr "正在升级 Nginx UI,请等待..."
 
 #: src/views/domain/ngx_conf/NgxUpstream.vue:170
 msgid "Upstream Name"
@@ -1850,11 +1891,11 @@ msgstr "Upstream 名称"
 msgid "Uptime:"
 msgstr "运行时间:"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr "URL"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "User"
 msgstr "用户"
 
@@ -1875,7 +1916,11 @@ msgstr "用户名 (*)"
 msgid "Valid"
 msgstr "有效的"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+msgid "Version"
+msgstr "版本"
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "查看"
@@ -1884,11 +1929,11 @@ msgstr "查看"
 msgid "View all notifications"
 msgstr "查看全部通知"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgstr "查看详情"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 msgid "View Mode"
 msgstr "预览模式"
 
@@ -1931,11 +1976,11 @@ msgstr "正在将证书写入磁盘"
 msgid "Yes"
 msgstr "是的"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr "您使用的是最新版本"
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "你可以在这个页面检查Nginx UI的升级。"
 

+ 129 - 83
app/src/language/zh_TW/app.po

@@ -31,15 +31,15 @@ msgstr "使用者名稱"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
-#: src/views/domain/DomainList.vue:47 src/views/environment/Environment.vue:129
+#: src/views/domain/DomainList.vue:47 src/views/environment/envColumns.tsx:131
 #: src/views/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:214
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:202
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -96,7 +96,7 @@ msgstr "API 代理"
 msgid "API Token"
 msgstr "API Token"
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgstr "架構"
 
@@ -111,16 +111,16 @@ msgstr "您確定要刪除嗎?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "您確定要清除聊天記錄嗎?"
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "您確定要清除聊天記錄嗎?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "您確定要刪除嗎?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgstr "您確定要刪除嗎?"
@@ -129,7 +129,7 @@ msgstr "您確定要刪除嗎?"
 msgid "Are you sure you want to delete?"
 msgstr "您確定要刪除嗎?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgstr "您確定要刪除這條指令嗎?"
@@ -147,11 +147,11 @@ msgstr "您確定要刪除這條指令嗎?"
 msgid "Are you sure you want to remove this location?"
 msgstr "您確定要刪除此 Location 嗎?"
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgstr "向 ChatGPT 尋求幫助"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgstr "助理"
 
@@ -196,6 +196,10 @@ msgstr "返回"
 msgid "Back Home"
 msgstr "返回首頁"
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgstr ""
@@ -224,10 +228,15 @@ msgid "Basic Mode"
 msgstr "基本模式"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgstr "批次修改"
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "升級"
+
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgstr "構建基於"
@@ -240,9 +249,9 @@ msgstr ""
 msgid "CADir"
 msgstr "CADir"
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:263
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:252
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
@@ -292,11 +301,11 @@ msgstr "驗證方式"
 msgid "Change Certificate"
 msgstr "更換憑證"
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgstr "通道"
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgstr "再次檢查"
 
@@ -304,7 +313,7 @@ msgstr "再次檢查"
 msgid "Cleaning environment variables"
 msgstr "清理環境變數"
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
@@ -348,7 +357,7 @@ msgstr "設定"
 msgid "Configure SSL"
 msgstr "設定 SSL"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgstr "已連結"
 
@@ -358,7 +367,7 @@ msgstr "已連結"
 msgid "Content"
 msgstr "內容"
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgstr "核心升級"
 
@@ -395,7 +404,7 @@ msgstr "認證"
 msgid "Credentials"
 msgstr "認證資訊"
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgstr "目前版本"
 
@@ -422,7 +431,7 @@ msgstr "資料庫 (可選,預設: database)"
 msgid "Days"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -430,7 +439,7 @@ msgstr ""
 msgid "Delete"
 msgstr "刪除"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgstr ""
 
@@ -443,7 +452,7 @@ msgstr "刪除網站:%{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "刪除網站:%{site_name}"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 msgid "Deleted successfully"
 msgstr "成功停用"
@@ -505,8 +514,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "關閉 %{name} 自動續簽失敗"
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
-#: src/views/domain/DomainList.vue:33 src/views/environment/Environment.vue:93
-#: src/views/stream/StreamEdit.vue:175 src/views/stream/StreamList.vue:33
+#: src/views/domain/DomainList.vue:33 src/views/environment/envColumns.tsx:113
+#: src/views/environment/envColumns.tsx:95 src/views/stream/StreamEdit.vue:175
+#: src/views/stream/StreamList.vue:33
 msgid "Disabled"
 msgstr "停用"
 
@@ -596,7 +606,7 @@ msgstr "下載最新版本錯誤"
 msgid "Downloading latest release"
 msgstr "正在下載最新版本"
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgstr "試運轉模式已啟用"
 
@@ -690,7 +700,8 @@ msgstr "啟用 TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
-#: src/views/environment/Environment.vue:102
+#: src/views/environment/envColumns.tsx:104
+#: src/views/environment/envColumns.tsx:110
 #: src/views/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -710,7 +721,7 @@ msgstr "成功啟用"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 Let's Encrypt 對網站進行加密"
 
-#: src/routes/index.ts:212 src/views/environment/Environment.vue:147
+#: src/routes/index.ts:212 src/views/environment/Environment.vue:34
 msgid "Environment"
 msgstr "環境"
 
@@ -719,7 +730,7 @@ msgstr "環境"
 msgid "Environment variables cleaned"
 msgstr "設定環境變數中"
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgstr "環境"
 
@@ -731,7 +742,7 @@ msgstr "錯誤"
 msgid "Error Logs"
 msgstr "錯誤日誌"
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgstr "可執行檔路徑"
 
@@ -831,7 +842,7 @@ msgstr "產生"
 msgid "Generating private key for registering account"
 msgstr "產生註冊帳號的私鑰"
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 msgid "Get release information error"
 msgstr "取得發布資訊錯誤"
 
@@ -950,7 +961,7 @@ msgstr "Jwt Secret"
 msgid "Key Type"
 msgstr "類型"
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgstr "上次檢查時間"
 
@@ -973,12 +984,12 @@ msgstr "留空表示不修改"
 msgid "License"
 msgstr "授權條款"
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgstr "連結開始"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgstr ""
 
@@ -987,11 +998,11 @@ msgstr ""
 msgid "Load Average:"
 msgstr "系統負載:"
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgstr ""
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 msgid "Load successfully"
 msgstr "儲存成功"
@@ -1091,9 +1102,9 @@ msgstr ""
 msgid "Model"
 msgstr "執行模式"
 
-#: src/components/ChatGPT/ChatGPT.vue:249
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:505
+#: src/components/ChatGPT/ChatGPT.vue:248
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
 msgid "Modify"
 msgstr "修改"
 
@@ -1106,7 +1117,7 @@ msgstr "憑證狀態"
 msgid "Modify Config"
 msgstr "修改設定"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "Modify Mode"
 msgstr "修改"
@@ -1124,7 +1135,7 @@ msgstr "多行指令"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
-#: src/views/environment/Environment.vue:12
+#: src/views/environment/envColumns.tsx:9
 #: src/views/stream/components/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1147,7 +1158,7 @@ msgstr "下載流量"
 msgid "Network Total Send"
 msgstr "上傳流量"
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgstr "新版本發布"
 
@@ -1189,11 +1200,11 @@ msgstr "Nginx 重新載入成功"
 msgid "Nginx restarted successfully"
 msgstr "Nginx 重啟成功"
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:521
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:535
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:548
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:562
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:576
 #: src/views/domain/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1244,22 +1255,22 @@ msgid "Obtaining certificate"
 msgstr "正在取得憑證"
 
 #: src/components/NodeSelector/NodeSelector.vue:95
-#: src/views/dashboard/Environments.vue:106
-#: src/views/environment/Environment.vue:88
+#: src/views/dashboard/Environments.vue:107
+#: src/views/environment/envColumns.tsx:90
 msgid "Offline"
 msgstr "離線"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgstr ""
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:522
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:536
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:549
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:563
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:577
 #: src/views/domain/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
@@ -1280,8 +1291,8 @@ msgstr ""
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
-#: src/views/dashboard/Environments.vue:99
-#: src/views/environment/Environment.vue:84
+#: src/views/dashboard/Environments.vue:100
+#: src/views/environment/envColumns.tsx:86
 msgid "Online"
 msgstr "線上"
 
@@ -1289,7 +1300,7 @@ msgstr "線上"
 msgid "OpenAI"
 msgstr "OpenAI"
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgstr "作業系統"
 
@@ -1325,6 +1336,10 @@ msgstr "密碼 (*)"
 msgid "Path"
 msgstr "路徑"
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgstr "執行核心升級錯誤"
@@ -1339,6 +1354,10 @@ msgid ""
 "provider."
 msgstr ""
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #, fuzzy
 msgid ""
@@ -1377,7 +1396,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgstr "請至少選擇一個節點!"
 
-#: src/views/system/Upgrade.vue:191 src/views/system/Upgrade.vue:251
+#: src/views/environment/BatchUpgrader.vue:169
+#: src/views/environment/BatchUpgrader.vue:222 src/views/system/Upgrade.vue:196
+#: src/views/system/Upgrade.vue:247
 msgid "Pre-release"
 msgstr "預先發布"
 
@@ -1415,11 +1436,11 @@ msgstr "讀取"
 msgid "Receive"
 msgstr "接收"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 msgid "Recovered Successfully"
 msgstr "儲存成功"
@@ -1429,7 +1450,7 @@ msgstr "儲存成功"
 msgid "Recursive Nameservers"
 msgstr "網站域名 (server_name)"
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgstr "重新產生回應"
 
@@ -1457,15 +1478,15 @@ msgstr "註冊使用者中"
 msgid "Registration Status"
 msgstr "註冊使用者中"
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgstr "重新安裝"
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgstr "發行公告"
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgstr "重新載入"
@@ -1523,7 +1544,7 @@ msgstr "啟用成功"
 msgid "Requested with wrong parameters"
 msgstr "請求參數錯誤"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgstr "重設"
 
@@ -1543,7 +1564,7 @@ msgstr "執行模式"
 msgid "Running"
 msgstr "執行中"
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1561,7 +1582,7 @@ msgid "Save error %{msg}"
 msgstr "儲存錯誤 %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:104
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:121
 #: src/views/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
@@ -1592,10 +1613,11 @@ msgstr "傳送"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:196
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:235
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:213
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:253
 #: src/views/config/ConfigEdit.vue:40 src/views/domain/DomainList.vue:81
-#: src/views/environment/Environment.vue:139 src/views/other/Install.vue:69
+#: src/views/environment/BatchUpgrader.vue:57
+#: src/views/environment/Environment.vue:15 src/views/other/Install.vue:69
 #: src/views/preference/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1677,13 +1699,15 @@ msgstr "SSL 憑證路徑"
 msgid "SSO Login"
 msgstr "登入"
 
-#: src/views/system/Upgrade.vue:188 src/views/system/Upgrade.vue:245
+#: src/views/environment/BatchUpgrader.vue:166
+#: src/views/environment/BatchUpgrader.vue:216 src/views/system/Upgrade.vue:193
+#: src/views/system/Upgrade.vue:241
 msgid "Stable"
 msgstr "穩定"
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
-#: src/views/environment/Environment.vue:76 src/views/stream/StreamList.vue:22
+#: src/views/environment/envColumns.tsx:78 src/views/stream/StreamList.vue:22
 msgid "Status"
 msgstr "狀態"
 
@@ -1808,6 +1832,13 @@ msgstr "SSL 憑證金鑰路徑"
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 
+#: src/views/dashboard/Environments.vue:148
+msgid ""
+"The remote Nginx UI version is not compatible with the local Nginx UI "
+"version. To avoid potential errors, please upgrade the remote Nginx UI to "
+"match the local version."
+msgstr ""
+
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1856,6 +1887,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr "此欄位不應為空"
 
+#: src/views/environment/BatchUpgrader.vue:184
+msgid ""
+"This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
@@ -1883,7 +1919,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgstr ""
 
@@ -1896,7 +1932,7 @@ msgstr "類型"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
-#: src/views/domain/DomainList.vue:41 src/views/environment/Environment.vue:122
+#: src/views/domain/DomainList.vue:41 src/views/environment/envColumns.tsx:124
 #: src/views/stream/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
@@ -1906,16 +1942,21 @@ msgstr "更新時間"
 msgid "Updated successfully"
 msgstr "更新成功"
 
-#: src/routes/index.ts:263 src/views/system/Upgrade.vue:140
-#: src/views/system/Upgrade.vue:232
+#: src/routes/index.ts:263 src/views/environment/Environment.vue:50
+#: src/views/system/Upgrade.vue:145 src/views/system/Upgrade.vue:228
 msgid "Upgrade"
 msgstr "升級"
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "升級成功"
+
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgstr "升級成功"
 
-#: src/views/system/Upgrade.vue:79
+#: src/views/environment/BatchUpgrader.vue:90 src/views/system/Upgrade.vue:79
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "正在升級 Nginx UI,請稍候..."
 
@@ -1927,11 +1968,11 @@ msgstr ""
 msgid "Uptime:"
 msgstr "運作時間:"
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgstr "URL"
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "User"
 msgstr "使用者名稱"
 
@@ -1952,7 +1993,12 @@ msgstr "使用者名稱 (*)"
 msgid "Valid"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:491
+#: src/views/environment/envColumns.tsx:31
+#, fuzzy
+msgid "Version"
+msgstr "目前版本"
+
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:518
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "檢視"
@@ -1962,11 +2008,11 @@ msgstr "檢視"
 msgid "View all notifications"
 msgstr "憑證"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 msgid "View Mode"
 msgstr "基本模式"
@@ -2011,11 +2057,11 @@ msgstr "將憑證寫入磁碟"
 msgid "Yes"
 msgstr "是的"
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgstr "您正在使用最新版本"
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "您可以在此頁面檢查 Nginx UI 的升級。"
 

+ 24 - 6
app/src/views/dashboard/Environments.vue

@@ -10,10 +10,11 @@ import pulse from '@/assets/svg/pulse.svg?component'
 import { formatDateTime } from '@/lib/helper'
 import NodeAnalyticItem from '@/views/dashboard/components/NodeAnalyticItem.vue'
 import analytic from '@/api/analytic'
+import { version } from '@/version.json'
 
 const data = ref([]) as Ref<Node[]>
 
-const node_map = computed(() => {
+const nodeMap = computed(() => {
   const o = {} as Record<number, Node>
 
   data.value.forEach(v => {
@@ -48,9 +49,9 @@ onMounted(() => {
       const key = Number.parseInt(v)
 
       // update node online status
-      if (node_map.value[key]) {
-        Object.assign(node_map.value[key], nodes[key])
-        node_map.value[key].response_at = new Date()
+      if (nodeMap.value[key]) {
+        Object.assign(nodeMap.value[key], nodes[key])
+        nodeMap.value[key].response_at = new Date()
       }
     })
   }
@@ -62,7 +63,7 @@ onUnmounted(() => {
 
 const { environment: env } = useSettingsStore()
 
-function link_start(node: Node) {
+function linkStart(node: Node) {
   env.id = node.id
   env.name = node.name
 }
@@ -130,14 +131,31 @@ const visible = computed(() => {
                 />
 
                 <AButton
+                  v-if="item.version === version"
                   type="primary"
                   :disabled="!item.status || env.id === item.id"
                   ghost
-                  @click="link_start(item)"
+                  @click="linkStart(item)"
                 >
                   <SendOutlined />
                   {{ env.id !== item.id ? $gettext('Link Start') : $gettext('Connected') }}
                 </AButton>
+                <ATooltip
+                  v-else
+                  placement="topLeft"
+                >
+                  <template #title>
+                    {{ $gettext('The remote Nginx UI version is not compatible with the local Nginx UI version. '
+                      + 'To avoid potential errors, please upgrade the remote Nginx UI to match the local version.') }}
+                  </template>
+                  <AButton
+                    ghost
+                    disabled
+                  >
+                    <SendOutlined />
+                    {{ $gettext('Link Start') }}
+                  </AButton>
+                </ATooltip>
               </div>
             </template>
           </AListItemMeta>

+ 279 - 0
app/src/views/environment/BatchUpgrader.vue

@@ -0,0 +1,279 @@
+<script setup lang="ts">
+import _ from 'lodash'
+import { message } from 'ant-design-vue'
+import type { Ref } from 'vue'
+import { marked } from 'marked'
+import { useRoute } from 'vue-router'
+import type { Environment } from '@/api/environment'
+import upgrade, { type RuntimeInfo } from '@/api/upgrade'
+import websocket from '@/lib/websocket'
+
+const route = useRoute()
+const visible = ref(false)
+const nodeIds = ref<number[]>([])
+const nodes = ref<Environment[]>([])
+const channel = ref('stable')
+const nodeNames = computed(() => nodes.value.map(v => v.name).join(', '))
+const loading = ref(false)
+
+const data = ref({
+  name: '',
+}) as Ref<RuntimeInfo>
+
+const modalVisible = ref(false)
+const modalClosable = ref(false)
+const getReleaseError = ref(false)
+const progressPercent = ref(0)
+const progressStatus = ref('active') as Ref<'normal' | 'active' | 'success' | 'exception'>
+const showLogContainer = ref(false)
+
+const progressStrokeColor = {
+  from: '#108ee9',
+  to: '#87d068',
+}
+
+const logContainer = ref()
+function log(msg: string) {
+  const para = document.createElement('p')
+
+  para.appendChild(document.createTextNode($gettext(msg)))
+
+  logContainer.value.appendChild(para)
+
+  logContainer.value.scroll({ top: 320, left: 0, behavior: 'smooth' })
+}
+
+const progressPercentComputed = computed(() => {
+  return Number.parseFloat(progressPercent.value.toFixed(1))
+})
+
+function getLatestRelease() {
+  loading.value = true
+  data.value.body = ''
+  upgrade.get_latest_release(channel.value).then(r => {
+    data.value = r
+  }).catch(e => {
+    getReleaseError.value = e?.message
+    message.error(e?.message ?? $gettext('Server error'))
+  }).finally(() => {
+    loading.value = false
+  })
+}
+
+function open(selectedNodeIds: Ref<number[]>, selectedNodes: Ref<Environment[]>) {
+  showLogContainer.value = false
+  visible.value = true
+  nodeIds.value = selectedNodeIds.value
+  nodes.value = _.cloneDeep(selectedNodes.value)
+  getLatestRelease()
+}
+
+watch(channel, getLatestRelease)
+
+defineExpose({
+  open,
+})
+
+const dryRun = computed(() => {
+  return !!route.query.dry_run
+})
+
+// eslint-disable-next-line sonarjs/cognitive-complexity
+async function performUpgrade() {
+  showLogContainer.value = true
+  progressStatus.value = 'active'
+  modalClosable.value = false
+  modalVisible.value = true
+  progressPercent.value = 0
+  logContainer.value.innerHTML = ''
+
+  log($gettext('Upgrading Nginx UI, please wait...'))
+
+  const nodesNum = nodes.value.length
+
+  for (let i = 0; i < nodesNum; i++) {
+    await new Promise(resolve => {
+      const ws = websocket(`/api/upgrade/perform?x_node_id=${nodeIds.value[i]}`, false)
+
+      let last = 0
+
+      ws.onopen = () => {
+        ws.send(JSON.stringify({
+          dry_run: dryRun.value,
+          channel: channel.value,
+        }))
+      }
+      let isFailed = false
+
+      ws.onmessage = async m => {
+        const r = JSON.parse(m.data)
+        if (r.message)
+          log(r.message)
+        switch (r.status) {
+          case 'info':
+            progressPercent.value += (10 / nodesNum)
+            break
+          case 'progress':
+            progressPercent.value += ((r.progress - last) / 2) / nodesNum
+            last = r.progress
+            break
+          case 'error':
+            log('Upgraded successfully')
+            isFailed = true
+            break
+          default:
+            modalClosable.value = true
+            break
+        }
+      }
+
+      ws.onerror = () => {
+        resolve({})
+      }
+
+      ws.onclose = async () => {
+        resolve({})
+
+        progressPercent.value = 100 * ((i + 1) / nodesNum)
+        if (!isFailed)
+          log($gettext('Upgraded Nginx UI on %{node} successfully 🎉', { node: nodes.value[i].name }))
+
+        if (i + 1 === nodesNum) {
+          progressStatus.value = 'success'
+          modalClosable.value = true
+        }
+      }
+    })
+  }
+}
+</script>
+
+<template>
+  <AModal
+    v-model:open="visible"
+    :title="$gettext('Batch Upgrade')"
+    :footer="false"
+    :mask="false"
+    width="800px"
+  >
+    <AForm layout="vertical">
+      <AFormItem
+        :label="$gettext('Channel')"
+        class="max-w-40"
+      >
+        <ASelect v-model:value="channel">
+          <ASelectOption key="stable">
+            {{ $gettext('Stable') }}
+          </ASelectOption>
+          <ASelectOption key="prerelease">
+            {{ $gettext('Pre-release') }}
+          </ASelectOption>
+        </ASelect>
+      </AFormItem>
+    </AForm>
+
+    <ASpin :spinning="loading">
+      <AAlert
+        v-if="getReleaseError"
+        type="error"
+        :title="$gettext('Get release information error')"
+        :message="getReleaseError"
+        banner
+      />
+      <template v-else>
+        <p>{{ $gettext('This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}.', { nodeNames, version: data.name }) }}</p>
+
+        <AAlert
+          v-if="dryRun"
+          type="info"
+          class="mb-4"
+          :message="$gettext('Dry run mode enabled')"
+          banner
+        />
+
+        <div
+          v-show="showLogContainer"
+          class="mb-4"
+        >
+          <AProgress
+            :stroke-color="progressStrokeColor"
+            :percent="progressPercentComputed"
+            :status="progressStatus"
+          />
+
+          <div
+            ref="logContainer"
+            class="core-upgrade-log-container"
+          />
+        </div>
+        <div v-show="!showLogContainer && data.body">
+          <h1 class="latest-version">
+            {{ data.name }}
+            <ATag
+              v-if="channel === 'stable'"
+              color="green"
+            >
+              {{ $gettext('Stable') }}
+            </ATag>
+            <ATag
+              v-if="channel === 'prerelease'"
+              color="blue"
+            >
+              {{ $gettext('Pre-release') }}
+            </ATag>
+          </h1>
+          <div v-html="marked.parse(data.body)" />
+        </div>
+
+        <div class="flex justify-end">
+          <AButton
+            v-if="!showLogContainer"
+            type="primary"
+            @click="performUpgrade"
+          >
+            {{ $gettext('Perform') }}
+          </AButton>
+        </div>
+      </template>
+    </ASpin>
+  </AModal>
+</template>
+
+<style scoped lang="less">
+.dark {
+  :deep(.core-upgrade-log-container) {
+    background-color: rgba(0, 0, 0, 0.84);
+  }
+}
+
+:deep(.core-upgrade-log-container) {
+  height: 320px;
+  overflow: scroll;
+  background-color: #f3f3f3;
+  border-radius: 4px;
+  margin-top: 15px;
+  padding: 10px;
+
+  p {
+    font-size: 12px;
+    line-height: 1.3;
+  }
+}
+
+.latest-version {
+  display: flex;
+  align-items: center;
+
+  span.ant-tag {
+    margin-left: 10px;
+  }
+}
+
+:deep(h1) {
+  font-size: 20px;
+}
+
+:deep(h2) {
+  font-size: 18px;
+}
+</style>

+ 38 - 144
app/src/views/environment/Environment.vue

@@ -1,142 +1,13 @@
 <script setup lang="tsx">
-import { h } from 'vue'
-import { Badge, Tag, message } from 'ant-design-vue'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { message } from 'ant-design-vue'
 import environment from '@/api/environment'
 import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
-import { input, switcher } from '@/components/StdDesign/StdDataEntry'
-import type { Column, JSXElements } from '@/components/StdDesign/types'
-
-const columns: Column[] = [{
-  title: () => $gettext('Name'),
-  dataIndex: 'name',
-  sortable: true,
-  pithy: true,
-  edit: {
-    type: input,
-  },
-  search: true,
-},
-{
-  title: () => $gettext('URL'),
-  dataIndex: 'url',
-  sortable: true,
-  pithy: true,
-  edit: {
-    type: input,
-    config: {
-      placeholder: () => 'https://10.0.0.1:9000',
-    },
-  },
-},
-{
-  title: () => $gettext('Version'),
-  dataIndex: 'version',
-  pithy: true,
-},
-{
-  title: () => 'NodeSecret',
-  dataIndex: 'token',
-  sortable: true,
-  hiddenInTable: true,
-  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', $gettext('Yes'))})
-//         } else {
-//             return h(Tag, {color: 'default'}, {default: ()=> h('span', $gettext('No'))})
-//         }
-//     },
-// }, {
-//     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',
-  customRender: (args: customRender) => {
-    const template: JSXElements = []
-    const { text } = args
-    if (args.record.enabled) {
-      if (text === true || text > 0) {
-        template.push(<Badge status="success"/>)
-        template.push($gettext('Online'))
-      }
-      else {
-        template.push(<Badge status="error"/>)
-        template.push($gettext('Offline'))
-      }
-    }
-    else {
-      template.push(<Badge status="default"/>)
-      template.push($gettext('Disabled'))
-    }
-
-    return h('div', template)
-  },
-  sortable: true,
-  pithy: true,
-},
-{
-  title: () => $gettext('Enabled'),
-  dataIndex: 'enabled',
-  customRender: (args: customRender) => {
-    const template: JSXElements = []
-    const { text } = args
-    if (text === true || text > 0)
-      template.push(<Tag color="green">{$gettext('Enabled')}</Tag>)
-
-    else
-      template.push(<Tag color="orange">{$gettext('Disabled')}</Tag>)
-
-    return h('div', template)
-  },
-  edit: {
-    type: switcher,
-  },
-  sortable: true,
-  pithy: true,
-},
-{
-  title: () => $gettext('Updated at'),
-  dataIndex: 'updated_at',
-  customRender: datetime,
-  sortable: true,
-  pithy: true,
-},
-{
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
-}]
+import envColumns from '@/views/environment/envColumns'
+import FooterToolBar from '@/components/FooterToolbar'
+import BatchUpgrader from '@/views/environment/BatchUpgrader.vue'
 
 const curd = ref()
-function load_from_settings() {
+function loadFromSettings() {
   environment.load_from_settings().then(() => {
     curd.value.get_list()
     message.success($gettext('Load successfully'))
@@ -144,19 +15,42 @@ function load_from_settings() {
     message.error(`${$gettext('Server error')} ${e?.message}`)
   })
 }
+const selectedNodeIds = ref([])
+const selectedNodes = ref([])
+const refUpgrader = ref()
+
+function batchUpgrade() {
+  refUpgrader.value.open(selectedNodeIds, selectedNodes)
+}
 </script>
 
 <template>
-  <StdCurd
-    ref="curd"
-    :title="$gettext('Environment')"
-    :api="environment"
-    :columns="columns"
-  >
-    <template #extra>
-      <a @click="load_from_settings">{{ $gettext('Load from settings') }}</a>
-    </template>
-  </StdCurd>
+  <div>
+    <StdCurd
+      ref="curd"
+      v-model:selected-row-keys="selectedNodeIds"
+      v-model:selected-rows="selectedNodes"
+      selection-type="checkbox"
+      :title="$gettext('Environment')"
+      :api="environment"
+      :columns="envColumns"
+    >
+      <template #extra>
+        <a @click="loadFromSettings">{{ $gettext('Load from settings') }}</a>
+      </template>
+    </StdCurd>
+
+    <BatchUpgrader ref="refUpgrader" />
+
+    <FooterToolBar v-if="selectedNodes?.length > 0">
+      <AButton
+        type="primary"
+        @click="batchUpgrade"
+      >
+        {{ $gettext('Upgrade') }}
+      </AButton>
+    </FooterToolBar>
+  </div>
 </template>
 
 <style lang="less" scoped>

+ 135 - 0
app/src/views/environment/envColumns.tsx

@@ -0,0 +1,135 @@
+import { h } from 'vue'
+import { Badge, Tag } from 'ant-design-vue'
+import type { Column, JSXElements } from '@/components/StdDesign/types'
+import { input, switcher } from '@/components/StdDesign/StdDataEntry'
+import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+
+const columns: Column[] = [{
+  title: () => $gettext('Name'),
+  dataIndex: 'name',
+  sortable: true,
+  pithy: true,
+  edit: {
+    type: input,
+  },
+  search: true,
+},
+{
+  title: () => $gettext('URL'),
+  dataIndex: 'url',
+  sortable: true,
+  pithy: true,
+  edit: {
+    type: input,
+    config: {
+      placeholder: () => 'https://10.0.0.1:9000',
+    },
+  },
+},
+{
+  title: () => $gettext('Version'),
+  dataIndex: 'version',
+  pithy: true,
+},
+{
+  title: () => 'NodeSecret',
+  dataIndex: 'token',
+  sortable: true,
+  hiddenInTable: true,
+  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', $gettext('Yes'))})
+//         } else {
+//             return h(Tag, {color: 'default'}, {default: ()=> h('span', $gettext('No'))})
+//         }
+//     },
+// }, {
+//     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',
+  customRender: (args: customRender) => {
+    const template: JSXElements = []
+    const { text } = args
+    if (args.record.enabled) {
+      if (text === true || text > 0) {
+        template.push(<Badge status="success"/>)
+        template.push($gettext('Online'))
+      }
+      else {
+        template.push(<Badge status="error"/>)
+        template.push($gettext('Offline'))
+      }
+    }
+    else {
+      template.push(<Badge status="default"/>)
+      template.push($gettext('Disabled'))
+    }
+
+    return h('div', template)
+  },
+  sortable: true,
+  pithy: true,
+},
+{
+  title: () => $gettext('Enabled'),
+  dataIndex: 'enabled',
+  customRender: (args: customRender) => {
+    const template: JSXElements = []
+    const { text } = args
+    if (text === true || text > 0)
+      template.push(<Tag color="green">{$gettext('Enabled')}</Tag>)
+
+    else
+      template.push(<Tag color="orange">{$gettext('Disabled')}</Tag>)
+
+    return h('div', template)
+  },
+  edit: {
+    type: switcher,
+  },
+  sortable: true,
+  pithy: true,
+},
+{
+  title: () => $gettext('Updated at'),
+  dataIndex: 'updated_at',
+  customRender: datetime,
+  sortable: true,
+  pithy: true,
+},
+{
+  title: () => $gettext('Action'),
+  dataIndex: 'action',
+}]
+
+export default columns

+ 23 - 32
app/src/views/system/Upgrade.vue

@@ -12,7 +12,7 @@ import upgrade from '@/api/upgrade'
 
 const route = useRoute()
 const data = ref({}) as Ref<RuntimeInfo>
-const last_check = ref('')
+const lastCheck = ref('')
 const loading = ref(false)
 const channel = ref('stable')
 
@@ -25,31 +25,31 @@ const modalVisible = ref(false)
 const progressPercent = ref(0)
 const progressStatus = ref('active') as Ref<'normal' | 'active' | 'success' | 'exception'>
 const modalClosable = ref(false)
-const get_release_error = ref(false)
+const getReleaseError = ref(false)
 
 const progressPercentComputed = computed(() => {
   return Number.parseFloat(progressPercent.value.toFixed(1))
 })
 
-function get_latest_release() {
+function getLatestRelease() {
   loading.value = true
   data.value.body = ''
   upgrade.get_latest_release(channel.value).then(r => {
     data.value = r
-    last_check.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
+    lastCheck.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
   }).catch(e => {
-    get_release_error.value = e?.message
+    getReleaseError.value = e?.message
     message.error(e?.message ?? $gettext('Server error'))
   }).finally(() => {
     loading.value = false
   })
 }
 
-get_latest_release()
+getLatestRelease()
 
-watch(channel, get_latest_release)
+watch(channel, getLatestRelease)
 
-const is_latest_ver = computed(() => {
+const isLatestVer = computed(() => {
   return data.value.name === `v${version.version}`
 })
 
@@ -65,11 +65,11 @@ function log(msg: string) {
   logContainer.value.scroll({ top: 320, left: 0, behavior: 'smooth' })
 }
 
-const dry_run = computed(() => {
+const dryRun = computed(() => {
   return !!route.query.dry_run
 })
 
-async function perform_upgrade() {
+async function performUpgrade() {
   progressStatus.value = 'active'
   modalClosable.value = false
   modalVisible.value = true
@@ -84,12 +84,12 @@ async function perform_upgrade() {
 
   ws.onopen = () => {
     ws.send(JSON.stringify({
-      dry_run: dry_run.value,
+      dry_run: dryRun.value,
       channel: channel.value,
     }))
   }
 
-  let is_fail = false
+  let isFailed = false
 
   ws.onmessage = async m => {
     const r = JSON.parse(m.data)
@@ -106,7 +106,7 @@ async function perform_upgrade() {
       case 'error':
         progressStatus.value = 'exception'
         modalClosable.value = true
-        is_fail = true
+        isFailed = true
         break
       default:
         modalClosable.value = true
@@ -115,13 +115,13 @@ async function perform_upgrade() {
   }
 
   ws.onerror = () => {
-    is_fail = true
+    isFailed = true
     progressStatus.value = 'exception'
     modalClosable.value = true
   }
 
   ws.onclose = async () => {
-    if (is_fail)
+    if (isFailed)
       return
 
     const t = setInterval(() => {
@@ -165,11 +165,11 @@ async function perform_upgrade() {
     <div class="upgrade-container">
       <p>{{ $gettext('You can check Nginx UI upgrade at this page.') }}</p>
       <h3>{{ $gettext('Current Version') }}: v{{ version.version }}</h3>
-      <template v-if="get_release_error">
+      <template v-if="getReleaseError">
         <AAlert
           type="error"
           :title="$gettext('Get release information error')"
-          :message="get_release_error"
+          :message="getReleaseError"
           banner
         />
       </template>
@@ -178,11 +178,11 @@ async function perform_upgrade() {
         <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
         <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
         <p>
-          {{ $gettext('Last checked at') }}: {{ last_check }}
+          {{ $gettext('Last checked at') }}: {{ lastCheck }}
           <AButton
             type="link"
             :loading="loading"
-            @click="get_latest_release"
+            @click="getLatestRelease"
           >
             {{ $gettext('Check again') }}
           </AButton>
@@ -199,7 +199,7 @@ async function perform_upgrade() {
         </AFormItem>
         <template v-if="!loading">
           <AAlert
-            v-if="is_latest_ver"
+            v-if="isLatestVer"
             type="success"
             :message="$gettext('You are using the latest version')"
             banner
@@ -210,7 +210,7 @@ async function perform_upgrade() {
             :message="$gettext('New version released')"
             banner
           />
-          <template v-if="dry_run">
+          <template v-if="dryRun">
             <br>
             <AAlert
               type="info"
@@ -221,20 +221,11 @@ async function perform_upgrade() {
           <div class="control-btn">
             <ASpace>
               <AButton
-                v-if="is_latest_ver"
                 type="primary"
                 ghost
-                @click="perform_upgrade"
+                @click="performUpgrade"
               >
-                {{ $gettext('Reinstall') }}
-              </AButton>
-              <AButton
-                v-else
-                type="primary"
-                ghost
-                @click="perform_upgrade"
-              >
-                {{ $gettext('Upgrade') }}
+                {{ isLatestVer ? $gettext('Reinstall') : $gettext('Upgrade') }}
               </AButton>
             </ASpace>
           </div>