diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index 28a2e35d3f..3f8dd31e6e 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -15,8 +15,13 @@ jobs: runs-on: ubuntu-latest steps: + - name: Lock Pull Request + run: | + curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -d '{"state":"pending", "description":"Action running, merge disabled", "context":"Lock PR"}' \ + "https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }}" - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -59,3 +64,9 @@ jobs: git push env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Unlock Pull Request + run: | + curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -d '{"state":"success", "description":"Action running, merge disabled", "context":"Lock PR"}' \ + "https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }}" diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 33addda888..2017994103 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -31,7 +31,7 @@ jobs: tag: ${{ steps.get_version.outputs.TAG }} - uses: actions/setup-node@v2 with: - node-version: '16.20' + node-version: '20.15' - name: Install dependencies run: yarn install - name: Build web diff --git a/src/assets/img/icons/cisco.png b/src/assets/img/icons/cisco.png index 9ce6827e99..9162ae4fff 100644 Binary files a/src/assets/img/icons/cisco.png and b/src/assets/img/icons/cisco.png differ diff --git a/src/assets/img/icons/clickhouse.png b/src/assets/img/icons/clickhouse.png index cf4a0a5b67..2f4604218b 100644 Binary files a/src/assets/img/icons/clickhouse.png and b/src/assets/img/icons/clickhouse.png differ diff --git a/src/assets/img/icons/db2.png b/src/assets/img/icons/db2.png index c7c8bcda75..9c916f0b69 100644 Binary files a/src/assets/img/icons/db2.png and b/src/assets/img/icons/db2.png differ diff --git a/src/assets/img/icons/gateway.png b/src/assets/img/icons/gateway.png index 2ca2415270..e492fc3296 100644 Binary files a/src/assets/img/icons/gateway.png and b/src/assets/img/icons/gateway.png differ diff --git a/src/assets/img/icons/general.png b/src/assets/img/icons/general.png index f6dc155684..3760e80753 100644 Binary files a/src/assets/img/icons/general.png and b/src/assets/img/icons/general.png differ diff --git a/src/assets/img/icons/h3c.png b/src/assets/img/icons/h3c.png new file mode 100644 index 0000000000..a76041f811 Binary files /dev/null and b/src/assets/img/icons/h3c.png differ diff --git a/src/assets/img/icons/linux.png b/src/assets/img/icons/linux.png index 0967cbe1ba..1e21304038 100644 Binary files a/src/assets/img/icons/linux.png and b/src/assets/img/icons/linux.png differ diff --git a/src/assets/img/icons/mariadb.png b/src/assets/img/icons/mariadb.png index cca0b5941a..93dcdd007c 100644 Binary files a/src/assets/img/icons/mariadb.png and b/src/assets/img/icons/mariadb.png differ diff --git a/src/assets/img/icons/mysql.png b/src/assets/img/icons/mysql.png index 982c7edfc6..b50287503d 100644 Binary files a/src/assets/img/icons/mysql.png and b/src/assets/img/icons/mysql.png differ diff --git a/src/assets/img/icons/oracle.png b/src/assets/img/icons/oracle.png index 003041b33f..27c45e55ff 100644 Binary files a/src/assets/img/icons/oracle.png and b/src/assets/img/icons/oracle.png differ diff --git a/src/assets/img/icons/other.png b/src/assets/img/icons/other.png index 312678ad2c..85a4db8fbb 100644 Binary files a/src/assets/img/icons/other.png and b/src/assets/img/icons/other.png differ diff --git a/src/assets/img/icons/private.png b/src/assets/img/icons/private.png index ecb0cc1e7b..25a61f3458 100644 Binary files a/src/assets/img/icons/private.png and b/src/assets/img/icons/private.png differ diff --git a/src/assets/img/icons/unix.png b/src/assets/img/icons/unix.png index 30fbcb4c1a..1846f26e81 100644 Binary files a/src/assets/img/icons/unix.png and b/src/assets/img/icons/unix.png differ diff --git a/src/assets/img/icons/vmware.png b/src/assets/img/icons/vmware.png index 61da1658cd..50f0dbfbc1 100644 Binary files a/src/assets/img/icons/vmware.png and b/src/assets/img/icons/vmware.png differ diff --git a/src/assets/img/icons/windows.png b/src/assets/img/icons/windows.png index d34b295eaf..9f48418566 100644 Binary files a/src/assets/img/icons/windows.png and b/src/assets/img/icons/windows.png differ diff --git a/src/components/Apps/AssetSelect/index.vue b/src/components/Apps/AssetSelect/index.vue index 9a418f5f99..aaad143916 100644 --- a/src/components/Apps/AssetSelect/index.vue +++ b/src/components/Apps/AssetSelect/index.vue @@ -37,6 +37,10 @@ export default { type: String, default: '/api/v1/assets/assets/' }, + defaultPageSize: { + type: Number, + default: 10 + }, baseNodeUrl: { type: String, default: '/api/v1/assets/nodes/' @@ -70,6 +74,7 @@ export default { value: iValue, multiple: true, clearable: true, + defaultPageSize: this.defaultPageSize, ajax: { url: this.baseUrl, transformOption: (item) => { diff --git a/src/components/Form/AutoDataForm/utils.js b/src/components/Form/AutoDataForm/utils.js index 5976e87515..55deadb1b0 100644 --- a/src/components/Form/AutoDataForm/utils.js +++ b/src/components/Form/AutoDataForm/utils.js @@ -197,22 +197,32 @@ export class FormFieldGenerator { return field } - afterGenerateField(field) { - field.label = toSentenceCase(field.label) - - if (field.placeholder) { - field.el.placeholder = field.placeholder - } - + setChoicesTips(field, fieldMeta, fieldRemoteMeta) { // 设置 checkbox 的 tips - if (field.tips && ['checkbox-group', 'radio-group'].indexOf(field.type) !== -1) { + if (['checkbox-group', 'radio-group'].indexOf(field.type) !== -1) { field.options.map(option => { - if (!option.tip && field.tips[option.value]) { + if (!option.tip && field.tips) { option.tip = field.tips[option.value] } + if (!option.tip) { + const match = option.label.match(/^(.+?)\s*\((.*?)\)$/) + if (match) { + option.label = match[1] + option.tip = match[2] + } + } }) } + } + + afterGenerateField(field) { + field.label = toSentenceCase(field.label) + + if (field.placeholder) { + field.el.placeholder = field.placeholder + } + this.setChoicesTips(field) return field } diff --git a/src/components/Form/FormFields/InputWithUnit.vue b/src/components/Form/FormFields/InputWithUnit.vue index fc8c205cf9..bcd4568a2a 100644 --- a/src/components/Form/FormFields/InputWithUnit.vue +++ b/src/components/Form/FormFields/InputWithUnit.vue @@ -1,5 +1,5 @@ @@ -15,6 +15,7 @@ export default { }, data() { return { + defaultValue: 24, displayMapper: { 'second': this.$t('Second'), // 'sec' is the default value of 'unit 'min': this.$t('Minute'), // 'min' is the default value of 'unit @@ -29,6 +30,9 @@ export default { computed: { iUnit() { return this.displayMapper[this.unit] || this.unit + }, + iValue() { + return this.$attrs.value ? this.$attrs.value : this.defaultValue } } } diff --git a/src/components/Form/FormFields/Select2.vue b/src/components/Form/FormFields/Select2.vue index a4487a6dcc..f65a2315ae 100644 --- a/src/components/Form/FormFields/Select2.vue +++ b/src/components/Form/FormFields/Select2.vue @@ -125,16 +125,19 @@ export default { allowCreate: { type: Boolean, default: false + }, + defaultPageSize: { + type: Number, + default: 10 } }, data() { const vm = this - const defaultPageSize = 10 const defaultParams = { search: '', page: 1, hasMore: true, - pageSize: defaultPageSize + pageSize: vm.defaultPageSize } // 设置axios全局报错提示不显示 const validateStatus = (status) => { @@ -194,7 +197,6 @@ export default { } }, iAjax() { - const defaultPageSize = 10 const defaultMakeParams = (params) => { const page = params.page || 1 const offset = (page - 1) * params.pageSize @@ -237,7 +239,7 @@ export default { } const defaultAjax = { url: '', - pageSize: defaultPageSize, + pageSize: this.defaultPageSize, makeParams: defaultMakeParams, transformOption: defaultTransformOption, processResults: defaultProcessResults, diff --git a/src/components/Table/ListTable/TableAction/ImportTable.vue b/src/components/Table/ListTable/TableAction/ImportTable.vue index 1133330e3c..ea1e06ca63 100644 --- a/src/components/Table/ListTable/TableAction/ImportTable.vue +++ b/src/components/Table/ListTable/TableAction/ImportTable.vue @@ -44,8 +44,9 @@ import DataTable from '@/components/Table/DataTable/index.vue' import { getUpdateObjURL } from '@/utils/common' import { sleep } from '@/utils/time' -import { EditableInputFormatter, StatusFormatter } from '@/components/Table/TableFormatters' +import { EditableInputFormatter } from '@/components/Table/TableFormatters' import { encryptPassword } from '@/utils/crypto' +import getStatusColumnMeta from '@/components/Table/ListTable/TableAction/const' export default { name: 'ImportTable', @@ -223,38 +224,7 @@ export default { }, methods: { generateTableColumns(tableTitles, tableData) { - const vm = this - const columns = [{ - prop: '@status', - label: vm.$t('Status'), - width: '80px', - align: 'center', - formatter: StatusFormatter, - formatterArgs: { - faChoices: { - ok: 'fa-check text-primary', - error: 'fa-times text-danger', - pending: 'fa-clock-o' - }, - getChoicesKey(val) { - if (val === 'ok' || val === 'pending') { - return val - } - return 'error' - }, - getTip(val) { - if (val === 'ok') { - return vm.$t('Success') - } else if (val === 'pending') { - return vm.$t('Pending') - } else if (val && val.name === 'error') { - return val.error - } - return '' - }, - hasTips: true - } - }] + const columns = [{ ...getStatusColumnMeta.bind(this)().status }] for (const item of tableTitles) { const dataItemLens = tableData.map(d => { if (!d) { diff --git a/src/components/Table/ListTable/TableAction/const.js b/src/components/Table/ListTable/TableAction/const.js new file mode 100644 index 0000000000..2b326c3e77 --- /dev/null +++ b/src/components/Table/ListTable/TableAction/const.js @@ -0,0 +1,40 @@ +import { StatusFormatter } from '@/components/Table/TableFormatters' +import i18n from '@/i18n/i18n' + +export const getStatusColumnMeta = (prop = '@status') => { + return { + status: { + prop: prop, + label: i18n.t('Status'), + width: '80px', + align: 'center', + formatter: StatusFormatter, + formatterArgs: { + faChoices: { + ok: 'fa-check text-primary', + error: 'fa-times text-danger', + pending: 'fa-clock-o' + }, + getChoicesKey: (val) => { + if (val === 'ok' || val === 'pending') { + return val + } + return 'error' + }, + getTip: (val) => { + if (val === 'ok') { + return i18n.t('Success') + } else if (val === 'pending') { + return i18n.t('Pending') + } else if ((val && val.name === 'error') || val.error !== undefined) { + return val.error + } + return '' + }, + hasTips: true + } + } + } +} + +export default getStatusColumnMeta diff --git a/src/components/Table/ListTable/index.vue b/src/components/Table/ListTable/index.vue index 26981f8d6a..4d963c4b59 100644 --- a/src/components/Table/ListTable/index.vue +++ b/src/components/Table/ListTable/index.vue @@ -189,22 +189,23 @@ export default { } }, mounted() { - this.urlUpdated[this.tableUrl] = location.href + this.$set(this.urlUpdated, this.tableUrl, location.href) }, deactivated() { this.isDeactivated = true }, activated() { - this.isDeactivated = false - const preURL = this.urlUpdated[this.tableUrl] - if (!preURL || preURL === location.href) { - return - } - this.urlUpdated[this.tableUrl] = location.href - this.$log.debug('Reload the table get latest data: pre ', preURL, ' current: ', location.href) - setTimeout(() => { + this.$nextTick(() => { + this.isDeactivated = false + const cleanUrl = this.tableUrl.split('?')[0] + const preURL = this.urlUpdated[cleanUrl] + + if (!preURL || preURL === location.href) return + + this.$set(this.urlUpdated, this.tableUrl, location.href) + this.$log.debug('Reload the table get latest data: pre ', preURL, ' current: ', location.href) this.reloadTable() - }, 500) + }) }, methods: { handleActionInitialDone() { diff --git a/src/components/Tree/DataZTree/index.vue b/src/components/Tree/DataZTree/index.vue index b56d48c24a..acb94117cd 100644 --- a/src/components/Tree/DataZTree/index.vue +++ b/src/components/Tree/DataZTree/index.vue @@ -39,7 +39,7 @@ export default { showRenameBtn: false, drag: { isCopy: false, - isMove: true + isMove: !this.$store.getters.currentOrgIsRoot } }, callback: { diff --git a/src/components/Widgets/Announcement/index.vue b/src/components/Widgets/Announcement/index.vue index f5bce77041..3ac0e5a7fd 100644 --- a/src/components/Widgets/Announcement/index.vue +++ b/src/components/Widgets/Announcement/index.vue @@ -35,13 +35,29 @@ export default { ]), announcement() { const ann = this.publicSettings.ANNOUNCEMENT - return { id: ann['ID'], subject: ann['SUBJECT'], content: ann['CONTENT'], link: ann['LINK'] } + return { + id: ann['ID'], + subject: ann['SUBJECT'], + content: ann['CONTENT'], + link: ann['LINK'], + date_start: ann['DATE_START'], + date_end: ann['DATE_END'] + } }, enabled() { - return this.publicSettings.ANNOUNCEMENT_ENABLED && (this.announcement.content || this.announcement.subject) + return this.publicSettings.ANNOUNCEMENT_ENABLED && (this.announcement.content || this.announcement.subject) && this.isDateValid }, title() { return this.$t('Announcement') + ': ' + this.announcement.subject + }, + isDateValid() { + if (this.announcement.date_start === undefined || this.announcement.date_end === undefined) { + return true + } + const now = new Date() + const start = new Date(this.announcement.date_start) + const end = new Date(this.announcement.date_end) + return now >= start && now <= end } }, methods: { diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue index 048b886413..2d8e95cf87 100644 --- a/src/layout/components/AppMain.vue +++ b/src/layout/components/AppMain.vue @@ -33,11 +33,13 @@ export default { query[k] = v } + let key if (this.$route.name.toLowerCase().includes('list')) { - return _.trimEnd(this.$route.path, '/') + '?' + new URLSearchParams(query).toString() + key = _.trimEnd(this.$route.path, '/') + '?' + new URLSearchParams(query).toString() } else { - return new Date().getTime() + key = new Date().getTime() } + return key }, chatAiEnabled() { return this.publicSettings?.CHAT_AI_ENABLED diff --git a/src/layout/components/NavHeader/AccountDropdown.vue b/src/layout/components/NavHeader/AccountDropdown.vue index a69c810732..9f924d7143 100644 --- a/src/layout/components/NavHeader/AccountDropdown.vue +++ b/src/layout/components/NavHeader/AccountDropdown.vue @@ -66,11 +66,15 @@ export default { break case 'logout': this.logout() - window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}` break } }, - logout() { + async logout() { + const currentOrg = this.$store.getters.currentOrg + if (currentOrg.autoEnter) { + await this.$store.dispatch('users/setCurrentOrg', this.$store.getters.preOrg) + } + window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}` } } } diff --git a/src/layout/components/NavHeader/SiteMessages.vue b/src/layout/components/NavHeader/SiteMessages.vue index 7c644fa5a5..626051ff7d 100644 --- a/src/layout/components/NavHeader/SiteMessages.vue +++ b/src/layout/components/NavHeader/SiteMessages.vue @@ -70,7 +70,7 @@ {{ formatDate(currentMsg.date_created) }}
- +
@@ -80,10 +80,14 @@ + + diff --git a/src/views/settings/Auth/Ldap/ImportDialog.vue b/src/views/settings/Auth/Ldap/ImportDialog.vue index be3ba4b2f2..c60c5a31fa 100644 --- a/src/views/settings/Auth/Ldap/ImportDialog.vue +++ b/src/views/settings/Auth/Ldap/ImportDialog.vue @@ -49,6 +49,7 @@ import { DEFAULT_ORG_ID, SYSTEM_ORG_ID } from '@/utils/org' import ListTable from '@/components/Table/ListTable/index.vue' import Dialog from '@/components/Dialog/index.vue' import Select2 from '@/components/Form/FormFields/Select2.vue' +import getStatusColumnMeta from '@/components/Table/ListTable/TableAction/const' export default { name: 'ImportDialog', @@ -57,6 +58,12 @@ export default { Dialog, Select2 }, + props: { + category: { + type: String, + required: true + } + }, data() { return { dialogLdapUserImportLoginStatus: false, @@ -75,9 +82,10 @@ export default { } }, tableConfig: { - url: '/api/v1/settings/ldap/users/', - columns: ['username', 'name', 'email', 'groups', 'existing'], + url: `/api/v1/settings/ldap/users/?category=${this.category}`, + columns: ['status', 'username', 'name', 'email', 'groups', 'existing'], columnsMeta: { + ...getStatusColumnMeta.bind(this)('status'), username: { label: this.$t('Username'), width: '180px' @@ -189,7 +197,7 @@ export default { enableWS() { const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws' const port = document.location.port ? ':' + document.location.port : '' - const url = '/ws/ldap/' + const url = `/ws/ldap/?category=${this.category}` const wsURL = scheme + '://' + document.location.hostname + port + url this.ws = new WebSocket(wsURL) }, diff --git a/src/views/settings/Auth/Ldap/Ldap.vue b/src/views/settings/Auth/Ldap/Ldap.vue new file mode 100644 index 0000000000..8ac47a93f2 --- /dev/null +++ b/src/views/settings/Auth/Ldap/Ldap.vue @@ -0,0 +1,151 @@ + + + + diff --git a/src/views/settings/Auth/Ldap/LdapHA.vue b/src/views/settings/Auth/Ldap/LdapHA.vue new file mode 100644 index 0000000000..42aeac136e --- /dev/null +++ b/src/views/settings/Auth/Ldap/LdapHA.vue @@ -0,0 +1,151 @@ + + + + diff --git a/src/views/settings/Auth/Ldap/TestLoginDialog.vue b/src/views/settings/Auth/Ldap/TestLoginDialog.vue index a81cdcb629..d0e587b0c0 100644 --- a/src/views/settings/Auth/Ldap/TestLoginDialog.vue +++ b/src/views/settings/Auth/Ldap/TestLoginDialog.vue @@ -40,6 +40,12 @@ export default { components: { Dialog }, + props: { + category: { + type: String, + required: true + } + }, data() { return { testLdapLoginStatus: false, @@ -69,7 +75,7 @@ export default { enableWS() { const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws' const port = document.location.port ? ':' + document.location.port : '' - const url = '/ws/ldap/' + const url = `/ws/ldap/?category=${this.category}` const wsURL = scheme + '://' + document.location.hostname + port + url this.ws = new WebSocket(wsURL) } diff --git a/src/views/settings/Auth/Ldap/index.vue b/src/views/settings/Auth/Ldap/index.vue index 76d0ac74a2..8b13789179 100644 --- a/src/views/settings/Auth/Ldap/index.vue +++ b/src/views/settings/Auth/Ldap/index.vue @@ -1,152 +1 @@ - - - - diff --git a/src/views/settings/Auth/index.vue b/src/views/settings/Auth/index.vue index 87f11b9822..8dde8d8c63 100644 --- a/src/views/settings/Auth/index.vue +++ b/src/views/settings/Auth/index.vue @@ -8,7 +8,8 @@