Browse Source

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

Jacky 1 year ago
parent
commit
d389bb07ae

+ 1 - 0
app/components.d.ts

@@ -54,6 +54,7 @@ declare module 'vue' {
     ASelect: typeof import('ant-design-vue/es')['Select']
     ASelect: typeof import('ant-design-vue/es')['Select']
     ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
     ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
     ASpace: typeof import('ant-design-vue/es')['Space']
     ASpace: typeof import('ant-design-vue/es')['Space']
+    ASpin: typeof import('ant-design-vue/es')['Spin']
     AStatistic: typeof import('ant-design-vue/es')['Statistic']
     AStatistic: typeof import('ant-design-vue/es')['Statistic']
     AStep: typeof import('ant-design-vue/es')['Step']
     AStep: typeof import('ant-design-vue/es')['Step']
     ASteps: typeof import('ant-design-vue/es')['Steps']
     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 gettext from '@/gettext'
 import loadTranslations from '@/api/translations'
 import loadTranslations from '@/api/translations'
 
 
+const route = useRoute()
+
 const media = window.matchMedia('(prefers-color-scheme: dark)')
 const media = window.matchMedia('(prefers-color-scheme: dark)')
 
 
 const callback = () => {
 const callback = () => {
@@ -51,7 +53,7 @@ const lang = computed(() => {
 const settings = useSettingsStore()
 const settings = useSettingsStore()
 const is_theme_dark = computed(() => settings.theme === 'dark')
 const is_theme_dark = computed(() => settings.theme === 'dark')
 
 
-loadTranslations()
+loadTranslations(route)
 </script>
 </script>
 
 
 <template>
 <template>

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

@@ -1,8 +1,7 @@
+import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
 import gettext from '@/gettext'
 import gettext from '@/gettext'
 
 
-export default async function loadTranslations() {
-  const route = useRoute()
-
+export default async function loadTranslations(route: RouteLocationNormalizedLoadedGeneric) {
   if (gettext.current !== 'en') {
   if (gettext.current !== 'en') {
     await fetch(`${import.meta.env.VITE_API_ROOT}/translation/${gettext.current}`).then(async r => {
     await fetch(`${import.meta.env.VITE_API_ROOT}/translation/${gettext.current}`).then(async r => {
       gettext.translations[gettext.current] = await r.json()
       gettext.translations[gettext.current] = await r.json()
@@ -12,7 +11,7 @@ export default async function loadTranslations() {
       document.title = `${route.meta.name?.()} | Nginx UI`
       document.title = `${route.meta.name?.()} | Nginx UI`
   }
   }
 
 
-  watch(route, () => {
+  watch(() => route, () => {
     if (route?.meta?.name)
     if (route?.meta?.name)
       document.title = `${route.meta.name?.()} | Nginx UI`
       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
 const languageAvailable = gettext.available
 
 
 watch(current, v => {
 watch(current, v => {
-  loadTranslations()
+  loadTranslations(route)
   settings.set_language(v)
   settings.set_language(v)
   gettext.current = 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 { message } from 'ant-design-vue'
 import type { ComputedRef } from 'vue'
 import type { ComputedRef } from 'vue'
 import type { StdTableProps } from './StdTable.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 type { Column } from '@/components/StdDesign/types'
 import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
 import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
 
 
-export interface StdCurdProps {
+export interface StdCurdProps<T> extends StdTableProps<T> {
   cardTitleKey?: string
   cardTitleKey?: string
   modalMaxWidth?: string | number
   modalMaxWidth?: string | number
   modalMask?: boolean
   modalMask?: boolean
@@ -16,28 +16,28 @@ export interface StdCurdProps {
 
 
   disableAdd?: boolean
   disableAdd?: boolean
   onClickAdd?: () => void
   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
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   beforeSave?: (data: any) => Promise<void>
   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 visible = ref(false)
-const update = ref(0)
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const data: any = reactive({ id: null })
 const data: any = reactive({ id: null })
 const modifyMode = ref(true)
 const modifyMode = ref(true)
 const editMode = ref<string>()
 const editMode = ref<string>()
+const shouldRefetchList = ref(false)
 
 
 provide('data', data)
 provide('data', data)
 provide('editMode', editMode)
 provide('editMode', editMode)
+provide('shouldRefetchList', shouldRefetchList)
 
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const error: any = reactive({})
 const error: any = reactive({})
 const selected = ref([])
 const selected = ref([])
+
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 function onSelect(keys: any) {
 function onSelect(keys: any) {
   selected.value = keys
   selected.value = keys
@@ -49,19 +49,35 @@ const editableColumns = computed(() => {
   })
   })
 }) as ComputedRef<Column[]>
 }) 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 => {
   Object.keys(data).forEach(v => {
     delete data[v]
     delete data[v]
   })
   })
 
 
+  if (preset)
+    Object.assign(data, preset)
+
   clear_error()
   clear_error()
   visible.value = true
   visible.value = true
   editMode.value = 'create'
   editMode.value = 'create'
   modifyMode.value = true
   modifyMode.value = true
 }
 }
+
 const table = ref()
 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({
 const getParams = reactive({
   trash: false,
   trash: false,
@@ -91,13 +107,14 @@ function clear_error() {
 }
 }
 
 
 const stdEntryRef = ref()
 const stdEntryRef = ref()
+
 async function ok() {
 async function ok() {
   const { formRef } = stdEntryRef.value
   const { formRef } = stdEntryRef.value
 
 
   clear_error()
   clear_error()
   try {
   try {
     await formRef.validateFields()
     await formRef.validateFields()
-    await props?.beforeSave?.(data)
+    props?.beforeSave?.(data)
     props
     props
       .api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } })
       .api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } })
       .then(r => {
       .then(r => {
@@ -111,37 +128,26 @@ async function ok() {
         Object.assign(error, e.errors)
         Object.assign(error, e.errors)
       })
       })
   }
   }
-  catch { }
+  catch {
+    message.error($gettext('Please fill in the required fields'))
+  }
 }
 }
 
 
 function cancel() {
 function cancel() {
   visible.value = false
   visible.value = false
 
 
   clear_error()
   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) {
 function edit(id: number | string) {
-  get(id, true).then(() => {
+  if (props.onClickEdit)
+    return
+  get(id).then(() => {
     visible.value = true
     visible.value = true
     modifyMode.value = true
     modifyMode.value = true
     editMode.value = 'modify'
     editMode.value = 'modify'
@@ -151,7 +157,7 @@ function edit(id: number | string) {
 }
 }
 
 
 function view(id: number | string) {
 function view(id: number | string) {
-  get(id, false).then(() => {
+  get(id).then(() => {
     visible.value = true
     visible.value = true
     modifyMode.value = false
     modifyMode.value = false
   }).catch(e => {
   }).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
   return props
     .api!.get(id, { ...props.overwriteParams })
     .api!.get(id, { ...props.overwriteParams })
     .then(async r => {
     .then(async r => {
@@ -168,32 +174,14 @@ async function get(id: number | string, _modifyMode: boolean) {
       })
       })
       data.id = null
       data.id = null
       Object.assign(data, r)
       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(() => {
 const modalTitle = computed(() => {
   return data.id ? modifyMode.value ? $gettext('Modify') : $gettext('View Details') : $gettext('Add')
   return data.id ? modifyMode.value ? $gettext('Modify') : $gettext('View Details') : $gettext('Add')
 })
 })
 
 
+const localOverwriteParams = reactive(props.overwriteParams ?? {})
 </script>
 </script>
 
 
 <template>
 <template>
@@ -209,7 +197,7 @@ const modalTitle = computed(() => {
         <ASpace>
         <ASpace>
           <slot name="beforeAdd" />
           <slot name="beforeAdd" />
           <a
           <a
-            v-if="!disableAdd"
+            v-if="!disableAdd && !getParams.trash"
             @click="add"
             @click="add"
           >{{ $gettext('Add') }}</a>
           >{{ $gettext('Add') }}</a>
           <slot name="extra" />
           <slot name="extra" />
@@ -224,7 +212,7 @@ const modalTitle = computed(() => {
               v-else
               v-else
               @click="getParams.trash = false"
               @click="getParams.trash = false"
             >
             >
-              {{ $gettext('List') }}
+              {{ $gettext('Back to list') }}
             </a>
             </a>
           </template>
           </template>
         </ASpace>
         </ASpace>
@@ -232,11 +220,12 @@ const modalTitle = computed(() => {
 
 
       <StdTable
       <StdTable
         ref="table"
         ref="table"
-        :key="update"
         v-model:selected-row-keys="selectedRowKeys"
         v-model:selected-row-keys="selectedRowKeys"
+        v-model:selected-rows="selectedRows"
         v-bind="{
         v-bind="{
           ...props,
           ...props,
           getParams,
           getParams,
+          overwriteParams: localOverwriteParams,
         }"
         }"
         @click-edit="edit"
         @click-edit="edit"
         @click-view="view"
         @click-view="view"
@@ -269,7 +258,7 @@ const modalTitle = computed(() => {
       @ok="ok"
       @ok="ok"
     >
     >
       <div
       <div
-        v-if="!disabledModify && !disabledView"
+        v-if="!disableModify && !disableView && editMode === 'modify'"
         class="m-2 flex justify-end"
         class="m-2 flex justify-end"
       >
       >
         <ASwitch
         <ASwitch
@@ -294,7 +283,7 @@ const modalTitle = computed(() => {
           ref="stdEntryRef"
           ref="stdEntryRef"
           :data-list="editableColumns"
           :data-list="editableColumns"
           :data-source="data"
           :data-source="data"
-          :error="error"
+          :errors="error"
         />
         />
 
 
         <slot
         <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 { message } from 'ant-design-vue'
 import { HolderOutlined } from '@ant-design/icons-vue'
 import { HolderOutlined } from '@ant-design/icons-vue'
 import type { ComputedRef, Ref } from '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 useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
 import type Curd from '@/api/curd'
 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
   title?: string
   mode?: string
   mode?: string
   rowKey?: string
   rowKey?: string
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  api: Curd<any>
+
+  api: Curd<T>
   columns: Column[]
   columns: Column[]
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   getParams?: Record<string, any>
   getParams?: Record<string, any>
@@ -31,25 +33,45 @@ export interface StdTableProps {
   exportMaterial?: boolean
   exportMaterial?: boolean
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   overwriteParams?: Record<string, any>
   overwriteParams?: Record<string, any>
-  disabledView?: boolean
-  disabledModify?: boolean
+  disableView?: boolean
+  disableModify?: boolean
   selectionType?: string
   selectionType?: string
   sortable?: boolean
   sortable?: boolean
   disableDelete?: boolean
   disableDelete?: boolean
   disablePagination?: boolean
   disablePagination?: boolean
   sortableMoveHook?: (oldRow: number[], newRow: number[]) => boolean
   sortableMoveHook?: (oldRow: number[], newRow: number[]) => boolean
   scrollX?: string | number
   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',
   rowKey: 'id',
 })
 })
 
 
 const emit = defineEmits(['clickEdit', 'clickView', 'clickBatchModify', 'update:selectedRowKeys'])
 const emit = defineEmits(['clickEdit', 'clickView', 'clickBatchModify', 'update:selectedRowKeys'])
 const route = useRoute()
 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([])
 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
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const rowsKeyIndexMap: Ref<Record<number, any>> = ref({})
 const rowsKeyIndexMap: Ref<Record<number, any>> = ref({})
 const loading = ref(true)
 const loading = ref(true)
@@ -72,11 +94,6 @@ const params = reactive({
   ...props.getParams,
   ...props.getParams,
 })
 })
 
 
-const get_list = _.debounce(_get_list, 200, {
-  leading: true,
-  trailing: false,
-})
-
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
 const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
   default: () => [],
   default: () => [],
@@ -105,9 +122,8 @@ const searchColumns = computed(() => {
           edit: column.search,
           edit: column.search,
         })
         })
       }
       }
-      else {
-        _searchColumns.push({ ...column })
-      }
+
+      else { _searchColumns.push({ ...column }) }
     }
     }
   })
   })
 
 
@@ -137,8 +153,9 @@ const batchColumns = computed(() => {
   return batch
   return batch
 })
 })
 
 
-watch(route, () => {
-  params.trash = route.query.trash === 'true'
+const get_list = _.debounce(_get_list, 100, {
+  leading: true,
+  trailing: false,
 })
 })
 
 
 const filterParams = reactive({})
 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) {
 async function _get_list(page_num = null, page_size = 20) {
+  dataSource.value = []
   loading.value = true
   loading.value = true
   if (page_num) {
   if (page_num) {
     params.page = page_num
     params.page = page_num
     params.page_size = page_size
     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
     dataSource.value = r.data
     rowsKeyIndexMap.value = {}
     rowsKeyIndexMap.value = {}
     if (props.sortable)
     if (props.sortable)
@@ -260,6 +278,7 @@ function onTableChange(_pagination: TablePaginationConfig, filters: Record<strin
       params[v] = filters[v]
       params[v] = filters[v]
     })
     })
   }
   }
+
   if (_pagination)
   if (_pagination)
     selectedRowKeys.value = []
     selectedRowKeys.value = []
 }
 }
@@ -297,15 +316,13 @@ async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
       selectedRows.value = filteredRows
       selectedRows.value = filteredRows
     })
     })
   }
   }
+  else if (selected) {
+    selectedRowKeys.value = record[props.rowKey]
+    selectedRows.value = [record]
+  }
   else {
   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(() => {
   router.push({ query: {} }).catch(() => {
   })
   })
 
 
+  Object.keys(filterParams).forEach(v => {
+    delete filterParams[v]
+  })
+
   updateFilter.value++
   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) {
 if (props.getParams) {
@@ -401,14 +429,12 @@ const rowSelection = computed(() => {
       selectedRowKeys: selectedRowKeys.value,
       selectedRowKeys: selectedRowKeys.value,
       onSelect,
       onSelect,
       onSelectAll,
       onSelectAll,
+      getCheckboxProps: props?.getCheckboxProps,
       type: (batchColumns.value.length > 0 || props.exportExcel) ? 'checkbox' : props.selectionType,
       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(() => {
 const hasSelectedRow = computed(() => {
   return batchColumns.value.length > 0 && selectedRowKeys.value.length > 0
   return batchColumns.value.length > 0 && selectedRowKeys.value.length > 0
@@ -435,6 +461,7 @@ const paginationSize = computed(() => {
   else
   else
     return 'default'
     return 'default'
 })
 })
+
 </script>
 </script>
 
 
 <template>
 <template>
@@ -482,7 +509,7 @@ const paginationSize = computed(() => {
           {{ text }}
           {{ text }}
         </template>
         </template>
         <template v-if="column.dataIndex === 'action'">
         <template v-if="column.dataIndex === 'action'">
-          <template v-if="!props.disabledView && !params.trash">
+          <template v-if="!props.disableView && !params.trash">
             <AButton
             <AButton
               type="link"
               type="link"
               size="small"
               size="small"
@@ -491,12 +518,12 @@ const paginationSize = computed(() => {
               {{ $gettext('View') }}
               {{ $gettext('View') }}
             </AButton>
             </AButton>
             <ADivider
             <ADivider
-              v-if="!props.disabledModify"
+              v-if="!props.disableModify"
               type="vertical"
               type="vertical"
             />
             />
           </template>
           </template>
 
 
-          <template v-if="!props.disabledModify && !params.trash">
+          <template v-if="!props.disableModify && !params.trash">
             <AButton
             <AButton
               type="link"
               type="link"
               size="small"
               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/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr ""
 msgid "API Token"
 msgid "API Token"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgid "Arch"
 msgstr ""
 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?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "Are you sure you want to remove this directive?"
 msgstr "Are you sure you want to remove this directive?"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Are you sure you want to remove this directive?"
 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
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Are you sure you want to remove this directive?"
 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
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr "Are you sure you want to remove this directive?"
 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Are you sure you want to remove this directive?"
 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
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgid "Are you sure you want to recover this item?"
 msgstr "Are you sure you want to remove this directive?"
 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?"
 msgid "Are you sure you want to remove this location?"
 msgstr "Are you sure you want to remove this directive?"
 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"
 msgid "Ask ChatGPT for Help"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr ""
 msgstr ""
 
 
@@ -194,6 +194,10 @@ msgstr "Back"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "Back"
 msgstr "Back"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
@@ -223,11 +227,15 @@ msgid "Basic Mode"
 msgstr "Basic Mode"
 msgstr "Basic Mode"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 #, fuzzy
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "Modify Config"
 msgstr "Modify Config"
 
 
+#: src/views/environment/BatchUpgrader.vue:154
+msgid "Batch Upgrade"
+msgstr ""
+
 #: src/views/system/About.vue:39
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "Build with"
 msgstr "Build with"
@@ -240,9 +248,9 @@ msgstr ""
 msgid "CADir"
 msgid "CADir"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
 #: 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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -293,11 +301,11 @@ msgstr ""
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "Certificate is valid"
 msgstr "Certificate is valid"
 
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgid "Channel"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr ""
 msgstr ""
 
 
@@ -305,7 +313,7 @@ msgstr ""
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -350,7 +358,7 @@ msgstr "Configurations"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "Configure SSL"
 msgstr "Configure SSL"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr ""
 msgstr ""
 
 
@@ -360,7 +368,7 @@ msgstr ""
 msgid "Content"
 msgid "Content"
 msgstr "Content"
 msgstr "Content"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr ""
 msgstr ""
 
 
@@ -397,7 +405,7 @@ msgstr ""
 msgid "Credentials"
 msgid "Credentials"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr ""
 msgstr ""
 
 
@@ -424,7 +432,7 @@ msgstr "Database (Optional, default: database)"
 msgid "Days"
 msgid "Days"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -432,7 +440,7 @@ msgstr ""
 msgid "Delete"
 msgid "Delete"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -444,7 +452,7 @@ msgstr ""
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 #, fuzzy
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "Disabled successfully"
 msgstr "Disabled successfully"
@@ -508,8 +516,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "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/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"
 msgid "Disabled"
 msgstr "Disabled"
 msgstr "Disabled"
 
 
@@ -604,7 +613,7 @@ msgstr ""
 msgid "Downloading latest release"
 msgid "Downloading latest release"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgid "Dry run mode enabled"
 msgstr ""
 msgstr ""
 
 
@@ -704,7 +713,8 @@ msgstr "Enable TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: 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"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "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"
 msgid "Environment"
 msgstr ""
 msgstr ""
 
 
@@ -732,7 +742,7 @@ msgstr ""
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr ""
 msgstr ""
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 #, fuzzy
 msgid "Environments"
 msgid "Environments"
 msgstr "Comments"
 msgstr "Comments"
@@ -745,7 +755,7 @@ msgstr ""
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr ""
 msgstr ""
 
 
@@ -846,7 +856,7 @@ msgstr ""
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 #, fuzzy
 #, fuzzy
 msgid "Get release information error"
 msgid "Get release information error"
 msgstr "Base information"
 msgstr "Base information"
@@ -965,7 +975,7 @@ msgstr ""
 msgid "Key Type"
 msgid "Key Type"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr ""
 msgstr ""
 
 
@@ -988,12 +998,12 @@ msgstr "Leave blank for no change"
 msgid "License"
 msgid "License"
 msgstr "License"
 msgstr "License"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgid "Link Start"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -1002,11 +1012,11 @@ msgstr ""
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "Load Averages:"
 msgstr "Load Averages:"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr ""
 msgstr ""
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 #, fuzzy
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "Saved successfully"
 msgstr "Saved successfully"
@@ -1109,9 +1119,9 @@ msgstr ""
 msgid "Model"
 msgid "Model"
 msgstr "Advance Mode"
 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
 #, fuzzy
 msgid "Modify"
 msgid "Modify"
 msgstr "Modify Config"
 msgstr "Modify Config"
@@ -1125,7 +1135,7 @@ msgstr "Certificate Status"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "Modify Config"
 msgstr "Modify Config"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "Modify Config"
 msgstr "Modify Config"
@@ -1144,7 +1154,7 @@ msgstr "Single Directive"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1167,7 +1177,7 @@ msgstr "Network Total Receive"
 msgid "Network Total Send"
 msgid "Network Total Send"
 msgstr "Network Total Send"
 msgstr "Network Total Send"
 
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgid "New version released"
 msgstr ""
 msgstr ""
 
 
@@ -1212,11 +1222,11 @@ msgstr "Saved successfully"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Saved successfully"
 msgstr "Saved successfully"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
 #: 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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1268,22 +1278,22 @@ msgid "Obtaining certificate"
 msgstr ""
 msgstr ""
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: src/views/domain/components/RightSettings.vue:50
@@ -1304,8 +1314,8 @@ msgstr ""
 
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr ""
 msgstr ""
 
 
@@ -1313,7 +1323,7 @@ msgstr ""
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 #, fuzzy
 msgid "OS"
 msgid "OS"
 msgstr "OS:"
 msgstr "OS:"
@@ -1350,6 +1360,10 @@ msgstr "Password (*)"
 msgid "Path"
 msgid "Path"
 msgstr "Path"
 msgstr "Path"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr ""
 msgstr ""
@@ -1364,6 +1378,10 @@ msgid ""
 "provider."
 "provider."
 msgstr ""
 msgstr ""
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1399,7 +1417,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgid "Please select at least one node!"
 msgstr ""
 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"
 msgid "Pre-release"
 msgstr ""
 msgstr ""
 
 
@@ -1438,11 +1458,11 @@ msgstr "Reads"
 msgid "Receive"
 msgid "Receive"
 msgstr "Receive"
 msgstr "Receive"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 #, fuzzy
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "Saved successfully"
 msgstr "Saved successfully"
@@ -1451,7 +1471,7 @@ msgstr "Saved successfully"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr ""
 msgstr ""
 
 
@@ -1477,16 +1497,16 @@ msgstr ""
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 #, fuzzy
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "Install"
 msgstr "Install"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr ""
 msgstr ""
@@ -1544,7 +1564,7 @@ msgstr "Enabled successfully"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgid "Reset"
 msgstr ""
 msgstr ""
 
 
@@ -1565,7 +1585,7 @@ msgstr "Advance Mode"
 msgid "Running"
 msgid "Running"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1583,7 +1603,7 @@ msgid "Save error %{msg}"
 msgstr "Save error %{msg}"
 msgstr "Save error %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 #, fuzzy
 #, fuzzy
@@ -1615,10 +1635,11 @@ msgstr "Send"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1701,14 +1722,16 @@ msgstr "Certificate Status"
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr "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
 #, fuzzy
 msgid "Stable"
 msgid "Stable"
 msgstr "Enabled"
 msgstr "Enabled"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "Status"
 msgstr "Status"
 
 
@@ -1833,6 +1856,13 @@ msgstr "Certificate Status"
 msgid "The path exists, but the file is not a private key"
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1880,6 +1910,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr ""
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1905,7 +1940,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -1918,7 +1953,7 @@ msgstr ""
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1929,17 +1964,22 @@ msgstr "Updated at"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "Saved 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"
 msgid "Upgrade"
 msgstr ""
 msgstr ""
 
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "Saved successfully"
+
 #: src/language/constants.ts:29
 #: src/language/constants.ts:29
 #, fuzzy
 #, fuzzy
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "Saved 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..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr ""
 msgstr ""
 
 
@@ -1951,11 +1991,11 @@ msgstr ""
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "Uptime:"
 msgstr "Uptime:"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 #, fuzzy
 msgid "User"
 msgid "User"
 msgstr "Username"
 msgstr "Username"
@@ -1977,7 +2017,11 @@ msgstr "Username (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr ""
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr ""
 msgstr ""
@@ -1987,11 +2031,11 @@ msgstr ""
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "Certificate is valid"
 msgstr "Certificate is valid"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgid "View Details"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Basic Mode"
 msgstr "Basic Mode"
@@ -2034,11 +2078,11 @@ msgstr ""
 msgid "Yes"
 msgid "Yes"
 msgstr "Yes"
 msgstr "Yes"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgid "You can check Nginx UI upgrade at this page."
 msgstr ""
 msgstr ""
 
 

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

@@ -31,15 +31,15 @@ msgstr "Usuario"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "Acción"
 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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -94,7 +94,7 @@ msgstr "Proxy de la API"
 msgid "API Token"
 msgid "API Token"
 msgstr "Token de la API"
 msgstr "Token de la API"
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgid "Arch"
 msgstr "Arquitectura"
 msgstr "Arquitectura"
 
 
@@ -108,16 +108,16 @@ msgstr "¿Está seguro de que quiere borrar?"
 msgid "Are you sure you want to clear all notifications?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "¿Está seguro de que desea borrar todas las notificaciones?"
 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?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "¿Está seguro de que desea borrar el registro del 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
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "¿Está seguro de que quiere borrar?"
 msgstr "¿Está seguro de que quiere borrar?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr "¿Está seguro de que quiere borrar?"
 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?"
 msgid "Are you sure you want to delete?"
 msgstr "¿Está seguro de que quiere borrar?"
 msgstr "¿Está seguro de que quiere borrar?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgid "Are you sure you want to recover this item?"
 msgstr "¿Está seguro de que quiere borrar esta directiva?"
 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?"
 msgid "Are you sure you want to remove this location?"
 msgstr "¿Está seguro de que quiere borrar esta ubicación?"
 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"
 msgid "Ask ChatGPT for Help"
 msgstr "Preguntar por ayuda a ChatGPT"
 msgstr "Preguntar por ayuda a ChatGPT"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr "Asistente"
 msgstr "Asistente"
 
 
@@ -193,6 +193,10 @@ msgstr "Volver"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "Volver al Inicio"
 msgstr "Volver al Inicio"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
@@ -221,10 +225,15 @@ msgid "Basic Mode"
 msgstr "Modo Básico"
 msgstr "Modo Básico"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "Modificar por lotes"
 msgstr "Modificar por lotes"
 
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "Actualizar"
+
 #: src/views/system/About.vue:39
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "Desarrollado con"
 msgstr "Desarrollado con"
@@ -237,9 +246,9 @@ msgstr ""
 msgid "CADir"
 msgid "CADir"
 msgstr "Directorio CA"
 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/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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -287,11 +296,11 @@ msgstr "Método de desafío"
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "Cambiar Certificado"
 msgstr "Cambiar Certificado"
 
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgid "Channel"
 msgstr "Canal"
 msgstr "Canal"
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr "Intentar nuevamente"
 msgstr "Intentar nuevamente"
 
 
@@ -299,7 +308,7 @@ msgstr "Intentar nuevamente"
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr "Borrar las variables de entorno"
 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/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -342,7 +351,7 @@ msgstr "Configuraciones"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "Configurar SSL"
 msgstr "Configurar SSL"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr "Conectado"
 msgstr "Conectado"
 
 
@@ -352,7 +361,7 @@ msgstr "Conectado"
 msgid "Content"
 msgid "Content"
 msgstr "Contenido"
 msgstr "Contenido"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Actualización del kernel"
 msgstr "Actualización del kernel"
 
 
@@ -388,7 +397,7 @@ msgstr "Credencial"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Credenciales"
 msgstr "Credenciales"
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr "Versión actual"
 msgstr "Versión actual"
 
 
@@ -415,7 +424,7 @@ msgstr "Base de datos (Opcional, default: database)"
 msgid "Days"
 msgid "Days"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -423,7 +432,7 @@ msgstr ""
 msgid "Delete"
 msgid "Delete"
 msgstr "Eliminar"
 msgstr "Eliminar"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -435,7 +444,7 @@ msgstr "Eliminar sitio: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Eliminar stream: %{site_name}"
 msgstr "Eliminar stream: %{site_name}"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "Borrado exitoso"
 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}"
 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/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"
 msgid "Disabled"
 msgstr "Desactivado"
 msgstr "Desactivado"
 
 
@@ -585,7 +595,7 @@ msgstr "Error al descargar la última versión"
 msgid "Downloading latest release"
 msgid "Downloading latest release"
 msgstr "Descargando la última versión"
 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"
 msgid "Dry run mode enabled"
 msgstr "Modo de ejecución de prueba habilitado"
 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/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: 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"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Encriptar sitio web con 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"
 msgid "Environment"
 msgstr "Entorno"
 msgstr "Entorno"
 
 
@@ -707,7 +718,7 @@ msgstr "Entorno"
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr "Configuración de variables de entorno"
 msgstr "Configuración de variables de entorno"
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgid "Environments"
 msgstr "Entornos"
 msgstr "Entornos"
 
 
@@ -719,7 +730,7 @@ msgstr "Error"
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Registros de acceso"
 msgstr "Registros de acceso"
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr "Ruta ejecutable"
 msgstr "Ruta ejecutable"
 
 
@@ -816,7 +827,7 @@ msgstr "Generar"
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Generando clave privada para registrar cuenta"
 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"
 msgid "Get release information error"
 msgstr "Obtener error de información de versión"
 msgstr "Obtener error de información de versión"
 
 
@@ -931,7 +942,7 @@ msgstr "Secreto Jwt"
 msgid "Key Type"
 msgid "Key Type"
 msgstr "Tipo"
 msgstr "Tipo"
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr "Comprobado por última vez el"
 msgstr "Comprobado por última vez el"
 
 
@@ -952,12 +963,12 @@ msgstr "Dejarlo en blanco no cambiará nada"
 msgid "License"
 msgid "License"
 msgstr "Licencia"
 msgstr "Licencia"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgid "Link Start"
 msgstr "Iniciar conexión"
 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"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -965,11 +976,11 @@ msgstr ""
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "Promedios de carga:"
 msgstr "Promedios de carga:"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr ""
 msgstr ""
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 #, fuzzy
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "Guardado con éxito"
 msgstr "Guardado con éxito"
@@ -1067,9 +1078,9 @@ msgstr ""
 msgid "Model"
 msgid "Model"
 msgstr "Modo de ejecución"
 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"
 msgid "Modify"
 msgstr "Modificar"
 msgstr "Modificar"
 
 
@@ -1081,7 +1092,7 @@ msgstr "Modificar Certificado"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "Modificar configuración"
 msgstr "Modificar configuración"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "Modificar"
 msgstr "Modificar"
@@ -1099,7 +1110,7 @@ msgstr "Directiva multilínea"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: 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"
 msgid "Network Total Send"
 msgstr "Total enviado por la red"
 msgstr "Total enviado por la red"
 
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgid "New version released"
 msgstr "Se liberó una nueva versión"
 msgstr "Se liberó una nueva versión"
 
 
@@ -1164,11 +1175,11 @@ msgstr "Nginx recargado con éxito"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Nginx reiniciado con éxito"
 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/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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1217,22 +1228,22 @@ msgid "Obtaining certificate"
 msgstr "Obteniendo certificado"
 msgstr "Obteniendo certificado"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr "Desconectado"
 msgstr "Desconectado"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: 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:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr "En línea"
 msgstr "En línea"
 
 
@@ -1262,7 +1273,7 @@ msgstr "En línea"
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr "OpenAI"
 msgstr "OpenAI"
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgid "OS"
 msgstr "SO"
 msgstr "SO"
 
 
@@ -1298,6 +1309,10 @@ msgstr "Contraseña (*)"
 msgid "Path"
 msgid "Path"
 msgstr "Ruta"
 msgstr "Ruta"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr "Error al ejecutar la actualización del kernel"
 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 "
 "Por favor, complete las credenciales de autenticación API proporcionadas por "
 "su proveedor de DNS."
 "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
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1356,7 +1375,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgid "Please select at least one node!"
 msgstr "¡Seleccione al menos un nodo!"
 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"
 msgid "Pre-release"
 msgstr "Prelanzamiento"
 msgstr "Prelanzamiento"
 
 
@@ -1394,11 +1415,11 @@ msgstr "Lecturas"
 msgid "Receive"
 msgid "Receive"
 msgstr "Recibido"
 msgstr "Recibido"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 #, fuzzy
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "Eliminado con éxito"
 msgstr "Eliminado con éxito"
@@ -1407,7 +1428,7 @@ msgstr "Eliminado con éxito"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr "Regenerar respuesta"
 msgstr "Regenerar respuesta"
 
 
@@ -1435,15 +1456,15 @@ msgstr "Registrando Usuario"
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr "Registrando Usuario"
 msgstr "Registrando Usuario"
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "Reinstalar"
 msgstr "Reinstalar"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr "Nota de versión"
 msgstr "Nota de versión"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr "Recargar"
 msgstr "Recargar"
@@ -1495,7 +1516,7 @@ msgstr "Renovado con éxito"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr "Pedido con parámetros incorrectos"
 msgstr "Pedido con parámetros incorrectos"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgid "Reset"
 msgstr "Limpiar"
 msgstr "Limpiar"
 
 
@@ -1515,7 +1536,7 @@ msgstr "Modo de ejecución"
 msgid "Running"
 msgid "Running"
 msgstr "Corriendo"
 msgstr "Corriendo"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1533,7 +1554,7 @@ msgid "Save error %{msg}"
 msgstr "Error al guardar %{msg}"
 msgstr "Error al guardar %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
 msgid "Save successfully"
@@ -1564,10 +1585,11 @@ msgstr "Enviado"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1644,13 +1666,15 @@ msgstr "Ruta del certificado SSL"
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr "Acceso SSO"
 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"
 msgid "Stable"
 msgstr "Estable"
 msgstr "Estable"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "Estado"
 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"
 msgid "The path exists, but the file is not a private key"
 msgstr "La ruta existe, pero el archivo no es una clave privada"
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "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"
 msgid "This field should not be empty"
 msgstr "Este campo no debe estar vacío"
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1850,7 +1886,7 @@ msgstr "El token no es válido"
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -1863,7 +1899,7 @@ msgstr "Tipo"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1873,16 +1909,21 @@ msgstr "Actualizado a"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "Actualización exitosa"
 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"
 msgid "Upgrade"
 msgstr "Actualizar"
 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
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "Actualización exitosa"
 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..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Actualizando Nginx UI, por favor espere..."
 msgstr "Actualizando Nginx UI, por favor espere..."
 
 
@@ -1894,11 +1935,11 @@ msgstr "Nombre de la Transmisión"
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "Tiempo encendido:"
 msgstr "Tiempo encendido:"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr "URL"
 msgstr "URL"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "User"
 msgid "User"
 msgstr "Usuario"
 msgstr "Usuario"
 
 
@@ -1919,7 +1960,12 @@ msgstr "Nombre de usuario (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr "Válido"
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Ver"
 msgstr "Ver"
@@ -1928,12 +1974,12 @@ msgstr "Ver"
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "Ver todas las notificaciones"
 msgstr "Ver todas las notificaciones"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 #, fuzzy
 #, fuzzy
 msgid "View Details"
 msgid "View Details"
 msgstr "Detalles"
 msgstr "Detalles"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Modo Básico"
 msgstr "Modo Básico"
@@ -1980,11 +2026,11 @@ msgstr "Escribir certificado a disco"
 msgid "Yes"
 msgid "Yes"
 msgstr "Si"
 msgstr "Si"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr "Estás usando la última versión"
 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."
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "Puede consultar la actualización de Nginx UI en esta página."
 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/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -94,7 +94,7 @@ msgstr "Proxy d'API"
 msgid "API Token"
 msgid "API Token"
 msgstr "Jeton d'API"
 msgstr "Jeton d'API"
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 #, fuzzy
 #, fuzzy
 msgid "Arch"
 msgid "Arch"
 msgstr "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?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "Voulez-vous vraiment effacer l'historique du chat ?"
 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?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Voulez-vous vraiment effacer l'historique du 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
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgid "Are you sure you want to recover this item?"
 msgstr "Voulez-vous vraiment supprimer cette directive ?"
 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?"
 msgid "Are you sure you want to remove this location?"
 msgstr "Voulez-vous vraiment supprimer cette localisation ?"
 msgstr "Voulez-vous vraiment supprimer cette localisation ?"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 #, fuzzy
 #, fuzzy
 msgid "Ask ChatGPT for Help"
 msgid "Ask ChatGPT for Help"
 msgstr "Modèle ChatGPT"
 msgstr "Modèle ChatGPT"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr ""
 msgstr ""
 
 
@@ -196,6 +196,10 @@ msgstr "Retour"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "Retour au menu principal"
 msgstr "Retour au menu principal"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
@@ -224,11 +228,16 @@ msgid "Basic Mode"
 msgstr "Mode simple"
 msgstr "Mode simple"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 #, fuzzy
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "Batch Modify"
 msgstr "Batch Modify"
 
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "Mettre à niveau"
+
 #: src/views/system/About.vue:39
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "Build avec"
 msgstr "Build avec"
@@ -241,9 +250,9 @@ msgstr ""
 msgid "CADir"
 msgid "CADir"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
 #: 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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -293,11 +302,11 @@ msgstr "Méthode de challenge"
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "Changer de certificat"
 msgstr "Changer de certificat"
 
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgid "Channel"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr "Revérifier"
 msgstr "Revérifier"
 
 
@@ -305,7 +314,7 @@ msgstr "Revérifier"
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr "Nettoyage des variables d'environnement"
 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/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -349,7 +358,7 @@ msgstr "Configurations"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "Configurer SSL"
 msgstr "Configurer SSL"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr ""
 msgstr ""
 
 
@@ -359,7 +368,7 @@ msgstr ""
 msgid "Content"
 msgid "Content"
 msgstr "Contenu"
 msgstr "Contenu"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Mise à jour du core"
 msgstr "Mise à jour du core"
 
 
@@ -396,7 +405,7 @@ msgstr "Identifiant"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Identifiants"
 msgstr "Identifiants"
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr "Version actuelle"
 msgstr "Version actuelle"
 
 
@@ -423,7 +432,7 @@ msgstr "Base de données (Facultatif, par défaut : database)"
 msgid "Days"
 msgid "Days"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -431,7 +440,7 @@ msgstr ""
 msgid "Delete"
 msgid "Delete"
 msgstr "Supprimer"
 msgstr "Supprimer"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -444,7 +453,7 @@ msgstr "Supprimer le site : %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Supprimer le site : %{site_name}"
 msgstr "Supprimer le site : %{site_name}"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 #, fuzzy
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "Désactivé avec succès"
 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}"
 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/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"
 msgid "Disabled"
 msgstr "Désactivé"
 msgstr "Désactivé"
 
 
@@ -603,7 +613,7 @@ msgstr "Erreur de téléchargement de la dernière version"
 msgid "Downloading latest release"
 msgid "Downloading latest release"
 msgstr "Téléchargement de la dernière version"
 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"
 msgid "Dry run mode enabled"
 msgstr ""
 msgstr ""
 
 
@@ -703,7 +713,8 @@ msgstr "Activer TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: 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"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Crypter le site Web avec 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"
 msgid "Environment"
 msgstr ""
 msgstr ""
 
 
@@ -732,7 +743,7 @@ msgstr ""
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr "Définition des variables d'environnement"
 msgstr "Définition des variables d'environnement"
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 #, fuzzy
 msgid "Environments"
 msgid "Environments"
 msgstr "Commentaires"
 msgstr "Commentaires"
@@ -745,7 +756,7 @@ msgstr "Erreur"
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Journaux d'erreurs"
 msgstr "Journaux d'erreurs"
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr "Chemin exécutable"
 msgstr "Chemin exécutable"
 
 
@@ -847,7 +858,7 @@ msgstr "Générer"
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Génération de clé privée pour l'enregistrement du compte"
 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"
 msgid "Get release information error"
 msgstr "Erreur d'obtention des informations sur la version"
 msgstr "Erreur d'obtention des informations sur la version"
 
 
@@ -965,7 +976,7 @@ msgstr "Secret Jwt"
 msgid "Key Type"
 msgid "Key Type"
 msgstr "Type"
 msgstr "Type"
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr "Dernière vérification le"
 msgstr "Dernière vérification le"
 
 
@@ -988,12 +999,12 @@ msgstr "Laisser vide pour aucun changement"
 msgid "License"
 msgid "License"
 msgstr "Licence"
 msgstr "Licence"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgid "Link Start"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -1002,11 +1013,11 @@ msgstr ""
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "Charges moyennes :"
 msgstr "Charges moyennes :"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr ""
 msgstr ""
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 #, fuzzy
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "Enregistré avec succès"
 msgstr "Enregistré avec succès"
@@ -1111,9 +1122,9 @@ msgstr ""
 msgid "Model"
 msgid "Model"
 msgstr "Mode d'exécution"
 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"
 msgid "Modify"
 msgstr "Modifier"
 msgstr "Modifier"
 
 
@@ -1126,7 +1137,7 @@ msgstr "État du certificat"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "Modifier la configuration"
 msgstr "Modifier la configuration"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "Modifier"
 msgstr "Modifier"
@@ -1144,7 +1155,7 @@ msgstr "Directive multiligne"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: 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"
 msgid "Network Total Send"
 msgstr "Envoi total réseau"
 msgstr "Envoi total réseau"
 
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgid "New version released"
 msgstr "Nouvelle version publiée"
 msgstr "Nouvelle version publiée"
 
 
@@ -1210,11 +1221,11 @@ msgstr "Nginx a été rechargé avec succès"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Nginx a redémarré avec succès"
 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/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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1266,22 +1277,22 @@ msgid "Obtaining certificate"
 msgstr "Obtention du certificat"
 msgstr "Obtention du certificat"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: src/views/domain/components/RightSettings.vue:50
@@ -1302,8 +1313,8 @@ msgstr ""
 
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr ""
 msgstr ""
 
 
@@ -1311,7 +1322,7 @@ msgstr ""
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr "OpenAI"
 msgstr "OpenAI"
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgid "OS"
 msgstr "OS"
 msgstr "OS"
 
 
@@ -1347,6 +1358,10 @@ msgstr "Mot de passe (*)"
 msgid "Path"
 msgid "Path"
 msgstr "Chemin"
 msgstr "Chemin"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr "Erreur lors de la mise a niveau du core"
 msgstr "Erreur lors de la mise a niveau du core"
@@ -1361,6 +1376,10 @@ msgid ""
 "provider."
 "provider."
 msgstr ""
 msgstr ""
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #, fuzzy
 #, fuzzy
 msgid ""
 msgid ""
@@ -1403,7 +1422,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgid "Please select at least one node!"
 msgstr ""
 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"
 msgid "Pre-release"
 msgstr ""
 msgstr ""
 
 
@@ -1444,11 +1465,11 @@ msgstr "Lectures"
 msgid "Receive"
 msgid "Receive"
 msgstr "Recevoir"
 msgstr "Recevoir"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 #, fuzzy
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "Enregistré avec succès"
 msgstr "Enregistré avec succès"
@@ -1457,7 +1478,7 @@ msgstr "Enregistré avec succès"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr "Régénérer la réponse"
 msgstr "Régénérer la réponse"
 
 
@@ -1485,15 +1506,15 @@ msgstr "Enregistrement de l'utilisateur"
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr "Enregistrement de l'utilisateur"
 msgstr "Enregistrement de l'utilisateur"
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "Réinstaller"
 msgstr "Réinstaller"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr "Note de version"
 msgstr "Note de version"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr "Recharger"
 msgstr "Recharger"
@@ -1551,7 +1572,7 @@ msgstr "Activé avec succès"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgid "Reset"
 msgstr "Réinitialiser"
 msgstr "Réinitialiser"
 
 
@@ -1571,7 +1592,7 @@ msgstr "Mode d'exécution"
 msgid "Running"
 msgid "Running"
 msgstr "En cours d'éxécution"
 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/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1589,7 +1610,7 @@ msgid "Save error %{msg}"
 msgstr "Enregistrer l'erreur %{msg}"
 msgstr "Enregistrer l'erreur %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
 msgid "Save successfully"
@@ -1620,10 +1641,11 @@ msgstr "Envoyer"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1705,14 +1727,16 @@ msgstr "Chemin du certificat SSL"
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr "Connexion"
 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
 #, fuzzy
 msgid "Stable"
 msgid "Stable"
 msgstr "Tableau"
 msgstr "Tableau"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "Statut"
 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"
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1889,6 +1920,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr ""
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1918,7 +1954,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -1931,7 +1967,7 @@ msgstr "Type"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1941,16 +1977,21 @@ msgstr "Mis à jour le"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "Mis à jour avec succés"
 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"
 msgid "Upgrade"
 msgstr "Mettre à niveau"
 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
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "Mise à niveau réussie"
 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..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Mise à jour de Nginx UI, veuillez patienter..."
 msgstr "Mise à jour de Nginx UI, veuillez patienter..."
 
 
@@ -1962,11 +2003,11 @@ msgstr ""
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "Disponibilité :"
 msgstr "Disponibilité :"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 #, fuzzy
 msgid "User"
 msgid "User"
 msgstr "Nom d'utilisateur"
 msgstr "Nom d'utilisateur"
@@ -1988,7 +2029,12 @@ msgstr "Nom d'utilisateur (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr ""
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Voir"
 msgstr "Voir"
@@ -1998,11 +2044,11 @@ msgstr "Voir"
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "Certification"
 msgstr "Certification"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgid "View Details"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Mode simple"
 msgstr "Mode simple"
@@ -2047,11 +2093,11 @@ msgstr "Écriture du certificat sur le disque"
 msgid "Yes"
 msgid "Yes"
 msgstr "Oui"
 msgstr "Oui"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr "Vous utilisez la dernière 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."
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "Vous pouvez vérifier la mise à niveau de Nginx UI sur cette 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/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "작업"
 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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -93,7 +93,7 @@ msgstr "API 프록시"
 msgid "API Token"
 msgid "API Token"
 msgstr "API 토큰"
 msgstr "API 토큰"
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgid "Arch"
 msgstr "아키텍처"
 msgstr "아키텍처"
 
 
@@ -107,16 +107,16 @@ msgstr "정말 삭제하시겠습니까?"
 msgid "Are you sure you want to clear all notifications?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "모든 알림을 지우시겠습니까?"
 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?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "기록을 지우시겠습니까?"
 msgstr "기록을 지우시겠습니까?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "정말 삭제하시겠습니까?"
 msgstr "정말 삭제하시겠습니까?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr "정말 삭제하시겠습니까?"
 msgstr "정말 삭제하시겠습니까?"
@@ -125,7 +125,7 @@ msgstr "정말 삭제하시겠습니까?"
 msgid "Are you sure you want to delete?"
 msgid "Are you sure you want to delete?"
 msgstr "정말 삭제하시겠습니까?"
 msgstr "정말 삭제하시겠습니까?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgid "Are you sure you want to recover this item?"
 msgstr "이 지시문을 정말로 제거하시겠습니까?"
 msgstr "이 지시문을 정말로 제거하시겠습니까?"
@@ -143,11 +143,11 @@ msgstr "이 지시문을 정말로 제거하시겠습니까?"
 msgid "Are you sure you want to remove this location?"
 msgid "Are you sure you want to remove this location?"
 msgstr "이 위치를 제거하시겠습니까?"
 msgstr "이 위치를 제거하시겠습니까?"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgid "Ask ChatGPT for Help"
 msgstr "ChatGPT에게 도움 요청"
 msgstr "ChatGPT에게 도움 요청"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr "조수"
 msgstr "조수"
 
 
@@ -192,6 +192,10 @@ msgstr "뒤로"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "홈으로"
 msgstr "홈으로"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
@@ -220,10 +224,15 @@ msgid "Basic Mode"
 msgstr "기본 모드"
 msgstr "기본 모드"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "일괄 수정"
 msgstr "일괄 수정"
 
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "업그레이드"
+
 #: src/views/system/About.vue:39
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "빌드 환경"
 msgstr "빌드 환경"
@@ -236,9 +245,9 @@ msgstr ""
 msgid "CADir"
 msgid "CADir"
 msgstr "CA 디렉토리"
 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/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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -286,11 +295,11 @@ msgstr "인증 방법"
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "인증서 변경"
 msgstr "인증서 변경"
 
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgid "Channel"
 msgstr "채널"
 msgstr "채널"
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr "다시 확인"
 msgstr "다시 확인"
 
 
@@ -298,7 +307,7 @@ msgstr "다시 확인"
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr "환경 변수 정리"
 msgstr "환경 변수 정리"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -340,7 +349,7 @@ msgstr "구성들"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "SSL 구성하기"
 msgstr "SSL 구성하기"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr "연결됨"
 msgstr "연결됨"
 
 
@@ -350,7 +359,7 @@ msgstr "연결됨"
 msgid "Content"
 msgid "Content"
 msgstr "내용"
 msgstr "내용"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "코어 업그레이드"
 msgstr "코어 업그레이드"
 
 
@@ -386,7 +395,7 @@ msgstr "인증 정보"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "인증 정보들"
 msgstr "인증 정보들"
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr "현재 버전"
 msgstr "현재 버전"
 
 
@@ -413,7 +422,7 @@ msgstr "데이터베이스 (선택사항, 기본값: database)"
 msgid "Days"
 msgid "Days"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -421,7 +430,7 @@ msgstr ""
 msgid "Delete"
 msgid "Delete"
 msgstr "삭제"
 msgstr "삭제"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -433,7 +442,7 @@ msgstr "사이트 삭제: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "스트림 삭제: %{stream_name}"
 msgstr "스트림 삭제: %{stream_name}"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "성공적으로 삭제됨"
 msgstr "성공적으로 삭제됨"
 
 
@@ -493,8 +502,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "%{name}의 자동 갱신 비활성화 실패"
 msgstr "%{name}의 자동 갱신 비활성화 실패"
 
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
 #: 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"
 msgid "Disabled"
 msgstr "비활성화됨"
 msgstr "비활성화됨"
 
 
@@ -582,7 +592,7 @@ msgstr "최신 릴리스 다운로드 오류"
 msgid "Downloading latest release"
 msgid "Downloading latest release"
 msgstr "최신 릴리스 다운로드 중"
 msgstr "최신 릴리스 다운로드 중"
 
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgid "Dry run mode enabled"
 msgstr "드라이런 모드 활성화됨"
 msgstr "드라이런 모드 활성화됨"
 
 
@@ -676,7 +686,8 @@ msgstr "TLS 활성화"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -696,7 +707,7 @@ msgstr "성공적으로 활성화됨"
 msgid "Encrypt website with Let's Encrypt"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "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"
 msgid "Environment"
 msgstr "환경"
 msgstr "환경"
 
 
@@ -705,7 +716,7 @@ msgstr "환경"
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr "환경 변수 설정"
 msgstr "환경 변수 설정"
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgid "Environments"
 msgstr "환경"
 msgstr "환경"
 
 
@@ -717,7 +728,7 @@ msgstr "오류"
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "오류 로그"
 msgstr "오류 로그"
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr "실행 가능 경로"
 msgstr "실행 가능 경로"
 
 
@@ -818,7 +829,7 @@ msgstr "생성"
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "계정 등록을 위한 개인 키 생성 중"
 msgstr "계정 등록을 위한 개인 키 생성 중"
 
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 #, fuzzy
 #, fuzzy
 msgid "Get release information error"
 msgid "Get release information error"
 msgstr "릴리스 정보 가져오기 오류"
 msgstr "릴리스 정보 가져오기 오류"
@@ -938,7 +949,7 @@ msgstr "Jwt 토큰"
 msgid "Key Type"
 msgid "Key Type"
 msgstr "키 유형"
 msgstr "키 유형"
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr "마지막 확인 시간"
 msgstr "마지막 확인 시간"
 
 
@@ -961,12 +972,12 @@ msgstr "변경사항이 없으면 비워두세요"
 msgid "License"
 msgid "License"
 msgstr "라이센스"
 msgstr "라이센스"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgid "Link Start"
 msgstr "링크 시작"
 msgstr "링크 시작"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -975,11 +986,11 @@ msgstr ""
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "부하 평균:"
 msgstr "부하 평균:"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr ""
 msgstr ""
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 #, fuzzy
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "성공적으로 저장됨"
 msgstr "성공적으로 저장됨"
@@ -1087,9 +1098,9 @@ msgstr "분"
 msgid "Model"
 msgid "Model"
 msgstr "실행 모드"
 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
 #, fuzzy
 msgid "Modify"
 msgid "Modify"
 msgstr "설정 수정"
 msgstr "설정 수정"
@@ -1103,7 +1114,7 @@ msgstr "인증서 상태"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "설정 수정"
 msgstr "설정 수정"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "설정 수정"
 msgstr "설정 수정"
@@ -1122,7 +1133,7 @@ msgstr "단일 지시문"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1145,7 +1156,7 @@ msgstr "네트워크 총 수신"
 msgid "Network Total Send"
 msgid "Network Total Send"
 msgstr "네트워크 총 송신"
 msgstr "네트워크 총 송신"
 
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgid "New version released"
 msgstr "새 버전 출시"
 msgstr "새 버전 출시"
 
 
@@ -1190,11 +1201,11 @@ msgstr "Nginx가 성공적으로 리로드됨"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Nginx가 성공적으로 재시작됨"
 msgstr "Nginx가 성공적으로 재시작됨"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
 #: 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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1246,22 +1257,22 @@ msgid "Obtaining certificate"
 msgstr "인증서 획득 중"
 msgstr "인증서 획득 중"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr "오프라인"
 msgstr "오프라인"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: src/views/domain/components/RightSettings.vue:50
@@ -1282,8 +1293,8 @@ msgstr "검증이 완료되면, 레코드는 제거됩니다."
 
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr "온라인"
 msgstr "온라인"
 
 
@@ -1291,7 +1302,7 @@ msgstr "온라인"
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr "오픈AI"
 msgstr "오픈AI"
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 #, fuzzy
 msgid "OS"
 msgid "OS"
 msgstr "OS"
 msgstr "OS"
@@ -1328,6 +1339,10 @@ msgstr "비밀번호 (*)"
 msgid "Path"
 msgid "Path"
 msgstr "경로"
 msgstr "경로"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr "핵심 업그레이드 오류 수행"
 msgstr "핵심 업그레이드 오류 수행"
@@ -1342,6 +1357,10 @@ msgid ""
 "provider."
 "provider."
 msgstr "DNS 제공자가 제공한 API 인증 자격 증명을 입력해주세요."
 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
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1379,7 +1398,9 @@ msgstr "아래의 시간 설정 단위는 모두 초 단위임을 유의해주
 msgid "Please select at least one node!"
 msgid "Please select at least one node!"
 msgstr "적어도 하나의 노드를 선택해주세요!"
 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"
 msgid "Pre-release"
 msgstr "사전 출시"
 msgstr "사전 출시"
 
 
@@ -1418,11 +1439,11 @@ msgstr "읽기"
 msgid "Receive"
 msgid "Receive"
 msgstr "수신"
 msgstr "수신"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 #, fuzzy
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "성공적으로 제거됨"
 msgstr "성공적으로 제거됨"
@@ -1431,7 +1452,7 @@ msgstr "성공적으로 제거됨"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr "응답 재생성"
 msgstr "응답 재생성"
 
 
@@ -1459,16 +1480,16 @@ msgstr "사용자 등록 중"
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr "사용자 등록 중"
 msgstr "사용자 등록 중"
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 #, fuzzy
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "재설치"
 msgstr "재설치"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr "릴리스 노트"
 msgstr "릴리스 노트"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr "리로드"
 msgstr "리로드"
@@ -1526,7 +1547,7 @@ msgstr "성공적으로 갱신됨"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr "잘못된 매개변수로 요청됨"
 msgstr "잘못된 매개변수로 요청됨"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgid "Reset"
 msgstr "재설정"
 msgstr "재설정"
 
 
@@ -1547,7 +1568,7 @@ msgstr "실행 모드"
 msgid "Running"
 msgid "Running"
 msgstr "실행 중"
 msgstr "실행 중"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1565,7 +1586,7 @@ msgid "Save error %{msg}"
 msgstr "저장 오류 %{msg}"
 msgstr "저장 오류 %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 #, fuzzy
 #, fuzzy
@@ -1597,10 +1618,11 @@ msgstr "보내기"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1682,14 +1704,16 @@ msgstr "SSL 인증서 경로"
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr "SSO 로그인"
 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
 #, fuzzy
 msgid "Stable"
 msgid "Stable"
 msgstr "활성화됨"
 msgstr "활성화됨"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "상태"
 msgstr "상태"
 
 
@@ -1814,6 +1838,13 @@ msgstr "Certificate Status"
 msgid "The path exists, but the file is not a private key"
 msgid "The path exists, but the file is not a private key"
 msgstr "경로는 존재하지만 파일은 개인 키가 아닙니다"
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1863,6 +1894,11 @@ msgstr "이 필드는 필수입니다"
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr "이 필드는 비워둘 수 없습니다"
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1891,7 +1927,7 @@ msgstr "토큰이 유효하지 않습니다"
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -1904,7 +1940,7 @@ msgstr "유형"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1915,17 +1951,22 @@ msgstr "업데이트됨"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "성공적으로 저장되었습니다"
 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"
 msgid "Upgrade"
 msgstr "업그레이드"
 msgstr "업그레이드"
 
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "성공적으로 저장되었습니다"
+
 #: src/language/constants.ts:29
 #: src/language/constants.ts:29
 #, fuzzy
 #, fuzzy
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "성공적으로 저장되었습니다"
 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..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Nginx UI를 업그레이드하는 중입니다. 잠시 기다려주세요..."
 msgstr "Nginx UI를 업그레이드하는 중입니다. 잠시 기다려주세요..."
 
 
@@ -1937,11 +1978,11 @@ msgstr "업스트림 이름"
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "가동 시간:"
 msgstr "가동 시간:"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr "URL"
 msgstr "URL"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 #, fuzzy
 msgid "User"
 msgid "User"
 msgstr "사용자 이름"
 msgstr "사용자 이름"
@@ -1963,7 +2004,12 @@ msgstr "사용자 이름 (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr "유효함"
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "보기"
 msgstr "보기"
@@ -1973,12 +2019,12 @@ msgstr "보기"
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "Certificate is valid"
 msgstr "Certificate is valid"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 #, fuzzy
 #, fuzzy
 msgid "View Details"
 msgid "View Details"
 msgstr "세부 사항"
 msgstr "세부 사항"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "View Mode"
 msgid "View Mode"
 msgstr "기본 모드"
 msgstr "기본 모드"
@@ -2025,11 +2071,11 @@ msgstr "인증서를 디스크에 쓰기"
 msgid "Yes"
 msgid "Yes"
 msgstr "예"
 msgstr "예"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr "최신 버전을 사용하고 있습니다"
 msgstr "최신 버전을 사용하고 있습니다"
 
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "이 페이지에서 Nginx UI 업그레이드를 확인할 수 있습니다."
 msgstr "이 페이지에서 Nginx UI 업그레이드를 확인할 수 있습니다."
 
 

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

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

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

@@ -26,15 +26,15 @@ msgstr "Пользователь"
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "Действие"
 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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr ""
 msgid "API Token"
 msgid "API Token"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgid "Arch"
 msgstr ""
 msgstr ""
 
 
@@ -106,17 +106,17 @@ msgstr "Вы уверены, что хотите удалить?"
 msgid "Are you sure you want to clear all notifications?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "Вы уверены, что хотите удалить все уведомления?"
 msgstr "Вы уверены, что хотите удалить все уведомления?"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:272
+#: src/components/ChatGPT/ChatGPT.vue:271
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Вы уверены, что хотите очистить сообщения чата?"
 msgstr "Вы уверены, что хотите очистить сообщения чата?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Вы уверены, что хотите удалить?"
 msgstr "Вы уверены, что хотите удалить?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr "Вы уверены, что хотите удалить?"
 msgstr "Вы уверены, что хотите удалить?"
@@ -126,7 +126,7 @@ msgstr "Вы уверены, что хотите удалить?"
 msgid "Are you sure you want to delete?"
 msgid "Are you sure you want to delete?"
 msgstr "Вы уверены, что хотите удалить?"
 msgstr "Вы уверены, что хотите удалить?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgid "Are you sure you want to recover this item?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
@@ -145,11 +145,11 @@ msgstr "Вы уверены, что хотите удалить эту дире
 msgid "Are you sure you want to remove this location?"
 msgid "Are you sure you want to remove this location?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgid "Ask ChatGPT for Help"
 msgstr "Обратитесь за помощью к ChatGPT"
 msgstr "Обратитесь за помощью к ChatGPT"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr "Ассистент"
 msgstr "Ассистент"
 
 
@@ -195,6 +195,10 @@ msgstr "Назад"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "Вернутся"
 msgstr "Вернутся"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
@@ -224,11 +228,16 @@ msgid "Basic Mode"
 msgstr "Простой режим"
 msgstr "Простой режим"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 #, fuzzy
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "Изменение конфигурации"
 msgstr "Изменение конфигурации"
 
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "Обновление"
+
 #: src/views/system/About.vue:39
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "Собрать с"
 msgstr "Собрать с"
@@ -241,9 +250,9 @@ msgstr ""
 msgid "CADir"
 msgid "CADir"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
 #: 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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -294,11 +303,11 @@ msgstr "Метод Challenge"
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "Сертификат действителен"
 msgstr "Сертификат действителен"
 
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgid "Channel"
 msgstr "Канал"
 msgstr "Канал"
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr "Проверить повторно"
 msgstr "Проверить повторно"
 
 
@@ -306,7 +315,7 @@ msgstr "Проверить повторно"
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr "Очистка переменных среды"
 msgstr "Очистка переменных среды"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -351,7 +360,7 @@ msgstr "Конфигурации"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "Настроить SSL"
 msgstr "Настроить SSL"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr "Подключено"
 msgstr "Подключено"
 
 
@@ -361,7 +370,7 @@ msgstr "Подключено"
 msgid "Content"
 msgid "Content"
 msgstr "Содержание"
 msgstr "Содержание"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Обновление ядра"
 msgstr "Обновление ядра"
 
 
@@ -398,7 +407,7 @@ msgstr "Учетные данные"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Учетные данные"
 msgstr "Учетные данные"
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr "Текущяя версия"
 msgstr "Текущяя версия"
 
 
@@ -425,7 +434,7 @@ msgstr "База данных (Опционально, по умолчанию:
 msgid "Days"
 msgid "Days"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -433,7 +442,7 @@ msgstr ""
 msgid "Delete"
 msgid "Delete"
 msgstr "Удалить"
 msgstr "Удалить"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -445,7 +454,7 @@ msgstr ""
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 #, fuzzy
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "Отключено успешно"
 msgstr "Отключено успешно"
@@ -509,8 +518,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "Не удалось отключить автоматическое продление для %{name}"
 msgstr "Не удалось отключить автоматическое продление для %{name}"
 
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
 #: 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"
 msgid "Disabled"
 msgstr "Отключено"
 msgstr "Отключено"
 
 
@@ -607,7 +617,7 @@ msgstr "Ошибка загрузки последней версии"
 msgid "Downloading latest release"
 msgid "Downloading latest release"
 msgstr "Загрузка последней версии"
 msgstr "Загрузка последней версии"
 
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgid "Dry run mode enabled"
 msgstr "Включен пробный режим"
 msgstr "Включен пробный режим"
 
 
@@ -707,7 +717,8 @@ msgstr "Включить TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -727,7 +738,7 @@ msgstr "Активировано успешно"
 msgid "Encrypt website with Let's Encrypt"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Использовать для сайта 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"
 msgid "Environment"
 msgstr "Окружение"
 msgstr "Окружение"
 
 
@@ -736,7 +747,7 @@ msgstr "Окружение"
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr "Настройка переменных сред"
 msgstr "Настройка переменных сред"
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 #, fuzzy
 msgid "Environments"
 msgid "Environments"
 msgstr "Комментарии"
 msgstr "Комментарии"
@@ -749,7 +760,7 @@ msgstr "Ошибка"
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Ошибка логирования"
 msgstr "Ошибка логирования"
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr "Исполняемый путь"
 msgstr "Исполняемый путь"
 
 
@@ -850,7 +861,7 @@ msgstr "Сгенерировать"
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Генерация приватного ключа для регистрации учетной записи"
 msgstr "Генерация приватного ключа для регистрации учетной записи"
 
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 #, fuzzy
 #, fuzzy
 msgid "Get release information error"
 msgid "Get release information error"
 msgstr "Ошибка получения информации о релизе"
 msgstr "Ошибка получения информации о релизе"
@@ -972,7 +983,7 @@ msgstr ""
 msgid "Key Type"
 msgid "Key Type"
 msgstr "Тип"
 msgstr "Тип"
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr "Последняя проверка в"
 msgstr "Последняя проверка в"
 
 
@@ -995,12 +1006,12 @@ msgstr "Оставьте пустым без изменений"
 msgid "License"
 msgid "License"
 msgstr "Лицензия"
 msgstr "Лицензия"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgid "Link Start"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -1009,11 +1020,11 @@ msgstr ""
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "Средняя нагрузка:"
 msgstr "Средняя нагрузка:"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr ""
 msgstr ""
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 #, fuzzy
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "Успешно сохранено"
 msgstr "Успешно сохранено"
@@ -1116,9 +1127,9 @@ msgstr ""
 msgid "Model"
 msgid "Model"
 msgstr "Расширенный режим"
 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
 #, fuzzy
 msgid "Modify"
 msgid "Modify"
 msgstr "Изменить"
 msgstr "Изменить"
@@ -1132,7 +1143,7 @@ msgstr "Статус сертификата"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "Изменить конфигурацию"
 msgstr "Изменить конфигурацию"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "Изменить"
 msgstr "Изменить"
@@ -1151,7 +1162,7 @@ msgstr "Одиночная директива"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1174,7 +1185,7 @@ msgstr "Всего получено"
 msgid "Network Total Send"
 msgid "Network Total Send"
 msgstr "Всего отправлено"
 msgstr "Всего отправлено"
 
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgid "New version released"
 msgstr "Вышла новая версия"
 msgstr "Вышла новая версия"
 
 
@@ -1220,11 +1231,11 @@ msgstr "Nginx перезагружен успешно"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Nginx успешно перезапущен"
 msgstr "Nginx успешно перезапущен"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
 #: 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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1276,22 +1287,22 @@ msgid "Obtaining certificate"
 msgstr "Получение сертификата"
 msgstr "Получение сертификата"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: src/views/domain/components/RightSettings.vue:50
@@ -1312,8 +1323,8 @@ msgstr ""
 
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr ""
 msgstr ""
 
 
@@ -1321,7 +1332,7 @@ msgstr ""
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 #, fuzzy
 msgid "OS"
 msgid "OS"
 msgstr "OS:"
 msgstr "OS:"
@@ -1358,6 +1369,10 @@ msgstr "Пароль (*)"
 msgid "Path"
 msgid "Path"
 msgstr "Путь"
 msgstr "Путь"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr ""
 msgstr ""
@@ -1372,6 +1387,10 @@ msgid ""
 "provider."
 "provider."
 msgstr ""
 msgstr ""
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1409,7 +1428,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgid "Please select at least one node!"
 msgstr ""
 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"
 msgid "Pre-release"
 msgstr ""
 msgstr ""
 
 
@@ -1448,11 +1469,11 @@ msgstr "Чтение"
 msgid "Receive"
 msgid "Receive"
 msgstr "Принято"
 msgstr "Принято"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 #, fuzzy
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "Успешно сохранено"
 msgstr "Успешно сохранено"
@@ -1461,7 +1482,7 @@ msgstr "Успешно сохранено"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr "Восстановить ответ"
 msgstr "Восстановить ответ"
 
 
@@ -1489,16 +1510,16 @@ msgstr "Регистрация пользователя"
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr "Регистрация пользователя"
 msgstr "Регистрация пользователя"
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 #, fuzzy
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "Переустановить"
 msgstr "Переустановить"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr "Что нового"
 msgstr "Что нового"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr "Перегрузить"
 msgstr "Перегрузить"
@@ -1556,7 +1577,7 @@ msgstr "Активировано успешно"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr "Запрос с неправильными параметрами"
 msgstr "Запрос с неправильными параметрами"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgid "Reset"
 msgstr "Сброс"
 msgstr "Сброс"
 
 
@@ -1577,7 +1598,7 @@ msgstr "Расширенный режим"
 msgid "Running"
 msgid "Running"
 msgstr "Выполняется"
 msgstr "Выполняется"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1595,7 +1616,7 @@ msgid "Save error %{msg}"
 msgstr "Ошибка сохранения %{msg}"
 msgstr "Ошибка сохранения %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 #, fuzzy
 #, fuzzy
@@ -1627,10 +1648,11 @@ msgstr "Отправлено"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1713,14 +1735,16 @@ msgstr "Путь к сертификату SSL"
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr "Логин"
 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
 #, fuzzy
 msgid "Stable"
 msgid "Stable"
 msgstr "Таблица"
 msgstr "Таблица"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "Статус"
 msgstr "Статус"
 
 
@@ -1845,6 +1869,13 @@ msgstr "Путь к ключу сертификата SSL"
 msgid "The path exists, but the file is not a private key"
 msgid "The path exists, but the file is not a private key"
 msgstr "Путь существует, но файл не является приватным ключом"
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1895,6 +1926,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr "Это поле обязательно к заполнению"
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1920,7 +1956,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -1933,7 +1969,7 @@ msgstr "Тип"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1944,17 +1980,22 @@ msgstr "Обновлено в"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "Обновлено успешно"
 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"
 msgid "Upgrade"
 msgstr "Обновление"
 msgstr "Обновление"
 
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "Обновление успешно выполнено"
+
 #: src/language/constants.ts:29
 #: src/language/constants.ts:29
 #, fuzzy
 #, fuzzy
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "Обновление успешно выполнено"
 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..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Обновление Nginx UI, подождите..."
 msgstr "Обновление Nginx UI, подождите..."
 
 
@@ -1966,11 +2007,11 @@ msgstr ""
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "Аптайм:"
 msgstr "Аптайм:"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 #, fuzzy
 msgid "User"
 msgid "User"
 msgstr "Пользователь"
 msgstr "Пользователь"
@@ -1992,7 +2033,12 @@ msgstr "Имя пользователя (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr "Действительный"
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Просмотр"
 msgstr "Просмотр"
@@ -2002,11 +2048,11 @@ msgstr "Просмотр"
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "Просмотреть все уведомления"
 msgstr "Просмотреть все уведомления"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgid "View Details"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Простой режим"
 msgstr "Простой режим"
@@ -2051,11 +2097,11 @@ msgstr "Запись сертификата на диск"
 msgid "Yes"
 msgid "Yes"
 msgstr "Да"
 msgstr "Да"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr "Вы используете последнюю версию"
 msgstr "Вы используете последнюю версию"
 
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "Вы можете проверить обновление Nginx UI на этой странице."
 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/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "Hành động"
 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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr ""
 msgid "API Token"
 msgid "API Token"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgid "Arch"
 msgstr ""
 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?"
 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 ?"
 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
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 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 ?"
 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
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "Bạn chắc chắn muốn xóa nó "
 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
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr "Bạn chắc chắn muốn xóa nó "
 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Bạn chắc chắn muốn xóa nó "
 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
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgid "Are you sure you want to recover this item?"
 msgstr "Bạn chắc chắn muốn xoá directive này ?"
 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?"
 msgid "Are you sure you want to remove this location?"
 msgstr "Bạn chắc chắn muốn xoá location này ?"
 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"
 msgid "Ask ChatGPT for Help"
 msgstr "Hỏi ChatGPT"
 msgstr "Hỏi ChatGPT"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr "Trợ lý"
 msgstr "Trợ lý"
 
 
@@ -195,6 +195,10 @@ msgstr "Quay lại"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "Quay lại"
 msgstr "Quay lại"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
@@ -224,11 +228,16 @@ msgid "Basic Mode"
 msgstr "Cơ bản"
 msgstr "Cơ bản"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 #, fuzzy
 #, fuzzy
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "Sửa đổi cấu hình"
 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
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "Xây dựng với"
 msgstr "Xây dựng với"
@@ -241,9 +250,9 @@ msgstr ""
 msgid "CADir"
 msgid "CADir"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:253
+#: src/components/ChatGPT/ChatGPT.vue:252
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:55
 #: 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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -294,11 +303,11 @@ msgstr "Phương pháp xác thực"
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "Thay đổi chứng chỉ"
 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"
 msgid "Channel"
 msgstr "Kênh"
 msgstr "Kênh"
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr "Kiểm tra lại"
 msgstr "Kiểm tra lại"
 
 
@@ -306,7 +315,7 @@ msgstr "Kiểm tra lại"
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr "Xoá các biến môi trường"
 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/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -351,7 +360,7 @@ msgstr "Cấu hình"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "Cấu hình SSL"
 msgstr "Cấu hình SSL"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr "Đã kết nối"
 msgstr "Đã kết nối"
 
 
@@ -361,7 +370,7 @@ msgstr "Đã kết nối"
 msgid "Content"
 msgid "Content"
 msgstr "Nội dung"
 msgstr "Nội dung"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Cập nhật core"
 msgstr "Cập nhật core"
 
 
@@ -398,7 +407,7 @@ msgstr "Chứng chỉ"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Chứng chỉ"
 msgstr "Chứng chỉ"
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr "Phiên bản hiện tại"
 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"
 msgid "Days"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -433,7 +442,7 @@ msgstr ""
 msgid "Delete"
 msgid "Delete"
 msgstr "Xoá"
 msgstr "Xoá"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -446,7 +455,7 @@ msgstr "Xoá trang web: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Xoá trang web: %{site_name}"
 msgstr "Xoá trang web: %{site_name}"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 #, fuzzy
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "Đã xoá thành công"
 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"
 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/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"
 msgid "Disabled"
 msgstr "Đã tắt"
 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"
 msgid "Downloading latest release"
 msgstr "Đang tải phiên bản mới nhất"
 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"
 msgid "Dry run mode enabled"
 msgstr "Đã bật chế độ Dry run"
 msgstr "Đã bật chế độ Dry run"
 
 
@@ -708,7 +718,8 @@ msgstr "Bật TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: 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"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Bảo mật trang web với 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"
 msgid "Environment"
 msgstr "Environment"
 msgstr "Environment"
 
 
@@ -737,7 +748,7 @@ msgstr "Environment"
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr "Đặt biến môi trường"
 msgstr "Đặt biến môi trường"
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 #, fuzzy
 #, fuzzy
 msgid "Environments"
 msgid "Environments"
 msgstr "Environments"
 msgstr "Environments"
@@ -750,7 +761,7 @@ msgstr "Lỗi"
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Log lỗi"
 msgstr "Log lỗi"
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr "Đường dẫn thực thi"
 msgstr "Đường dẫn thực thi"
 
 
@@ -852,7 +863,7 @@ msgstr "Tạo"
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Tạo khóa riêng để đăng ký tài khoản"
 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
 #, fuzzy
 msgid "Get release information error"
 msgid "Get release information error"
 msgstr "Nhận lỗi thông tin phát hành"
 msgstr "Nhận lỗi thông tin phát hành"
@@ -974,7 +985,7 @@ msgstr ""
 msgid "Key Type"
 msgid "Key Type"
 msgstr "Loại"
 msgstr "Loại"
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr "Kiểm tra lần cuối lúc"
 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"
 msgid "License"
 msgstr "Giấy phép"
 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"
 msgid "Link Start"
 msgstr "Liên kết bắt đầu"
 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"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -1011,11 +1022,11 @@ msgstr ""
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "Tải trung bình:"
 msgstr "Tải trung bình:"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr ""
 msgstr ""
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 #, fuzzy
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "Lưu thành công"
 msgstr "Lưu thành công"
@@ -1117,9 +1128,9 @@ msgstr ""
 msgid "Model"
 msgid "Model"
 msgstr "Run Mode"
 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
 #, fuzzy
 msgid "Modify"
 msgid "Modify"
 msgstr "Sửa"
 msgstr "Sửa"
@@ -1133,7 +1144,7 @@ msgstr "Sửa chứng chỉ"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "Sửa cấu hình"
 msgstr "Sửa cấu hình"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "Sửa"
 msgstr "Sửa"
@@ -1152,7 +1163,7 @@ msgstr "Single Directive"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: 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"
 msgid "Network Total Send"
 msgstr "Tổng lưu lượng mạng đã gửi"
 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"
 msgid "New version released"
 msgstr "Đã có phiên bản mới"
 msgstr "Đã có phiên bản mới"
 
 
@@ -1220,11 +1231,11 @@ msgstr "Reload Nginx thành công"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Restart Nginx thành công"
 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/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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1276,22 +1287,22 @@ msgid "Obtaining certificate"
 msgstr "Đang nhận chứng chỉ"
 msgstr "Đang nhận chứng chỉ"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr "Ngoại tuyến"
 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
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: 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:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr "Trực tuyến"
 msgstr "Trực tuyến"
 
 
@@ -1321,7 +1332,7 @@ msgstr "Trực tuyến"
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr ""
 msgstr ""
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 #, fuzzy
 #, fuzzy
 msgid "OS"
 msgid "OS"
 msgstr "Hệ điều hành"
 msgstr "Hệ điều hành"
@@ -1358,6 +1369,10 @@ msgstr "Mật khẩu (*)"
 msgid "Path"
 msgid "Path"
 msgstr "Đường dẫn"
 msgstr "Đường dẫn"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr "Nâng cấp core không thành công"
 msgstr "Nâng cấp core không thành công"
@@ -1373,6 +1388,10 @@ msgid ""
 msgstr ""
 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"
 "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
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
 "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!"
 msgid "Please select at least one node!"
 msgstr ""
 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"
 msgid "Pre-release"
 msgstr ""
 msgstr ""
 
 
@@ -1450,11 +1471,11 @@ msgstr "Đọc"
 msgid "Receive"
 msgid "Receive"
 msgstr "Nhận"
 msgstr "Nhận"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 #, fuzzy
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "Xoá thành công"
 msgstr "Xoá thành công"
@@ -1463,7 +1484,7 @@ msgstr "Xoá thành công"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr "Tạo lại câu trả lời"
 msgstr "Tạo lại câu trả lời"
 
 
@@ -1491,16 +1512,16 @@ msgstr "Đăng ký người dùng"
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr "Đăng ký người dùng"
 msgstr "Đăng ký người dùng"
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 #, fuzzy
 #, fuzzy
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "Cài lại"
 msgstr "Cài lại"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr "Ghi chú phát hành"
 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
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr "Tải lại"
 msgstr "Tải lại"
@@ -1558,7 +1579,7 @@ msgstr "Gia hạn chứng chỉ SSL"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr "Yêu cầu có chứa tham số sai"
 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"
 msgid "Reset"
 msgstr "Đặt lại"
 msgstr "Đặt lại"
 
 
@@ -1579,7 +1600,7 @@ msgstr "Run Mode"
 msgid "Running"
 msgid "Running"
 msgstr "Running"
 msgstr "Running"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: 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}"
 msgstr "Đã xảy ra lỗi khi lưu %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 #, fuzzy
 #, fuzzy
@@ -1629,10 +1650,11 @@ msgstr "Gửi"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1711,14 +1733,16 @@ msgstr ""
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr ""
 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
 #, fuzzy
 msgid "Stable"
 msgid "Stable"
 msgstr "Ổn định"
 msgstr "Ổn định"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "Trạng thái"
 msgstr "Trạng thái"
 
 
@@ -1841,6 +1865,13 @@ msgstr ""
 msgid "The path exists, but the file is not a private key"
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1887,6 +1918,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr "Trường này không được để trống"
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1916,7 +1952,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -1929,7 +1965,7 @@ msgstr "Loại"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1940,17 +1976,22 @@ msgstr "Ngày cập nhật"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "Cập nhật thành công"
 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"
 msgid "Upgrade"
 msgstr "Cập nhật"
 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
 #: src/language/constants.ts:29
 #, fuzzy
 #, fuzzy
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "Cập nhật thành công"
 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..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "Đang cập nhật Nginx UI, vui lòng đợi..."
 msgstr "Đang cập nhật Nginx UI, vui lòng đợi..."
 
 
@@ -1962,11 +2003,11 @@ msgstr ""
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "Thời gian hoạt động:"
 msgstr "Thời gian hoạt động:"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 #, fuzzy
 #, fuzzy
 msgid "User"
 msgid "User"
 msgstr "Người dùng"
 msgstr "Người dùng"
@@ -1988,7 +2029,12 @@ msgstr "Username (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr "Hợp lệ"
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Xem"
 msgstr "Xem"
@@ -1998,12 +2044,12 @@ msgstr "Xem"
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "Xem tất cả thông báo"
 msgstr "Xem tất cả thông báo"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 #, fuzzy
 #, fuzzy
 msgid "View Details"
 msgid "View Details"
 msgstr "Chi tiết"
 msgstr "Chi tiết"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Cơ bản"
 msgstr "Cơ bản"
@@ -2050,11 +2096,11 @@ msgstr "Ghi chứng chỉ vào disk"
 msgid "Yes"
 msgid "Yes"
 msgstr "Có"
 msgstr "Có"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr "Bạn đang sử dụng phiên bản mới nhất"
 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."
 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"
 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/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "操作"
 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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -91,7 +91,7 @@ msgstr "API 代理"
 msgid "API Token"
 msgid "API Token"
 msgstr "API Token"
 msgstr "API Token"
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgid "Arch"
 msgstr "架构"
 msgstr "架构"
 
 
@@ -104,15 +104,15 @@ msgstr "您确定要立即删除这个被禁用的 IP 吗?"
 msgid "Are you sure you want to clear all notifications?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "您确定要清除所有通知吗?"
 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?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "你确定你要清除聊天记录吗?"
 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?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "您确定要永久删除此项目吗?"
 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?"
 msgid "Are you sure you want to delete this item?"
 msgstr "你确定要删除这个项目吗?"
 msgstr "你确定要删除这个项目吗?"
 
 
@@ -120,7 +120,7 @@ msgstr "你确定要删除这个项目吗?"
 msgid "Are you sure you want to delete?"
 msgid "Are you sure you want to delete?"
 msgstr "您确定要删除吗?"
 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?"
 msgid "Are you sure you want to recover this item?"
 msgstr "您确定要恢复这个项目吗?"
 msgstr "您确定要恢复这个项目吗?"
 
 
@@ -136,11 +136,11 @@ msgstr "您确定要删除这个项目吗?"
 msgid "Are you sure you want to remove this location?"
 msgid "Are you sure you want to remove this location?"
 msgstr "您确定要删除这个 Location?"
 msgstr "您确定要删除这个 Location?"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgid "Ask ChatGPT for Help"
 msgstr "与ChatGPT聊天"
 msgstr "与ChatGPT聊天"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr "助手"
 msgstr "助手"
 
 
@@ -184,6 +184,10 @@ msgstr "返回"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "返回首页"
 msgstr "返回首页"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr "返回列表"
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "禁止阈值(分钟)"
 msgstr "禁止阈值(分钟)"
@@ -212,10 +216,14 @@ msgid "Basic Mode"
 msgstr "基本模式"
 msgstr "基本模式"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "批量修改"
 msgstr "批量修改"
 
 
+#: src/views/environment/BatchUpgrader.vue:154
+msgid "Batch Upgrade"
+msgstr "批量升级"
+
 #: src/views/system/About.vue:39
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "构建基于"
 msgstr "构建基于"
@@ -228,9 +236,9 @@ msgstr "CA Dir"
 msgid "CADir"
 msgid "CADir"
 msgstr "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/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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -277,11 +285,11 @@ msgstr "挑战方法"
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "更改证书"
 msgstr "更改证书"
 
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgid "Channel"
 msgstr "通道"
 msgstr "通道"
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr "重新检查"
 msgstr "重新检查"
 
 
@@ -289,7 +297,7 @@ msgstr "重新检查"
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr "正在清理环境变量"
 msgstr "正在清理环境变量"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -331,7 +339,7 @@ msgstr "配置"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "配置 SSL"
 msgstr "配置 SSL"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr "已连接"
 msgstr "已连接"
 
 
@@ -341,7 +349,7 @@ msgstr "已连接"
 msgid "Content"
 msgid "Content"
 msgstr "内容"
 msgstr "内容"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "核心升级"
 msgstr "核心升级"
 
 
@@ -377,7 +385,7 @@ msgstr "DNS 凭证"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "凭证"
 msgstr "凭证"
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr "当前版本"
 msgstr "当前版本"
 
 
@@ -404,7 +412,7 @@ msgstr "数据库 (可选,默认: database)"
 msgid "Days"
 msgid "Days"
 msgstr "天"
 msgstr "天"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -412,7 +420,7 @@ msgstr "天"
 msgid "Delete"
 msgid "Delete"
 msgstr "删除"
 msgstr "删除"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr "彻底删除"
 msgstr "彻底删除"
 
 
@@ -424,7 +432,7 @@ msgstr "删除站点: %{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "删除 Stream: %{stream_name}"
 msgstr "删除 Stream: %{stream_name}"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "删除成功"
 msgstr "删除成功"
 
 
@@ -484,8 +492,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "关闭 %{name} 自动续签失败"
 msgstr "关闭 %{name} 自动续签失败"
 
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
 #: 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"
 msgid "Disabled"
 msgstr "禁用"
 msgstr "禁用"
 
 
@@ -571,7 +580,7 @@ msgstr "下载最新版本错误"
 msgid "Downloading latest release"
 msgid "Downloading latest release"
 msgstr "下载最新版本"
 msgstr "下载最新版本"
 
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgid "Dry run mode enabled"
 msgstr "试运行模式已启动"
 msgstr "试运行模式已启动"
 
 
@@ -663,7 +672,8 @@ msgstr "启用 TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -683,7 +693,7 @@ msgstr "启用成功"
 msgid "Encrypt website with Let's Encrypt"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 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"
 msgid "Environment"
 msgstr "环境"
 msgstr "环境"
 
 
@@ -691,7 +701,7 @@ msgstr "环境"
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr "环境变量已清理"
 msgstr "环境变量已清理"
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgid "Environments"
 msgstr "环境"
 msgstr "环境"
 
 
@@ -703,7 +713,7 @@ msgstr "错误"
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "错误日志"
 msgstr "错误日志"
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr "可执行文件路径"
 msgstr "可执行文件路径"
 
 
@@ -798,7 +808,7 @@ msgstr "生成"
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "正在生成私钥用于注册账户"
 msgstr "正在生成私钥用于注册账户"
 
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 msgid "Get release information error"
 msgid "Get release information error"
 msgstr "获取发布信息错误"
 msgstr "获取发布信息错误"
 
 
@@ -912,7 +922,7 @@ msgstr "Jwt 密钥"
 msgid "Key Type"
 msgid "Key Type"
 msgstr "密钥类型"
 msgstr "密钥类型"
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr "最后检查时间"
 msgstr "最后检查时间"
 
 
@@ -933,12 +943,12 @@ msgstr "留空不做任何更改"
 msgid "License"
 msgid "License"
 msgstr "开源许可"
 msgstr "开源许可"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgid "Link Start"
 msgstr "链接"
 msgstr "链接"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgid "List"
 msgstr "列表"
 msgstr "列表"
 
 
@@ -946,11 +956,11 @@ msgstr "列表"
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "系统负载:"
 msgstr "系统负载:"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr "从设置中加载"
 msgstr "从设置中加载"
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "加载成功"
 msgstr "加载成功"
 
 
@@ -1050,9 +1060,9 @@ msgstr "分钟"
 msgid "Model"
 msgid "Model"
 msgstr "模型"
 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"
 msgid "Modify"
 msgstr "修改"
 msgstr "修改"
 
 
@@ -1064,7 +1074,7 @@ msgstr "修改证书"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "修改配置文件"
 msgstr "修改配置文件"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "修改模式"
 msgstr "修改模式"
 
 
@@ -1081,7 +1091,7 @@ msgstr "多行指令"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1104,7 +1114,7 @@ msgstr "下载流量"
 msgid "Network Total Send"
 msgid "Network Total Send"
 msgstr "上传流量"
 msgstr "上传流量"
 
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgid "New version released"
 msgstr "新版本发布"
 msgstr "新版本发布"
 
 
@@ -1146,11 +1156,11 @@ msgstr "Nginx 重载成功"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Nginx 重启成功"
 msgstr "Nginx 重启成功"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
 #: 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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1199,22 +1209,22 @@ msgid "Obtaining certificate"
 msgstr "正在获取证书"
 msgstr "正在获取证书"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr "离线"
 msgstr "离线"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr "确定"
 msgstr "确定"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: src/views/domain/components/RightSettings.vue:50
@@ -1235,8 +1245,8 @@ msgstr "一旦验证完成,这些记录将被删除。"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr "在线"
 msgstr "在线"
 
 
@@ -1244,7 +1254,7 @@ msgstr "在线"
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr "OpenAI"
 msgstr "OpenAI"
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgid "OS"
 msgstr "OS"
 msgstr "OS"
 
 
@@ -1280,6 +1290,10 @@ msgstr "密码 (*)"
 msgid "Path"
 msgid "Path"
 msgstr "路径"
 msgstr "路径"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr "执行"
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr "执行核心升级错误"
 msgstr "执行核心升级错误"
@@ -1294,6 +1308,10 @@ msgid ""
 "provider."
 "provider."
 msgstr "请填写 DNS 提供商提供的 API 验证凭据。"
 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
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 msgid ""
 msgid ""
 "Please first add credentials in Certification > DNS Credentials, and then "
 "Please first add credentials in Certification > DNS Credentials, and then "
@@ -1331,7 +1349,9 @@ msgstr "请注意,下面的时间单位配置均以秒为单位。"
 msgid "Please select at least one node!"
 msgid "Please select at least one node!"
 msgstr "请至少选择一个节点!"
 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"
 msgid "Pre-release"
 msgstr "预发布"
 msgstr "预发布"
 
 
@@ -1369,11 +1389,11 @@ msgstr "读"
 msgid "Receive"
 msgid "Receive"
 msgstr "下载"
 msgstr "下载"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr "恢复"
 msgstr "恢复"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "恢复成功"
 msgstr "恢复成功"
 
 
@@ -1381,7 +1401,7 @@ msgstr "恢复成功"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr "递归域名服务器"
 msgstr "递归域名服务器"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr "重新生成响应"
 msgstr "重新生成响应"
 
 
@@ -1405,15 +1425,15 @@ msgstr "正在注册用户"
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr "注册状态"
 msgstr "注册状态"
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "重新安装"
 msgstr "重新安装"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr "发行日志"
 msgstr "发行日志"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr "重载"
 msgstr "重载"
@@ -1464,7 +1484,7 @@ msgstr "更新成功"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr "请求参数错误"
 msgstr "请求参数错误"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgid "Reset"
 msgstr "重置"
 msgstr "重置"
 
 
@@ -1484,7 +1504,7 @@ msgstr "运行模式"
 msgid "Running"
 msgid "Running"
 msgstr "运行中"
 msgstr "运行中"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1502,7 +1522,7 @@ msgid "Save error %{msg}"
 msgstr "保存错误 %{msg}"
 msgstr "保存错误 %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
 msgid "Save successfully"
@@ -1533,10 +1553,11 @@ msgstr "上传"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1612,13 +1633,15 @@ msgstr "SSL证书路径"
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr "SSO 登录"
 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"
 msgid "Stable"
 msgstr "稳定"
 msgstr "稳定"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "状态"
 msgstr "状态"
 
 
@@ -1735,6 +1758,15 @@ msgstr "路径存在,但文件不是证书"
 msgid "The path exists, but the file is not a private key"
 msgid "The path exists, but the file is not a private key"
 msgstr "路径存在,但文件不是私钥"
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1779,6 +1811,11 @@ msgstr "此字段必填"
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr "该字段不能为空"
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1806,7 +1843,7 @@ msgstr "Token 无效"
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr "登录失败次数过多,请稍后再试"
 msgstr "登录失败次数过多,请稍后再试"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr "回收站"
 msgstr "回收站"
 
 
@@ -1819,7 +1856,7 @@ msgstr "类型"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1829,18 +1866,22 @@ msgstr "修改时间"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "更新成功"
 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"
 msgid "Upgrade"
 msgstr "升级"
 msgstr "升级"
 
 
+#: src/views/environment/BatchUpgrader.vue:139
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "成功升级 %{node} 上的 Nginx UI 🎉"
+
 #: src/language/constants.ts:29
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "升级成功"
 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..."
 msgid "Upgrading Nginx UI, please wait..."
-msgstr "正在升级Nginx UI,请等待..."
+msgstr "正在升级 Nginx UI,请等待..."
 
 
 #: src/views/domain/ngx_conf/NgxUpstream.vue:170
 #: src/views/domain/ngx_conf/NgxUpstream.vue:170
 msgid "Upstream Name"
 msgid "Upstream Name"
@@ -1850,11 +1891,11 @@ msgstr "Upstream 名称"
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "运行时间:"
 msgstr "运行时间:"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr "URL"
 msgstr "URL"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "User"
 msgid "User"
 msgstr "用户"
 msgstr "用户"
 
 
@@ -1875,7 +1916,11 @@ msgstr "用户名 (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr "有效的"
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "查看"
 msgstr "查看"
@@ -1884,11 +1929,11 @@ msgstr "查看"
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "查看全部通知"
 msgstr "查看全部通知"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgid "View Details"
 msgstr "查看详情"
 msgstr "查看详情"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 msgid "View Mode"
 msgid "View Mode"
 msgstr "预览模式"
 msgstr "预览模式"
 
 
@@ -1931,11 +1976,11 @@ msgstr "正在将证书写入磁盘"
 msgid "Yes"
 msgid "Yes"
 msgstr "是的"
 msgstr "是的"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr "您使用的是最新版本"
 msgstr "您使用的是最新版本"
 
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "你可以在这个页面检查Nginx UI的升级。"
 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/ACMEUser.vue:59
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/Certificate.vue:113
 #: src/views/certificate/DNSCredential.vue:33 src/views/config/config.ts:34
 #: 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/notification/Notification.vue:37
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/preference/AuthSettings.vue:26 src/views/stream/StreamList.vue:47
 #: src/views/user/User.vue:43
 #: src/views/user/User.vue:43
 msgid "Action"
 msgid "Action"
 msgstr "操作"
 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/config_template/ConfigTemplate.vue:117
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxServer.vue:167
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
 #: src/views/domain/ngx_conf/NgxUpstream.vue:152
@@ -96,7 +96,7 @@ msgstr "API 代理"
 msgid "API Token"
 msgid "API Token"
 msgstr "API Token"
 msgstr "API Token"
 
 
-#: src/views/system/Upgrade.vue:173
+#: src/views/system/Upgrade.vue:178
 msgid "Arch"
 msgid "Arch"
 msgstr "架構"
 msgstr "架構"
 
 
@@ -111,16 +111,16 @@ msgstr "您確定要刪除嗎?"
 msgid "Are you sure you want to clear all notifications?"
 msgid "Are you sure you want to clear all notifications?"
 msgstr "您確定要清除聊天記錄嗎?"
 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?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "您確定要清除聊天記錄嗎?"
 msgstr "您確定要清除聊天記錄嗎?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:551
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:578
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr "您確定要刪除嗎?"
 msgstr "您確定要刪除嗎?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:523
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:550
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr "您確定要刪除嗎?"
 msgstr "您確定要刪除嗎?"
@@ -129,7 +129,7 @@ msgstr "您確定要刪除嗎?"
 msgid "Are you sure you want to delete?"
 msgid "Are you sure you want to delete?"
 msgstr "您確定要刪除嗎?"
 msgstr "您確定要刪除嗎?"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:537
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:564
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to recover this item?"
 msgid "Are you sure you want to recover this item?"
 msgstr "您確定要刪除這條指令嗎?"
 msgstr "您確定要刪除這條指令嗎?"
@@ -147,11 +147,11 @@ msgstr "您確定要刪除這條指令嗎?"
 msgid "Are you sure you want to remove this location?"
 msgid "Are you sure you want to remove this location?"
 msgstr "您確定要刪除此 Location 嗎?"
 msgstr "您確定要刪除此 Location 嗎?"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:216
+#: src/components/ChatGPT/ChatGPT.vue:215
 msgid "Ask ChatGPT for Help"
 msgid "Ask ChatGPT for Help"
 msgstr "向 ChatGPT 尋求幫助"
 msgstr "向 ChatGPT 尋求幫助"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "Assistant"
 msgid "Assistant"
 msgstr "助理"
 msgstr "助理"
 
 
@@ -196,6 +196,10 @@ msgstr "返回"
 msgid "Back Home"
 msgid "Back Home"
 msgstr "返回首頁"
 msgstr "返回首頁"
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:215
+msgid "Back to list"
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:68
 #: src/views/preference/AuthSettings.vue:68
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
@@ -224,10 +228,15 @@ msgid "Basic Mode"
 msgstr "基本模式"
 msgstr "基本模式"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:54
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:459
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:486
 msgid "Batch Modify"
 msgid "Batch Modify"
 msgstr "批次修改"
 msgstr "批次修改"
 
 
+#: src/views/environment/BatchUpgrader.vue:154
+#, fuzzy
+msgid "Batch Upgrade"
+msgstr "升級"
+
 #: src/views/system/About.vue:39
 #: src/views/system/About.vue:39
 msgid "Build with"
 msgid "Build with"
 msgstr "構建基於"
 msgstr "構建基於"
@@ -240,9 +249,9 @@ msgstr ""
 msgid "CADir"
 msgid "CADir"
 msgstr "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/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/components/StdDesign/StdDataEntry/components/StdSelector.vue:153
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/cert/components/ObtainCert.vue:137
 #: src/views/domain/components/Deploy.vue:21
 #: src/views/domain/components/Deploy.vue:21
@@ -292,11 +301,11 @@ msgstr "驗證方式"
 msgid "Change Certificate"
 msgid "Change Certificate"
 msgstr "更換憑證"
 msgstr "更換憑證"
 
 
-#: src/views/system/Upgrade.vue:185
+#: src/views/environment/BatchUpgrader.vue:161 src/views/system/Upgrade.vue:190
 msgid "Channel"
 msgid "Channel"
 msgstr "通道"
 msgstr "通道"
 
 
-#: src/views/system/Upgrade.vue:182
+#: src/views/system/Upgrade.vue:187
 msgid "Check again"
 msgid "Check again"
 msgstr "再次檢查"
 msgstr "再次檢查"
 
 
@@ -304,7 +313,7 @@ msgstr "再次檢查"
 msgid "Cleaning environment variables"
 msgid "Cleaning environment variables"
 msgstr "清理環境變數"
 msgstr "清理環境變數"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:276
+#: src/components/ChatGPT/ChatGPT.vue:275
 #: src/components/Notification/Notification.vue:91
 #: src/components/Notification/Notification.vue:91
 #: src/views/notification/Notification.vue:77
 #: src/views/notification/Notification.vue:77
 msgid "Clear"
 msgid "Clear"
@@ -348,7 +357,7 @@ msgstr "設定"
 msgid "Configure SSL"
 msgid "Configure SSL"
 msgstr "設定 SSL"
 msgstr "設定 SSL"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
 msgid "Connected"
 msgid "Connected"
 msgstr "已連結"
 msgstr "已連結"
 
 
@@ -358,7 +367,7 @@ msgstr "已連結"
 msgid "Content"
 msgid "Content"
 msgstr "內容"
 msgstr "內容"
 
 
-#: src/views/system/Upgrade.vue:143
+#: src/views/system/Upgrade.vue:148
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "核心升級"
 msgstr "核心升級"
 
 
@@ -395,7 +404,7 @@ msgstr "認證"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "認證資訊"
 msgstr "認證資訊"
 
 
-#: src/views/system/Upgrade.vue:162
+#: src/views/system/Upgrade.vue:167
 msgid "Current Version"
 msgid "Current Version"
 msgstr "目前版本"
 msgstr "目前版本"
 
 
@@ -422,7 +431,7 @@ msgstr "資料庫 (可選,預設: database)"
 msgid "Days"
 msgid "Days"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:530
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:557
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/DomainList.vue:155
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxServer.vue:114
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
 #: src/views/domain/ngx_conf/NgxUpstream.vue:126
@@ -430,7 +439,7 @@ msgstr ""
 msgid "Delete"
 msgid "Delete"
 msgstr "刪除"
 msgstr "刪除"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:558
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:585
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -443,7 +452,7 @@ msgstr "刪除網站:%{site_name}"
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "刪除網站:%{site_name}"
 msgstr "刪除網站:%{site_name}"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:185
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:202
 #, fuzzy
 #, fuzzy
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr "成功停用"
 msgstr "成功停用"
@@ -505,8 +514,9 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "關閉 %{name} 自動續簽失敗"
 msgstr "關閉 %{name} 自動續簽失敗"
 
 
 #: src/views/domain/cert/ChangeCert.vue:44 src/views/domain/DomainEdit.vue:183
 #: 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"
 msgid "Disabled"
 msgstr "停用"
 msgstr "停用"
 
 
@@ -596,7 +606,7 @@ msgstr "下載最新版本錯誤"
 msgid "Downloading latest release"
 msgid "Downloading latest release"
 msgstr "正在下載最新版本"
 msgstr "正在下載最新版本"
 
 
-#: src/views/system/Upgrade.vue:212
+#: src/views/environment/BatchUpgrader.vue:190 src/views/system/Upgrade.vue:217
 msgid "Dry run mode enabled"
 msgid "Dry run mode enabled"
 msgstr "試運轉模式已啟用"
 msgstr "試運轉模式已啟用"
 
 
@@ -690,7 +700,8 @@ msgstr "啟用 TLS"
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/cert/ChangeCert.vue:40
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/components/RightSettings.vue:77
 #: src/views/domain/DomainEdit.vue:177 src/views/domain/DomainList.vue:29
 #: 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/preference/LogrotateSettings.vue:20
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/components/RightSettings.vue:76
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
 #: src/views/stream/StreamEdit.vue:169 src/views/stream/StreamList.vue:29
@@ -710,7 +721,7 @@ msgstr "成功啟用"
 msgid "Encrypt website with Let's Encrypt"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 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"
 msgid "Environment"
 msgstr "環境"
 msgstr "環境"
 
 
@@ -719,7 +730,7 @@ msgstr "環境"
 msgid "Environment variables cleaned"
 msgid "Environment variables cleaned"
 msgstr "設定環境變數中"
 msgstr "設定環境變數中"
 
 
-#: src/views/dashboard/Environments.vue:82
+#: src/views/dashboard/Environments.vue:83
 msgid "Environments"
 msgid "Environments"
 msgstr "環境"
 msgstr "環境"
 
 
@@ -731,7 +742,7 @@ msgstr "錯誤"
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "錯誤日誌"
 msgstr "錯誤日誌"
 
 
-#: src/views/system/Upgrade.vue:174
+#: src/views/system/Upgrade.vue:179
 msgid "Executable Path"
 msgid "Executable Path"
 msgstr "可執行檔路徑"
 msgstr "可執行檔路徑"
 
 
@@ -831,7 +842,7 @@ msgstr "產生"
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "產生註冊帳號的私鑰"
 msgstr "產生註冊帳號的私鑰"
 
 
-#: src/views/system/Upgrade.vue:166
+#: src/views/environment/BatchUpgrader.vue:179 src/views/system/Upgrade.vue:171
 msgid "Get release information error"
 msgid "Get release information error"
 msgstr "取得發布資訊錯誤"
 msgstr "取得發布資訊錯誤"
 
 
@@ -950,7 +961,7 @@ msgstr "Jwt Secret"
 msgid "Key Type"
 msgid "Key Type"
 msgstr "類型"
 msgstr "類型"
 
 
-#: src/views/system/Upgrade.vue:176
+#: src/views/system/Upgrade.vue:181
 msgid "Last checked at"
 msgid "Last checked at"
 msgstr "上次檢查時間"
 msgstr "上次檢查時間"
 
 
@@ -973,12 +984,12 @@ msgstr "留空表示不修改"
 msgid "License"
 msgid "License"
 msgstr "授權條款"
 msgstr "授權條款"
 
 
-#: src/views/dashboard/Environments.vue:139
+#: src/views/dashboard/Environments.vue:141
+#: src/views/dashboard/Environments.vue:156
 msgid "Link Start"
 msgid "Link Start"
 msgstr "連結開始"
 msgstr "連結開始"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:204
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:227
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:192
 msgid "List"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -987,11 +998,11 @@ msgstr ""
 msgid "Load Average:"
 msgid "Load Average:"
 msgstr "系統負載:"
 msgstr "系統負載:"
 
 
-#: src/views/environment/Environment.vue:152
+#: src/views/environment/Environment.vue:39
 msgid "Load from settings"
 msgid "Load from settings"
 msgstr ""
 msgstr ""
 
 
-#: src/views/environment/Environment.vue:137
+#: src/views/environment/Environment.vue:13
 #, fuzzy
 #, fuzzy
 msgid "Load successfully"
 msgid "Load successfully"
 msgstr "儲存成功"
 msgstr "儲存成功"
@@ -1091,9 +1102,9 @@ msgstr ""
 msgid "Model"
 msgid "Model"
 msgstr "執行模式"
 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"
 msgid "Modify"
 msgstr "修改"
 msgstr "修改"
 
 
@@ -1106,7 +1117,7 @@ msgstr "憑證狀態"
 msgid "Modify Config"
 msgid "Modify Config"
 msgstr "修改設定"
 msgstr "修改設定"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "Modify Mode"
 msgid "Modify Mode"
 msgstr "修改"
 msgstr "修改"
@@ -1124,7 +1135,7 @@ msgstr "多行指令"
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/components/SiteDuplicate.vue:129
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/DomainList.vue:13
 #: src/views/domain/ngx_conf/NgxUpstream.vue:175
 #: 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/RightSettings.vue:82
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/components/StreamDuplicate.vue:129
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
 #: src/views/stream/StreamList.vue:13 src/views/stream/StreamList.vue:187
@@ -1147,7 +1158,7 @@ msgstr "下載流量"
 msgid "Network Total Send"
 msgid "Network Total Send"
 msgstr "上傳流量"
 msgstr "上傳流量"
 
 
-#: src/views/system/Upgrade.vue:205
+#: src/views/system/Upgrade.vue:210
 msgid "New version released"
 msgid "New version released"
 msgstr "新版本發布"
 msgstr "新版本發布"
 
 
@@ -1189,11 +1200,11 @@ msgstr "Nginx 重新載入成功"
 msgid "Nginx restarted successfully"
 msgid "Nginx restarted successfully"
 msgstr "Nginx 重啟成功"
 msgstr "Nginx 重啟成功"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:270
+#: src/components/ChatGPT/ChatGPT.vue:269
 #: src/components/Notification/Notification.vue:84
 #: 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/DomainList.vue:144
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:90
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
 #: src/views/domain/ngx_conf/LocationEditor.vue:71
@@ -1244,22 +1255,22 @@ msgid "Obtaining certificate"
 msgstr "正在取得憑證"
 msgstr "正在取得憑證"
 
 
 #: src/components/NodeSelector/NodeSelector.vue:95
 #: 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"
 msgid "Offline"
 msgstr "離線"
 msgstr "離線"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:264
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:253
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:154
 msgid "Ok"
 msgid "Ok"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ChatGPT/ChatGPT.vue:271
+#: src/components/ChatGPT/ChatGPT.vue:270
 #: src/components/Notification/Notification.vue:85
 #: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:56
 #: 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/cert/components/ObtainCert.vue:136
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/Deploy.vue:20
 #: src/views/domain/components/RightSettings.vue:50
 #: src/views/domain/components/RightSettings.vue:50
@@ -1280,8 +1291,8 @@ msgstr ""
 
 
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:74
 #: src/components/NodeSelector/NodeSelector.vue:89
 #: 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"
 msgid "Online"
 msgstr "線上"
 msgstr "線上"
 
 
@@ -1289,7 +1300,7 @@ msgstr "線上"
 msgid "OpenAI"
 msgid "OpenAI"
 msgstr "OpenAI"
 msgstr "OpenAI"
 
 
-#: src/views/system/Upgrade.vue:172
+#: src/views/system/Upgrade.vue:177
 msgid "OS"
 msgid "OS"
 msgstr "作業系統"
 msgstr "作業系統"
 
 
@@ -1325,6 +1336,10 @@ msgstr "密碼 (*)"
 msgid "Path"
 msgid "Path"
 msgstr "路徑"
 msgstr "路徑"
 
 
+#: src/views/environment/BatchUpgrader.vue:234
+msgid "Perform"
+msgstr ""
+
 #: src/language/constants.ts:28
 #: src/language/constants.ts:28
 msgid "Perform core upgrade error"
 msgid "Perform core upgrade error"
 msgstr "執行核心升級錯誤"
 msgstr "執行核心升級錯誤"
@@ -1339,6 +1354,10 @@ msgid ""
 "provider."
 "provider."
 msgstr ""
 msgstr ""
 
 
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:132
+msgid "Please fill in the required fields"
+msgstr ""
+
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #: src/views/domain/cert/components/AutoCertStepOne.vue:63
 #, fuzzy
 #, fuzzy
 msgid ""
 msgid ""
@@ -1377,7 +1396,9 @@ msgstr ""
 msgid "Please select at least one node!"
 msgid "Please select at least one node!"
 msgstr "請至少選擇一個節點!"
 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"
 msgid "Pre-release"
 msgstr "預先發布"
 msgstr "預先發布"
 
 
@@ -1415,11 +1436,11 @@ msgstr "讀取"
 msgid "Receive"
 msgid "Receive"
 msgstr "接收"
 msgstr "接收"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:544
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:571
 msgid "Recover"
 msgid "Recover"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:193
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:210
 #, fuzzy
 #, fuzzy
 msgid "Recovered Successfully"
 msgid "Recovered Successfully"
 msgstr "儲存成功"
 msgstr "儲存成功"
@@ -1429,7 +1450,7 @@ msgstr "儲存成功"
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
 msgstr "網站域名 (server_name)"
 msgstr "網站域名 (server_name)"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/ChatGPT/ChatGPT.vue:282
 msgid "Regenerate response"
 msgid "Regenerate response"
 msgstr "重新產生回應"
 msgstr "重新產生回應"
 
 
@@ -1457,15 +1478,15 @@ msgstr "註冊使用者中"
 msgid "Registration Status"
 msgid "Registration Status"
 msgstr "註冊使用者中"
 msgstr "註冊使用者中"
 
 
-#: src/views/system/Upgrade.vue:224
+#: src/views/system/Upgrade.vue:228
 msgid "Reinstall"
 msgid "Reinstall"
 msgstr "重新安裝"
 msgstr "重新安裝"
 
 
-#: src/views/system/Upgrade.vue:255
+#: src/views/system/Upgrade.vue:251
 msgid "Release Note"
 msgid "Release Note"
 msgstr "發行公告"
 msgstr "發行公告"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:259
+#: src/components/ChatGPT/ChatGPT.vue:258
 #: src/components/NginxControl/NginxControl.vue:100
 #: src/components/NginxControl/NginxControl.vue:100
 msgid "Reload"
 msgid "Reload"
 msgstr "重新載入"
 msgstr "重新載入"
@@ -1523,7 +1544,7 @@ msgstr "啟用成功"
 msgid "Requested with wrong parameters"
 msgid "Requested with wrong parameters"
 msgstr "請求參數錯誤"
 msgstr "請求參數錯誤"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:453
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:480
 msgid "Reset"
 msgid "Reset"
 msgstr "重設"
 msgstr "重設"
 
 
@@ -1543,7 +1564,7 @@ msgstr "執行模式"
 msgid "Running"
 msgid "Running"
 msgstr "執行中"
 msgstr "執行中"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:252
+#: src/components/ChatGPT/ChatGPT.vue:251
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/certificate/CertificateEditor.vue:249
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/config/ConfigEdit.vue:96 src/views/domain/DomainEdit.vue:261
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:120
@@ -1561,7 +1582,7 @@ msgid "Save error %{msg}"
 msgstr "儲存錯誤 %{msg}"
 msgstr "儲存錯誤 %{msg}"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:39
 #: 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/certificate/CertificateEditor.vue:46
 #: src/views/preference/Preference.vue:74
 #: src/views/preference/Preference.vue:74
 msgid "Save successfully"
 msgid "Save successfully"
@@ -1592,10 +1613,11 @@ msgstr "傳送"
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts:46
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/methods/sortable.ts:126
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
 #: 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/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/AuthSettings.vue:49
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/preference/Preference.vue:78 src/views/stream/StreamList.vue:113
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
 #: src/views/stream/StreamList.vue:81 src/views/system/Upgrade.vue:42
@@ -1677,13 +1699,15 @@ msgstr "SSL 憑證路徑"
 msgid "SSO Login"
 msgid "SSO Login"
 msgstr "登入"
 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"
 msgid "Stable"
 msgstr "穩定"
 msgstr "穩定"
 
 
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/ACMEUser.vue:42
 #: src/views/certificate/Certificate.vue:88 src/views/domain/DomainList.vue:22
 #: 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"
 msgid "Status"
 msgstr "狀態"
 msgstr "狀態"
 
 
@@ -1808,6 +1832,13 @@ msgstr "SSL 憑證金鑰路徑"
 msgid "The path exists, but the file is not a private key"
 msgid "The path exists, but the file is not a private key"
 msgstr ""
 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
 #: src/views/preference/BasicSettings.vue:120
 msgid ""
 msgid ""
 "The server name should only contain letters, unicode, numbers, hyphens, "
 "The server name should only contain letters, unicode, numbers, hyphens, "
@@ -1856,6 +1887,11 @@ msgstr ""
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr "此欄位不應為空"
 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/AuthSettings.vue:59
 #: src/views/preference/LogrotateSettings.vue:12
 #: src/views/preference/LogrotateSettings.vue:12
 msgid "Tips"
 msgid "Tips"
@@ -1883,7 +1919,7 @@ msgstr ""
 msgid "Too many login failed attempts, please try again later"
 msgid "Too many login failed attempts, please try again later"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:221
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:209
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -1896,7 +1932,7 @@ msgstr "類型"
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/certificate/DNSCredential.vue:27 src/views/config/config.ts:27
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/config/ConfigEdit.vue:121
 #: src/views/domain/components/RightSettings.vue:86
 #: 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/components/RightSettings.vue:85
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 #: src/views/stream/StreamList.vue:41 src/views/user/User.vue:37
 msgid "Updated at"
 msgid "Updated at"
@@ -1906,16 +1942,21 @@ msgstr "更新時間"
 msgid "Updated successfully"
 msgid "Updated successfully"
 msgstr "更新成功"
 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"
 msgid "Upgrade"
 msgstr "升級"
 msgstr "升級"
 
 
+#: src/views/environment/BatchUpgrader.vue:139
+#, fuzzy
+msgid "Upgraded Nginx UI on %{node} successfully 🎉"
+msgstr "升級成功"
+
 #: src/language/constants.ts:29
 #: src/language/constants.ts:29
 msgid "Upgraded successfully"
 msgid "Upgraded successfully"
 msgstr "升級成功"
 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..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr "正在升級 Nginx UI,請稍候..."
 msgstr "正在升級 Nginx UI,請稍候..."
 
 
@@ -1927,11 +1968,11 @@ msgstr ""
 msgid "Uptime:"
 msgid "Uptime:"
 msgstr "運作時間:"
 msgstr "運作時間:"
 
 
-#: src/views/environment/Environment.vue:22
+#: src/views/environment/envColumns.tsx:19
 msgid "URL"
 msgid "URL"
 msgstr "URL"
 msgstr "URL"
 
 
-#: src/components/ChatGPT/ChatGPT.vue:230
+#: src/components/ChatGPT/ChatGPT.vue:229
 msgid "User"
 msgid "User"
 msgstr "使用者名稱"
 msgstr "使用者名稱"
 
 
@@ -1952,7 +1993,12 @@ msgstr "使用者名稱 (*)"
 msgid "Valid"
 msgid "Valid"
 msgstr ""
 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
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "檢視"
 msgstr "檢視"
@@ -1962,11 +2008,11 @@ msgstr "檢視"
 msgid "View all notifications"
 msgid "View all notifications"
 msgstr "憑證"
 msgstr "憑證"
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:194
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
 msgid "View Details"
 msgid "View Details"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:279
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:268
 #, fuzzy
 #, fuzzy
 msgid "View Mode"
 msgid "View Mode"
 msgstr "基本模式"
 msgstr "基本模式"
@@ -2011,11 +2057,11 @@ msgstr "將憑證寫入磁碟"
 msgid "Yes"
 msgid "Yes"
 msgstr "是的"
 msgstr "是的"
 
 
-#: src/views/system/Upgrade.vue:199
+#: src/views/system/Upgrade.vue:204
 msgid "You are using the latest version"
 msgid "You are using the latest version"
 msgstr "您正在使用最新版本"
 msgstr "您正在使用最新版本"
 
 
-#: src/views/system/Upgrade.vue:161
+#: src/views/system/Upgrade.vue:166
 msgid "You can check Nginx UI upgrade at this page."
 msgid "You can check Nginx UI upgrade at this page."
 msgstr "您可以在此頁面檢查 Nginx UI 的升級。"
 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 { formatDateTime } from '@/lib/helper'
 import NodeAnalyticItem from '@/views/dashboard/components/NodeAnalyticItem.vue'
 import NodeAnalyticItem from '@/views/dashboard/components/NodeAnalyticItem.vue'
 import analytic from '@/api/analytic'
 import analytic from '@/api/analytic'
+import { version } from '@/version.json'
 
 
 const data = ref([]) as Ref<Node[]>
 const data = ref([]) as Ref<Node[]>
 
 
-const node_map = computed(() => {
+const nodeMap = computed(() => {
   const o = {} as Record<number, Node>
   const o = {} as Record<number, Node>
 
 
   data.value.forEach(v => {
   data.value.forEach(v => {
@@ -48,9 +49,9 @@ onMounted(() => {
       const key = Number.parseInt(v)
       const key = Number.parseInt(v)
 
 
       // update node online status
       // 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()
 const { environment: env } = useSettingsStore()
 
 
-function link_start(node: Node) {
+function linkStart(node: Node) {
   env.id = node.id
   env.id = node.id
   env.name = node.name
   env.name = node.name
 }
 }
@@ -130,14 +131,31 @@ const visible = computed(() => {
                 />
                 />
 
 
                 <AButton
                 <AButton
+                  v-if="item.version === version"
                   type="primary"
                   type="primary"
                   :disabled="!item.status || env.id === item.id"
                   :disabled="!item.status || env.id === item.id"
                   ghost
                   ghost
-                  @click="link_start(item)"
+                  @click="linkStart(item)"
                 >
                 >
                   <SendOutlined />
                   <SendOutlined />
                   {{ env.id !== item.id ? $gettext('Link Start') : $gettext('Connected') }}
                   {{ env.id !== item.id ? $gettext('Link Start') : $gettext('Connected') }}
                 </AButton>
                 </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>
               </div>
             </template>
             </template>
           </AListItemMeta>
           </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">
 <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 environment from '@/api/environment'
 import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 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()
 const curd = ref()
-function load_from_settings() {
+function loadFromSettings() {
   environment.load_from_settings().then(() => {
   environment.load_from_settings().then(() => {
     curd.value.get_list()
     curd.value.get_list()
     message.success($gettext('Load successfully'))
     message.success($gettext('Load successfully'))
@@ -144,19 +15,42 @@ function load_from_settings() {
     message.error(`${$gettext('Server error')} ${e?.message}`)
     message.error(`${$gettext('Server error')} ${e?.message}`)
   })
   })
 }
 }
+const selectedNodeIds = ref([])
+const selectedNodes = ref([])
+const refUpgrader = ref()
+
+function batchUpgrade() {
+  refUpgrader.value.open(selectedNodeIds, selectedNodes)
+}
 </script>
 </script>
 
 
 <template>
 <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>
 </template>
 
 
 <style lang="less" scoped>
 <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 route = useRoute()
 const data = ref({}) as Ref<RuntimeInfo>
 const data = ref({}) as Ref<RuntimeInfo>
-const last_check = ref('')
+const lastCheck = ref('')
 const loading = ref(false)
 const loading = ref(false)
 const channel = ref('stable')
 const channel = ref('stable')
 
 
@@ -25,31 +25,31 @@ const modalVisible = ref(false)
 const progressPercent = ref(0)
 const progressPercent = ref(0)
 const progressStatus = ref('active') as Ref<'normal' | 'active' | 'success' | 'exception'>
 const progressStatus = ref('active') as Ref<'normal' | 'active' | 'success' | 'exception'>
 const modalClosable = ref(false)
 const modalClosable = ref(false)
-const get_release_error = ref(false)
+const getReleaseError = ref(false)
 
 
 const progressPercentComputed = computed(() => {
 const progressPercentComputed = computed(() => {
   return Number.parseFloat(progressPercent.value.toFixed(1))
   return Number.parseFloat(progressPercent.value.toFixed(1))
 })
 })
 
 
-function get_latest_release() {
+function getLatestRelease() {
   loading.value = true
   loading.value = true
   data.value.body = ''
   data.value.body = ''
   upgrade.get_latest_release(channel.value).then(r => {
   upgrade.get_latest_release(channel.value).then(r => {
     data.value = 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 => {
   }).catch(e => {
-    get_release_error.value = e?.message
+    getReleaseError.value = e?.message
     message.error(e?.message ?? $gettext('Server error'))
     message.error(e?.message ?? $gettext('Server error'))
   }).finally(() => {
   }).finally(() => {
     loading.value = false
     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}`
   return data.value.name === `v${version.version}`
 })
 })
 
 
@@ -65,11 +65,11 @@ function log(msg: string) {
   logContainer.value.scroll({ top: 320, left: 0, behavior: 'smooth' })
   logContainer.value.scroll({ top: 320, left: 0, behavior: 'smooth' })
 }
 }
 
 
-const dry_run = computed(() => {
+const dryRun = computed(() => {
   return !!route.query.dry_run
   return !!route.query.dry_run
 })
 })
 
 
-async function perform_upgrade() {
+async function performUpgrade() {
   progressStatus.value = 'active'
   progressStatus.value = 'active'
   modalClosable.value = false
   modalClosable.value = false
   modalVisible.value = true
   modalVisible.value = true
@@ -84,12 +84,12 @@ async function perform_upgrade() {
 
 
   ws.onopen = () => {
   ws.onopen = () => {
     ws.send(JSON.stringify({
     ws.send(JSON.stringify({
-      dry_run: dry_run.value,
+      dry_run: dryRun.value,
       channel: channel.value,
       channel: channel.value,
     }))
     }))
   }
   }
 
 
-  let is_fail = false
+  let isFailed = false
 
 
   ws.onmessage = async m => {
   ws.onmessage = async m => {
     const r = JSON.parse(m.data)
     const r = JSON.parse(m.data)
@@ -106,7 +106,7 @@ async function perform_upgrade() {
       case 'error':
       case 'error':
         progressStatus.value = 'exception'
         progressStatus.value = 'exception'
         modalClosable.value = true
         modalClosable.value = true
-        is_fail = true
+        isFailed = true
         break
         break
       default:
       default:
         modalClosable.value = true
         modalClosable.value = true
@@ -115,13 +115,13 @@ async function perform_upgrade() {
   }
   }
 
 
   ws.onerror = () => {
   ws.onerror = () => {
-    is_fail = true
+    isFailed = true
     progressStatus.value = 'exception'
     progressStatus.value = 'exception'
     modalClosable.value = true
     modalClosable.value = true
   }
   }
 
 
   ws.onclose = async () => {
   ws.onclose = async () => {
-    if (is_fail)
+    if (isFailed)
       return
       return
 
 
     const t = setInterval(() => {
     const t = setInterval(() => {
@@ -165,11 +165,11 @@ async function perform_upgrade() {
     <div class="upgrade-container">
     <div class="upgrade-container">
       <p>{{ $gettext('You can check Nginx UI upgrade at this page.') }}</p>
       <p>{{ $gettext('You can check Nginx UI upgrade at this page.') }}</p>
       <h3>{{ $gettext('Current Version') }}: v{{ version.version }}</h3>
       <h3>{{ $gettext('Current Version') }}: v{{ version.version }}</h3>
-      <template v-if="get_release_error">
+      <template v-if="getReleaseError">
         <AAlert
         <AAlert
           type="error"
           type="error"
           :title="$gettext('Get release information error')"
           :title="$gettext('Get release information error')"
-          :message="get_release_error"
+          :message="getReleaseError"
           banner
           banner
         />
         />
       </template>
       </template>
@@ -178,11 +178,11 @@ async function perform_upgrade() {
         <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
         <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
         <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
         <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
         <p>
         <p>
-          {{ $gettext('Last checked at') }}: {{ last_check }}
+          {{ $gettext('Last checked at') }}: {{ lastCheck }}
           <AButton
           <AButton
             type="link"
             type="link"
             :loading="loading"
             :loading="loading"
-            @click="get_latest_release"
+            @click="getLatestRelease"
           >
           >
             {{ $gettext('Check again') }}
             {{ $gettext('Check again') }}
           </AButton>
           </AButton>
@@ -199,7 +199,7 @@ async function perform_upgrade() {
         </AFormItem>
         </AFormItem>
         <template v-if="!loading">
         <template v-if="!loading">
           <AAlert
           <AAlert
-            v-if="is_latest_ver"
+            v-if="isLatestVer"
             type="success"
             type="success"
             :message="$gettext('You are using the latest version')"
             :message="$gettext('You are using the latest version')"
             banner
             banner
@@ -210,7 +210,7 @@ async function perform_upgrade() {
             :message="$gettext('New version released')"
             :message="$gettext('New version released')"
             banner
             banner
           />
           />
-          <template v-if="dry_run">
+          <template v-if="dryRun">
             <br>
             <br>
             <AAlert
             <AAlert
               type="info"
               type="info"
@@ -221,20 +221,11 @@ async function perform_upgrade() {
           <div class="control-btn">
           <div class="control-btn">
             <ASpace>
             <ASpace>
               <AButton
               <AButton
-                v-if="is_latest_ver"
                 type="primary"
                 type="primary"
                 ghost
                 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>
               </AButton>
             </ASpace>
             </ASpace>
           </div>
           </div>