Browse Source

feat: generate password for user

0xJacky 2 years ago
parent
commit
5490e48d9f

+ 3 - 0
frontend/components.d.ts

@@ -60,6 +60,9 @@ declare module '@vue/runtime-core' {
     SetLanguage: typeof import('./src/components/SetLanguage/SetLanguage.vue')['default']
     StdCurd: typeof import('./src/components/StdDataDisplay/StdCurd.vue')['default']
     StdPagination: typeof import('./src/components/StdDataDisplay/StdPagination.vue')['default']
+    StdPassword: typeof import('./src/components/StdDataEntry/compontents/StdPassword.vue')['default']
+    StdSelect: typeof import('./src/components/StdDataEntry/compontents/StdSelect.vue')['default']
+    StdSelector: typeof import('./src/components/StdDataEntry/compontents/StdSelector.vue')['default']
     StdTable: typeof import('./src/components/StdDataDisplay/StdTable.vue')['default']
   }
 }

+ 34 - 24
frontend/src/components/StdDataDisplay/StdCurd.vue

@@ -1,7 +1,5 @@
 <script setup lang="ts">
 import gettext from '@/gettext'
-
-const {$gettext, interpolate} = gettext
 import StdTable from './StdTable.vue'
 
 import StdDataEntry from '@/components/StdDataEntry'
@@ -9,6 +7,8 @@ import StdDataEntry from '@/components/StdDataEntry'
 import {reactive, ref} from 'vue'
 import {message} from 'ant-design-vue'
 
