40 changed files with 1291 additions and 129 deletions
@ -1,29 +1,408 @@ |
|||||
<template> |
<template> |
||||
<BasicModal |
<BasicModal |
||||
@register="registerModal" |
@register="registerModal" |
||||
:title="title" |
:title="L('Applications')" |
||||
:can-fullscreen="false" |
:can-fullscreen="false" |
||||
:show-ok-btn="false" |
|
||||
:width="800" |
:width="800" |
||||
:height="500" |
:height="500" |
||||
|
:close-func="handleBeforeClose" |
||||
|
@ok="handleSubmit" |
||||
> |
> |
||||
|
<Form |
||||
|
ref="formRef" |
||||
|
:model="state.application" |
||||
|
:rules="state.formRules" |
||||
|
:label-col="{ span: 6 }" |
||||
|
:wrapper-col="{ span: 18 }" |
||||
|
> |
||||
|
<Tabs v-model:active-key="state.activeTab" @change="handleTabChange"> |
||||
|
<!-- Basic --> |
||||
|
<TabPane key="basic" :tab="L('BasicInfo')"> |
||||
|
<FormItem name="clientId" :label="L('DisplayName:ClientId')"> |
||||
|
<Input :disabled="state.isEdit" v-model:value="state.application.clientId" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="type" :label="L('DisplayName:Type')"> |
||||
|
<Input v-model:value="state.application.type" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="clientUri" :label="L('DisplayName:ClientUri')"> |
||||
|
<Input v-model:value="state.application.clientUri" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="logoUri" :label="L('DisplayName:LogoUri')"> |
||||
|
<Input v-model:value="state.application.logoUri" /> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
name="consentType" |
||||
|
:label="L('DisplayName:ConsentType')" |
||||
|
:label-col="{ span: 4 }" |
||||
|
:wrapper-col="{ span: 20 }" |
||||
|
> |
||||
|
<Select :options="consentTypes" v-model:value="state.application.consentType" /> |
||||
|
</FormItem> |
||||
|
</TabPane> |
||||
|
<!-- DisplayName --> |
||||
|
<TabPane key="displayName" :tab="L('DisplayNames')"> |
||||
|
<FormItem name="displayName" :label="L('DisplayName:DefaultDisplayName')"> |
||||
|
<Input v-model:value="state.application.displayName" /> |
||||
|
</FormItem> |
||||
|
<DisplayNameForm |
||||
|
:displayNames="state.application.displayNames" |
||||
|
@create="handleNewDisplayName" |
||||
|
@delete="handleDelDisplayName" |
||||
|
/> |
||||
|
</TabPane> |
||||
|
<!-- Endpoints --> |
||||
|
<TabPane key="endpoints"> |
||||
|
<template #tab> |
||||
|
<Dropdown> |
||||
|
<span |
||||
|
>{{ L('Endpoints') }} |
||||
|
<DownOutlined /> |
||||
|
</span> |
||||
|
<template #overlay> |
||||
|
<Menu @click="handleClickUrisMenu"> |
||||
|
<MenuItem |
||||
|
key="redirectUris" |
||||
|
> |
||||
|
{{ L('DisplayName:RedirectUris') }} |
||||
|
</MenuItem> |
||||
|
<MenuItem |
||||
|
key="postLogoutRedirectUris" |
||||
|
> |
||||
|
{{ L('DisplayName:PostLogoutRedirectUris') }} |
||||
|
</MenuItem> |
||||
|
</Menu> |
||||
|
</template> |
||||
|
</Dropdown> |
||||
|
</template> |
||||
|
<component |
||||
|
:is="componentsRef[state.endPoint.component]" |
||||
|
:uris="state.endPoint.uris" |
||||
|
@create-redirect-uri="handleNewRedirectUri" |
||||
|
@delete-redirect-uri="handleDelRedirectUri" |
||||
|
@create-logout-uri="handleNewLogoutUri" |
||||
|
@delete-logout-uri="handleDelLogoutUri" |
||||
|
/> |
||||
|
</TabPane> |
||||
|
<!-- Scopes --> |
||||
|
<TabPane key="permissions" :tab="L('Scopes')"> |
||||
|
<ApplicationScope |
||||
|
:scopes="state.application.scopes" |
||||
|
:support-scopes="state.openIdConfiguration?.scopes_supported" |
||||
|
@create="handleNewScope" |
||||
|
@delete="handleDelScope" |
||||
|
/> |
||||
|
</TabPane> |
||||
|
<!-- Authorizations --> |
||||
|
<TabPane key="authorizations" :tab="L('Authorizations')"> |
||||
|
<FormItem |
||||
|
name="endpoints" |
||||
|
:label="L('DisplayName:Endpoints')" |
||||
|
:label-col="{ span: 4 }" |
||||
|
:wrapper-col="{ span: 20 }" |
||||
|
> |
||||
|
<Select :options="endpoints" mode="tags" v-model:value="state.application.endpoints" /> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
name="grantTypes" |
||||
|
:label="L('DisplayName:GrantTypes')" |
||||
|
:label-col="{ span: 4 }" |
||||
|
:wrapper-col="{ span: 20 }" |
||||
|
> |
||||
|
<Select :options="grantTypes" mode="tags" v-model:value="state.application.grantTypes" /> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
name="responseTypes" |
||||
|
:label="L('DisplayName:ResponseTypes')" |
||||
|
:label-col="{ span: 4 }" |
||||
|
:wrapper-col="{ span: 20 }" |
||||
|
> |
||||
|
<Select :options="responseTypes" mode="tags" v-model:value="state.application.responseTypes" /> |
||||
|
</FormItem> |
||||
|
</TabPane> |
||||
|
<!-- Propertites --> |
||||
|
<TabPane key="propertites" :tab="L('Propertites')"> |
||||
|
<PropertyForm |
||||
|
:properties="state.application.properties" |
||||
|
@create="handleNewProperty" |
||||
|
@delete="handleDelProperty" |
||||
|
/> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</Form> |
||||
</BasicModal> |
</BasicModal> |
||||
</template> |
</template> |
||||
|
|
||||
<script lang="ts" setup> |
<script lang="ts" setup> |
||||
import { computed } from 'vue'; |
import type { FormInstance } from 'ant-design-vue/lib/form'; |
||||
import { Form } from 'ant-design-vue'; |
import { cloneDeep } from 'lodash-es'; |
||||
|
import { computed, nextTick, ref, unref, reactive, shallowRef, onMounted, watch } from 'vue'; |
||||
|
import { DownOutlined } from '@ant-design/icons-vue'; |
||||
|
import { Dropdown, Form, Menu, Input, Select, Tabs } from 'ant-design-vue'; |
||||
import { BasicModal, useModalInner } from '/@/components/Modal'; |
import { BasicModal, useModalInner } from '/@/components/Modal'; |
||||
|
import { useMessage } from '/@/hooks/web/useMessage'; |
||||
|
import { useValidation } from '/@/hooks/abp/useValidation'; |
||||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { ApplicationState } from '../types/props'; |
||||
|
import { GetAsyncById, CreateAsyncByInput, UpdateAsyncByIdAndInput } from '/@/api/openiddict/open-iddict-application'; |
||||
|
import { OpenIddictApplicationDto } from '/@/api/openiddict/open-iddict-application/model'; |
||||
|
import { discovery } from '/@/api/identity-server/identityServer'; |
||||
|
import RedirectUri from './RedirectUri.vue'; |
||||
|
import PostLogoutRedirectUri from './PostLogoutRedirectUri.vue'; |
||||
|
import DisplayNameForm from '../../components/DisplayNames/DisplayNameForm.vue'; |
||||
|
import ApplicationScope from './ApplicationScope.vue'; |
||||
|
import PropertyForm from '../../components/Properties/PropertyForm.vue'; |
||||
|
|
||||
const FormItem = Form.Item; |
const FormItem = Form.Item; |
||||
|
const TabPane = Tabs.TabPane; |
||||
|
const MenuItem = Menu.Item; |
||||
|
|
||||
|
const emits = defineEmits(['register', 'change']); |
||||
|
const { L } = useLocalization(['AbpOpenIddict']); |
||||
|
const { ruleCreator } = useValidation(); |
||||
|
const { createMessage, createConfirm } = useMessage(); |
||||
|
const componentsRef = shallowRef({ |
||||
|
'redirectUris': RedirectUri, |
||||
|
'postLogoutRedirectUris': PostLogoutRedirectUri, |
||||
|
}); |
||||
|
const formRef = ref<FormInstance>(); |
||||
|
const state = reactive<ApplicationState>({ |
||||
|
activeTab: 'basic', |
||||
|
isEdit: false, |
||||
|
entityChanged: false, |
||||
|
application: {} as OpenIddictApplicationDto, |
||||
|
formRules: { |
||||
|
clientId: ruleCreator.fieldRequired({ |
||||
|
name: 'ClientId', |
||||
|
resourceName: 'AbpOpenIddict', |
||||
|
trigger: 'blur', |
||||
|
}), |
||||
|
}, |
||||
|
endPoint: { |
||||
|
component: '', |
||||
|
uris: [], |
||||
|
}, |
||||
|
}); |
||||
|
watch( |
||||
|
() => state.application, |
||||
|
() => { |
||||
|
state.entityChanged = true; |
||||
|
}, |
||||
|
{ |
||||
|
deep: true, |
||||
|
}, |
||||
|
); |
||||
|
const consentTypes = reactive([ |
||||
|
{ label: 'explicit', value: 'explicit' }, |
||||
|
{ label: 'external', value: 'external' }, |
||||
|
{ label: 'implicit', value: 'implicit' }, |
||||
|
{ label: 'systematic', value: 'systematic' }, |
||||
|
]); |
||||
|
const endpoints = reactive([ |
||||
|
{ label: 'authorization', value: 'authorization' }, |
||||
|
{ label: 'token', value: 'token' }, |
||||
|
{ label: 'logout', value: 'logout' }, |
||||
|
{ label: 'device', value: 'device' }, |
||||
|
{ label: 'revocation', value: 'revocation' }, |
||||
|
{ label: 'introspection', value: 'introspection' }, |
||||
|
]); |
||||
|
const grantTypes = computed(() => { |
||||
|
if (!state.openIdConfiguration) return[]; |
||||
|
const types = state.openIdConfiguration.grant_types_supported; |
||||
|
return types.map((type) => { |
||||
|
return { |
||||
|
label: type, |
||||
|
value: type, |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
const responseTypes = computed(() => { |
||||
|
if (!state.openIdConfiguration) return[]; |
||||
|
const types = state.openIdConfiguration.response_types_supported; |
||||
|
return types.map((type) => { |
||||
|
return { |
||||
|
label: type, |
||||
|
value: type, |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
const [registerModal, { changeLoading, changeOkLoading, closeModal }] = useModalInner((data) => { |
||||
|
nextTick(() => { |
||||
|
fetch(data?.id); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
onMounted(initOpenidDiscovery); |
||||
|
|
||||
const { L } = useLocalization('AbpOpenIddict'); |
function initOpenidDiscovery() { |
||||
const [registerModal] = useModalInner((data) => { |
discovery().then((openIdConfiuration) => { |
||||
console.log(data); |
state.openIdConfiguration = openIdConfiuration; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function fetch(id?: string) { |
||||
|
state.activeTab = 'basic'; |
||||
|
state.isEdit = false; |
||||
|
state.entityChanged = false; |
||||
|
state.application = {} as OpenIddictApplicationDto; |
||||
|
const form = unref(formRef); |
||||
|
form?.resetFields(); |
||||
|
if (!id) { |
||||
|
nextTick(() => { |
||||
|
state.isEdit = false; |
||||
|
state.entityChanged = false; |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
changeLoading(true); |
||||
|
GetAsyncById(id).then((application) => { |
||||
|
state.application = application; |
||||
|
nextTick(() => { |
||||
|
state.isEdit = true; |
||||
|
state.entityChanged = false; |
||||
|
}); |
||||
|
}).finally(() => { |
||||
|
changeLoading(false); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleTabChange(activeKey) { |
||||
|
state.activeTab = activeKey; |
||||
|
switch (activeKey) { |
||||
|
case 'endpoints': |
||||
|
if (!state.endPoint.component) { |
||||
|
state.endPoint = { |
||||
|
component : 'redirectUris', |
||||
|
uris: state.application?.redirectUris, |
||||
|
}; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleNewLogoutUri(uri: string) { |
||||
|
if (!state.application) { |
||||
|
return; |
||||
|
} |
||||
|
state.application.postLogoutRedirectUris ??= []; |
||||
|
state.application.postLogoutRedirectUris.push(uri); |
||||
|
} |
||||
|
|
||||
|
function handleDelLogoutUri(uri: string) { |
||||
|
if (!state.application || !state.application.postLogoutRedirectUris) { |
||||
|
return; |
||||
|
} |
||||
|
const index = state.application.postLogoutRedirectUris.findIndex(logoutUri => logoutUri === uri); |
||||
|
if (!index) { |
||||
|
state.application.postLogoutRedirectUris.splice(index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleNewRedirectUri(uri: string) { |
||||
|
if (!state.application) { |
||||
|
return; |
||||
|
} |
||||
|
state.application.redirectUris ??= []; |
||||
|
state.application.redirectUris.push(uri); |
||||
|
} |
||||
|
|
||||
|
function handleDelRedirectUri(uri: string) { |
||||
|
if (!state.application || !state.application.redirectUris) { |
||||
|
return; |
||||
|
} |
||||
|
const index = state.application.redirectUris.findIndex(redirectUri => redirectUri === uri); |
||||
|
if (!index) { |
||||
|
state.application.redirectUris.splice(index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleNewDisplayName(record) { |
||||
|
if (!state.application) { |
||||
|
return; |
||||
|
} |
||||
|
state.application.displayNames ??= {}; |
||||
|
state.application.displayNames[record.culture] = record.displayName; |
||||
|
} |
||||
|
|
||||
|
function handleDelDisplayName(record) { |
||||
|
if (!state.application || !state.application.displayNames) { |
||||
|
return; |
||||
|
} |
||||
|
delete state.application.displayNames[record.culture]; |
||||
|
} |
||||
|
|
||||
|
function handleNewProperty(record) { |
||||
|
if (!state.application) { |
||||
|
return; |
||||
|
} |
||||
|
state.application.properties ??= {}; |
||||
|
state.application.properties[record.key] = record.value; |
||||
|
} |
||||
|
|
||||
|
function handleDelProperty(record) { |
||||
|
if (!state.application || !state.application.properties) { |
||||
|
return; |
||||
|
} |
||||
|
delete state.application.properties[record.key]; |
||||
|
} |
||||
|
|
||||
|
function handleNewScope(scopes: string[]) { |
||||
|
if (!state.application) { |
||||
|
return; |
||||
|
} |
||||
|
state.application.scopes ??= []; |
||||
|
state.application.scopes.push(...scopes); |
||||
|
} |
||||
|
|
||||
|
function handleDelScope(scopes: string[]) { |
||||
|
if (!state.application || !state.application.scopes) { |
||||
|
return; |
||||
|
} |
||||
|
state.application.scopes = state.application.scopes.filter((scope) => !scopes.includes(scope)); |
||||
|
} |
||||
|
|
||||
|
function handleClickUrisMenu(e) { |
||||
|
state.endPoint = { |
||||
|
component : e.key, |
||||
|
uris: state.application[e.key], |
||||
|
}; |
||||
|
state.activeTab = 'endpoints'; |
||||
|
} |
||||
|
|
||||
|
function handleBeforeClose(): Promise<boolean> { |
||||
|
console.log(state); |
||||
|
return new Promise((resolve) => { |
||||
|
if (!state.entityChanged) { |
||||
|
return resolve(true); |
||||
|
} |
||||
|
createConfirm({ |
||||
|
iconType: 'warning', |
||||
|
title: L('AreYouSure'), |
||||
|
content: L('AreYouSureYouWantToCancelEditingWarningMessage'), |
||||
|
onOk: () => { |
||||
|
resolve(true); |
||||
|
}, |
||||
|
onCancel: () => { |
||||
|
resolve(false); |
||||
|
} |
||||
|
}); |
||||
}); |
}); |
||||
const title = computed(() => { |
} |
||||
|
|
||||
|
function handleSubmit() { |
||||
|
const form = unref(formRef); |
||||
|
form?.validate().then(() => { |
||||
|
changeOkLoading(true); |
||||
|
const api = state.application.id |
||||
|
? UpdateAsyncByIdAndInput(state.application.id, cloneDeep(state.application)) |
||||
|
: CreateAsyncByInput(cloneDeep(state.application)); |
||||
|
api.then((res) => { |
||||
|
createMessage.success(L('Successful')); |
||||
|
emits('change', res); |
||||
|
closeModal(); |
||||
|
}).finally(() => { |
||||
|
changeOkLoading(false); |
||||
|
}) |
||||
}); |
}); |
||||
|
} |
||||
</script> |
</script> |
||||
|
|||||
@ -0,0 +1,68 @@ |
|||||
|
<template> |
||||
|
<PermissionForm :resources="getSupportScopes" :targetResources="targetResources" @change="handleChange" /> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, ref, unref, onMounted } from 'vue'; |
||||
|
import { GetListAsyncByInput } from '/@/api/openiddict/open-iddict-scope'; |
||||
|
import PermissionForm from '../../components/Permissions/PermissionForm.vue'; |
||||
|
|
||||
|
const emits = defineEmits(['create', 'delete']); |
||||
|
const props = defineProps({ |
||||
|
scopes: { |
||||
|
type: [Array] as PropType<string[]>, |
||||
|
}, |
||||
|
supportScopes: { |
||||
|
type: [Array] as PropType<string[]>, |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const resourcesRef = ref<{ key: string; title: string }[]>([]); |
||||
|
const targetResources = computed(() => { |
||||
|
if (!props.scopes) return []; |
||||
|
const targetScopes = getSupportScopes.value.filter((item) => |
||||
|
props.scopes?.some((scope) => scope === item.key), |
||||
|
); |
||||
|
|
||||
|
return targetScopes.map((item) => item.key); |
||||
|
}); |
||||
|
const getSupportScopes = computed(() => { |
||||
|
const resources = unref(resourcesRef); |
||||
|
if (props.supportScopes) { |
||||
|
const supportScopes = props.supportScopes.map(scope => { |
||||
|
return { |
||||
|
key: scope, |
||||
|
title: scope, |
||||
|
} |
||||
|
}); |
||||
|
supportScopes.push(...resources); |
||||
|
return supportScopes; |
||||
|
} |
||||
|
return resources; |
||||
|
}); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
GetListAsyncByInput({ |
||||
|
skipCount: 0, |
||||
|
maxResultCount: 100, |
||||
|
}).then((res) => { |
||||
|
resourcesRef.value = res.items.map((item) => { |
||||
|
return { |
||||
|
key: item.name, |
||||
|
title: item.displayName ?? item.name, |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
function handleChange(_, direction, moveKeys: string[]) { |
||||
|
switch (direction) { |
||||
|
case 'left': |
||||
|
emits('delete', moveKeys); |
||||
|
break; |
||||
|
case 'right': |
||||
|
emits('create', moveKeys); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,60 @@ |
|||||
|
<template> |
||||
|
<UriEditForm |
||||
|
:title="L('DisplayName:PostLogoutRedirectUris')" |
||||
|
:schemas="schemas" |
||||
|
:columns="columns" |
||||
|
:data-source="dataSource" |
||||
|
rowKey="uri" |
||||
|
@create="handleAddNew" |
||||
|
@delete="handleDelete" |
||||
|
/> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed } from 'vue'; |
||||
|
import { FormSchema } from '/@/components/Form'; |
||||
|
import { BasicColumn } from '/@/components/Table'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import UriEditForm from '../../components/Uris/UriForm.vue'; |
||||
|
|
||||
|
const emits = defineEmits(['create-logout-uri', 'delete-logout-uri']); |
||||
|
const props = defineProps({ |
||||
|
uris: { |
||||
|
type: Array as PropType<String[]>, |
||||
|
}, |
||||
|
}); |
||||
|
const { L } = useLocalization(['AbpOpenIddict']); |
||||
|
const dataSource = computed(() => { |
||||
|
if (!props.uris) return []; |
||||
|
return props.uris.map(uri => { |
||||
|
return { |
||||
|
uri: uri, |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
const schemas: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'uri', |
||||
|
component: 'Input', |
||||
|
label: 'Uri', |
||||
|
colProps: { span: 24 }, |
||||
|
required: true, |
||||
|
}, |
||||
|
]; |
||||
|
const columns: BasicColumn[] = [ |
||||
|
{ |
||||
|
dataIndex: 'uri', |
||||
|
align: 'left', |
||||
|
width: 'auto', |
||||
|
sorter: true, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
function handleAddNew(record) { |
||||
|
emits('create-logout-uri', record.uri); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
emits('delete-logout-uri', record.uri); |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,60 @@ |
|||||
|
<template> |
||||
|
<UriEditForm |
||||
|
:title="L('DisplayName:RedirectUris')" |
||||
|
:schemas="schemas" |
||||
|
:columns="columns" |
||||
|
:data-source="dataSource" |
||||
|
rowKey="uri" |
||||
|
@create="handleAddNew" |
||||
|
@delete="handleDelete" |
||||
|
/> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed } from 'vue'; |
||||
|
import { FormSchema } from '/@/components/Form'; |
||||
|
import { BasicColumn } from '/@/components/Table'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import UriEditForm from '../../components/Uris/UriForm.vue'; |
||||
|
|
||||
|
const emits = defineEmits(['create-redirect-uri', 'delete-redirect-uri']); |
||||
|
const props = defineProps({ |
||||
|
uris: { |
||||
|
type: Array as PropType<String[]>, |
||||
|
}, |
||||
|
}); |
||||
|
const { L } = useLocalization(['AbpOpenIddict']); |
||||
|
const dataSource = computed(() => { |
||||
|
if (!props.uris) return []; |
||||
|
return props.uris.map(uri => { |
||||
|
return { |
||||
|
uri: uri, |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
const schemas: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'uri', |
||||
|
component: 'Input', |
||||
|
label: 'Uri', |
||||
|
colProps: { span: 24 }, |
||||
|
required: true, |
||||
|
}, |
||||
|
]; |
||||
|
const columns: BasicColumn[] = [ |
||||
|
{ |
||||
|
dataIndex: 'uri', |
||||
|
align: 'left', |
||||
|
width: 'auto', |
||||
|
sorter: true, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
function handleAddNew(record) { |
||||
|
emits('create-redirect-uri', record.uri); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
emits('delete-redirect-uri', record.uri); |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { Rule } from 'ant-design-vue/lib/form'; |
||||
|
import { OpenIdConfiguration } from '/@/api/identity-server/model/basicModel'; |
||||
|
import { OpenIddictApplicationDto } from '/@/api/openiddict/open-iddict-application/model'; |
||||
|
|
||||
|
export interface EndPointComponent { |
||||
|
component: string; |
||||
|
uris?: string[]; |
||||
|
} |
||||
|
|
||||
|
export interface ApplicationState { |
||||
|
activeTab: string; |
||||
|
formRules?: Dictionary<string, Rule>, |
||||
|
application: OpenIddictApplicationDto; |
||||
|
endPoint: EndPointComponent; |
||||
|
openIdConfiguration?: OpenIdConfiguration; |
||||
|
entityChanged: boolean; |
||||
|
isEdit: boolean; |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
<template> |
||||
|
<Card :title="L('DisplayName:DisplayNames')"> |
||||
|
<BasicForm @register="registerForm" @submit="handleSubmit" /> |
||||
|
<BasicTable @register="registerTable" :data-source="dataSource"> |
||||
|
<template #bodyCell="{ column, record }"> |
||||
|
<template v-if="column.key === 'action'"> |
||||
|
<TableAction |
||||
|
:actions="[ |
||||
|
{ |
||||
|
color: 'error', |
||||
|
icon: 'ant-design:delete-outlined', |
||||
|
label: L('Delete'), |
||||
|
onClick: handleDelete.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
</Card> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, ref } from 'vue'; |
||||
|
import { Card } from 'ant-design-vue'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { BasicForm, useForm } from '/@/components/Form'; |
||||
|
import { BasicTable, TableAction, useTable } from '/@/components/Table'; |
||||
|
import { getList as getLanguages } from '/@/api/localization/languages'; |
||||
|
|
||||
|
const emits = defineEmits(['create', 'delete']); |
||||
|
const props = defineProps({ |
||||
|
displayNames: { |
||||
|
type: Object as PropType<Recordable>, |
||||
|
default: () => {}, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const { L } = useLocalization(['AbpOpenIddict', 'AbpLocalization', 'AbpUi']); |
||||
|
const modelRef = ref({}); |
||||
|
const [registerForm, { resetFields }] = useForm({ |
||||
|
model: modelRef, |
||||
|
schemas: [ |
||||
|
{ |
||||
|
field: 'culture', |
||||
|
component: 'ApiSelect', |
||||
|
label: L('DisplayName:CultureName'), |
||||
|
colProps: { span: 24 }, |
||||
|
required: true, |
||||
|
componentProps: { |
||||
|
api: getLanguages, |
||||
|
params: { |
||||
|
skipCount: 0, |
||||
|
maxResultCount: 100, |
||||
|
}, |
||||
|
resultField: 'items', |
||||
|
labelField: 'displayName', |
||||
|
valueField: 'cultureName', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'displayName', |
||||
|
component: 'Input', |
||||
|
label: L('DisplayName:DisplayName'), |
||||
|
colProps: { span: 24 }, |
||||
|
required: true, |
||||
|
}, |
||||
|
], |
||||
|
showResetButton: false, |
||||
|
submitButtonOptions: { |
||||
|
text: L('DisplayName:AddNew'), |
||||
|
// icon: 'ant-design:plus-outlined', |
||||
|
}, |
||||
|
}); |
||||
|
const [registerTable] = useTable({ |
||||
|
rowKey: 'culture', |
||||
|
showHeader: false, |
||||
|
columns: [ |
||||
|
{ |
||||
|
dataIndex: 'culture', |
||||
|
align: 'left', |
||||
|
width: 100, |
||||
|
sorter: true, |
||||
|
}, |
||||
|
{ |
||||
|
dataIndex: 'displayName', |
||||
|
align: 'left', |
||||
|
width: 'auto', |
||||
|
sorter: true, |
||||
|
}, |
||||
|
], |
||||
|
pagination: false, |
||||
|
striped: false, |
||||
|
useSearchForm: false, |
||||
|
showTableSetting: false, |
||||
|
showIndexColumn: false, |
||||
|
bordered: false, |
||||
|
actionColumn: { |
||||
|
width: 200, |
||||
|
title: L('Actions'), |
||||
|
dataIndex: 'action', |
||||
|
}, |
||||
|
}); |
||||
|
const dataSource = computed(() => { |
||||
|
if (!props.displayNames) { |
||||
|
return []; |
||||
|
} |
||||
|
return Object.keys(props.displayNames).map((key) => { |
||||
|
return { |
||||
|
culture: key, |
||||
|
displayName: props.displayNames[key] |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
function handleSubmit(input) { |
||||
|
if (!props.displayNames && !props.displayNames[input.culture]) { |
||||
|
return; |
||||
|
} |
||||
|
emits('create', input); |
||||
|
resetFields(); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
emits('delete', record); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.title { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,41 @@ |
|||||
|
<template> |
||||
|
<Transfer |
||||
|
:dataSource="resources" |
||||
|
:targetKeys="targetResources" |
||||
|
:titles="[L('Assigned'), L('Available')]" |
||||
|
:render="(item) => item.title" |
||||
|
:list-style="getListStyle" |
||||
|
@change="handleChange" |
||||
|
/> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed } from 'vue'; |
||||
|
import { Transfer } from 'ant-design-vue'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
|
||||
|
const emits = defineEmits(['change']); |
||||
|
const props = defineProps({ |
||||
|
resources: { |
||||
|
type: [Array] as PropType<{ key: string; title: string }[]>, |
||||
|
required: true, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
targetResources: { type: [Array] as PropType<string[]>, required: true, default: () => [] }, |
||||
|
listStyle: { type: Object, required: false }, |
||||
|
}); |
||||
|
|
||||
|
const { L } = useLocalization(['AbpOpenIddict']); |
||||
|
const defaultListStyle = { |
||||
|
width: '48%', |
||||
|
height: '500px', |
||||
|
minHeight: '500px', |
||||
|
}; |
||||
|
const getListStyle = computed(() => { |
||||
|
return {...defaultListStyle, ...props.listStyle} |
||||
|
}); |
||||
|
|
||||
|
function handleChange(targetKeys, direction, moveKeys) { |
||||
|
emits('change', targetKeys, direction, moveKeys); |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,120 @@ |
|||||
|
<template> |
||||
|
<Card :title="L('Propertites')"> |
||||
|
<BasicTable @register="registerTable" :data-source="dataSource"> |
||||
|
<template #toolbar> |
||||
|
<Button type="primary" @click="handleAddNew">{{ L('Propertites:New') }}</Button> |
||||
|
</template> |
||||
|
<template #bodyCell="{ column, record }"> |
||||
|
<template v-if="column.key === 'action'"> |
||||
|
<TableAction |
||||
|
:actions="[ |
||||
|
{ |
||||
|
color: 'error', |
||||
|
icon: 'ant-design:delete-outlined', |
||||
|
label: L('Delete'), |
||||
|
onClick: handleDelete.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
<BasicModal v-bind="$attrs" @register="registerModal" @ok="handleSubmit" :title="title"> |
||||
|
<BasicForm @register="registerForm" /> |
||||
|
</BasicModal> |
||||
|
</Card> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, ref } from 'vue'; |
||||
|
import { Button, Card } from 'ant-design-vue'; |
||||
|
import { BasicForm, useForm } from '/@/components/Form'; |
||||
|
import { BasicModal, useModal } from '/@/components/Modal'; |
||||
|
import { BasicTable, TableAction, useTable } from '/@/components/Table'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
|
||||
|
const emits = defineEmits(['create', 'delete']); |
||||
|
const props = defineProps({ |
||||
|
properties: { |
||||
|
type: Object as PropType<Dictionary<string, string>>, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const { L } = useLocalization(['AbpOpenIddict']); |
||||
|
const title = ref(''); |
||||
|
const [registerForm, { validate, resetFields }] = useForm({ |
||||
|
labelWidth: 120, |
||||
|
showActionButtonGroup: false, |
||||
|
schemas: [ |
||||
|
{ |
||||
|
field: 'key', |
||||
|
component: 'Input', |
||||
|
label: L('Propertites:Key'), |
||||
|
colProps: { span: 24 }, |
||||
|
required: true, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'value', |
||||
|
component: 'Input', |
||||
|
label: L('Propertites:Value'), |
||||
|
colProps: { span: 24 }, |
||||
|
required: true, |
||||
|
}, |
||||
|
], |
||||
|
}); |
||||
|
const dataSource = computed(() => { |
||||
|
if (!props.properties) return []; |
||||
|
return Object.keys(props.properties).map((key) => { |
||||
|
return { |
||||
|
key: key, |
||||
|
value: props.properties[key], |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
const [registerTable] = useTable({ |
||||
|
rowKey: "key", |
||||
|
columns: [ |
||||
|
{ |
||||
|
title: L('Propertites:Key'), |
||||
|
dataIndex: 'key', |
||||
|
align: 'left', |
||||
|
width: 180, |
||||
|
sorter: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: L('Propertites:Value'), |
||||
|
dataIndex: 'value', |
||||
|
align: 'left', |
||||
|
width: 180, |
||||
|
sorter: true, |
||||
|
}, |
||||
|
], |
||||
|
pagination: false, |
||||
|
showTableSetting: true, |
||||
|
maxHeight: 230, |
||||
|
actionColumn: { |
||||
|
width: 100, |
||||
|
title: L('Actions'), |
||||
|
dataIndex: 'action', |
||||
|
}, |
||||
|
}); |
||||
|
const [registerModal, { openModal, closeModal }] = useModal(); |
||||
|
|
||||
|
function handleAddNew() { |
||||
|
title.value = L('Propertites:New'); |
||||
|
openModal(true); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
emits('delete', record); |
||||
|
} |
||||
|
|
||||
|
function handleSubmit() { |
||||
|
validate().then((input) => { |
||||
|
emits('create', input); |
||||
|
resetFields(); |
||||
|
closeModal(); |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,124 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<BasicTitle class="title">{{ title }}</BasicTitle> |
||||
|
<BasicForm @register="registerForm" @submit="handleSubmit" /> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #bodyCell="{ column, record }"> |
||||
|
<template v-if="column.key === 'action'"> |
||||
|
<TableAction |
||||
|
:actions="[ |
||||
|
{ |
||||
|
color: 'error', |
||||
|
icon: 'ant-design:delete-outlined', |
||||
|
label: L('Delete'), |
||||
|
onClick: handleDelete.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { ref, watch } from 'vue'; |
||||
|
import { BasicTitle } from '/@/components/Basic'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { BasicForm, FormSchema, useForm } from '/@/components/Form'; |
||||
|
import { BasicTable, BasicColumn, TableAction, useTable } from '/@/components/Table'; |
||||
|
|
||||
|
const emits = defineEmits(['create', 'delete']); |
||||
|
const props = defineProps({ |
||||
|
schemas: { |
||||
|
type: [Array] as PropType<FormSchema[]>, |
||||
|
required: true, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
labelWidth: { |
||||
|
type: Number, |
||||
|
}, |
||||
|
columns: { |
||||
|
type: [Array] as PropType<BasicColumn[]>, |
||||
|
required: true, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
dataSource: { |
||||
|
type: [Array] as PropType<Recordable[]>, |
||||
|
required: true, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
rowKey: { |
||||
|
type: String, |
||||
|
default: () => 'id', |
||||
|
}, |
||||
|
showHeader: { |
||||
|
type: Boolean, |
||||
|
default: () => false, |
||||
|
}, |
||||
|
title: { |
||||
|
type: String, |
||||
|
default: () => '', |
||||
|
}, |
||||
|
tableTitle: { |
||||
|
type: String, |
||||
|
default: () => '', |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const { L } = useLocalization(['AbpOpenIddict', 'AbpUi']); |
||||
|
const modelRef = ref({}); |
||||
|
const [registerForm, { resetFields }] = useForm({ |
||||
|
model: modelRef, |
||||
|
labelWidth: props.labelWidth, |
||||
|
schemas: props.schemas, |
||||
|
showResetButton: false, |
||||
|
submitButtonOptions: { |
||||
|
text: L('Uri:AddNew'), |
||||
|
// icon: 'ant-design:plus-outlined', |
||||
|
}, |
||||
|
}); |
||||
|
const [registerTable, { setTableData }] = useTable({ |
||||
|
rowKey: props.rowKey, |
||||
|
showHeader: props.showHeader, |
||||
|
title: props.tableTitle, |
||||
|
columns: props.columns, |
||||
|
dataSource: props.dataSource, |
||||
|
pagination: false, |
||||
|
striped: false, |
||||
|
useSearchForm: false, |
||||
|
showTableSetting: false, |
||||
|
showIndexColumn: false, |
||||
|
bordered: false, |
||||
|
actionColumn: { |
||||
|
width: 200, |
||||
|
title: L('Actions'), |
||||
|
dataIndex: 'action', |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => props.dataSource, |
||||
|
(data) => { |
||||
|
setTableData(data); |
||||
|
}, |
||||
|
{ |
||||
|
deep: true, |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
function handleSubmit(input) { |
||||
|
emits('create', input); |
||||
|
resetFields(); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
emits('delete', record); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.title { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
</style> |
||||
@ -1,8 +1,14 @@ |
|||||
using System; |
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.OpenIddict.Applications; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
namespace LINGYUN.Abp.OpenIddict.Applications; |
namespace LINGYUN.Abp.OpenIddict.Applications; |
||||
|
|
||||
[Serializable] |
[Serializable] |
||||
public class OpenIddictApplicationCreateDto : OpenIddictApplicationCreateOrUpdateDto |
public class OpenIddictApplicationCreateDto : OpenIddictApplicationCreateOrUpdateDto |
||||
{ |
{ |
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(OpenIddictApplicationConsts), nameof(OpenIddictApplicationConsts.ClientIdMaxLength))] |
||||
|
public string ClientId { get; set; } |
||||
} |
} |
||||
|
|||||
@ -1,8 +1,10 @@ |
|||||
using System; |
using System; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
namespace LINGYUN.Abp.OpenIddict.Applications; |
namespace LINGYUN.Abp.OpenIddict.Applications; |
||||
|
|
||||
[Serializable] |
[Serializable] |
||||
public class OpenIddictApplicationUpdateDto : OpenIddictApplicationCreateOrUpdateDto |
public class OpenIddictApplicationUpdateDto : OpenIddictApplicationCreateOrUpdateDto, IHasConcurrencyStamp |
||||
{ |
{ |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
} |
} |
||||
|
|||||
@ -1,8 +1,10 @@ |
|||||
using System; |
using System; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
namespace LINGYUN.Abp.OpenIddict.Scopes; |
||||
|
|
||||
[Serializable] |
[Serializable] |
||||
public class OpenIddictScopeUpdateDto : OpenIddictScopeCreateOrUpdateDto |
public class OpenIddictScopeUpdateDto : OpenIddictScopeCreateOrUpdateDto, IHasConcurrencyStamp |
||||
{ |
{ |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue