mirror of
https://github.com/jumpserver/lina.git
synced 2025-11-08 19:02:40 +00:00
Compare commits
69 Commits
repr@dev_v
...
v4.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32cd030479 | ||
|
|
607bb476db | ||
|
|
1d676ec9b7 | ||
|
|
614ebb4121 | ||
|
|
8c2719a95d | ||
|
|
c6b0a958a6 | ||
|
|
3edebf3f4f | ||
|
|
f63405978e | ||
|
|
c582c8de98 | ||
|
|
c44e79ed3b | ||
|
|
fc7d5b9c29 | ||
|
|
335febd4a5 | ||
|
|
5ea2918fe7 | ||
|
|
7668d10ba5 | ||
|
|
df15d141da | ||
|
|
e2f4bbde79 | ||
|
|
3ca7de42af | ||
|
|
e6a577ba9b | ||
|
|
6290179460 | ||
|
|
4f3c9e9353 | ||
|
|
aa28a8f765 | ||
|
|
98d0a52fa2 | ||
|
|
8d9e1ffadb | ||
|
|
b3be312a4d | ||
|
|
7c41d148aa | ||
|
|
6dee911642 | ||
|
|
6dd35e5173 | ||
|
|
fdaa33f68d | ||
|
|
0b813bb719 | ||
|
|
7ba6b8d4e4 | ||
|
|
ef99e25fde | ||
|
|
d1e7b907e3 | ||
|
|
83f7dda5e7 | ||
|
|
80f929fdea | ||
|
|
1ec3d02933 | ||
|
|
fdf148cc2b | ||
|
|
c9e6ef89dc | ||
|
|
59f9f88f8b | ||
|
|
16417ae843 | ||
|
|
58bd0a17c8 | ||
|
|
c4a1eb6938 | ||
|
|
b7d9031889 | ||
|
|
96b29a9dc2 | ||
|
|
336e176639 | ||
|
|
2df4a9d66d | ||
|
|
45a102cff1 | ||
|
|
57ebfa0812 | ||
|
|
4e9dd57efe | ||
|
|
3fcc4ca160 | ||
|
|
958760811c | ||
|
|
b60e0251c2 | ||
|
|
3db0ed756e | ||
|
|
453c4b1e4e | ||
|
|
bf4d8ce7a6 | ||
|
|
34effdbe15 | ||
|
|
274db466f2 | ||
|
|
cbe697f9dc | ||
|
|
fb7acb100e | ||
|
|
ebb36847df | ||
|
|
beba4f1994 | ||
|
|
452796e3f5 | ||
|
|
02a7969d90 | ||
|
|
798dbec151 | ||
|
|
0e11a56e37 | ||
|
|
be5344344c | ||
|
|
e75d711e0a | ||
|
|
46ee116f3e | ||
|
|
f4b304338f | ||
|
|
e93e78307c |
61
.github/workflows/build-base-image.yml
vendored
Normal file
61
.github/workflows/build-base-image.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Build and Push Base Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'pr*'
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'yarn.lock'
|
||||
- 'Dockerfile-base'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract date
|
||||
id: vars
|
||||
run: echo "IMAGE_TAG=$(date +'%Y%m%d_%H%M%S')" >> $GITHUB_ENV
|
||||
|
||||
- name: Extract repository name
|
||||
id: repo
|
||||
run: echo "REPO=$(basename ${{ github.repository }})" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: Dockerfile-base
|
||||
tags: jumpserver/${{ env.REPO }}-base:${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Update Dockerfile
|
||||
run: |
|
||||
sed -i 's|-base:.* AS stage-build|-base:${{ env.IMAGE_TAG }} AS stage-build|' Dockerfile
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add Dockerfile
|
||||
git commit -m "perf: Update Dockerfile with new base image tag"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
46
.github/workflows/jms-build-test.yml
vendored
46
.github/workflows/jms-build-test.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: "Run Build Test"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile*'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
component: [lina]
|
||||
version: [v4]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Prepare Build
|
||||
run: |
|
||||
sed -i 's@registry.npmmirror.com@registry.yarnpkg.com@g' yarn.lock
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: Dockerfile
|
||||
tags: ghcr.io/jumpserver/${{ matrix.component }}:${{ matrix.version }}
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
VERSION=${{ matrix.version }}
|
||||
APT_MIRROR=http://deb.debian.org
|
||||
NPM_REGISTRY=https://registry.yarnpkg.com
|
||||
outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
46
.github/workflows/jms-build-test.yml.disabled
vendored
Normal file
46
.github/workflows/jms-build-test.yml.disabled
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: "Run Build Test"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile*'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
component: [ lina ]
|
||||
version: [ v4 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Prepare Build
|
||||
run: |
|
||||
sed -i 's@registry.npmmirror.com@registry.yarnpkg.com@g' yarn.lock
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: Dockerfile
|
||||
tags: ghcr.io/jumpserver/${{ matrix.component }}:${{ matrix.version }}
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
VERSION=${{ matrix.version }}
|
||||
APT_MIRROR=http://deb.debian.org
|
||||
NPM_REGISTRY=https://registry.yarnpkg.com
|
||||
outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
32
Dockerfile
32
Dockerfile
@@ -1,34 +1,4 @@
|
||||
FROM node:16.20-bullseye-slim AS stage-build
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG DEPENDENCIES=" \
|
||||
g++ \
|
||||
make \
|
||||
python3"
|
||||
|
||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
set -ex \
|
||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
|
||||
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
||||
&& apt-get update \
|
||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||
&& echo "no" | dpkg-reconfigure dash
|
||||
|
||||
ARG NPM_REGISTRY="https://registry.npmmirror.com"
|
||||
|
||||
RUN set -ex \
|
||||
&& npm config set registry ${NPM_REGISTRY} \
|
||||
&& yarn config set registry ${NPM_REGISTRY}
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked \
|
||||
--mount=type=bind,source=package.json,target=package.json \
|
||||
--mount=type=bind,source=yarn.lock,target=yarn.lock \
|
||||
yarn install
|
||||
FROM jumpserver/lina-base:20240723_084702 AS stage-build
|
||||
|
||||
ARG VERSION
|
||||
ENV VERSION=$VERSION
|
||||
|
||||
22
Dockerfile-base
Normal file
22
Dockerfile-base
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM node:20.15-bullseye-slim
|
||||
|
||||
ARG DEPENDENCIES=" \
|
||||
g++ \
|
||||
make \
|
||||
python3"
|
||||
|
||||
RUN set -ex \
|
||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
|
||||
&& apt-get update \
|
||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||
&& echo "no" | dpkg-reconfigure dash
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
ARG NPM_MIRROR="https://registry.npmjs.org"
|
||||
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=yarn-cache \
|
||||
sed -i "s|https://registry.npmmirror.com|${NPM_MIRROR}|g" yarn.lock \
|
||||
&& yarn install
|
||||
10
package.json
10
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "Lina",
|
||||
"name": "lina",
|
||||
"version": "v4.0.0",
|
||||
"description": "JumpServer Web UI",
|
||||
"author": "JumpServer Team <support@fit2cloud.com>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"dev": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
|
||||
"serve": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
|
||||
"build": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
|
||||
"build:prod": "vue-cli-service build",
|
||||
"build:stage": "vue-cli-service build --mode staging",
|
||||
"preview": "node build/index.js --preview",
|
||||
@@ -30,11 +30,13 @@
|
||||
"@ztree/ztree_v3": "3.5.44",
|
||||
"axios": "0.28.0",
|
||||
"axios-retry": "^3.1.9",
|
||||
"caniuse-lite": "^1.0.30001642",
|
||||
"cron-parser": "^4.0.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"css-color-function": "^1.3.3",
|
||||
"decimal.js": "^10.4.3",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dompurify": "^3.1.6",
|
||||
"echarts": "4.7.0",
|
||||
"element-ui": "2.15.14",
|
||||
"eslint-plugin-html": "^6.0.0",
|
||||
|
||||
@@ -65,10 +65,6 @@ export function logout() {
|
||||
})
|
||||
}
|
||||
|
||||
export function refreshSessionIdAge() {
|
||||
return getProfile()
|
||||
}
|
||||
|
||||
export default {
|
||||
getProfile,
|
||||
getUserList
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<Dialog
|
||||
:close-on-click-modal="false"
|
||||
:title="$tc('Assets')"
|
||||
:disabled-status="!isLoaded"
|
||||
custom-class="asset-select-dialog"
|
||||
top="2vh"
|
||||
v-bind="$attrs"
|
||||
@@ -22,6 +23,8 @@
|
||||
:url="baseUrl"
|
||||
class="tree-table"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@loaded="handleTableLoaded"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -64,6 +67,7 @@ export default {
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
isLoaded: false,
|
||||
dialogVisible: false,
|
||||
rowSelected: _.cloneDeep(this.value) || [],
|
||||
rowsAdd: [],
|
||||
@@ -124,6 +128,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTableLoaded() {
|
||||
this.isLoaded = true
|
||||
},
|
||||
handleClose() {
|
||||
this.$refs.ListPage.$refs.TreeList.componentKey += 1
|
||||
},
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<script>
|
||||
import Select2 from '@/components/Form/FormFields/Select2.vue'
|
||||
import AssetSelectDialog from './dialog.vue'
|
||||
import { b } from 'css-color-function/lib/adjusters'
|
||||
|
||||
export default {
|
||||
componentName: 'AssetSelect',
|
||||
@@ -81,7 +80,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
b,
|
||||
handleFocus() {
|
||||
this.$refs.select2.selectRef.blur()
|
||||
this.dialogVisible = true
|
||||
|
||||
@@ -157,30 +157,37 @@ export default {
|
||||
$('#m_show_asset_only_current_node').css('color', '#606266')
|
||||
}
|
||||
},
|
||||
|
||||
getAssetsUrl(treeNode) {
|
||||
let url = this.treeSetting?.url || this.url
|
||||
|
||||
const setParam = (param, value, delay) => {
|
||||
setTimeout(() => {
|
||||
url = setUrlParam(url, param, value)
|
||||
})
|
||||
}
|
||||
|
||||
if (treeNode.meta.type === 'node') {
|
||||
const nodeId = treeNode.meta.data.id
|
||||
url = setUrlParam(url, 'node_id', nodeId)
|
||||
url = setUrlParam(url, 'asset_id', '')
|
||||
setParam('node_id', nodeId)
|
||||
setParam('asset_id', '')
|
||||
} else if (treeNode.meta.type === 'asset') {
|
||||
const assetId = treeNode.meta.data?.id || treeNode.id
|
||||
url = setUrlParam(url, 'node_id', '')
|
||||
url = setUrlParam(url, 'asset_id', assetId)
|
||||
setParam('node_id', '')
|
||||
setParam('asset_id', assetId)
|
||||
} else if (treeNode.meta.type === 'category') {
|
||||
url = setUrlParam(url, 'category', treeNode.meta.category)
|
||||
setParam('category', treeNode.meta.category)
|
||||
} else if (treeNode.meta.type === 'type') {
|
||||
url = setUrlParam(url, 'category', treeNode.meta.category)
|
||||
url = setUrlParam(url, 'type', treeNode.meta._type)
|
||||
setParam('category', treeNode.meta.category)
|
||||
setParam('type', treeNode.meta._type)
|
||||
} else if (treeNode.meta.type === 'platform') {
|
||||
url = setUrlParam(url, 'platform', treeNode.id)
|
||||
setParam('platform', treeNode.id)
|
||||
}
|
||||
const query = this.setTreeUrlQuery()
|
||||
url = query ? `${url}&${query}` : url
|
||||
|
||||
setTimeout(() => {
|
||||
const query = this.setTreeUrlQuery()
|
||||
url = query ? `${url}&${query}` : url
|
||||
this.$set(this.tableConfig, 'url', url)
|
||||
}, 300)
|
||||
})
|
||||
|
||||
if (this.treeSetting.selectSyncToRoute !== false) {
|
||||
setRouterQuery(this, url)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<AssetTreeTable
|
||||
ref="AssetTreeTable"
|
||||
:header-actions="headerActions"
|
||||
:table-config="tableConfig"
|
||||
:tree-setting="treeSetting"
|
||||
@@ -47,9 +48,19 @@ export default {
|
||||
return this.tableUrl.replace('/assets/', `/assets/${row.id}/accounts/`)
|
||||
}
|
||||
},
|
||||
nameDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
name: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail',
|
||||
can: true
|
||||
}
|
||||
})
|
||||
},
|
||||
comment: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -80,11 +91,7 @@ export default {
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail',
|
||||
can: !this.nameDisabled
|
||||
}
|
||||
...this.name
|
||||
},
|
||||
labels: {
|
||||
formatterArgs: {
|
||||
@@ -99,7 +106,8 @@ export default {
|
||||
formatter: AccountInfoFormatter,
|
||||
width: '100px'
|
||||
},
|
||||
connectivity: connectivityMeta
|
||||
connectivity: connectivityMeta,
|
||||
comment: { ...this.comment }
|
||||
},
|
||||
tableAttrs: {
|
||||
rowClassName({ row }) {
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<slot />
|
||||
<div v-loading="disabledStatus">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div v-if="showButtons" slot="footer" class="dialog-footer">
|
||||
<slot name="footer">
|
||||
<el-button v-if="showCancel && showButtons" size="small" @click="onCancel">{{ cancelTitle }}</el-button>
|
||||
<el-button
|
||||
v-if="showConfirm && showButtons"
|
||||
:loading="loadingStatus"
|
||||
:disabled="disabledStatus"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="onConfirm"
|
||||
@@ -69,7 +72,7 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadingStatus: {
|
||||
disabledStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
@@ -79,7 +82,8 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iWidth() {
|
||||
|
||||
@@ -85,6 +85,7 @@ export default {
|
||||
// 如果不想等,证明是 value 自己变化导致的, 需要重新渲染
|
||||
if (valJson !== this.formJson) {
|
||||
this.iValue = val
|
||||
this.$log.debug('Sub form value changed, rerender form: ', this.formJson, valJson)
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
@@ -95,11 +96,12 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
outputValue: _.debounce(function(val) {
|
||||
this.$emit('input', val)
|
||||
}),
|
||||
updateValue(val) {
|
||||
this.iValue = val
|
||||
setTimeout(() => {
|
||||
this.$emit('input', val)
|
||||
}, 100)
|
||||
this.outputValue(val)
|
||||
},
|
||||
objectToString(obj) {
|
||||
let data = ''
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
>
|
||||
{{ iSubmitBtnText }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="defaultButton && hasSaveContinue"
|
||||
size="small"
|
||||
@@ -40,6 +41,7 @@
|
||||
>
|
||||
{{ $t("SaveAndAddAnother") }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="defaultButton && hasReset"
|
||||
size="small"
|
||||
@@ -47,6 +49,7 @@
|
||||
>
|
||||
{{ $t("Reset") }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-for="button in moreButtons"
|
||||
v-show="!button.hidden"
|
||||
|
||||
@@ -8,99 +8,117 @@
|
||||
:label="item.name"
|
||||
:prop="item.name"
|
||||
>
|
||||
|
||||
<template v-if="item.type === 'button' && !item.isVisible">
|
||||
<el-button
|
||||
:type="item.el && item.el.type"
|
||||
class="start-stop-btn"
|
||||
size="mini"
|
||||
@click="item.callback()"
|
||||
>
|
||||
<i :class="item.icon" />{{ item.name }}
|
||||
</el-button>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-button
|
||||
:type="item.el && item.el.type"
|
||||
class="start-stop-btn"
|
||||
size="mini"
|
||||
@click="item.callback()"
|
||||
>
|
||||
<i :class="item.icon" />
|
||||
|
||||
{{ item.name }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'input' && item.el && item.el.autoComplete">
|
||||
<el-autocomplete
|
||||
v-model="formModel[item.name]"
|
||||
:fetch-suggestions="item.el.query"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@change="handleInputChange(item)"
|
||||
@select="handleInputChange(item)"
|
||||
/>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-autocomplete
|
||||
v-model="formModel[item.name]"
|
||||
:fetch-suggestions="item.el.query"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@change="handleInputChange(item)"
|
||||
@select="handleInputChange(item)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.type === 'input'">
|
||||
<el-input
|
||||
v-model="formModel[item.name]"
|
||||
:class="!isFold ? 'special-style' : ''"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-input
|
||||
v-model="formModel[item.name]"
|
||||
:class="!isFold ? 'special-style' : ''"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'select' && item.el && item.el.create">
|
||||
<span class="filter-label">{{ item.name }}:</span>
|
||||
<el-select
|
||||
v-if="item.type === 'select' && item.el && item.el.create"
|
||||
:key="index"
|
||||
v-model="formModel[item.name]"
|
||||
:allow-create="item.el.create || false"
|
||||
:filterable="item.el.create || false"
|
||||
:multiple="item.el.multiple"
|
||||
:placeholder="item.name"
|
||||
class="autoWidth-select"
|
||||
default-first-option
|
||||
size="mini"
|
||||
@change="item.callback(item.value)"
|
||||
>
|
||||
<template slot="prefix">{{ item.label + ':' + item.value }}</template>
|
||||
<el-option
|
||||
v-for="(option, id) in item.options"
|
||||
:key="id"
|
||||
:label="option.label"
|
||||
:title="option.value"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<span class="filter-label">{{ item.name }}:</span>
|
||||
<el-select
|
||||
v-if="item.type === 'select' && item.el && item.el.create"
|
||||
:key="index"
|
||||
v-model="formModel[item.name]"
|
||||
:allow-create="item.el.create || false"
|
||||
:filterable="item.el.create || false"
|
||||
:multiple="item.el.multiple"
|
||||
:placeholder="item.name"
|
||||
class="autoWidth-select"
|
||||
default-first-option
|
||||
size="mini"
|
||||
@change="item.callback(item.value)"
|
||||
>
|
||||
<template slot="prefix">{{ item.label + ':' + item.value }}</template>
|
||||
<el-option
|
||||
v-for="(option, id) in item.options"
|
||||
:key="id"
|
||||
:label="option.label"
|
||||
:title="option.value"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'select' && (!item.el || !item.el.create)">
|
||||
<el-dropdown
|
||||
class="select-dropdown"
|
||||
trigger="click"
|
||||
@command="(command) => {
|
||||
item.value = command
|
||||
item.callback(command)
|
||||
}"
|
||||
>
|
||||
<el-button size="mini" type="primary">
|
||||
<div class="text-content">
|
||||
<span class="content">
|
||||
{{ getLabel(item.value, item.options) }}
|
||||
<i class="el-icon-arrow-down el-icon--right" />
|
||||
</span>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-dropdown-menu v-slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="(option, i) in item.options"
|
||||
:key="i"
|
||||
:command="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-dropdown
|
||||
class="select-dropdown"
|
||||
trigger="click"
|
||||
@command="(command) => {
|
||||
item.value = command
|
||||
item.callback(command)
|
||||
}"
|
||||
>
|
||||
<el-button size="mini" type="primary">
|
||||
<div class="text-content">
|
||||
<span class="content">
|
||||
{{ getLabel(item.value, item.options) }}
|
||||
<i class="el-icon-arrow-down el-icon--right" />
|
||||
</span>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-dropdown-menu v-slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="(option, i) in item.options"
|
||||
:key="i"
|
||||
:command="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'switch'">
|
||||
<el-switch
|
||||
v-model="formModel[item.name]"
|
||||
:active-text="item.name"
|
||||
:disabled="item.disabled"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-switch
|
||||
v-model="formModel[item.name]"
|
||||
:active-text="item.name"
|
||||
:disabled="item.disabled"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<div
|
||||
@@ -134,7 +152,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
<codemirror ref="myCm" v-model="iValue" :options="iOptions" class="editor" />
|
||||
<codemirror
|
||||
ref="myCm"
|
||||
v-model="iValue"
|
||||
:options="iOptions"
|
||||
class="editor"
|
||||
:style="iActions.length > 0 ? { marginLeft: '30px' } : {}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -208,7 +232,7 @@ export default {
|
||||
|
||||
Object.values(actionsObj).forEach(action => {
|
||||
if (action.name === this.$t('RunAs') && action.type === 'input') {
|
||||
rules[action.name] = [{ required: true, message: '请输入运行用户', trigger: 'blur' }]
|
||||
rules[action.name] = [{ required: true, message: this.$t('RequiredRunas'), trigger: 'blur' }]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -373,7 +397,6 @@ $input-border-color: #C0C4CC;
|
||||
}
|
||||
|
||||
.editor {
|
||||
margin-left: 30px;
|
||||
border: 1px solid var(--color-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
:close-on-click-modal="false"
|
||||
:title="label"
|
||||
:visible.sync="showTransfer"
|
||||
:disabled-status="!isLoaded"
|
||||
class="the-dialog"
|
||||
width="730px"
|
||||
@cancel="handleTransCancel"
|
||||
@confirm="handleTransConfirm"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<krryPaging v-if="selectInitialized" ref="pageTransfer" class="transfer" v-bind="pagingTransfer" />
|
||||
</Dialog>
|
||||
@@ -77,13 +79,16 @@ export default {
|
||||
if (keyword) {
|
||||
params['search'] = keyword
|
||||
}
|
||||
this.isLoaded = false
|
||||
const data = await this.$axios.get(url, { params })
|
||||
this.isLoaded = true
|
||||
return data['results'].map(item => {
|
||||
const n = transformOption(item)
|
||||
return { id: n.value, label: n.label }
|
||||
})
|
||||
}
|
||||
return {
|
||||
isLoaded: false,
|
||||
showTransfer: false,
|
||||
selectInitialized: false,
|
||||
select2: {
|
||||
@@ -167,7 +172,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -90,8 +90,14 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('showColumnSettingPopover', this.showColumnSettingPopoverHandler)
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showColumnSettingPopover', ({ url }) => {
|
||||
this.$eventBus.$on('showColumnSettingPopover', this.showColumnSettingPopoverHandler)
|
||||
},
|
||||
methods: {
|
||||
showColumnSettingPopoverHandler({ url }) {
|
||||
if (url === this.url) {
|
||||
this.checkAll = false
|
||||
this.showColumnSettingPopover = true
|
||||
@@ -105,9 +111,7 @@ export default {
|
||||
this.checkAll = false
|
||||
this.isIndeterminate = true
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
handleColumnConfirm() {
|
||||
this.showColumnSettingPopover = false
|
||||
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })
|
||||
|
||||
@@ -153,6 +153,8 @@ export default {
|
||||
this.toggleRowSelection(row, true)
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit('loaded')
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
localStorage.setItem('paginationSize', val)
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query'
|
||||
import { download } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'ExportDialog',
|
||||
@@ -170,14 +171,18 @@ export default {
|
||||
]
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('showExportDialog', this.showExportDialogHandler)
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showExportDialog', ({ selectedRows, url, name }) => {
|
||||
this.$eventBus.$on('showExportDialog', this.showExportDialogHandler)
|
||||
},
|
||||
methods: {
|
||||
showExportDialogHandler({ selectedRows, url, name }) {
|
||||
if (url === this.url || url.indexOf(this.url) > -1) {
|
||||
this.showExportDialog()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
showExportDialog() {
|
||||
if (!this.mfaVerifyRequired) {
|
||||
this.exportDialogShow = true
|
||||
@@ -197,10 +202,7 @@ export default {
|
||||
})
|
||||
},
|
||||
downloadCsv(url) {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
download(url)
|
||||
},
|
||||
async defaultPerformExport(selectRows, exportOption, q, exportTypeOption) {
|
||||
const url = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import ImportTable from '@/components/Table/ListTable/TableAction/ImportTable.vue'
|
||||
import { getErrorResponseMsg } from '@/utils/common'
|
||||
import { download, getErrorResponseMsg } from '@/utils/common'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
|
||||
export default {
|
||||
@@ -142,14 +142,18 @@ export default {
|
||||
this.showTable = false
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('showImportDialog', this.showImportEventHandler)
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showImportDialog', ({ url }) => {
|
||||
this.$eventBus.$on('showImportDialog', this.showImportEventHandler)
|
||||
},
|
||||
methods: {
|
||||
showImportEventHandler({ url }) {
|
||||
if (url === this.url) {
|
||||
this.showImportDialog = true
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
closeDialog() {
|
||||
this.showImportDialog = false
|
||||
this.$emit('importDialogClose')
|
||||
@@ -226,10 +230,7 @@ export default {
|
||||
this.$message.success(msg)
|
||||
},
|
||||
downloadCsv(url) {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
download(url)
|
||||
},
|
||||
async handleImportConfirm() {
|
||||
await this.$refs['importTable'].performUpload()
|
||||
|
||||
@@ -71,7 +71,16 @@ export default {
|
||||
this.listenViewPort()
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('labelSearch', label => {
|
||||
this.$eventBus.$on('labelSearch', this.labelSearchHandler)
|
||||
},
|
||||
beforeDestroy(label) {
|
||||
this.$eventBus.$off('labelSearch', this.labelSearchHandler)
|
||||
},
|
||||
methods: {
|
||||
handleCascaderFocus() {
|
||||
this.setSearchFocus()
|
||||
},
|
||||
labelSearchHandler(label) {
|
||||
if (!label) {
|
||||
this.labelValue = []
|
||||
this.showLabelSearch = true
|
||||
@@ -82,14 +91,6 @@ export default {
|
||||
setTimeout(() => {
|
||||
this.showLabelSearch = true
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
this.$eventBus.$off('labelSearch')
|
||||
},
|
||||
methods: {
|
||||
handleCascaderFocus() {
|
||||
this.setSearchFocus()
|
||||
},
|
||||
handleCascaderVisibleChange(visible) {
|
||||
const input = this.$refs.labelCascader.$el
|
||||
|
||||
@@ -124,6 +124,9 @@ export default {
|
||||
return this.iHasLeftActions ? 'right' : 'left'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$emit('done')
|
||||
},
|
||||
methods: {
|
||||
handleTagSearch(val) {
|
||||
this.searchTable(val)
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
:selected-rows="selectedRows"
|
||||
:table-url="tableUrl"
|
||||
v-bind="iHeaderActions"
|
||||
@done="handleActionInitialDone"
|
||||
/>
|
||||
<IBox class="table-content">
|
||||
<AutoDataTable
|
||||
v-if="actionInit"
|
||||
ref="dataTable"
|
||||
:config="iTableConfig"
|
||||
:filter-table="filter"
|
||||
@@ -73,9 +75,11 @@ export default {
|
||||
return {
|
||||
selectedRows: [],
|
||||
init: false,
|
||||
extraQuery: extraQuery,
|
||||
urlUpdated: {},
|
||||
isDeactivated: false
|
||||
isDeactivated: false,
|
||||
extraQuery: extraQuery,
|
||||
actionInit: this.headerActions.has === false,
|
||||
initQuery: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -203,13 +207,35 @@ export default {
|
||||
}, 500)
|
||||
},
|
||||
methods: {
|
||||
handleActionInitialDone() {
|
||||
setTimeout(() => {
|
||||
this.actionInit = true
|
||||
}, 100)
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.selectedRows = val
|
||||
},
|
||||
reloadTable() {
|
||||
this.dataTable?.getList()
|
||||
},
|
||||
updateInitQuery(attrs) {
|
||||
if (!this.actionInit) {
|
||||
this.initQuery = attrs
|
||||
for (const key in attrs) {
|
||||
this.$set(this.extraQuery, key, attrs[key])
|
||||
}
|
||||
return true
|
||||
}
|
||||
const removeKeys = Object.keys(this.initQuery).filter(key => !attrs[key])
|
||||
for (const key of removeKeys) {
|
||||
this.$delete(this.extraQuery, key)
|
||||
}
|
||||
},
|
||||
search(attrs) {
|
||||
const init = this.updateInitQuery(attrs)
|
||||
if (init) {
|
||||
return
|
||||
}
|
||||
this.$log.debug('ListTable: search table', attrs)
|
||||
this.$emit('TagSearch', attrs)
|
||||
this.$refs.dataTable?.$refs.dataTable?.search(attrs, true)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<span>{{ value }}</span>
|
||||
<span class="date">{{ dateValue }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -10,24 +10,31 @@ export default {
|
||||
name: 'DateFormatter',
|
||||
extends: BaseFormatter,
|
||||
data() {
|
||||
let value
|
||||
if (this.cellValue) {
|
||||
value = toSafeLocalDateStr(this.cellValue)
|
||||
} else {
|
||||
value = '-'
|
||||
}
|
||||
// let value
|
||||
// if (this.cellValue) {
|
||||
// value = toSafeLocalDateStr(this.cellValue)
|
||||
// } else {
|
||||
// value = '-'
|
||||
// }
|
||||
// const locale = this.$i18n.locale
|
||||
// const value = dt.toLocaleString(locale, { hourCycle: 'h23' })
|
||||
// debug(this.$i18n.locale)
|
||||
return {
|
||||
value: value
|
||||
}
|
||||
// return {
|
||||
// value: value
|
||||
// }
|
||||
// return {
|
||||
// value: `${year}-${month}-${date} ${hour}:${minutes}:${seconds}`
|
||||
// }
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
dateValue() {
|
||||
if (this.cellValue) {
|
||||
return toSafeLocalDateStr(this.cellValue)
|
||||
} else {
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div style="width: 100%;min-height: 20px" @click.stop="editCell">
|
||||
<div class="edit-container" style="min-height: 20px" @click.stop="editCell">
|
||||
<el-input
|
||||
v-if="inEditMode"
|
||||
ref="inputRef"
|
||||
v-model="value"
|
||||
class="editInput"
|
||||
size="mini"
|
||||
@@ -9,8 +10,17 @@
|
||||
@keyup.enter.native="onInputEnter"
|
||||
/>
|
||||
<template v-else>
|
||||
<span>{{ iCellValue }}</span>
|
||||
<span class="cellValue">{{ iCellValue }}</span>
|
||||
<a
|
||||
v-if="formatterArgs.showEditBtn"
|
||||
:class="[{ 'disabled-link': this.$store.getters.currentOrgIsRoot },'edit-btn']"
|
||||
style="padding-left: 5px"
|
||||
@click="editCell"
|
||||
>
|
||||
<i class="fa fa-edit" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,6 +71,9 @@ export default {
|
||||
editCell() {
|
||||
if (this.formatterArgs.canEdit) {
|
||||
this.inEditMode = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.inputRef.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
getCellValue(val) {
|
||||
@@ -88,7 +101,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.editInput ::v-deep .el-input__inner {
|
||||
padding: 2px;
|
||||
line-height: 12px;
|
||||
@@ -97,4 +110,35 @@ export default {
|
||||
.editInput {
|
||||
padding: -6px;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
transition: all 1s;
|
||||
|
||||
& > i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.edit-container {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&:hover {
|
||||
.edit-btn {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.cellValue {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -4,28 +4,29 @@
|
||||
<div class="action-bar">
|
||||
<div class="action">
|
||||
<span>
|
||||
<i :class="[!isShow ? 'fa-eye' : 'fa-eye-slash']" class="fa" @click="onView" />
|
||||
<i class="fa" :class="[!isShow ? 'fa-eye' : 'fa-eye-slash']" @click="onView" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-col :span="span" :style="{'height': height + 'px' }">
|
||||
<el-input
|
||||
v-model="iValue"
|
||||
:rows="rows"
|
||||
autosize
|
||||
:rows="rows"
|
||||
type="textarea"
|
||||
@change="onChange"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col v-show="isShow" :span="span">
|
||||
<VueMarkdown :html="false" :show="true" :source="iValue" class="result-html" />
|
||||
<VueMarkdown class="result-html" :source="sanitizedValue" :html="false" :show="true" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<VueMarkdown v-else :html="false" :source="iValue" class="source" />
|
||||
<VueMarkdown v-else class="source" :html="false" :source="sanitizedValue" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DOMPurify from 'dompurify'
|
||||
import VueMarkdown from 'vue-markdown'
|
||||
|
||||
export default {
|
||||
@@ -55,6 +56,17 @@ export default {
|
||||
iValue: this.value
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sanitizedValue() {
|
||||
// 转义特殊字符
|
||||
let content = this.iValue.replace(/\\/g, '\\\\').replace(/\$/g, '\\$')
|
||||
|
||||
// 使用 DOMPurify 进行 XSS 过滤
|
||||
content = DOMPurify.sanitize(content)
|
||||
|
||||
return content
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
|
||||
@@ -23,8 +23,18 @@ export default {
|
||||
'publicSettings'
|
||||
]),
|
||||
key() {
|
||||
// 想让创建后回来 List 页面不刷新,但是完全不刷新 table 会不对,所以创建完成后,会更新 order 和 updated
|
||||
// query 去掉这两个,如果变了再刷新
|
||||
const query = {}
|
||||
for (const [k, v] of Object.entries(this.$route.query)) {
|
||||
if (k.includes('updated') || k.includes('order')) {
|
||||
continue
|
||||
}
|
||||
query[k] = v
|
||||
}
|
||||
|
||||
if (this.$route.name.toLowerCase().includes('list')) {
|
||||
return _.trimEnd(this.$route.path, '/')
|
||||
return _.trimEnd(this.$route.path, '/') + '?' + new URLSearchParams(query).toString()
|
||||
} else {
|
||||
return new Date().getTime()
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export default {
|
||||
this.$router.push({ name: 'Profile' })
|
||||
break
|
||||
case 'PasswordAndSSHKey':
|
||||
this.$router.push({ name: 'PasswordAndSSHKey' })
|
||||
this.$router.push({ name: 'SSHKeyList' })
|
||||
break
|
||||
case 'Preferences':
|
||||
this.$router.push({ name: 'Preferences' })
|
||||
|
||||
@@ -28,13 +28,39 @@ export default {
|
||||
},
|
||||
{
|
||||
path: '/profile/password-and-ssh-key',
|
||||
name: 'PasswordAndSSHKey',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/index'),
|
||||
component: empty,
|
||||
meta: {
|
||||
title: i18n.t('PasswordAndSSHKey'),
|
||||
icon: 'personal',
|
||||
permissions: []
|
||||
}
|
||||
icon: 'personal'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/index'),
|
||||
name: 'SSHKeyList',
|
||||
icon: 'key',
|
||||
meta: { title: i18n.t('PasswordAndSSHKey'), permissions: ['authentication.view_sshkey'] }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/SSHKey/SSHKeyCreateUpdate.vue'),
|
||||
name: 'SSHKeyCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('SSHKey'),
|
||||
permissions: ['authentication.add_sshkey']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/SSHKey/SSHKeyCreateUpdate.vue'),
|
||||
name: 'SSHKeyUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('SSHKey'),
|
||||
permissions: ['authentication.change_sshkey']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/profile/passkeys',
|
||||
|
||||
@@ -205,7 +205,7 @@ export default {
|
||||
hidden: true,
|
||||
component: () => import('@/views/ops/Template/Adhoc/AdhocUpdateCreate'),
|
||||
meta: {
|
||||
title: i18n.t('createAdhoc'),
|
||||
title: i18n.t('AdhocUpdate'),
|
||||
permissions: ['ops.add_adhoc'],
|
||||
activeMenu: '/workbench/ops/templates'
|
||||
}
|
||||
|
||||
@@ -217,13 +217,19 @@ export function downloadText(content, filename) {
|
||||
}
|
||||
|
||||
export function download(downloadUrl, filename) {
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.style.display = 'none'
|
||||
document.body.appendChild(iframe)
|
||||
const a = document.createElement('a')
|
||||
a.href = downloadUrl
|
||||
if (filename) {
|
||||
a.download = filename
|
||||
}
|
||||
iframe.contentWindow.document.body.appendChild(a)
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(downloadUrl)
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(iframe)
|
||||
}, 1000 * 60 * 30) // If you can't download it in half an hour, don't download it.
|
||||
}
|
||||
|
||||
export function diffObject(object, base) {
|
||||
|
||||
@@ -26,7 +26,8 @@ Object.assign(Table.components.TableBody.methods, {
|
||||
const range = document.createRange()
|
||||
range.setStart(cellChild, 0)
|
||||
range.setEnd(cellChild, cellChild.childNodes.length)
|
||||
const rangeWidth = range.getBoundingClientRect().width
|
||||
// rangeWidth 有可能是小数,因此就会导致原本 rangeWidth + padding = cellChild.offsetWidth 的大于了 cellChild.offsetWidth
|
||||
const rangeWidth = Math.floor(range.getBoundingClientRect().width)
|
||||
const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
|
||||
(parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0)
|
||||
if (
|
||||
|
||||
@@ -3,7 +3,6 @@ import i18n from '@/i18n/i18n'
|
||||
import { eventBus } from '@/utils/const'
|
||||
import { getTokenFromCookie } from '@/utils/auth'
|
||||
import { getErrorResponseMsg } from '@/utils/common'
|
||||
import { refreshSessionIdAge } from '@/api/users'
|
||||
import { MessageBox } from 'element-ui'
|
||||
import { message } from '@/utils/message'
|
||||
import store from '@/store'
|
||||
@@ -102,20 +101,6 @@ export function flashErrorMsg({ response, error }) {
|
||||
}
|
||||
}
|
||||
|
||||
let timer = null
|
||||
|
||||
function refreshSessionAgeDelay(response) {
|
||||
if (response.request.responseURL.indexOf('/users/profile/') !== -1) {
|
||||
return
|
||||
}
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
timer = setTimeout(function() {
|
||||
refreshSessionIdAge()
|
||||
}, 30 * 1000)
|
||||
}
|
||||
|
||||
function ifConfirmRequired({ response, error }) {
|
||||
if (response.status !== 412) {
|
||||
return null
|
||||
@@ -142,7 +127,6 @@ service.interceptors.response.use(
|
||||
*/
|
||||
response => {
|
||||
// NProgress.done()
|
||||
refreshSessionAgeDelay(response)
|
||||
const res = response.data
|
||||
store.dispatch('common/digestSQLQuery', response).then()
|
||||
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
const moment = require('moment')
|
||||
|
||||
function getUserLang() {
|
||||
const userLangEN = document.cookie.indexOf('django_language=en')
|
||||
if (userLangEN === -1) {
|
||||
return 'zh-CN'
|
||||
} else {
|
||||
return 'en-US'
|
||||
}
|
||||
}
|
||||
import { getLangCode } from '@/i18n/utils'
|
||||
|
||||
function getTimeUnits(u) {
|
||||
const units = {
|
||||
@@ -16,10 +8,11 @@ function getTimeUnits(u) {
|
||||
'm': '分',
|
||||
's': '秒'
|
||||
}
|
||||
if (getUserLang() === 'zh-CN') {
|
||||
if (getLangCode() === 'zh') {
|
||||
return units[u]
|
||||
} else {
|
||||
return u
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
export function timeOffset(a, b) {
|
||||
|
||||
@@ -173,7 +173,7 @@ export default {
|
||||
},
|
||||
{
|
||||
key: this.$t('Region'),
|
||||
value: this.object.task?.regions,
|
||||
value: this.object.task?.regions_display,
|
||||
formatter(row, value) {
|
||||
return (<div>{
|
||||
value?.map((content) => {
|
||||
|
||||
@@ -64,8 +64,11 @@ export default {
|
||||
formatter: DateFormatter
|
||||
},
|
||||
{
|
||||
prop: 'summary.triggerMode',
|
||||
label: this.$t('TriggerMode')
|
||||
prop: 'trigger',
|
||||
label: this.$t('TriggerMode'),
|
||||
formatter: row => {
|
||||
return row.trigger.label
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'actions',
|
||||
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
detailFields: [
|
||||
'name', 'assets_amount',
|
||||
{
|
||||
key: this.$t('Gateways amount'),
|
||||
key: this.$t('Gateway'),
|
||||
value: `${this.object.gateways.length}`
|
||||
},
|
||||
'date_created', 'comment'
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
@click="onSetting"
|
||||
/>
|
||||
<Dialog
|
||||
v-if="isVisible"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
:title="title"
|
||||
@@ -34,7 +32,7 @@
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import AutoDataForm from '@/components/Form/AutoDataForm'
|
||||
import { DynamicInput } from '@/components/Form/FormFields'
|
||||
import { DynamicInput, Switcher } from '@/components/Form/FormFields'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -147,6 +145,9 @@ export default {
|
||||
case 'list':
|
||||
component = DynamicInput
|
||||
break
|
||||
case 'boolean':
|
||||
component = Switcher
|
||||
break
|
||||
}
|
||||
|
||||
if (param) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<BaseList v-bind="tableConfig" />
|
||||
<BaseList v-bind="config" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseList from '../../Asset/AssetList/components/BaseList'
|
||||
import { DetailFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -12,20 +13,34 @@ export default {
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => { }
|
||||
default: () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableConfig: {
|
||||
config: {
|
||||
category: 'all',
|
||||
url: `/api/v1/assets/assets/?platform=${this.object.id}`,
|
||||
tableConfig: {
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
can: false
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
has: false
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasCreate: false,
|
||||
hasRefresh: true,
|
||||
hasRefresh: false,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasMoreActions: true
|
||||
hasMoreActions: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<script>
|
||||
import { GenericListTable, TabPage } from '@/layout/components'
|
||||
import { ChoicesFormatter, ProtocolsFormatter } from '../../../components/Table/TableFormatters'
|
||||
import AmountFormatter from '@/components/Table/TableFormatters/AmountFormatter.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -35,9 +36,28 @@ export default {
|
||||
columnsExclude: ['automation'],
|
||||
columnsShow: {
|
||||
min: ['name', 'actions'],
|
||||
default: ['name', 'category', 'type', 'actions']
|
||||
default: ['name', 'assets_amount', 'category', 'type', 'actions']
|
||||
},
|
||||
columnsMeta: {
|
||||
assets_amount: {
|
||||
width: '160px',
|
||||
formatter: AmountFormatter,
|
||||
formatterArgs: {
|
||||
async: true,
|
||||
permissions: 'assets.view_asset',
|
||||
getRoute({ row }) {
|
||||
return {
|
||||
name: 'PlatformDetail',
|
||||
params: {
|
||||
id: row.id
|
||||
},
|
||||
query: {
|
||||
tab: 'Assets'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
type: {
|
||||
formatter: ChoicesFormatter
|
||||
},
|
||||
@@ -72,7 +92,10 @@ export default {
|
||||
width: '140px'
|
||||
},
|
||||
internal: {
|
||||
width: '100px'
|
||||
width: '100px',
|
||||
formatterArgs: {
|
||||
showFalse: false
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
formatterArgs: {
|
||||
|
||||
@@ -120,16 +120,20 @@ export default {
|
||||
autoComplete: true,
|
||||
query: (query, cb) => {
|
||||
const { hosts, nodes } = this.getSelectedNodesAndHosts()
|
||||
this.$axios.post('/api/v1/ops/username-hints/', {
|
||||
nodes: nodes,
|
||||
assets: hosts,
|
||||
query: query
|
||||
}).then(data => {
|
||||
const ns = data.map(item => {
|
||||
return { value: item.username }
|
||||
if (hosts.length > 0 && nodes.length > 0) {
|
||||
this.$axios.post('/api/v1/ops/username-hints/', {
|
||||
nodes: nodes,
|
||||
assets: hosts,
|
||||
query: query
|
||||
}).then(data => {
|
||||
const ns = data.map(item => {
|
||||
return { value: item.username }
|
||||
})
|
||||
cb(ns)
|
||||
})
|
||||
cb(ns)
|
||||
})
|
||||
} else {
|
||||
cb([])
|
||||
}
|
||||
}
|
||||
},
|
||||
options: [],
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</template>
|
||||
<template slot="table">
|
||||
<div class="transition-box" style="width: calc(100% - 17px);">
|
||||
<el-tabs v-model="activeEditorId" :closable="true" @tab-remove="onCloseEditor">
|
||||
<el-tabs v-model="activeEditorId" :closable="true" class="workspace-tab" @tab-remove="onCloseEditor">
|
||||
<el-tab-pane
|
||||
v-for="(editor,key) in openedEditor"
|
||||
:key="key"
|
||||
@@ -298,6 +298,12 @@ export default {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.workspace-tab {
|
||||
::v-deep .el-tabs__header {
|
||||
margin: 0 0 15px 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tree {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div slot="tip" class="el-upload__tip">
|
||||
<span :class="{'hasError': hasFileFormatOrSizeError }" />
|
||||
<div v-if="renderError" class="hasError">{{ renderError }}</div>
|
||||
<h5>请上传包含以下示例结构目录的 .zip 压缩文件</h5>
|
||||
<h5>{{ $t('UploadHelpText') }}</h5>
|
||||
<pre style="display:flex; line-height: 1.2em">
|
||||
./
|
||||
├── roles
|
||||
|
||||
@@ -105,6 +105,7 @@ export const AssetPermissionTableMeta = {
|
||||
accounts: {
|
||||
formatter: AmountFormatter,
|
||||
formatterArgs: {
|
||||
cellValueToRemove: ['@SPEC'],
|
||||
getItem(item) {
|
||||
if (item !== '@SPEC') {
|
||||
return AccountLabelMapper[item] || item
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
|
||||
import UserPassword from '@/components/Form/FormFields/UserPassword'
|
||||
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm/index.vue'
|
||||
import UserPassword from '@/components/Form/FormFields/UserPassword.vue'
|
||||
import { IBox } from '@/components'
|
||||
import rules from '@/components/Form/DataForm/rules'
|
||||
import { PasswordInput } from '@/components/Form/FormFields'
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<IBox>
|
||||
<GenericCreateUpdateForm
|
||||
ref="GenericCreateUpdateForm"
|
||||
:fields="fields"
|
||||
:fields-meta="fieldsMeta"
|
||||
:initial="object"
|
||||
:more-buttons="moreButtons"
|
||||
:on-perform-success="onPerformSuccess"
|
||||
:submit-method="submitMethod"
|
||||
:after-get-form-value="afterGetFormValue"
|
||||
:url="url"
|
||||
/>
|
||||
</IBox>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
|
||||
import { IBox } from '@/components'
|
||||
|
||||
export default {
|
||||
name: 'SSHKey',
|
||||
components: {
|
||||
GenericCreateUpdateForm,
|
||||
IBox
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: '/api/v1/users/profile/public-key/',
|
||||
fields: [
|
||||
['', ['current_public_key']],
|
||||
['', ['public_key']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
current_public_key: {
|
||||
label: this.$t('OldPublicKey'),
|
||||
disabled: true
|
||||
},
|
||||
public_key: {
|
||||
label: this.$t('NewPublicKey'),
|
||||
el: {
|
||||
type: 'textarea',
|
||||
placeholder: 'ssh-rsa AAAA...',
|
||||
autosize: { minRows: 3 }
|
||||
},
|
||||
helpText: this.$t('SSHKeyOfProfileSSHUpdatePage')
|
||||
}
|
||||
},
|
||||
moreButtons: [
|
||||
{
|
||||
title: this.$t('ResetAndDownloadSSHKey'),
|
||||
callback: function() {
|
||||
window.open(`/core/auth/profile/pubkey/generate/`, '_blank')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
afterGetFormValue(value) {
|
||||
const publicKey = value['public_key_hash_md5'] ? `${value['public_key_comment']} (${value['public_key_hash_md5']})` : ' '
|
||||
value['current_public_key'] = publicKey
|
||||
return value
|
||||
},
|
||||
submitMethod() {
|
||||
return 'put'
|
||||
},
|
||||
onPerformSuccess() {
|
||||
this.$refs.GenericCreateUpdateForm.$refs.form.$refs.dataForm.resetForm('form')
|
||||
this.$message.success(this.$tc('UpdateSuccessMsg'))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<GenericCreateUpdatePage v-if="!loading" v-bind="$data" :after-get-form-value="afterGetFormValue" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericCreateUpdatePage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
url: '/api/v1/authentication/ssh-key/',
|
||||
fields: [
|
||||
'name',
|
||||
'current_public_key',
|
||||
'public_key',
|
||||
'is_active'
|
||||
],
|
||||
fieldsMeta: {
|
||||
name: {},
|
||||
public_key: {
|
||||
label: this.$t('NewPublicKey'),
|
||||
el: {
|
||||
type: 'textarea',
|
||||
placeholder: 'ssh-rsa AAAA...',
|
||||
autosize: { minRows: 3 }
|
||||
}
|
||||
},
|
||||
current_public_key: {
|
||||
hidden: () => {
|
||||
return this.$route?.meta.action === 'create'
|
||||
},
|
||||
disabled: true,
|
||||
label: this.$t('OldPublicKey')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
afterGetFormValue(value) {
|
||||
const publicKey = value['public_key_hash_md5'] ? `${value['public_key_comment']} (${value['public_key_hash_md5']})` : ' '
|
||||
value['current_public_key'] = publicKey
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
51
src/views/profile/PasswordAndSSHKey/SSHKey/SSHKeyList.vue
Normal file
51
src/views/profile/PasswordAndSSHKey/SSHKey/SSHKeyList.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<ListTable ref="listTable" :header-actions="headerActions" :table-config="tableConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ListTable } from '@/components'
|
||||
import { DateFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'SSHKeyList',
|
||||
components: {
|
||||
ListTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
headerActions: {
|
||||
hasMoreActions: false,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasBulkDelete: false,
|
||||
hasCreate: true
|
||||
},
|
||||
tableConfig: {
|
||||
hasSelection: true,
|
||||
url: '/api/v1/authentication/ssh-key/',
|
||||
columns: ['id', 'name', 'is_active', 'date_created', 'date_last_used'],
|
||||
columnsShow: {
|
||||
min: ['id', 'name']
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: function(row) {
|
||||
return row.name || '-'
|
||||
}
|
||||
},
|
||||
id: {
|
||||
label: 'ID'
|
||||
},
|
||||
date_created: {
|
||||
label: this.$t('DateCreated'),
|
||||
formatter: DateFormatter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
<script>
|
||||
import { GenericDetailPage } from '@/layout/components'
|
||||
import Password from './Password'
|
||||
import SSHKey from './SSHKey'
|
||||
import Password from './Password/Password.vue'
|
||||
import SSHKeyList from './SSHKey/SSHKeyList.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericDetailPage,
|
||||
Password,
|
||||
SSHKey
|
||||
SSHKeyList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -42,7 +42,7 @@ export default {
|
||||
},
|
||||
{
|
||||
title: this.$t('LoginSSHKeySetting'),
|
||||
name: 'SSHKey',
|
||||
name: 'SSHKeyList',
|
||||
disabled: !this.$store.state.users.profile.can_public_key_auth
|
||||
}
|
||||
]
|
||||
|
||||
@@ -22,6 +22,7 @@ import isFalsey from '@/components/Table/DataTable/compenents/el-data-table/util
|
||||
import deepmerge from 'deepmerge'
|
||||
import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
import { download } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'CommandList',
|
||||
@@ -134,10 +135,7 @@ export default {
|
||||
queryUtil.stringify(query, '=', '&')
|
||||
url = url + queryStr
|
||||
this.$log.debug('Export url: ', this.url, '=>', url)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url + queryStr)
|
||||
download(url + queryStr)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
hidden: () => !this.$hasPerm('terminal.view_command')
|
||||
},
|
||||
{
|
||||
title: this.$t('Activity'),
|
||||
title: this.$t('SessionJoinRecords'),
|
||||
name: 'SessionJoinRecords',
|
||||
hidden: () => !this.$hasPerm('terminal.view_sessionjoinrecord')
|
||||
},
|
||||
|
||||
@@ -41,7 +41,7 @@ export default {
|
||||
return {
|
||||
tableConfig: {
|
||||
url: '/api/v1/terminal/applet-hosts/',
|
||||
columnsExclude: ['info'],
|
||||
columnsExclude: ['info', 'auto_config', 'gathered_info', 'deploy_options'],
|
||||
columnsShow: {
|
||||
min: ['name', 'actions'],
|
||||
default: [
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import BaseAuth from './Base'
|
||||
import { JsonRequiredUserNameMapped } from '@/components/Form/DataForm/rules'
|
||||
import { JsonEditor } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'Cas',
|
||||
@@ -28,14 +29,15 @@ export default {
|
||||
'CAS_RENAME_ATTRIBUTES'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'CAS_CREATE_USER', 'CAS_LOGOUT_COMPLETELY'
|
||||
'CAS_ORG_IDS', 'CAS_CREATE_USER', 'CAS_LOGOUT_COMPLETELY'
|
||||
]]
|
||||
],
|
||||
fieldsMeta: {
|
||||
CAS_RENAME_ATTRIBUTES: {
|
||||
component: JsonEditor,
|
||||
rules: [JsonRequiredUserNameMapped]
|
||||
}
|
||||
},
|
||||
CAS_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
submitMethod: () => 'patch',
|
||||
afterGetFormValue(obj) {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
<script>
|
||||
import BaseAuth from './Base'
|
||||
import { UpdateToken } from '@/components/Form/FormFields'
|
||||
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'DingTalk',
|
||||
@@ -39,13 +40,23 @@ export default {
|
||||
],
|
||||
encryptedFields: ['DINGTALK_APPSECRET'],
|
||||
fields: [
|
||||
'AUTH_DINGTALK', 'DINGTALK_AGENTID',
|
||||
'DINGTALK_APPKEY', 'DINGTALK_APPSECRET'
|
||||
[this.$t('Basic'), [
|
||||
'AUTH_DINGTALK', 'DINGTALK_AGENTID',
|
||||
'DINGTALK_APPKEY', 'DINGTALK_APPSECRET',
|
||||
'DINGTALK_RENAME_ATTRIBUTES'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'DINGTALK_ORG_IDS'
|
||||
]]
|
||||
],
|
||||
fieldsMeta: {
|
||||
DINGTALK_APPSECRET: {
|
||||
component: UpdateToken
|
||||
}
|
||||
},
|
||||
DINGTALK_RENAME_ATTRIBUTES: {
|
||||
component: JsonEditor
|
||||
},
|
||||
DINGTALK_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
hasDetailInMsg: false,
|
||||
submitMethod() {
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
|
||||
<script>
|
||||
import BaseAuth from './Base'
|
||||
import { UpdateToken } from '@/components/Form/FormFields'
|
||||
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'Feishu',
|
||||
@@ -33,7 +34,26 @@ export default {
|
||||
},
|
||||
formFields: {
|
||||
type: Array,
|
||||
default: () => ['AUTH_FEISHU', 'FEISHU_APP_ID', 'FEISHU_APP_SECRET']
|
||||
default() {
|
||||
return [[this.$t('Basic'), [
|
||||
'AUTH_FEISHU', 'FEISHU_APP_ID',
|
||||
'FEISHU_APP_SECRET', 'FEISHU_RENAME_ATTRIBUTES'
|
||||
]], [this.$t('Other'), [
|
||||
'FEISHU_ORG_IDS'
|
||||
]]
|
||||
]
|
||||
}
|
||||
},
|
||||
formFieldsMeta: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
FEISHU_RENAME_ATTRIBUTES: {
|
||||
component: JsonEditor
|
||||
},
|
||||
FEISHU_ORG_IDS: getOrgSelect2Meta()
|
||||
}
|
||||
}
|
||||
},
|
||||
enableFieldName: {
|
||||
type: String,
|
||||
@@ -59,12 +79,15 @@ export default {
|
||||
vm.$message.success(res['msg'])
|
||||
}).catch(() => {
|
||||
vm.$log.error('err occur')
|
||||
}).finally(() => { btn.loading = false })
|
||||
}).finally(() => {
|
||||
btn.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
],
|
||||
encryptedFields: this.encryptedFields,
|
||||
fields: this.formFields,
|
||||
fieldsMeta: this.formFieldsMeta,
|
||||
// 不清理的话,编辑secret,在删除提交会报错
|
||||
cleanFormValue(data) {
|
||||
this.encryptedFields.forEach(field => {
|
||||
@@ -81,12 +104,14 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.settings.fieldsMeta = this.encryptedFields.reduce((acc, field) => {
|
||||
const newFieldsMeta = this.encryptedFields.reduce((acc, field) => {
|
||||
acc[field] = {
|
||||
component: UpdateToken
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
Object.assign(this.settings.fieldsMeta, newFieldsMeta)
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
:form-fields="formFields"
|
||||
:title="$tc('setting.Lark')"
|
||||
:encrypted-fields="encryptedFields"
|
||||
:form-fields-meta="formFieldsMeta"
|
||||
:enable-field-name="enableFieldName"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FeiShu from './FeiShu'
|
||||
import { JsonEditor } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'Lark',
|
||||
@@ -24,10 +27,26 @@ export default {
|
||||
return 'AUTH_LARK'
|
||||
},
|
||||
formFields() {
|
||||
return ['AUTH_LARK', 'LARK_APP_ID', 'LARK_APP_SECRET']
|
||||
return [
|
||||
[this.$t('Basic'), [
|
||||
'AUTH_LARK', 'LARK_APP_ID',
|
||||
'LARK_APP_SECRET', 'LARK_RENAME_ATTRIBUTES'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'LARK_ORG_IDS'
|
||||
]]
|
||||
]
|
||||
},
|
||||
encryptedFields() {
|
||||
return ['LARK_APP_SECRET']
|
||||
},
|
||||
formFieldsMeta() {
|
||||
return {
|
||||
LARK_RENAME_ATTRIBUTES: {
|
||||
component: JsonEditor
|
||||
},
|
||||
LARK_ORG_IDS: getOrgSelect2Meta()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { JsonEditor } from '@/components/Form/FormFields'
|
||||
import { JsonRequired } from '@/components/Form/DataForm/rules'
|
||||
import { UploadField } from '@/components'
|
||||
import request from '@/utils/request'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'OAuth2',
|
||||
@@ -43,6 +44,7 @@ export default {
|
||||
'AUTH_OAUTH2_USER_ATTR_MAP'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'OAUTH2_ORG_IDS',
|
||||
'AUTH_OAUTH2_ALWAYS_UPDATE_USER',
|
||||
'AUTH_OAUTH2_LOGOUT_COMPLETELY'
|
||||
]]
|
||||
@@ -66,7 +68,8 @@ export default {
|
||||
rules: [JsonRequired]
|
||||
},
|
||||
AUTH_OAUTH2_ACCESS_TOKEN_METHOD: {
|
||||
}
|
||||
},
|
||||
OAUTH2_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
submitMethod: () => 'patch',
|
||||
afterGetFormValue(obj) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import BaseAuth from './Base'
|
||||
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
|
||||
import { JsonRequired } from '@/components/Form/DataForm/rules'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'OIDC',
|
||||
@@ -39,6 +40,9 @@ export default {
|
||||
]],
|
||||
[this.$t('Search'), [
|
||||
'AUTH_OPENID_USER_ATTR_MAP'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'OPENID_ORG_IDS'
|
||||
]]
|
||||
],
|
||||
fieldsMeta: {
|
||||
@@ -122,7 +126,8 @@ export default {
|
||||
'AUTH_OPENID_USER_ATTR_MAP': {
|
||||
component: JsonEditor,
|
||||
rules: [JsonRequired]
|
||||
}
|
||||
},
|
||||
OPENID_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
submitMethod: () => 'patch',
|
||||
afterGetFormValue(obj) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<script>
|
||||
import BaseAuth from './Base'
|
||||
import { UpdateToken } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'Cas',
|
||||
@@ -22,12 +23,14 @@ export default {
|
||||
encryptedFields: ['RADIUS_SECRET'],
|
||||
fields: [
|
||||
[this.$t('Basic'), ['AUTH_RADIUS', 'RADIUS_SERVER', 'RADIUS_PORT', 'RADIUS_SECRET']],
|
||||
[this.$t('MFA'), ['OTP_IN_RADIUS']]
|
||||
[this.$t('MFA'), ['OTP_IN_RADIUS']],
|
||||
[this.$t('Other'), ['RADIUS_ORG_IDS']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
RADIUS_SECRET: {
|
||||
component: UpdateToken
|
||||
}
|
||||
},
|
||||
RADIUS_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
submitMethod: () => 'patch'
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import BaseAuth from './Base'
|
||||
import { JsonRequired } from '@/components/Form/DataForm/rules'
|
||||
import { UploadKey } from '@/components'
|
||||
import { JsonEditor } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'SAML2',
|
||||
@@ -36,7 +37,7 @@ export default {
|
||||
'SAML2_RENAME_ATTRIBUTES'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'AUTH_SAML2_ALWAYS_UPDATE_USER', 'SAML2_LOGOUT_COMPLETELY'
|
||||
'SAML2_ORG_IDS', 'AUTH_SAML2_ALWAYS_UPDATE_USER', 'SAML2_LOGOUT_COMPLETELY'
|
||||
]]
|
||||
],
|
||||
fieldsMeta: {
|
||||
@@ -67,7 +68,8 @@ export default {
|
||||
SAML2_RENAME_ATTRIBUTES: {
|
||||
component: JsonEditor,
|
||||
rules: [JsonRequired]
|
||||
}
|
||||
},
|
||||
SAML2_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
submitMethod: () => 'patch',
|
||||
afterGetFormValue(obj) {
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
|
||||
<script>
|
||||
import BaseAuth from './Base'
|
||||
import { UpdateToken } from '@/components/Form/FormFields'
|
||||
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'Slack',
|
||||
@@ -41,7 +42,13 @@ export default {
|
||||
],
|
||||
encryptedFields: ['SLACK_SECRET'],
|
||||
fields: [
|
||||
'AUTH_SLACK', 'SLACK_CLIENT_ID', 'SLACK_CLIENT_SECRET', 'SLACK_BOT_TOKEN'
|
||||
[this.$t('Basic'), [
|
||||
'AUTH_SLACK', 'SLACK_CLIENT_ID', 'SLACK_CLIENT_SECRET',
|
||||
'SLACK_BOT_TOKEN', 'SLACK_RENAME_ATTRIBUTES'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'SLACK_ORG_IDS'
|
||||
]]
|
||||
],
|
||||
fieldsMeta: {
|
||||
SLACK_APP_SECRET: {
|
||||
@@ -49,7 +56,11 @@ export default {
|
||||
},
|
||||
SLACK_BOT_TOKEN: {
|
||||
component: UpdateToken
|
||||
}
|
||||
},
|
||||
SLACK_RENAME_ATTRIBUTES: {
|
||||
component: JsonEditor
|
||||
},
|
||||
SLACK_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
// 不清理的话,编辑secret,在删除提交会报错
|
||||
cleanFormValue(data) {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
<script>
|
||||
import BaseAuth from './Base'
|
||||
import { UpdateToken } from '@/components/Form/FormFields'
|
||||
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
|
||||
import { getOrgSelect2Meta } from '@/views/settings/Auth/const'
|
||||
|
||||
export default {
|
||||
name: 'WeCom',
|
||||
@@ -34,18 +35,30 @@ export default {
|
||||
vm.$message.success(res['msg'])
|
||||
}).catch(() => {
|
||||
vm.$log.error('err occur')
|
||||
}).finally(() => { btn.loading = false })
|
||||
}).finally(() => {
|
||||
btn.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
],
|
||||
encryptedFields: ['WECOM_SECRET'],
|
||||
fields: [
|
||||
'AUTH_WECOM', 'WECOM_CORPID', 'WECOM_AGENTID', 'WECOM_SECRET'
|
||||
[this.$t('Basic'), [
|
||||
'AUTH_WECOM', 'WECOM_CORPID', 'WECOM_AGENTID',
|
||||
'WECOM_SECRET', 'WECOM_RENAME_ATTRIBUTES'
|
||||
]],
|
||||
[this.$t('Other'), [
|
||||
'WECOM_ORG_IDS'
|
||||
]]
|
||||
],
|
||||
fieldsMeta: {
|
||||
WECOM_SECRET: {
|
||||
component: UpdateToken
|
||||
}
|
||||
},
|
||||
WECOM_RENAME_ATTRIBUTES: {
|
||||
component: JsonEditor
|
||||
},
|
||||
WECOM_ORG_IDS: getOrgSelect2Meta()
|
||||
},
|
||||
// 不清理的话,编辑secret,在删除提交会报错
|
||||
cleanFormValue(data) {
|
||||
@@ -60,8 +73,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
20
src/views/settings/Auth/const.js
Normal file
20
src/views/settings/Auth/const.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Select2 } from '@/components/Form/FormFields'
|
||||
import store from '@/store'
|
||||
|
||||
export function getOrgSelect2Meta() {
|
||||
return {
|
||||
component: Select2,
|
||||
el: {
|
||||
multiple: true,
|
||||
ajax: {
|
||||
url: '/api/v1/orgs/orgs/',
|
||||
transformOption: (item) => {
|
||||
return { label: item.name, value: item.id }
|
||||
}
|
||||
}
|
||||
},
|
||||
hidden: () => {
|
||||
return !store.getters.hasValidLicense
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,7 @@ export default {
|
||||
},
|
||||
EMAIL_HOST_USER: {
|
||||
rules: [
|
||||
rules.EmailCheck,
|
||||
rules.Required
|
||||
rules.EmailCheck
|
||||
]
|
||||
},
|
||||
EMAIL_CUSTOM_USER_CREATED_BODY: {
|
||||
|
||||
@@ -64,8 +64,7 @@ export default {
|
||||
method: 'push_account_method',
|
||||
assets: this.asset_ids,
|
||||
nodes: this.node_ids
|
||||
},
|
||||
helpText: this.$t('ViewBlockedIPSHelpText')
|
||||
}
|
||||
}
|
||||
},
|
||||
cleanFormValue(value) {
|
||||
|
||||
@@ -52,6 +52,14 @@ export default {
|
||||
el: {
|
||||
hiddenGroup: true
|
||||
}
|
||||
},
|
||||
TERMINAL_MAGNUS_ENABLED: {
|
||||
hidden: () => {
|
||||
return !this.$store.getters.hasValidLicense
|
||||
},
|
||||
el: {
|
||||
hiddenGroup: true
|
||||
}
|
||||
}
|
||||
},
|
||||
getUrl: () => '/api/v1/settings/setting/?category=terminal',
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
loading: true,
|
||||
fields: [
|
||||
[this.$t('Basic'), ['type']],
|
||||
[this.$t('ApprovaLevel'), ['approval_level', 'rules']]
|
||||
[this.$t('ApprovalLevel'), ['approval_level', 'rules']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
type: {
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<!-- 自定义组件 my-input -->
|
||||
<div>
|
||||
<div v-for="(item, i) of approveData" :key="i" style="margin-bottom: 10px">
|
||||
<div v-for="(item, i) of approveData" :key="i">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>{{ i + 1 + ' ' + vm.$t('LevelApproval') }}</span>
|
||||
<span>{{ `${i + 1} ${$t('LevelApproval')}` }}</span>
|
||||
</div>
|
||||
<el-radio-group v-model="item.strategy.value" @change="onChange()">
|
||||
<el-radio label="super_admin">{{ vm.$t('SuperAdmin') }}</el-radio>
|
||||
<el-radio label="org_admin">{{ vm.$t('OrgAdmin') }}</el-radio>
|
||||
<el-radio label="super_org_admin">{{ vm.$t('SuperOrgAdmin') }}</el-radio>
|
||||
<el-radio label="custom_user">{{ vm.$t('CustomUser') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<Select2 v-show="item.strategy.value === 'custom_user'" v-model="item.assignees" v-bind="select2Option" @change="onChange()" />
|
||||
<JSONManyToManySelect
|
||||
:value="item.users"
|
||||
:resource="userComponentMeta.el.resource"
|
||||
:select2="userComponentMeta.el.select2"
|
||||
:attrs="userComponentMeta.el.attrs"
|
||||
@input="handleInput(i, $event)"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Select2 from '@/components/Form/FormFields/Select2'
|
||||
import { JSONManyToManySelect } from '@/components/Form/FormFields'
|
||||
import { userJSONSelectMeta } from '@/views/users/const'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Select2
|
||||
JSONManyToManySelect
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
@@ -37,49 +37,56 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
vm: this,
|
||||
userComponentMeta: userJSONSelectMeta(this),
|
||||
defaultRule: [
|
||||
{
|
||||
strategy: {
|
||||
value: 'super_admin'
|
||||
},
|
||||
assignees_read_only: []
|
||||
users: {
|
||||
type: 'attrs',
|
||||
attrs: [
|
||||
{
|
||||
match: 'm2m',
|
||||
name: 'system_roles',
|
||||
'value': ['00000000-0000-0000-0000-000000000001']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
select2Option: {
|
||||
url: '/api/v1/users/users/?oid=root'
|
||||
},
|
||||
fields: [
|
||||
]
|
||||
rules: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
approveData() {
|
||||
let rules = []
|
||||
return this.getSortedRules()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
level: {
|
||||
handler() {
|
||||
this.updateApproveData()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateApproveData()
|
||||
},
|
||||
methods: {
|
||||
getSortedRules() {
|
||||
return [...this.rules].sort((a, b) => a.level - b.level)
|
||||
},
|
||||
updateApproveData() {
|
||||
let rules = [...this.value]
|
||||
if (this.value.length === 2 && this.level === 1) {
|
||||
rules = this.value.slice(0, this.level)
|
||||
} else if (this.value.length === 1 && this.level === 2) {
|
||||
rules = this.value.concat(this.defaultRule)
|
||||
} else {
|
||||
rules = this.value
|
||||
}
|
||||
rules.forEach(rule => {
|
||||
if (rule.assignees_read_only) {
|
||||
rule['assignees'] = rule.assignees_read_only
|
||||
delete rule.assignees_read_only
|
||||
}
|
||||
})
|
||||
rules = rules.sort((a, b) => a.level - b.level)
|
||||
this.$emit('input', rules)
|
||||
return rules
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('input', this.approveData)
|
||||
},
|
||||
methods: {
|
||||
onChange() {
|
||||
this.$emit('input', this.approveData)
|
||||
this.rules = rules
|
||||
this.$emit('input', this.rules)
|
||||
},
|
||||
handleInput(index, event) {
|
||||
this.$set(this.rules, index, { 'users': event })
|
||||
this.$emit('input', this.rules)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,25 +99,16 @@ export default {
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 18px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
width: 600px;
|
||||
width: 96%;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: unset !important;
|
||||
|
||||
::v-deep .el-card__body {
|
||||
padding: 10px 30px !important;
|
||||
|
||||
.el-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.el-radio {
|
||||
padding: 5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
</a>
|
||||
<div class="media-body ">
|
||||
<strong>{{ item.user_display }}</strong>
|
||||
<small class="text-muted">{{ formatTime(item.date_created) }}</small>
|
||||
<br>
|
||||
<small class="text-muted">{{ item.date_created | date }}</small>
|
||||
<MarkDown :value="item.body" />
|
||||
</div>
|
||||
|
||||
@@ -37,12 +37,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -174,8 +174,8 @@ export default {
|
||||
},
|
||||
afterGetFormValue(obj) {
|
||||
if (obj?.id) {
|
||||
obj.org_roles = obj.org_roles.map(({ id }) => id)
|
||||
obj.system_roles = obj.system_roles.map(({ id }) => id)
|
||||
obj.org_roles = obj.org_roles?.map(({ id }) => id)
|
||||
obj.system_roles = obj.system_roles?.map(({ id }) => id)
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
<template>
|
||||
<Page>
|
||||
<GrantedAssets :actions="actions" :table-url="tableUrl" :tree-url="treeUrl" />
|
||||
<GrantedAssets
|
||||
ref="grantedAssets"
|
||||
:name="name"
|
||||
:comment="comment"
|
||||
:actions="actions"
|
||||
:table-url="tableUrl"
|
||||
:tree-url="treeUrl"
|
||||
/>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GrantedAssets from '@/components/Apps/GrantedAssets/index.vue'
|
||||
import Page from '@/layout/components/Page/index.vue'
|
||||
import { EditableInputFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -48,7 +56,27 @@ export default {
|
||||
]
|
||||
}
|
||||
},
|
||||
allFavorites: []
|
||||
allFavorites: [],
|
||||
name: {
|
||||
formatter: EditableInputFormatter,
|
||||
formatterArgs: {
|
||||
canEdit: true,
|
||||
showEditBtn: true,
|
||||
onEnter: ({ row, col, oldValue, newValue }) => {
|
||||
this.updateAssetCustomAttr(row, col, oldValue, newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
comment: {
|
||||
formatter: EditableInputFormatter,
|
||||
formatterArgs: {
|
||||
canEdit: true,
|
||||
showEditBtn: true,
|
||||
onEnter: ({ row, col, oldValue, newValue }) => {
|
||||
this.updateAssetCustomAttr(row, col, oldValue, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -94,6 +122,23 @@ export default {
|
||||
}
|
||||
})
|
||||
return ok
|
||||
},
|
||||
updateAssetCustomAttr(row, col, oldValue, newValue) {
|
||||
if (oldValue.toString() === newValue.toString()) {
|
||||
return
|
||||
}
|
||||
const colProp = col.prop
|
||||
|
||||
this.$axios.post('/api/v1/assets/my-asset/', {
|
||||
asset: row.id,
|
||||
[colProp]: newValue
|
||||
}).catch((e) => {
|
||||
this.$message.error(e?.response?.request?.responseText || this.$t('BadRequestErrorMsg'))
|
||||
return Promise.reject(e)
|
||||
}).then(() => {
|
||||
this.$set(row, colProp, newValue)
|
||||
this.$message.success(this.$t('UpdateSuccessMsg'))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ module.exports = {
|
||||
changeOrigin: true
|
||||
},
|
||||
'/ws/': {
|
||||
target: process.env.VUE_APP_CORE_WS || 'ws://1237.0.0.1:8080',
|
||||
target: process.env.VUE_APP_CORE_WS || 'ws://127.0.0.1:8080',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user