Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ui/src/api/admin/system/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const getWorkspaceMembers = (workspaceId: string, query?: RequestParams) => {
return get<CommonUserOption[]>(`/workspace/${workspaceId}/user_member`, query)
}



export default {
getDefaultPassword,
getAllUsers,
Expand Down
2 changes: 1 addition & 1 deletion ui/src/api/admin/system/resource-authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type {

/** 系统管理资源授权 */
const prefix = (workspaceId: string) => `/workspace/${workspaceId}/user_resource_permission`

/** 获取指定空间、指定用户、指定资源类型的权限列表。 */
const getUserResourcePermissions = (
workspaceId: string,
Expand All @@ -35,6 +34,7 @@ const putUserResourcePermissions = (
)
}


export default {
getUserResourcePermissions,
putUserResourcePermissions,
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/global/mk-list-item/index.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script setup lang="ts" generic="T extends Record<string, unknown> = Record<string, unknown>">
<script setup lang="ts" generic="T extends object = Record<string, unknown>">
import { computed } from 'vue'

defineOptions({ name: 'MkListItem' })
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/mk-search-list/index.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script setup lang="ts" generic="T extends Record<string, unknown>">
<script setup lang="ts" generic="T extends object">
import type { ScrollbarDirection, ScrollbarInstance } from 'element-plus'
import { computed, nextTick, ref, watch } from 'vue'

Expand Down
50 changes: 25 additions & 25 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,31 +81,31 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功

## 页面功能登记

| 页面 | 功能说明 |
| --------------------------------------------------------------- | ---------------------------------- | --- |
| `application/ApplicationView.vue` | 工作空间智能体目录与智能体卡片页面 | |
| `workflow/ApplicationWorkflowView.vue` | 智能体工作流页面头部与全屏画布 |
| `chat/ChatView.vue` | Chat 入口的对话页面 |
| `error/NotFoundView.vue` | Admin 未匹配路由和全局 404 页面 |
| `home/HomeView.vue` | Workspace 首页 |
| `knowledge/KnowledgeDetailView.vue` | 知识库详情页面 |
| `knowledge/DocumentDetailView.vue` | 知识库文档详情页面 |
| `login/LoginView.vue` | Admin 登录页面 |
| `login/ForgotPasswordView.vue` | 忘记密码页面 |
| `model/ModelView.vue` | 工作空间模型目录与模型卡片页面 |
| `system/SystemView.vue` | System 模块通用占位页面 |
| `system/identity/groups/UserGroupListView.vue` | 用户组列表页面 |
| `system/identity/resource-authorization/ResourceAuthorizationView.vue` | 工作空间成员的分类资源授权页面 |
| `system/identity/roles/RoleListView.vue` | 角色列表页面 |
| `system/identity/users/UserListView.vue` | 用户列表页面 |
| `system/identity/workspaces/WorkspaceListView.vue` | 工作空间列表页面 |
| `system/chat/user-groups/GroupsListView.vue` | 对话用户组及组成员管理页面 |
| `system/chat/users/UserListView.vue` | 对话用户列表及用户导入管理页面 |
| `system/settings/AppearanceSettingsView.vue` | 系统外观设置和登录外观预览页面 |
| `system/settings/authentication/AuthenticationSettingsView.vue` | 系统登录及认证源配置页面 |
| `system/settings/email/EmailSettingsView.vue` | 系统邮件 SMTP 服务配置页面 |
| `system/operate-logs/OperateLogListView.vue` | 系统操作日志查询与清理页面 |
| `tool/ToolView.vue` | 工作空间工具目录与工具卡片页面 |
| 页面 | 功能说明 |
| ---------------------------------------------------------------------- | -------------------------------------- | --- |
| `application/ApplicationView.vue` | 工作空间智能体目录与智能体卡片页面 | |
| `workflow/ApplicationWorkflowView.vue` | 智能体工作流页面头部与全屏画布 |
| `chat/ChatView.vue` | Chat 入口的对话页面 |
| `error/NotFoundView.vue` | Admin 未匹配路由和全局 404 页面 |
| `home/HomeView.vue` | Workspace 首页 |
| `knowledge/KnowledgeDetailView.vue` | 知识库详情页面 |
| `knowledge/DocumentDetailView.vue` | 知识库文档详情页面 |
| `login/LoginView.vue` | Admin 登录页面 |
| `login/ForgotPasswordView.vue` | 忘记密码页面 |
| `model/ModelView.vue` | 工作空间模型目录与模型卡片页面 |
| `system/SystemView.vue` | System 模块通用占位页面 |
| `system/identity/groups/UserGroupListView.vue` | 用户组列表页面 |
| `system/identity/resource-authorization/ResourceAuthorizationView.vue` | 工作空间用户组与用户的分类资源授权页面 |
| `system/identity/roles/RoleListView.vue` | 角色列表页面 |
| `system/identity/users/UserListView.vue` | 用户列表页面 |
| `system/identity/workspaces/WorkspaceListView.vue` | 工作空间列表页面 |
| `system/chat/user-groups/GroupsListView.vue` | 对话用户组及组成员管理页面 |
| `system/chat/users/UserListView.vue` | 对话用户列表及用户导入管理页面 |
| `system/settings/AppearanceSettingsView.vue` | 系统外观设置和登录外观预览页面 |
| `system/settings/authentication/AuthenticationSettingsView.vue` | 系统登录及认证源配置页面 |
| `system/settings/email/EmailSettingsView.vue` | 系统邮件 SMTP 服务配置页面 |
| `system/operate-logs/OperateLogListView.vue` | 系统操作日志查询与清理页面 |
| `tool/ToolView.vue` | 工作空间工具目录与工具卡片页面 |

## 备注要求

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import WorkspaceApi from '@/api/admin/system/workspace'
import CommonSystemApi from '@/api/admin/system/common'
import UserGroupsApi from '@/api/admin/system/user-groups'
import ResourceAuthorizationApi from '@/api/admin/system/resource-authorization'
import { RESOURCE_TYPE, RESOURCE_PERMISSION } from '@/api/enums'
import type {
ResourceAuthorizationType,
ResourcePermissionItem,
ResourcePermissionPayload,
CommonUserOption,
OptionItem,
SystemUserGroup,
WorkspaceItem,
} from '@/api/types'
import { useStore } from '@/stores'
import { MsgSuccess } from '@/utils/message'
import WorkspaceDropdown from '@/components/business/workspace-dropdown/index.vue'
import MkSearchList from '@/components/mk-search-list/index.vue'
import PermissionTable from './components/PermissionTable.vue'
import UserAuthorizationList from './components/UserAuthorizationList.vue'
import UserGroupAuthorizationList from './components/UserGroupAuthorizationList.vue'
import { RESOURCE_AUTHORIZATION_LABELS } from './constants'

const { auth } = useStore()
const route = useRoute()

const resourceType = ref<ResourceAuthorizationType>(RESOURCE_TYPE.APPLICATION)
const resourceType = ref<ResourceAuthorizationType>(
(route.meta.resource as ResourceAuthorizationType) ?? RESOURCE_TYPE.APPLICATION,
)
const resourceTypeOptions: OptionItem<ResourceAuthorizationType>[] = [
RESOURCE_TYPE.APPLICATION,
RESOURCE_TYPE.KNOWLEDGE,
RESOURCE_TYPE.TOOL,
RESOURCE_TYPE.MODEL,
].map((value) => ({ label: RESOURCE_AUTHORIZATION_LABELS[value], value }))

/* 选择工作空间列表 */
const loadingView = ref(false)
Expand All @@ -28,7 +43,10 @@ const workspaceOptions = ref<WorkspaceItem[]>([])

function handleWorkspaceSelect(workspace: WorkspaceItem) {
selectedWorkspaceId.value = workspace.id ?? 'default'
loadWorkspaceMembers()
loadingView.value = true
loadAuthorizationTargets().finally(() => {
loadingView.value = false
})
}

function loadWorkspaceOptions() {
Expand All @@ -37,44 +55,68 @@ function loadWorkspaceOptions() {
})
}

/* 选择用户(普通用户) */

/* 授权对象 */
const targetType = ref<'user-group' | 'user'>('user-group')
const userGroups = ref<SystemUserGroup[]>([])
const workspaceMembers = ref<CommonUserOption[]>([])
const selectedMemberId = ref('')
const selectedUserGroupId = ref('')
const selectedUserId = ref('')

function loadWorkspaceMembers() {
return CommonSystemApi.getWorkspaceMembers(selectedWorkspaceId.value).then((members) => {
workspaceMembers.value = members
selectedMemberId.value = members[0]?.id ?? ''
function loadAuthorizationTargets() {
return Promise.all([
UserGroupsApi.getSystemUserGroups(selectedWorkspaceId.value),
CommonSystemApi.getWorkspaceMembers(selectedWorkspaceId.value),
]).then(([groups, users]) => {
userGroups.value = groups
workspaceMembers.value = users
selectedUserGroupId.value = groups[0]?.id ?? ''
selectedUserId.value = users[0]?.id ?? ''
return loadResourcePermissions()
})
}

function handleMemberSelect(member: CommonUserOption) {
selectedMemberId.value = member.id
function handleTargetTypeChange() {
loadResourcePermissions()
}

function getMemberRoleText(member: CommonUserOption) {
return member.roles?.join(',') ?? ''
function handleUserGroupSelect(userGroup: SystemUserGroup) {
selectedUserGroupId.value = userGroup.id
loadResourcePermissions()
}

function handleUserSelect(user: CommonUserOption) {
selectedUserId.value = user.id
loadResourcePermissions()
}

/* 资源权限 */
const loadingPermissions = ref(false)
const resourcePermissions = ref<ResourcePermissionItem[]>([])

function loadResourcePermissions() {
if (!selectedMemberId.value) {
const targetId =
targetType.value === 'user-group' ? selectedUserGroupId.value : selectedUserId.value

if (!targetId) {
resourcePermissions.value = []
return Promise.resolve()
}

loadingPermissions.value = true
return ResourceAuthorizationApi.getUserResourcePermissions(
selectedWorkspaceId.value,
selectedMemberId.value,
resourceType.value,
)
const permissionRequest =
targetType.value === 'user-group'
? ResourceAuthorizationApi.getUserResourcePermissions(
selectedWorkspaceId.value,
targetId,
resourceType.value,
)
: ResourceAuthorizationApi.getUserResourcePermissions(
selectedWorkspaceId.value,
targetId,
resourceType.value,
)

return permissionRequest
.then((permissions) => {
resourcePermissions.value = buildResourceTree(permissions)
})
Expand Down Expand Up @@ -114,15 +156,28 @@ function buildResourceTree(resourceItems: ResourcePermissionItem[]) {
/* 保存权限 */

function handlePermissionsSubmit(permissions: ResourcePermissionPayload[]) {
if (!selectedMemberId.value) return
const targetId =
targetType.value === 'user-group' ? selectedUserGroupId.value : selectedUserId.value

if (!targetId) return

loadingPermissions.value = true
ResourceAuthorizationApi.putUserResourcePermissions(
selectedWorkspaceId.value,
selectedMemberId.value,
resourceType.value,
permissions,
)
const permissionRequest =
targetType.value === 'user-group'
? ResourceAuthorizationApi.putUserResourcePermissions(
selectedWorkspaceId.value,
targetId,
resourceType.value,
permissions,
)
: ResourceAuthorizationApi.putUserResourcePermissions(
selectedWorkspaceId.value,
targetId,
resourceType.value,
permissions,
)

permissionRequest
.then(() => {
MsgSuccess('提交成功')
return loadResourcePermissions()
Expand All @@ -135,7 +190,7 @@ function handlePermissionsSubmit(permissions: ResourcePermissionPayload[]) {
onMounted(() => {
loadingView.value = true
Promise.all(
auth.isEE ? [loadWorkspaceOptions(), loadWorkspaceMembers()] : [loadWorkspaceMembers()],
auth.isEE ? [loadWorkspaceOptions(), loadAuthorizationTargets()] : [loadAuthorizationTargets()],
).finally(() => {
loadingView.value = false
})
Expand All @@ -154,33 +209,45 @@ onMounted(() => {

<template #aside="{ Header }">
<component :is="Header">
<h4>按用户</h4>
<el-tabs class="w-full" v-model="targetType" @tab-change="handleTargetTypeChange">
<el-tab-pane label="按用户组" name="user-group" />
<el-tab-pane label="按用户" name="user" />
</el-tabs>
</component>
<MkSearchList
:data="workspaceMembers"
:default-active="selectedMemberId"
:props="{ label: 'nick_name', value: 'id' }"
@click="handleMemberSelect"
>
<template #default="{ row }">
<div class="flex min-w-0 flex-1 items-center gap-2">
<span class="min-w-0 truncate" :title="row.nick_name">{{ row.nick_name }}</span>
<span
v-if="(auth.isEE || auth.isPE) && row.roles?.length"
class="min-w-0 truncate text-N600"
:title="getMemberRoleText(row)"
>
({{ getMemberRoleText(row) }})
</span>
</div>
</template>
</MkSearchList>

<UserGroupAuthorizationList
v-if="targetType === 'user-group'"
:active-id="selectedUserGroupId"
:user-groups="userGroups"
@select="handleUserGroupSelect"
/>
<UserAuthorizationList
v-else
:active-id="selectedUserId"
:users="workspaceMembers"
@select="handleUserSelect"
/>
</template>

<template #default="{ Header }">
<component :is="Header">
<h4>资源权限配置</h4>
<div class="w-full">
<h4 class="mb-4">资源权限配置</h4>
<el-tabs
v-model="resourceType"
class="w-full"
@tab-change="loadResourcePermissions"
>
<el-tab-pane
v-for="resourceTypeOption in resourceTypeOptions"
:key="resourceTypeOption.value"
:label="resourceTypeOption.label"
:name="resourceTypeOption.value"
/>
</el-tabs>
</div>
</component>

<PermissionTable
v-loading="loadingPermissions"
:data="resourcePermissions"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup lang="ts">
import type { CommonUserOption } from '@/api/types'
import { useStore } from '@/stores'
import MkSearchList from '@/components/mk-search-list/index.vue'

defineOptions({ name: 'UserAuthorizationList' })

defineProps<{
activeId: string
users: CommonUserOption[]
}>()
const emit = defineEmits<{
select: [user: CommonUserOption]
}>()

const { auth } = useStore()

function getUserRoleText(user: CommonUserOption) {
return user.roles?.join(',') ?? ''
}

function handleUserSelect(user: CommonUserOption) {
emit('select', user)
}
</script>

<template>
<MkSearchList
:data="users"
:default-active="activeId"
:props="{ label: 'nick_name', value: 'id' }"
@click="handleUserSelect"
>
<template #default="{ row }">
<div class="flex min-w-0 flex-1 items-center gap-2">
<span class="min-w-0 truncate" :title="row.nick_name">{{ row.nick_name }}</span>
<span
v-if="(auth.isEE || auth.isPE) && row.roles?.length"
class="min-w-0 truncate text-N600"
:title="getUserRoleText(row)"
>
({{ getUserRoleText(row) }})
</span>
</div>
</template>
</MkSearchList>
</template>
Loading
Loading