mirror of
https://github.com/cuigh/swirl
synced 2025-06-26 18:16:50 +00:00
Support token auth
This commit is contained in:
@@ -17,6 +17,10 @@ export interface User {
|
||||
status: number;
|
||||
email: string;
|
||||
roles: string[];
|
||||
tokens: {
|
||||
name: string;
|
||||
value: string;
|
||||
}[];
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
createdBy: {
|
||||
|
||||
@@ -29,6 +29,7 @@ export default {
|
||||
"update": "Update",
|
||||
"submit": "Submit",
|
||||
"home": "Return to home",
|
||||
"copy": "Copy",
|
||||
},
|
||||
"perms": {
|
||||
"view": "View",
|
||||
@@ -182,6 +183,7 @@ export default {
|
||||
"admins": "Admins",
|
||||
"active": "Active",
|
||||
"blocked": "Blocked",
|
||||
"tokens": "Tokens",
|
||||
"perms": "Permissions",
|
||||
"margin": "Margin",
|
||||
"metrics": "Metrics",
|
||||
@@ -372,6 +374,7 @@ export default {
|
||||
"profile": "User personal information",
|
||||
"password": "User login password",
|
||||
"preference": "User personalization",
|
||||
"copied": "Copied",
|
||||
"required_rule": "Cannot be empty",
|
||||
"email_rule": "Incorrect email format",
|
||||
"length_rule": "The length must be {min}-{max} bits",
|
||||
|
||||
@@ -29,6 +29,7 @@ export default {
|
||||
"update": "更新",
|
||||
"submit": "提交",
|
||||
"home": "返回首页",
|
||||
"copy": "复制",
|
||||
},
|
||||
"perms": {
|
||||
"view": "浏览",
|
||||
@@ -182,6 +183,7 @@ export default {
|
||||
"admins": "@:fields.admin",
|
||||
"active": "正常",
|
||||
"blocked": "屏蔽",
|
||||
"tokens": "凭证",
|
||||
"perms": "权限",
|
||||
"margin": "边距",
|
||||
"metrics": "指标",
|
||||
@@ -372,6 +374,7 @@ export default {
|
||||
"profile": "用户个人资料",
|
||||
"password": "用户登录密码",
|
||||
"preference": "用户个性化设置",
|
||||
"copied": "已复制",
|
||||
"required_rule": "不能为空",
|
||||
"email_rule": "电子邮箱格式不正确",
|
||||
"length_rule": "长度必须为 {min}-{max} 位",
|
||||
|
||||
@@ -28,6 +28,38 @@
|
||||
<n-form-item-gi :label="t('fields.email')" path="email">
|
||||
<n-input :placeholder="t('fields.email')" v-model:value="profile.email" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :label="t('fields.tokens')" path="tokens" span="2">
|
||||
<n-dynamic-input
|
||||
v-model:value="profile.tokens"
|
||||
#="{ index, value }"
|
||||
:on-create="() => ({ name: '', value: guid() })"
|
||||
>
|
||||
<n-input
|
||||
:placeholder="t('fields.name')"
|
||||
v-model:value="value.name"
|
||||
style="width: 300px"
|
||||
/>
|
||||
<div style="height: 34px; line-height: 34px; margin: 0 8px">=</div>
|
||||
<n-input-group>
|
||||
<n-input :placeholder="t('fields.value')" v-model:value="value.value" readonly></n-input>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button
|
||||
type="default"
|
||||
#icon
|
||||
@click="() => copy(value.value)"
|
||||
v-if="isSupported"
|
||||
>
|
||||
<n-icon>
|
||||
<copy-icon />
|
||||
</n-icon>
|
||||
</n-button>
|
||||
</template>
|
||||
{{ t(copied ? 'tips.copied' : 'buttons.copy') }}
|
||||
</n-tooltip>
|
||||
</n-input-group>
|
||||
</n-dynamic-input>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
<n-button
|
||||
@@ -153,6 +185,7 @@ import {
|
||||
NButton,
|
||||
NSpace,
|
||||
NInput,
|
||||
NInputGroup,
|
||||
NIcon,
|
||||
NForm,
|
||||
NFormItem,
|
||||
@@ -161,9 +194,12 @@ import {
|
||||
NRadioButton,
|
||||
NRadioGroup,
|
||||
NAlert,
|
||||
NDynamicInput,
|
||||
NTooltip,
|
||||
} from "naive-ui";
|
||||
import {
|
||||
SaveOutline as SaveIcon,
|
||||
CopyOutline as CopyIcon,
|
||||
} from "@vicons/ionicons5";
|
||||
import XPageHeader from "@/components/PageHeader.vue";
|
||||
import XPanel from "@/components/Panel.vue";
|
||||
@@ -173,6 +209,8 @@ import { useForm, emailRule, requiredRule, customRule, lengthRule } from "@/util
|
||||
import { Mutations } from "@/store/mutations";
|
||||
import { useStore } from "vuex";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { guid } from "@/utils";
|
||||
|
||||
const { t } = useI18n()
|
||||
const panel = ref('')
|
||||
@@ -193,6 +231,7 @@ const profileRules: any = {
|
||||
};
|
||||
const profileForm = ref();
|
||||
const { submit: modifyProfile, submiting: profileSubmiting } = useForm(profileForm, () => userApi.modifyProfile(profile.value))
|
||||
const { copy, copied, isSupported } = useClipboard()
|
||||
|
||||
// password
|
||||
const password = reactive({
|
||||
|
||||
@@ -1,39 +1,6 @@
|
||||
<template>
|
||||
<x-page-header />
|
||||
<n-space class="page-body" vertical :size="12">
|
||||
<x-panel title="Deployment" divider="bottom" :collapsed="panel !== 'deploy'" v-if="false">
|
||||
<template #action>
|
||||
<n-button
|
||||
secondary
|
||||
strong
|
||||
class="toggle"
|
||||
size="small"
|
||||
@click="togglePanel('deploy')"
|
||||
>{{ panel === 'deploy' ? t('buttons.collapse') : t('buttons.expand') }}</n-button>
|
||||
</template>
|
||||
<div style="padding: 4px 0 0 12px">
|
||||
<n-form :model="setting" ref="formDeploy" :show-feedback="false">
|
||||
<n-form-item :label="t('fields.keys')" path="deploy.keys">
|
||||
<n-dynamic-input
|
||||
v-model:value="setting.deploy.keys"
|
||||
#="{ index, value }"
|
||||
:on-create="newKey"
|
||||
>
|
||||
<n-input-group>
|
||||
<n-input :placeholder="t('fields.name')" v-model:value="value.name" />
|
||||
<n-input :placeholder="t('fields.token')" v-model:value="value.token" />
|
||||
<n-date-picker :placeholder="t('fields.expiry')" v-model:value="value.expiry" type="date" clearable style="min-width: 200px"/>
|
||||
</n-input-group>
|
||||
</n-dynamic-input>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button
|
||||
type="primary"
|
||||
style="margin-top: 12px"
|
||||
@click="() => save('deploy', setting.deploy)"
|
||||
>{{ t('buttons.save') }}</n-button>
|
||||
</div>
|
||||
</x-panel>
|
||||
<x-panel title="LDAP" :subtitle="t('tips.ldap')" divider="bottom" :collapsed="panel !== 'ldap'">
|
||||
<template #action>
|
||||
<n-button
|
||||
@@ -181,10 +148,8 @@ import {
|
||||
NFormItemGi,
|
||||
NRadioGroup,
|
||||
NRadio,
|
||||
NDynamicInput,
|
||||
NSwitch,
|
||||
NAlert,
|
||||
NDatePicker,
|
||||
} from "naive-ui";
|
||||
import XPageHeader from "@/components/PageHeader.vue";
|
||||
import XPanel from "@/components/Panel.vue";
|
||||
@@ -211,10 +176,6 @@ function togglePanel(name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function newKey() {
|
||||
return { name: '', token: '', expiry: undefined }
|
||||
}
|
||||
|
||||
async function save(id: string, options: any) {
|
||||
await settingApi.save(id, options)
|
||||
window.message.info(t('texts.action_success'));
|
||||
|
||||
@@ -71,6 +71,38 @@
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :label="t('fields.tokens', 2)" span="2" path="tokens">
|
||||
<n-dynamic-input
|
||||
v-model:value="user.tokens"
|
||||
#="{ index, value }"
|
||||
:on-create="() => ({ name: '', value: guid() })"
|
||||
>
|
||||
<n-input
|
||||
:placeholder="t('fields.name')"
|
||||
v-model:value="value.name"
|
||||
style="width: 300px"
|
||||
/>
|
||||
<div style="height: 34px; line-height: 34px; margin: 0 8px">=</div>
|
||||
<n-input-group>
|
||||
<n-input :placeholder="t('fields.value')" v-model:value="value.value" readonly></n-input>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button
|
||||
type="default"
|
||||
#icon
|
||||
@click="() => copy(value.value)"
|
||||
v-if="isSupported"
|
||||
>
|
||||
<n-icon>
|
||||
<copy-icon />
|
||||
</n-icon>
|
||||
</n-button>
|
||||
</template>
|
||||
{{ t(copied ? 'tips.copied' : 'buttons.copy') }}
|
||||
</n-tooltip>
|
||||
</n-input-group>
|
||||
</n-dynamic-input>
|
||||
</n-form-item-gi>
|
||||
<n-gi :span="2">
|
||||
<n-button
|
||||
:disabled="submiting"
|
||||
@@ -97,6 +129,7 @@ import {
|
||||
NButton,
|
||||
NSpace,
|
||||
NInput,
|
||||
NInputGroup,
|
||||
NIcon,
|
||||
NForm,
|
||||
NGrid,
|
||||
@@ -107,10 +140,13 @@ import {
|
||||
NCheckbox,
|
||||
NRadioGroup,
|
||||
NRadio,
|
||||
NDynamicInput,
|
||||
NTooltip,
|
||||
} from "naive-ui";
|
||||
import {
|
||||
ArrowBackCircleOutline as BackIcon,
|
||||
SaveOutline as SaveIcon,
|
||||
CopyOutline as CopyIcon,
|
||||
} from "@vicons/ionicons5";
|
||||
import XPageHeader from "@/components/PageHeader.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
@@ -119,8 +155,10 @@ import userApi from "@/api/user";
|
||||
import roleApi from "@/api/role";
|
||||
import type { User } from "@/api/user";
|
||||
import type { Role } from "@/api/role";
|
||||
import { useForm, emailRule, requiredRule } from "@/utils/form";
|
||||
import { useForm, emailRule, requiredRule, customRule } from "@/utils/form";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { guid } from "@/utils";
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute();
|
||||
@@ -132,12 +170,16 @@ const rules: any = {
|
||||
email: [requiredRule(), emailRule()],
|
||||
password: requiredRule(),
|
||||
passwordConfirm: requiredRule(),
|
||||
tokens: customRule((rule: any, value: any[]) => {
|
||||
return value?.every(v => v.name && v.value)
|
||||
}, t('tips.required_rule')),
|
||||
};
|
||||
const form = ref();
|
||||
const { submit, submiting } = useForm(form, () => userApi.save(user.value), () => {
|
||||
window.message.info(t('texts.action_success'));
|
||||
router.push({ name: 'user_list' })
|
||||
})
|
||||
const { copy, copied, isSupported } = useClipboard()
|
||||
|
||||
async function fetchData() {
|
||||
const id = route.params.id as string || ''
|
||||
|
||||
@@ -46,4 +46,12 @@ export function isEmpty(...arrs: (any[] | undefined)[]): boolean {
|
||||
|
||||
export function toTitle(s: string): string {
|
||||
return s ? s[0].toUpperCase() + s.substring(1) : s
|
||||
}
|
||||
|
||||
export function guid() {
|
||||
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4()
|
||||
}
|
||||
|
||||
function s4() {
|
||||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export const perms = [
|
||||
},
|
||||
{
|
||||
key: 'service',
|
||||
actions: ['view', 'edit', 'delete', 'restart', 'rollback', 'logs'],
|
||||
actions: ['view', 'edit', 'delete', 'deploy', 'restart', 'rollback', 'logs'],
|
||||
},
|
||||
{
|
||||
key: 'task',
|
||||
|
||||
Reference in New Issue
Block a user