+const {$gettext} = gettext
+
 const props = defineProps({
     api: Object,
     columns: Array,
@@ -44,13 +44,20 @@ const props = defineProps({
         type: Boolean,
         default: true
     },
+    beforeSave: {
+        type: Function,
+        default: null
+    },
+    exportCsv: {
+        type: Boolean,
+        default: false
+    }
 })
 
 const visible = ref(false)
 const update = ref(0)
 const data: any = reactive({id: null})
 const error: any = reactive({})
-const params = reactive({})
 const selected = reactive([])
 
 function onSelect(keys: any) {
@@ -67,7 +74,7 @@ function add() {
     Object.keys(data).forEach(v => {
         delete data[v]
     })
-    
+
     clear_error()
     visible.value = true
 }
@@ -86,7 +93,7 @@ function clear_error() {
 
 const ok = async () => {
     clear_error()
-
+    await props.beforeSave(data)
     props.api!.save(data.id, data).then((r: any) => {
         message.success($gettext('Save Successfully'))
         Object.assign(data, r)
@@ -124,11 +131,13 @@ function edit(id: any) {
             </template>
 
             <std-table
-                ref="table"
-                v-bind="props"
-                @clickEdit="edit"
-                @selected="onSelect"
-                :key="update"
+                    ref="table"
+                    v-bind="props"
+                    @clickEdit="edit"
+                    @selected="onSelect"
+                    :key="update"
+                    :get_params="get_params"
+                    :exportCsv="exportCsv"
             >
                 <template v-slot:actions="slotProps">
                     <slot name="actions" :actions="slotProps.record"/>
@@ -137,23 +146,24 @@ function edit(id: any) {
         </a-card>
 
         <a-modal
-            class="std-curd-edit-modal"
-            :mask="false"
-            :title="data.id ? $gettext('Modify') : $gettext('Add')"
-            :visible="visible"
-            :cancel-text="$gettext('Cancel')"
-            :ok-text="$gettext('OK')"
-            @cancel="cancel"
-            @ok="ok"
-            :width="600"
-            destroyOnClose
+                class="std-curd-edit-modal"
+                :mask="false"
+                :title="data.id ? $gettext('Modify') : $gettext('Add')"
+                :visible="visible"
+                :cancel-text="$gettext('Cancel')"
+                :ok-text="$gettext('OK')"
+                @cancel="cancel"
+                @ok="ok"
+                :width="600"
+                destroyOnClose
         >
             <std-data-entry
-                ref="std_data_entry"
-                :data-list="editableColumns()"
-                :data-source="data"
-                :error="error"
+                    ref="std_data_entry"
+                    :data-list="editableColumns()"
+                    v-model:data-source="data"
+                    :error="error"
             />
+            <slot name="edit" :data="data"/>
         </a-modal>
     </div>
 </template>

+ 6 - 6
frontend/src/components/StdDataDisplay/StdPagination.vue

@@ -13,12 +13,12 @@ function changePage(num: number) {
 <template>
     <div v-if="pagination.total>pagination.per_page">
         <a-pagination
-            :current="pagination.current_page"
-            :pageSize="pagination.per_page"
-            :size="size"
-            :total="pagination.total"
-            class="pagination"
-            @change="changePage"
+                :current="pagination.current_page"
+                :pageSize="pagination.per_page"
+                :size="size"
+                :total="pagination.total"
+                class="pagination"
+                @change="changePage"
         />
     </div>
 </template>

+ 153 - 35
frontend/src/components/StdDataDisplay/StdTable.vue

@@ -1,13 +1,15 @@
 <script setup lang="ts">
 import gettext from '@/gettext'
-
-const {$gettext, interpolate} = gettext
-
 import StdDataEntry from '@/components/StdDataEntry'
 import StdPagination from './StdPagination.vue'
-import {nextTick, reactive, ref, watch} from 'vue'
+import {computed, onMounted, reactive, ref, watch} from 'vue'
 import {useRoute, useRouter} from 'vue-router'
 import {message} from 'ant-design-vue'
+import {downloadCsv} from '@/lib/helper'
+
+const {$gettext, interpolate} = gettext
+
+const emit = defineEmits(['onSelected', 'onSelectedRecord', 'clickEdit'])
 
 const props = defineProps({
     api: Object,
@@ -20,6 +22,10 @@ const props = defineProps({
         type: Boolean,
         default: false
     },
+    disable_query_params: {
+        type: Boolean,
+        default: false
+    },
     disable_add: {
         type: Boolean,
         default: false
@@ -41,7 +47,6 @@ const props = defineProps({
     },
     selectionType: {
         type: String,
-        default: 'checkbox',
         validator: function (value: string) {
             return ['checkbox', 'radio'].indexOf(value) !== -1
         }
@@ -57,6 +62,10 @@ const props = defineProps({
     rowKey: {
         type: String,
         default: 'id'
+    },
+    exportCsv: {
+        type: Boolean,
+        default: false
     }
 })
 
@@ -70,17 +79,21 @@ const pagination = reactive({
     total_pages: 1
 })
 const route = useRoute()
-let params = reactive({
-    ...route.query,
+const params = reactive({
     ...props.get_params
 })
+
 const selectedRowKeys = ref([])
-const rowSelection = reactive({})
 
 const searchColumns = getSearchColumns()
 const pithyColumns = getPithyColumns()
 
-get_list()
+onMounted(() => {
+    if (!props.disable_query_params) {
+        Object.assign(params, route.query)
+    }
+    get_list()
+})
 
 defineExpose({
     get_list
@@ -117,9 +130,17 @@ function stdChange(pagination: any, filters: any, sorter: any) {
     if (sorter) {
         params['order_by'] = sorter.field
         params['sort'] = sorter.order === 'ascend' ? 'asc' : 'desc'
-        nextTick(() => {
-            get_list()
-        })
+        switch (sorter.order) {
+            case 'ascend':
+                params['sort'] = 'asc'
+                break
+            case 'descend':
+                params['sort'] = 'desc'
+                break
+            default:
+                params['sort'] = null
+                break
+        }
     }
 }
 
@@ -150,11 +171,11 @@ function checked(c: any) {
 
 function onSelectChange(_selectedRowKeys: any) {
     selectedRowKeys.value = _selectedRowKeys
-    // this.$emit('selected', selectedRowKeys)
+    emit('onSelected', selectedRowKeys.value)
 }
 
 function onSelect(record: any) {
-    // this.$emit('selectedRecord', record)
+    emit('onSelectedRecord', record)
 }
 
 const router = useRouter()
@@ -163,6 +184,11 @@ const reset_search = async () => {
     Object.keys(params).forEach(v => {
         delete params[v]
     })
+
+    Object.assign(params, {
+        ...props.get_params
+    })
+
     router.push({query: {}}).catch(() => {
     })
 }
@@ -171,37 +197,125 @@ watch(params, () => {
     router.push({query: params})
     get_list()
 })
+
+const rowSelection = computed(() => {
+    if (props.selectionType) {
+        return {
+            selectedRowKeys: selectedRowKeys, onChange: onSelectChange,
+            onSelect: onSelect, type: props.selectionType
+        }
+    } else {
+        return null
+    }
+})
+
+function fn(obj: Object, desc: string) {
+    const arr: string[] = desc.split('.')
+    while (arr.length) {
+        // @ts-ignore
+        const top = obj[arr.shift()]
+        if (top === undefined) {
+            return null
+        }
+        obj = top
+    }
+    return obj
+}
+
+async function export_csv() {
+    let header = []
+    let headerKeys: any[] = []
+    const showColumnsMap: any = {}
+    // @ts-ignore
+    for (let showColumnsKey in pithyColumns) {
+        // @ts-ignore
+        if (pithyColumns[showColumnsKey].dataIndex === 'action') continue
+        // @ts-ignore
+        let t = pithyColumns[showColumnsKey].title
+
+        if (typeof t === 'function') {
+            t = t()
+        }
+        header.push({
+            title: t,
+            // @ts-ignore
+            key: pithyColumns[showColumnsKey].dataIndex
+        })
+        // @ts-ignore
+        headerKeys.push(pithyColumns[showColumnsKey].dataIndex)
+        // @ts-ignore
+        showColumnsMap[pithyColumns[showColumnsKey].dataIndex] = pithyColumns[showColumnsKey]
+    }
+
+    let dataSource: any = []
+    let hasMore = true
+    let page = 1
+    while (hasMore) {
+        // 准备 DataSource
+        await props.api!.get_list({page}).then((response: any) => {
+            if (response.data.length === 0) {
+                hasMore = false
+                return
+            }
+            if (response[props.data_key] === undefined) {
+                dataSource = dataSource.concat(...response.data)
+            } else {
+                dataSource = dataSource.concat(...response[props.data_key])
+            }
+        }).catch((e: any) => {
+            message.error(e.message ?? '系统错误')
+        })
+        page += 1
+    }
+    const data: any[] = []
+    dataSource.forEach((row: Object) => {
+        let obj: any = {}
+        headerKeys.forEach(key => {
+            console.log(row, key)
+            let data = fn(row, key)
+            const c = showColumnsMap[key]
+            console.log(c)
+            data = c?.customRender?.({text: data}) ?? data
+            obj[c.dataIndex] = data
+        })
+        data.push(obj)
+    })
+    console.log(header, data)
+    downloadCsv(header, data, '测试.csv')
+}
 </script>
 
 <template>
     <div class="std-table">
         <std-data-entry
-            v-if="!disable_search"
-            :data-list="searchColumns"
-            v-model:data-source="params"
-            layout="inline"
+                v-if="!disable_search"
+                :data-list="searchColumns"
+                v-model:data-source="params"
+                layout="inline"
         >
             <template #action>
-                <div class="reset-btn">
+                <a-space class="reset-btn">
+                    <a-button @click="export_csv" type="primary" ghost>
+                        <translate>Export</translate>
+                    </a-button>
                     <a-button @click="reset_search">
                         <translate>Reset</translate>
                     </a-button>
-                </div>
+                </a-space>
             </template>
         </std-data-entry>
         <a-table
-            :columns="pithyColumns"
-            :data-source="data_source"
-            :loading="loading"
-            :pagination="false"
-            :row-key="rowKey"
-            :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange,
-            onSelect: onSelect, type: selectionType}"
-            @change="stdChange"
-            :scroll="{ x: scrollX }"
+                :columns="pithyColumns"
+                :data-source="data_source"
+                :loading="loading"
+                :pagination="false"
+                :row-key="rowKey"
+                :rowSelection="rowSelection"
+                @change="stdChange"
+                :scroll="{ x: scrollX }"
         >
             <template
-                v-slot:bodyCell="{text, record, index, column}"
+                    v-slot:bodyCell="{text, record, index, column}"
             >
                 <template v-if="column.dataIndex === 'action'">
                     <a v-if="props.editable" @click="$emit('clickEdit', record[props.rowKey], record)">
@@ -211,10 +325,10 @@ watch(params, () => {
                     <template v-if="props.deletable">
                         <a-divider type="vertical"/>
                         <a-popconfirm
-                            :cancelText="$gettext('No')"
-                            :okText="$gettext('OK')"
-                            :title="$gettext('Are you sure you want to delete ?')"
-                            @confirm="destroy(record[rowKey])">
+                                :cancelText="$gettext('No')"
+                                :okText="$gettext('OK')"
+                                :title="$gettext('Are you sure you want to delete ?')"
+                                @confirm="destroy(record[rowKey])">
                             <a v-translate>Delete</a>
                         </a-popconfirm>
                     </template>
@@ -252,6 +366,10 @@ watch(params, () => {
     // min-height: 50px;
     height: 100%;
     display: flex;
-    align-items: flex-end;
+    align-items: flex-start;
+}
+
+:deep(.ant-form-inline .ant-form-item) {
+    margin-bottom: 10px;
 }
 </style>

+ 14 - 0
frontend/src/components/StdDataDisplay/StdTableTransformer.tsx

@@ -15,3 +15,17 @@ export const datetime = (args: customRender) => {
 export const date = (args: customRender) => {
     return dayjs(args.text).format('YYYY-MM-DD')
 }
+
+export const mask = (args: customRender, maskObj: any) => {
+    let v
+
+    if (typeof maskObj?.[args.text] === 'function') {
+        v = maskObj[args.text]()
+    } else if (typeof maskObj?.[args.text] === 'string') {
+        v = maskObj[args.text]
+    } else {
+        v = args.text
+    }
+
+    return <div>{v}</div>
+}

+ 51 - 0
frontend/src/components/StdDataEntry/compontents/StdPassword.vue

@@ -0,0 +1,51 @@
+<script setup lang="ts">
+import {computed, ref} from 'vue'
+
+const props = defineProps(['value', 'generate', 'placeholder'])
+const emit = defineEmits(['update:value'])
+
+const M_value = computed({
+    get() {
+        return props.value
+    },
+    set(v) {
+        emit('update:value', v)
+    }
+})
+const visibility = ref(false)
+
+function handle_generate() {
+    visibility.value = true
+    M_value.value = 'xxxx'
+
+    const chars = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+    const passwordLength = 12
+    let password = ''
+    for (let i = 0; i <= passwordLength; i++) {
+        const randomNumber = Math.floor(Math.random() * chars.length)
+        password += chars.substring(randomNumber, randomNumber + 1)
+    }
+
+    M_value.value = password
+
+}
+</script>
+
+<template>
+    <a-input-group compact>
+        <a-input-password
+                v-if="!visibility"
+                :class="{compact: generate}"
+                v-model:value="M_value" :placeholoder="placeholder"/>
+        <a-input v-else :class="{compact: generate}" v-model:value="M_value" :placeholoder="placeholder"/>
+        <a-button @click="handle_generate" v-if="generate" type="primary">
+            <translate>Generate</translate>
+        </a-button>
+    </a-input-group>
+</template>
+
+<style scoped>
+.compact {
+    width: calc(100% - 91px)
+}
+</style>

+ 45 - 0
frontend/src/components/StdDataEntry/compontents/StdSelect.vue

@@ -0,0 +1,45 @@
+<script setup lang="ts">
+import {computed, ref} from 'vue'
+import {SelectProps} from 'ant-design-vue'
+
+const props = defineProps(['value', 'mask'])
+const emit = defineEmits(['update:value'])
+
+const options = computed(() => {
+    const _options = ref<SelectProps['options']>([])
+
+    for (const [key, value] of Object.entries(props.mask)) {
+        const v = value as any
+        _options.value!.push({label: v?.(), value: key})
+    }
+
+    return _options
+})
+
+const _value = computed({
+    get() {
+        let v
+
+        if (typeof props.mask?.[props.value] === 'function') {
+            v = props.mask[props.value]()
+        } else if (typeof props.mask?.[props.value] === 'string') {
+            v = props.mask[props.value]
+        } else {
+            v = props.value
+        }
+        return v
+    },
+    set(v) {
+        emit('update:value', v)
+    }
+})
+</script>
+
+<template>
+    <a-select v-model:value="_value"
+              :options="options.value" style="min-width: 180px"/>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 135 - 0
frontend/src/components/StdDataEntry/compontents/StdSelector.vue

@@ -0,0 +1,135 @@
+<script setup lang="ts">
+import {onMounted, reactive, ref, watch} from 'vue'
+import StdTable from '@/components/StdDataDisplay/StdTable.vue'
+
+const props = defineProps(['selectedKey', 'value', 'recordValueIndex',
+    'selectionType', 'api', 'columns', 'data_key',
+    'disable_search', 'get_params', 'description'])
+const emit = defineEmits(['update:selectedKey', 'changeSelect'])
+const visible = ref(false)
+const M_value = ref('')
+
+onMounted(() => {
+    init()
+})
+
+const selected = ref([])
+
+const record: any = reactive({})
+
+function init() {
+    if (props.selectedKey && !props.value && props.selectionType === 'radio') {
+        props.api.get(props.selectedKey).then((r: any) => {
+            Object.assign(record, r)
+            M_value.value = r[props.recordValueIndex]
+        })
+    }
+}
+
+function show() {
+    visible.value = true
+}
+
+function onSelect(_selected: any) {
+    selected.value = _selected
+}
+
+function onSelectedRecord(r: any) {
+    Object.assign(record, r)
+}
+
+function ok() {
+    visible.value = false
+    if (props.selectionType == 'radio') {
+        emit('update:selectedKey', selected.value[0])
+    } else {
+        emit('update:selectedKey', selected.value)
+    }
+    M_value.value = record[props.recordValueIndex]
+    emit('changeSelect', record)
+}
+
+watch(props, () => {
+    if (!props?.selectedKey) {
+        M_value.value = ''
+    } else if (props.value) {
+        M_value.value = props.value
+    } else {
+        init()
+    }
+})
+</script>
+
+<template>
+    <div class="std-selector-container">
+        <div class="std-selector" @click="show()">
+            <a-input v-model="selectedKey" disabled hidden/>
+            <div class="value">
+                {{ M_value }}
+            </div>
+            <a-modal
+                    :mask="false"
+                    :visible="visible"
+                    cancel-text="取消"
+                    ok-text="选择"
+                    title="选择器"
+                    @cancel="visible=false"
+                    @ok="ok()"
+                    :width="800"
+                    destroyOnClose
+            >
+                {{ description }}
+                <std-table
+                        :api="api"
+                        :columns="columns"
+                        :data_key="data_key"
+                        :disable_search="disable_search"
+                        :pithy="true"
+                        :get_params="get_params"
+                        :selectionType="selectionType"
+                        :disable_query_params="true"
+                        @onSelected="onSelect"
+                        @onSelectedRecord="onSelectedRecord"
+                />
+            </a-modal>
+        </div>
+    </div>
+</template>
+
+<style lang="less" scoped>
+.std-selector-container {
+    height: 39.9px;
+    display: flex;
+    align-items: center;
+
+    .std-selector {
+        box-sizing: border-box;
+        font-variant: tabular-nums;
+        list-style: none;
+        font-feature-settings: 'tnum';
+        height: 32px;
+        padding: 4px 11px;
+        color: rgba(0, 0, 0, 0.85);
+        font-size: 14px;
+        line-height: 1.5;
+        background-color: #fff;
+        background-image: none;
+        border: 1px solid #d9d9d9;
+        border-radius: 4px;
+        transition: all 0.3s;
+        margin: 0 10px 0 0;
+        cursor: pointer;
+        min-width: 180px;
+
+        @media (prefers-color-scheme: dark) {
+            background-color: #1e1f20;
+            border: 1px solid #666666;
+            color: rgba(255, 255, 255, 0.99);
+        }
+
+        .value {
+
+        }
+    }
+}
+</style>

+ 0 - 40
frontend/src/components/StdDataEntry/index.ts

@@ -1,40 +0,0 @@
-import StdDataEntry from './StdDataEntry.js'
-import {h} from 'vue'
-import {Input} from 'ant-design-vue'
-
-interface IEdit {
-    type: Function
-    placeholder: any
-}
-
-function readonly(edit: IEdit, dataSource: any, dataIndex: any) {
-    return h('p', dataSource[dataIndex])
-}
-
-function input(edit: IEdit, dataSource: any, dataIndex: any) {
-    return h(Input, {
-        placeholder: edit.placeholder?.() ?? '',
-        value: dataSource?.[dataIndex],
-        'onUpdate:value': value => {
-            dataSource[dataIndex] = value
-            // Object.assign(dataSource, {[dataIndex]: value})
-        }
-    })
-}
-
-function textarea(edit: IEdit, dataSource: any, dataIndex: any) {
-    return h('a-textarea')
-}
-
-function select(edit: IEdit, dataSource: any, dataIndex: any) {
-    return h('a-select')
-}
-
-export {
-    readonly,
-    input,
-    textarea,
-    select
-}
-
-export default StdDataEntry

+ 88 - 0
frontend/src/components/StdDataEntry/index.tsx

@@ -0,0 +1,88 @@
+import StdDataEntry from './StdDataEntry.js'
+import {h} from 'vue'
+import {Input, Textarea, InputPassword} from 'ant-design-vue'
+import StdSelector from './compontents/StdSelector.vue'
+import StdSelect from './compontents/StdSelect.vue'
+import StdPassword from './compontents/StdPassword.vue'
+
+interface IEdit {
+    type: Function
+    placeholder: any
+    mask: any
+    key: any
+    value: any
+    recordValueIndex: any
+    selectionType: any
+    api: Object,
+    columns: any,
+    data_key: any,
+    disable_search: boolean,
+    get_params: Object,
+    description: string
+    generate: boolean
+}
+
+function readonly(edit: IEdit, dataSource: any, dataIndex: any) {
+    return h('p', dataSource[dataIndex])
+}
+
+function input(edit: IEdit, dataSource: any, dataIndex: any) {
+    return h(Input, {
+        placeholder: edit.placeholder?.() ?? '',
+        value: dataSource?.[dataIndex],
+        'onUpdate:value': value => {
+            dataSource[dataIndex] = value
+        }
+    })
+}
+
+function textarea(edit: IEdit, dataSource: any, dataIndex: any) {
+    return h(Textarea, {
+        placeholder: edit.placeholder?.() ?? '',
+        value: dataSource?.[dataIndex],
+        'onUpdate:value': value => {
+            dataSource[dataIndex] = value
+        }
+    })
+}
+
+function password(edit: IEdit, dataSource: any, dataIndex: any) {
+    return <StdPassword
+        v-model:value={dataSource[dataIndex]}
+        generate={edit.generate}
+        placeholder={edit.placeholder}
+    />
+}
+
+function select(edit: IEdit, dataSource: any, dataIndex: any) {
+    return <StdSelect
+        v-model:value={dataSource[dataIndex]}
+        mask={edit.mask}
+    />
+}
+
+function selector(edit: IEdit, dataSource: any, dataIndex: any) {
+    return <StdSelector
+        v-model:selectedKey={dataSource[dataIndex]}
+        value={edit.value}
+        recordValueIndex={edit.recordValueIndex}
+        selectionType={edit.selectionType}
+        api={edit.api}
+        columns={edit.columns}
+        data_key={edit.data_key}
+        disable_search={edit.disable_search}
+        get_params={edit.get_params}
+        description={edit.description}
+    />
+}
+
+export {
+    readonly,
+    input,
+    textarea,
+    select,
+    selector,
+    password
+}
+
+export default StdDataEntry

+ 5 - 5
frontend/src/components/StdDataEntry/style.less

@@ -1,7 +1,7 @@
 .std-data-entry-action {
-    @media (max-width: 375px) {
-        display: block;
-        width: 100%;
-        margin: 10px 0;
-    }
+  @media (max-width: 375px) {
+    display: block;
+    width: 100%;
+    margin: 10px 0;
+  }
 }

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

@@ -13,17 +13,17 @@ msgstr ""
 msgid "About"
 msgstr "About"
 
-#: src/routes/index.ts:99
+#: src/routes/index.ts:99 src/views/domain/ngx_conf/LogEntry.vue:64
 msgid "Access Logs"
 msgstr ""
 
 #: src/views/config/Config.vue:24 src/views/domain/DomainList.vue:42
-#: src/views/user/User.vue:42
+#: src/views/user/User.vue:43
 msgid "Action"
 msgstr "Action"
 
-#: src/components/StdDataDisplay/StdCurd.vue:123
-#: src/components/StdDataDisplay/StdCurd.vue:24
+#: src/components/StdDataDisplay/StdCurd.vue:130
+#: src/components/StdDataDisplay/StdCurd.vue:26
 msgid "Add"
 msgstr ""
 
@@ -46,11 +46,7 @@ msgstr "Add Site"
 msgid "Advance Mode"
 msgstr "Advance Mode"
 
-#: src/views/nginx_log/NginxLog.vue:100
-msgid "All logs"
-msgstr ""
-
-#: src/components/StdDataDisplay/StdTable.vue:41
+#: src/components/StdDataDisplay/StdTable.vue:43
 #: src/views/domain/DomainList.vue:27
 #, fuzzy
 msgid "Are you sure you want to delete ?"
@@ -77,7 +73,7 @@ msgstr "Auto-renewal disabled for %{name}"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "Auto-renewal enabled for %{name}"
 
-#: src/views/domain/DomainEdit.vue:178 src/views/nginx_log/NginxLog.vue:115
+#: src/views/domain/DomainEdit.vue:178 src/views/nginx_log/NginxLog.vue:162
 msgid "Back"
 msgstr "Back"
 
@@ -98,7 +94,7 @@ msgstr "Basic Mode"
 msgid "Build with"
 msgstr "Build with"
 
-#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdCurd.vue:28
 #: src/views/config/ConfigEdit.vue:49
 msgid "Cancel"
 msgstr "Cancel"
@@ -118,7 +114,7 @@ msgstr "Certificate Status"
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:29
 #: src/views/domain/ngx_conf/LocationEditor.vue:21
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:161
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:175
 msgid "Comments"
 msgstr "Comments"
 
@@ -151,7 +147,7 @@ msgstr "CPU:"
 msgid "Create Another"
 msgstr "Create Another"
 
-#: src/views/user/User.vue:30
+#: src/views/user/User.vue:31
 msgid "Created at"
 msgstr "Created at"
 
@@ -167,12 +163,12 @@ msgstr "Dashboard"
 msgid "Database (Optional, default: database)"
 msgstr "Database (Optional, default: database)"
 
-#: src/components/StdDataDisplay/StdTable.vue:218
+#: src/components/StdDataDisplay/StdTable.vue:332
 #: src/views/domain/DomainList.vue:111
 msgid "Delete"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:92
+#: src/components/StdDataDisplay/StdTable.vue:105
 msgid "Delete ID: %{id}"
 msgstr ""
 
@@ -256,7 +252,7 @@ msgstr "Enabled successfully"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Encrypt website with Let's Encrypt"
 
-#: src/routes/index.ts:103
+#: src/routes/index.ts:103 src/views/domain/ngx_conf/LogEntry.vue:68
 msgid "Error Logs"
 msgstr ""
 
@@ -264,6 +260,10 @@ msgstr ""
 msgid "Expiration Date: %{date}"
 msgstr "Expiration Date: %{date}"
 
+#: src/components/StdDataDisplay/StdTable.vue:299
+msgid "Export"
+msgstr ""
+
 #: src/views/domain/DomainEdit.vue:115 src/views/domain/DomainList.vue:68
 msgid "Failed to disable %{msg}"
 msgstr "Failed to disable %{msg}"
@@ -276,10 +276,6 @@ msgstr "Failed to enable %{msg}"
 msgid "Failed to get certificate information"
 msgstr ""
 
-#: src/views/nginx_log/NginxLog.vue:7
-msgid "Fetch"
-msgstr ""
-
 #: src/views/other/Error.vue:3 src/views/other/Error.vue:4
 msgid "File Not Found"
 msgstr "File Not Found"
@@ -288,6 +284,10 @@ msgstr "File Not Found"
 msgid "Finished"
 msgstr "Finished"
 
+#: src/components/StdDataEntry/compontents/StdPassword.vue:42
+msgid "Generate"
+msgstr ""
+
 #: src/language/constants.ts:11
 msgid "Generating private key for registering account"
 msgstr ""
@@ -374,12 +374,12 @@ msgstr "Memory"
 msgid "Memory and Storage"
 msgstr "Memory and Storage"
 
-#: src/components/StdDataDisplay/StdCurd.vue:24
-#: src/components/StdDataDisplay/StdTable.vue:16
-#: src/components/StdDataDisplay/StdTable.vue:17
-#: src/components/StdDataDisplay/StdTable.vue:21
-#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdTable.vue:18
+#: src/components/StdDataDisplay/StdTable.vue:19
+#: src/components/StdDataDisplay/StdTable.vue:24
 #: src/components/StdDataDisplay/StdTable.vue:33
+#: src/components/StdDataDisplay/StdTable.vue:35
 #, fuzzy
 msgid "Modify"
 msgstr "Modify Config"
@@ -408,10 +408,6 @@ msgstr "Network Total Receive"
 msgid "Network Total Send"
 msgstr "Network Total Send"
 
-#: src/views/nginx_log/NginxLog.vue:103
-msgid "New logs"
-msgstr ""
-
 #: src/views/domain/DomainAdd.vue:137
 msgid "Next"
 msgstr "Next"
@@ -420,7 +416,7 @@ msgstr "Next"
 msgid "Nginx Log"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:39
+#: src/components/StdDataDisplay/StdTable.vue:41
 #: src/views/domain/DomainList.vue:25
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:17
 #: src/views/domain/ngx_conf/LocationEditor.vue:11
@@ -447,8 +443,8 @@ msgstr ""
 msgid "Obtaining certificate"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdCurd.vue:27
-#: src/components/StdDataDisplay/StdTable.vue:40
+#: src/components/StdDataDisplay/StdCurd.vue:29
+#: src/components/StdDataDisplay/StdTable.vue:42
 #: src/views/domain/DomainList.vue:26
 msgid "OK"
 msgstr ""
@@ -519,7 +515,7 @@ msgstr ""
 msgid "Reloading nginx"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:187
+#: src/components/StdDataDisplay/StdTable.vue:302
 msgid "Reset"
 msgstr ""
 
@@ -537,7 +533,7 @@ msgstr "Save Directive"
 msgid "Save error %{msg}"
 msgstr "Save error %{msg}"
 
-#: src/components/StdDataDisplay/StdCurd.vue:91
+#: src/components/StdDataDisplay/StdCurd.vue:98
 #, fuzzy
 msgid "Save Successfully"
 msgstr "Saved successfully"
@@ -551,7 +547,7 @@ msgstr "Saved successfully"
 msgid "Send"
 msgstr "Send"
 
-#: src/components/StdDataDisplay/StdTable.vue:112
+#: src/components/StdDataDisplay/StdTable.vue:125
 #: src/views/config/ConfigEdit.vue:22 src/views/domain/DomainEdit.vue:56
 #: src/views/domain/DomainEdit.vue:68 src/views/domain/DomainEdit.vue:77
 #: src/views/domain/DomainEdit.vue:94 src/views/domain/DomainList.vue:78
@@ -631,7 +627,7 @@ msgid "The username or password is incorrect"
 msgstr ""
 
 #: src/views/config/Config.vue:17 src/views/domain/DomainList.vue:36
-#: src/views/user/User.vue:36
+#: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "Updated at"
 

+ 32 - 34
frontend/src/language/messages.pot

@@ -7,17 +7,18 @@ msgid "About"
 msgstr ""
 
 #: src/routes/index.ts:99
+#: src/views/domain/ngx_conf/LogEntry.vue:64
 msgid "Access Logs"
 msgstr ""
 
 #: src/views/config/Config.vue:24
 #: src/views/domain/DomainList.vue:42
-#: src/views/user/User.vue:42
+#: src/views/user/User.vue:43
 msgid "Action"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdCurd.vue:123
-#: src/components/StdDataDisplay/StdCurd.vue:24
+#: src/components/StdDataDisplay/StdCurd.vue:130
+#: src/components/StdDataDisplay/StdCurd.vue:26
 msgid "Add"
 msgstr ""
 
@@ -41,11 +42,7 @@ msgstr ""
 msgid "Advance Mode"
 msgstr ""
 
-#: src/views/nginx_log/NginxLog.vue:100
-msgid "All logs"
-msgstr ""
-
-#: src/components/StdDataDisplay/StdTable.vue:41
+#: src/components/StdDataDisplay/StdTable.vue:43
 #: src/views/domain/DomainList.vue:27
 msgid "Are you sure you want to delete ?"
 msgstr ""
@@ -71,7 +68,7 @@ msgid "Auto-renewal enabled for %{name}"
 msgstr ""
 
 #: src/views/domain/DomainEdit.vue:178
-#: src/views/nginx_log/NginxLog.vue:115
+#: src/views/nginx_log/NginxLog.vue:162
 msgid "Back"
 msgstr ""
 
@@ -91,7 +88,7 @@ msgstr ""
 msgid "Build with"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdCurd.vue:28
 #: src/views/config/ConfigEdit.vue:49
 msgid "Cancel"
 msgstr ""
@@ -111,7 +108,7 @@ msgstr ""
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:29
 #: src/views/domain/ngx_conf/LocationEditor.vue:21
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:161
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:175
 msgid "Comments"
 msgstr ""
 
@@ -144,7 +141,7 @@ msgstr ""
 msgid "Create Another"
 msgstr ""
 
-#: src/views/user/User.vue:30
+#: src/views/user/User.vue:31
 msgid "Created at"
 msgstr ""
 
@@ -160,12 +157,12 @@ msgstr ""
 msgid "Database (Optional, default: database)"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:218
+#: src/components/StdDataDisplay/StdTable.vue:332
 #: src/views/domain/DomainList.vue:111
 msgid "Delete"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:92
+#: src/components/StdDataDisplay/StdTable.vue:105
 msgid "Delete ID: %{id}"
 msgstr ""
 
@@ -257,6 +254,7 @@ msgid "Encrypt website with Let's Encrypt"
 msgstr ""
 
 #: src/routes/index.ts:103
+#: src/views/domain/ngx_conf/LogEntry.vue:68
 msgid "Error Logs"
 msgstr ""
 
@@ -264,6 +262,10 @@ msgstr ""
 msgid "Expiration Date: %{date}"
 msgstr ""
 
+#: src/components/StdDataDisplay/StdTable.vue:299
+msgid "Export"
+msgstr ""
+
 #: src/views/domain/DomainEdit.vue:115
 #: src/views/domain/DomainList.vue:68
 msgid "Failed to disable %{msg}"
@@ -278,10 +280,6 @@ msgstr ""
 msgid "Failed to get certificate information"
 msgstr ""
 
-#: src/views/nginx_log/NginxLog.vue:7
-msgid "Fetch"
-msgstr ""
-
 #: src/views/other/Error.vue:3
 #: src/views/other/Error.vue:4
 msgid "File Not Found"
@@ -292,6 +290,10 @@ msgstr ""
 msgid "Finished"
 msgstr ""
 
+#: src/components/StdDataEntry/compontents/StdPassword.vue:42
+msgid "Generate"
+msgstr ""
+
 #: src/language/constants.ts:11
 msgid "Generating private key for registering account"
 msgstr ""
@@ -376,12 +378,12 @@ msgstr ""
 msgid "Memory and Storage"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdCurd.vue:24
-#: src/components/StdDataDisplay/StdTable.vue:16
-#: src/components/StdDataDisplay/StdTable.vue:17
-#: src/components/StdDataDisplay/StdTable.vue:21
-#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdTable.vue:18
+#: src/components/StdDataDisplay/StdTable.vue:19
+#: src/components/StdDataDisplay/StdTable.vue:24
 #: src/components/StdDataDisplay/StdTable.vue:33
+#: src/components/StdDataDisplay/StdTable.vue:35
 msgid "Modify"
 msgstr ""
 
@@ -410,10 +412,6 @@ msgstr ""
 msgid "Network Total Send"
 msgstr ""
 
-#: src/views/nginx_log/NginxLog.vue:103
-msgid "New logs"
-msgstr ""
-
 #: src/views/domain/DomainAdd.vue:137
 msgid "Next"
 msgstr ""
@@ -423,7 +421,7 @@ msgstr ""
 msgid "Nginx Log"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:39
+#: src/components/StdDataDisplay/StdTable.vue:41
 #: src/views/domain/DomainList.vue:25
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:17
 #: src/views/domain/ngx_conf/LocationEditor.vue:11
@@ -448,8 +446,8 @@ msgstr ""
 msgid "Obtaining certificate"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdCurd.vue:27
-#: src/components/StdDataDisplay/StdTable.vue:40
+#: src/components/StdDataDisplay/StdCurd.vue:29
+#: src/components/StdDataDisplay/StdTable.vue:42
 #: src/views/domain/DomainList.vue:26
 msgid "OK"
 msgstr ""
@@ -523,7 +521,7 @@ msgstr ""
 msgid "Reloading nginx"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:187
+#: src/components/StdDataDisplay/StdTable.vue:302
 msgid "Reset"
 msgstr ""
 
@@ -543,7 +541,7 @@ msgstr ""
 msgid "Save error %{msg}"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdCurd.vue:91
+#: src/components/StdDataDisplay/StdCurd.vue:98
 msgid "Save Successfully"
 msgstr ""
 
@@ -558,7 +556,7 @@ msgstr ""
 msgid "Send"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:112
+#: src/components/StdDataDisplay/StdTable.vue:125
 #: src/views/config/ConfigEdit.vue:22
 #: src/views/domain/DomainEdit.vue:56
 #: src/views/domain/DomainEdit.vue:68
@@ -638,7 +636,7 @@ msgstr ""
 
 #: src/views/config/Config.vue:17
 #: src/views/domain/DomainList.vue:36
-#: src/views/user/User.vue:36
+#: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr ""
 

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


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


+ 41 - 36
frontend/src/language/zh_CN/app.po

@@ -16,17 +16,17 @@ msgstr ""
 msgid "About"
 msgstr "关于"
 
-#: src/routes/index.ts:99
+#: src/routes/index.ts:99 src/views/domain/ngx_conf/LogEntry.vue:64
 msgid "Access Logs"
 msgstr "访问日志"
 
 #: src/views/config/Config.vue:24 src/views/domain/DomainList.vue:42
-#: src/views/user/User.vue:42
+#: src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
-#: src/components/StdDataDisplay/StdCurd.vue:123
-#: src/components/StdDataDisplay/StdCurd.vue:24
+#: src/components/StdDataDisplay/StdCurd.vue:130
+#: src/components/StdDataDisplay/StdCurd.vue:26
 msgid "Add"
 msgstr "添加"
 
@@ -49,11 +49,7 @@ msgstr "添加站点"
 msgid "Advance Mode"
 msgstr "高级模式"
 
-#: src/views/nginx_log/NginxLog.vue:100
-msgid "All logs"
-msgstr "所有日志"
-
-#: src/components/StdDataDisplay/StdTable.vue:41
+#: src/components/StdDataDisplay/StdTable.vue:43
 #: src/views/domain/DomainList.vue:27
 msgid "Are you sure you want to delete ?"
 msgstr "您确定要删除吗?"
@@ -78,7 +74,7 @@ msgstr "成功关闭 %{name} 自动续签"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "成功启用 %{name} 自动续签"
 
-#: src/views/domain/DomainEdit.vue:178 src/views/nginx_log/NginxLog.vue:115
+#: src/views/domain/DomainEdit.vue:178 src/views/nginx_log/NginxLog.vue:162
 msgid "Back"
 msgstr "返回"
 
@@ -98,7 +94,7 @@ msgstr "基本模式"
 msgid "Build with"
 msgstr "构建基于"
 
-#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdCurd.vue:28
 #: src/views/config/ConfigEdit.vue:49
 msgid "Cancel"
 msgstr "取消"
@@ -118,7 +114,7 @@ msgstr "证书状态"
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:29
 #: src/views/domain/ngx_conf/LocationEditor.vue:21
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:161
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:175
 msgid "Comments"
 msgstr "注释"
 
@@ -151,7 +147,7 @@ msgstr ""
 msgid "Create Another"
 msgstr "再创建一个"
 
-#: src/views/user/User.vue:30
+#: src/views/user/User.vue:31
 msgid "Created at"
 msgstr "创建时间"
 
@@ -167,12 +163,12 @@ msgstr "仪表盘"
 msgid "Database (Optional, default: database)"
 msgstr "数据库 (可选,默认: database)"
 
-#: src/components/StdDataDisplay/StdTable.vue:218
+#: src/components/StdDataDisplay/StdTable.vue:332
 #: src/views/domain/DomainList.vue:111
 msgid "Delete"
 msgstr "删除"
 
-#: src/components/StdDataDisplay/StdTable.vue:92
+#: src/components/StdDataDisplay/StdTable.vue:105
 msgid "Delete ID: %{id}"
 msgstr "删除 ID: %{id}"
 
@@ -256,7 +252,7 @@ msgstr "启用成功"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 Let's Encrypt 对网站进行加密"
 
-#: src/routes/index.ts:103
+#: src/routes/index.ts:103 src/views/domain/ngx_conf/LogEntry.vue:68
 msgid "Error Logs"
 msgstr "错误日志"
 
@@ -264,6 +260,10 @@ msgstr "错误日志"
 msgid "Expiration Date: %{date}"
 msgstr "过期时间: %{date}"
 
+#: src/components/StdDataDisplay/StdTable.vue:299
+msgid "Export"
+msgstr ""
+
 #: src/views/domain/DomainEdit.vue:115 src/views/domain/DomainList.vue:68
 msgid "Failed to disable %{msg}"
 msgstr "禁用失败 %{msg}"
@@ -276,10 +276,6 @@ msgstr "启用失败 %{msg}"
 msgid "Failed to get certificate information"
 msgstr "获取证书信息失败"
 
-#: src/views/nginx_log/NginxLog.vue:7
-msgid "Fetch"
-msgstr "日志范围"
-
 #: src/views/other/Error.vue:3 src/views/other/Error.vue:4
 msgid "File Not Found"
 msgstr "未找到文件"
@@ -288,6 +284,10 @@ msgstr "未找到文件"
 msgid "Finished"
 msgstr "完成"
 
+#: src/components/StdDataEntry/compontents/StdPassword.vue:42
+msgid "Generate"
+msgstr "生成"
+
 #: src/language/constants.ts:11
 msgid "Generating private key for registering account"
 msgstr "正在生成私钥用于注册账户"
@@ -372,12 +372,12 @@ msgstr "内存"
 msgid "Memory and Storage"
 msgstr "内存与存储"
 
-#: src/components/StdDataDisplay/StdCurd.vue:24
-#: src/components/StdDataDisplay/StdTable.vue:16
-#: src/components/StdDataDisplay/StdTable.vue:17
-#: src/components/StdDataDisplay/StdTable.vue:21
-#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdTable.vue:18
+#: src/components/StdDataDisplay/StdTable.vue:19
+#: src/components/StdDataDisplay/StdTable.vue:24
 #: src/components/StdDataDisplay/StdTable.vue:33
+#: src/components/StdDataDisplay/StdTable.vue:35
 msgid "Modify"
 msgstr "修改"
 
@@ -405,10 +405,6 @@ msgstr "下载流量"
 msgid "Network Total Send"
 msgstr "上传流量"
 
-#: src/views/nginx_log/NginxLog.vue:103
-msgid "New logs"
-msgstr "新增日志"
-
 #: src/views/domain/DomainAdd.vue:137
 msgid "Next"
 msgstr "下一步"
@@ -417,7 +413,7 @@ msgstr "下一步"
 msgid "Nginx Log"
 msgstr "Nginx 日志"
 
-#: src/components/StdDataDisplay/StdTable.vue:39
+#: src/components/StdDataDisplay/StdTable.vue:41
 #: src/views/domain/DomainList.vue:25
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:17
 #: src/views/domain/ngx_conf/LocationEditor.vue:11
@@ -442,8 +438,8 @@ msgstr "注意:当前配置中的 server_name 必须为需要申请证书的
 msgid "Obtaining certificate"
 msgstr "正在获取证书"
 
-#: src/components/StdDataDisplay/StdCurd.vue:27
-#: src/components/StdDataDisplay/StdTable.vue:40
+#: src/components/StdDataDisplay/StdCurd.vue:29
+#: src/components/StdDataDisplay/StdTable.vue:42
 #: src/views/domain/DomainList.vue:26
 msgid "OK"
 msgstr "确定"
@@ -513,7 +509,7 @@ msgstr "正在注册用户"
 msgid "Reloading nginx"
 msgstr "正在重载 Nginx"
 
-#: src/components/StdDataDisplay/StdTable.vue:187
+#: src/components/StdDataDisplay/StdTable.vue:302
 msgid "Reset"
 msgstr "重置"
 
@@ -531,7 +527,7 @@ msgstr "保存指令"
 msgid "Save error %{msg}"
 msgstr "保存错误 %{msg}"
 
-#: src/components/StdDataDisplay/StdCurd.vue:91
+#: src/components/StdDataDisplay/StdCurd.vue:98
 msgid "Save Successfully"
 msgstr "保存成功"
 
@@ -544,7 +540,7 @@ msgstr "保存成功"
 msgid "Send"
 msgstr "上传"
 
-#: src/components/StdDataDisplay/StdTable.vue:112
+#: src/components/StdDataDisplay/StdTable.vue:125
 #: src/views/config/ConfigEdit.vue:22 src/views/domain/DomainEdit.vue:56
 #: src/views/domain/DomainEdit.vue:68 src/views/domain/DomainEdit.vue:77
 #: src/views/domain/DomainEdit.vue:94 src/views/domain/DomainList.vue:78
@@ -621,7 +617,7 @@ msgid "The username or password is incorrect"
 msgstr "用户名或密码错误"
 
 #: src/views/config/Config.vue:17 src/views/domain/DomainList.vue:36
-#: src/views/user/User.vue:36
+#: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "修改时间"
 
@@ -668,6 +664,15 @@ msgctxt "Project"
 msgid "License"
 msgstr "开源许可"
 
+#~ msgid "All logs"
+#~ msgstr "所有日志"
+
+#~ msgid "Fetch"
+#~ msgstr "日志范围"
+
+#~ msgid "New logs"
+#~ msgstr "新增日志"
+
 #~ msgid "404 Not Found"
 #~ msgstr "404 未找到页面"
 

+ 32 - 36
frontend/src/language/zh_TW/app.po

@@ -17,17 +17,17 @@ msgstr ""
 msgid "About"
 msgstr "關於"
 
-#: src/routes/index.ts:99
+#: src/routes/index.ts:99 src/views/domain/ngx_conf/LogEntry.vue:64
 msgid "Access Logs"
 msgstr ""
 
 #: src/views/config/Config.vue:24 src/views/domain/DomainList.vue:42
-#: src/views/user/User.vue:42
+#: src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
-#: src/components/StdDataDisplay/StdCurd.vue:123
-#: src/components/StdDataDisplay/StdCurd.vue:24
+#: src/components/StdDataDisplay/StdCurd.vue:130
+#: src/components/StdDataDisplay/StdCurd.vue:26
 msgid "Add"
 msgstr ""
 
@@ -50,11 +50,7 @@ msgstr "新增站點"
 msgid "Advance Mode"
 msgstr "高階模式"
 
-#: src/views/nginx_log/NginxLog.vue:100
-msgid "All logs"
-msgstr ""
-
-#: src/components/StdDataDisplay/StdTable.vue:41
+#: src/components/StdDataDisplay/StdTable.vue:43
 #: src/views/domain/DomainList.vue:27
 #, fuzzy
 msgid "Are you sure you want to delete ?"
@@ -81,7 +77,7 @@ msgstr "已關閉 %{name} 自動續簽"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "已啟用 %{name} 自動續簽"
 
-#: src/views/domain/DomainEdit.vue:178 src/views/nginx_log/NginxLog.vue:115
+#: src/views/domain/DomainEdit.vue:178 src/views/nginx_log/NginxLog.vue:162
 msgid "Back"
 msgstr "返回"
 
@@ -102,7 +98,7 @@ msgstr "基本模式"
 msgid "Build with"
 msgstr "構建基於"
 
-#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdCurd.vue:28
 #: src/views/config/ConfigEdit.vue:49
 msgid "Cancel"
 msgstr "取消"
@@ -122,7 +118,7 @@ msgstr "憑證狀態"
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:29
 #: src/views/domain/ngx_conf/LocationEditor.vue:21
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:161
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:175
 msgid "Comments"
 msgstr "註釋"
 
@@ -155,7 +151,7 @@ msgstr "中央處理器:"
 msgid "Create Another"
 msgstr "再創建一個"
 
-#: src/views/user/User.vue:30
+#: src/views/user/User.vue:31
 msgid "Created at"
 msgstr "建立時間"
 
@@ -171,12 +167,12 @@ msgstr "儀表盤"
 msgid "Database (Optional, default: database)"
 msgstr "資料庫 (可選,預設: database)"
 
-#: src/components/StdDataDisplay/StdTable.vue:218
+#: src/components/StdDataDisplay/StdTable.vue:332
 #: src/views/domain/DomainList.vue:111
 msgid "Delete"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:92
+#: src/components/StdDataDisplay/StdTable.vue:105
 msgid "Delete ID: %{id}"
 msgstr "刪除 ID: %{id}"
 
@@ -261,7 +257,7 @@ msgstr "啟用成功"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 Let's Encrypt 對網站進行加密"
 
-#: src/routes/index.ts:103
+#: src/routes/index.ts:103 src/views/domain/ngx_conf/LogEntry.vue:68
 msgid "Error Logs"
 msgstr ""
 
@@ -269,6 +265,10 @@ msgstr ""
 msgid "Expiration Date: %{date}"
 msgstr "過期時間: %{date}"
 
+#: src/components/StdDataDisplay/StdTable.vue:299
+msgid "Export"
+msgstr ""
+
 #: src/views/domain/DomainEdit.vue:115 src/views/domain/DomainList.vue:68
 msgid "Failed to disable %{msg}"
 msgstr "禁用失敗 %{msg}"
@@ -281,10 +281,6 @@ msgstr "啟用失敗 %{msg}"
 msgid "Failed to get certificate information"
 msgstr ""
 
-#: src/views/nginx_log/NginxLog.vue:7
-msgid "Fetch"
-msgstr ""
-
 #: src/views/other/Error.vue:3 src/views/other/Error.vue:4
 msgid "File Not Found"
 msgstr "未找到檔案"
@@ -293,6 +289,10 @@ msgstr "未找到檔案"
 msgid "Finished"
 msgstr "完成"
 
+#: src/components/StdDataEntry/compontents/StdPassword.vue:42
+msgid "Generate"
+msgstr ""
+
 #: src/language/constants.ts:11
 msgid "Generating private key for registering account"
 msgstr ""
@@ -380,12 +380,12 @@ msgstr "記憶體"
 msgid "Memory and Storage"
 msgstr "記憶體和存儲"
 
-#: src/components/StdDataDisplay/StdCurd.vue:24
-#: src/components/StdDataDisplay/StdTable.vue:16
-#: src/components/StdDataDisplay/StdTable.vue:17
-#: src/components/StdDataDisplay/StdTable.vue:21
-#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdCurd.vue:26
+#: src/components/StdDataDisplay/StdTable.vue:18
+#: src/components/StdDataDisplay/StdTable.vue:19
+#: src/components/StdDataDisplay/StdTable.vue:24
 #: src/components/StdDataDisplay/StdTable.vue:33
+#: src/components/StdDataDisplay/StdTable.vue:35
 #, fuzzy
 msgid "Modify"
 msgstr "修改配置"
@@ -414,10 +414,6 @@ msgstr "下載流量"
 msgid "Network Total Send"
 msgstr "上傳流量"
 
-#: src/views/nginx_log/NginxLog.vue:103
-msgid "New logs"
-msgstr ""
-
 #: src/views/domain/DomainAdd.vue:137
 msgid "Next"
 msgstr "下一步"
@@ -426,7 +422,7 @@ msgstr "下一步"
 msgid "Nginx Log"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:39
+#: src/components/StdDataDisplay/StdTable.vue:41
 #: src/views/domain/DomainList.vue:25
 #: src/views/domain/ngx_conf/directive/DirectiveEditor.vue:17
 #: src/views/domain/ngx_conf/LocationEditor.vue:11
@@ -452,8 +448,8 @@ msgstr "注意:當前配置中的 server_name 必須為需要申請憑證的
 msgid "Obtaining certificate"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdCurd.vue:27
-#: src/components/StdDataDisplay/StdTable.vue:40
+#: src/components/StdDataDisplay/StdCurd.vue:29
+#: src/components/StdDataDisplay/StdTable.vue:42
 #: src/views/domain/DomainList.vue:26
 msgid "OK"
 msgstr "確定"
@@ -524,7 +520,7 @@ msgstr ""
 msgid "Reloading nginx"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:187
+#: src/components/StdDataDisplay/StdTable.vue:302
 msgid "Reset"
 msgstr ""
 
@@ -542,7 +538,7 @@ msgstr "儲存指令"
 msgid "Save error %{msg}"
 msgstr "儲存錯誤 %{msg}"
 
-#: src/components/StdDataDisplay/StdCurd.vue:91
+#: src/components/StdDataDisplay/StdCurd.vue:98
 #, fuzzy
 msgid "Save Successfully"
 msgstr "儲存成功"
@@ -556,7 +552,7 @@ msgstr "儲存成功"
 msgid "Send"
 msgstr "上傳"
 
-#: src/components/StdDataDisplay/StdTable.vue:112
+#: src/components/StdDataDisplay/StdTable.vue:125
 #: src/views/config/ConfigEdit.vue:22 src/views/domain/DomainEdit.vue:56
 #: src/views/domain/DomainEdit.vue:68 src/views/domain/DomainEdit.vue:77
 #: src/views/domain/DomainEdit.vue:94 src/views/domain/DomainList.vue:78
@@ -637,7 +633,7 @@ msgid "The username or password is incorrect"
 msgstr ""
 
 #: src/views/config/Config.vue:17 src/views/domain/DomainList.vue:36
-#: src/views/user/User.vue:36
+#: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "修改時間"
 

+ 24 - 1
frontend/src/lib/helper/index.ts

@@ -9,6 +9,29 @@ function bytesToSize(bytes: number) {
     return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
 }
 
+function downloadCsv(header: any, data: any[], fileName: string) {
+    if (!header || !Array.isArray(header) || !Array.isArray(data) || !header.length) {
+        return
+    }
+    let csvContent = 'data:text/csv;charset=utf-8,\ufeff'
+    const _header = header.map(h => h.title).join(',')
+    const keys = header.map(item => item.key)
+    csvContent += _header + '\n'
+    data.forEach((item, index) => {
+        let dataString = ''
+        for (let i = 0; i < keys.length; i++) {
+            dataString += item[keys[i]] + ','
+        }
+        csvContent += index < data.length ? dataString.replace(/,$/, '\n') : dataString.replace(/,$/, '')
+    })
+    const a = document.createElement('a')
+    a.href = encodeURI(csvContent)
+    a.download = fileName
+    a.click()
+    window.URL.revokeObjectURL(csvContent)
+}
+
 export {
-    bytesToSize
+    bytesToSize,
+    downloadCsv
 }

+ 4 - 3
frontend/src/views/user/User.vue

@@ -3,7 +3,7 @@ import StdCurd from '@/components/StdDataDisplay/StdCurd.vue'
 import gettext from '@/gettext'
 import user from '@/api/user'
 import {datetime} from '@/components/StdDataDisplay/StdTableTransformer'
-import {input} from '@/components/StdDataEntry'
+import {input, password} from '@/components/StdDataEntry'
 
 const {$gettext} = gettext
 
@@ -22,8 +22,9 @@ const columns = [{
     sorter: true,
     pithy: true,
     edit: {
-        type: input,
-        placeholder: () => $gettext('Leave blank for no change')
+        type: password,
+        placeholder: () => $gettext('Leave blank for no change'),
+        generate: true
     },
     display: false
 }, {

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