<script lang="ts" setup>
|
import type { DmsAccessExclusion, DmsPermissionGrant, DmsPermissionResource } from '#/api/x/dms/permission';
|
|
import { computed, h, onMounted, ref } from 'vue';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { useDrawer } from '@jnpf/ui/drawer';
|
import { useModal } from '@jnpf/ui/modal';
|
import { formatToDateTime } from '@jnpf/utils';
|
|
import {
|
EyeOutlined,
|
FileOutlined,
|
FileTextOutlined,
|
FolderAddOutlined,
|
FolderOutlined,
|
HistoryOutlined,
|
PlusOutlined,
|
ReloadOutlined,
|
SafetyCertificateOutlined,
|
StopOutlined,
|
UploadOutlined,
|
UserDeleteOutlined,
|
} from '@ant-design/icons-vue';
|
import { Upload as AUpload, Empty, Result } from 'ant-design-vue';
|
|
import { getAccessExclusions, getPermissionAdminCapabilities, getResourceGrants } from '#/api/x/dms/permission';
|
|
import UploadFileModal from '../file-center/UploadFileModal.vue';
|
import CreateFolderModal from './CreateFolderModal.vue';
|
import ExclusionDrawer from './ExclusionDrawer.vue';
|
import ExclusionRevokeModal from './ExclusionRevokeModal.vue';
|
import GrantDetailDrawer from './GrantDetailDrawer.vue';
|
import GrantDrawer from './GrantDrawer.vue';
|
import {
|
actionLabel,
|
canExcludeRead,
|
principalDisplayName,
|
principalDisplayTitle,
|
principalSearchValue,
|
principalTypeLabel,
|
scopeLabel,
|
statusColor,
|
statusLabel,
|
} from './permissionRules';
|
import ResourceExplorer from './ResourceExplorer.vue';
|
import RevokeModal from './RevokeModal.vue';
|
import UsageDrawer from './UsageDrawer.vue';
|
|
defineOptions({ name: 'DmsPermissionAdmin' });
|
|
const checkingCapability = ref(true);
|
const permissionAdministrator = ref(false);
|
const capabilityError = ref(false);
|
const selectedResource = ref<DmsPermissionResource>();
|
const currentFolder = ref<DmsPermissionResource>();
|
const grants = ref<DmsPermissionGrant[]>([]);
|
const exclusions = ref<DmsAccessExclusion[]>([]);
|
const grantsLoading = ref(false);
|
const exclusionsLoading = ref(false);
|
const exclusionsLoadingMore = ref(false);
|
const exclusionsHasMore = ref(false);
|
const exclusionsCursor = ref<string>();
|
const permissionKeyword = ref('');
|
const inspectorMode = ref<'exclusions' | 'grants'>('grants');
|
const folderNavigationCollapsed = ref(false);
|
const resourceExplorerRef = ref<InstanceType<typeof ResourceExplorer>>();
|
const { createMessage } = useMessage();
|
|
const [registerGrantDrawer, { openDrawer: openGrantDrawer }] = useDrawer();
|
const [registerDetailDrawer, { openDrawer: openDetailDrawer }] = useDrawer();
|
const [registerUsageDrawer, { openDrawer: openUsageDrawer }] = useDrawer();
|
const [registerExclusionDrawer, { openDrawer: openExclusionDrawer }] = useDrawer();
|
const [registerRevokeModal, { openModal: openRevokeModal }] = useModal();
|
const [registerExclusionRevokeModal, { openModal: openExclusionRevokeModal }] = useModal();
|
const [registerCreateFolderModal, { openModal: openCreateFolderModal }] = useModal();
|
const [registerUploadFileModal, { openModal: openUploadFileModal }] = useModal();
|
|
const resourceTypeLabel = computed(() => {
|
if (!selectedResource.value) return '';
|
return { DOCUMENT: '文件', FILE_VERSION: '文件版本', FOLDER: '文件夹' }[selectedResource.value.resourceType];
|
});
|
const directGrantCount = computed(() => grants.value.filter((item) => !item.inherited).length);
|
const inheritedGrantCount = computed(() => grants.value.filter((item) => item.inherited).length);
|
const activeGrantCount = computed(() => grants.value.filter((item) => item.status === 'ACTIVE').length);
|
const activeExclusionCount = computed(() => exclusions.value.filter((item) => item.status === 'ACTIVE').length);
|
const exclusionSupported = computed(() => !!selectedResource.value && canExcludeRead(selectedResource.value.resourceType));
|
const filteredGrants = computed(() => {
|
const keyword = permissionKeyword.value.trim().toLowerCase();
|
if (!keyword) return grants.value;
|
return grants.value.filter((grant) =>
|
`${principalSearchValue(grant.principalName, grant.principalId)} ${principalTypeLabel(grant.principalType)} ${actionLabel(grant.actionCode)} ${grant.sourceId}`
|
.toLowerCase()
|
.includes(keyword),
|
);
|
});
|
const filteredExclusions = computed(() => {
|
const keyword = permissionKeyword.value.trim().toLowerCase();
|
if (!keyword) return exclusions.value;
|
return exclusions.value.filter((exclusion) =>
|
`${principalSearchValue(exclusion.userName, exclusion.userId)} ${exclusion.excludeReason || ''} ${exclusion.sourceType} ${exclusion.sourceId}`
|
.toLowerCase()
|
.includes(keyword),
|
);
|
});
|
|
async function checkCapability() {
|
checkingCapability.value = true;
|
capabilityError.value = false;
|
try {
|
const response = await getPermissionAdminCapabilities();
|
permissionAdministrator.value = !!response.data.permissionAdministrator;
|
} catch {
|
capabilityError.value = true;
|
} finally {
|
checkingCapability.value = false;
|
}
|
}
|
|
function handleFolderChange(folder: DmsPermissionResource) {
|
currentFolder.value = folder;
|
}
|
|
async function handleResourceSelect(resource: DmsPermissionResource) {
|
selectedResource.value = resource;
|
permissionKeyword.value = '';
|
if (!canExcludeRead(resource.resourceType)) inspectorMode.value = 'grants';
|
await Promise.all([loadGrants(), loadExclusions()]);
|
}
|
|
async function loadGrants() {
|
if (!selectedResource.value) return;
|
grantsLoading.value = true;
|
try {
|
const response = await getResourceGrants(selectedResource.value.id);
|
grants.value = response.data || [];
|
} catch {
|
grants.value = [];
|
} finally {
|
grantsLoading.value = false;
|
}
|
}
|
|
async function loadExclusions(append = false) {
|
if (!selectedResource.value || !canExcludeRead(selectedResource.value.resourceType)) {
|
exclusions.value = [];
|
exclusionsCursor.value = undefined;
|
exclusionsHasMore.value = false;
|
return;
|
}
|
if (append) exclusionsLoadingMore.value = true;
|
else exclusionsLoading.value = true;
|
try {
|
const response = await getAccessExclusions({
|
cursor: append ? exclusionsCursor.value : undefined,
|
pageSize: 100,
|
resourceId: selectedResource.value.id,
|
});
|
const page = response.data;
|
exclusions.value = append ? [...exclusions.value, ...(page?.list || [])] : page?.list || [];
|
exclusionsCursor.value = page?.nextCursor;
|
exclusionsHasMore.value = !!page?.hasMore;
|
} catch {
|
if (!append) exclusions.value = [];
|
} finally {
|
exclusionsLoading.value = false;
|
exclusionsLoadingMore.value = false;
|
}
|
}
|
|
function handleGrant() {
|
if (!selectedResource.value) return;
|
openGrantDrawer(true, { resource: selectedResource.value });
|
}
|
|
function handleExclusion() {
|
if (!selectedResource.value || !canExcludeRead(selectedResource.value.resourceType)) return;
|
openExclusionDrawer(true, { resource: selectedResource.value });
|
}
|
|
function handleCreateFolder() {
|
const parent = selectedResource.value?.resourceType === 'FOLDER' ? selectedResource.value : currentFolder.value;
|
if (!parent) return;
|
openCreateFolderModal(true, { parent });
|
}
|
|
async function handleFolderCreated(parent: DmsPermissionResource, folder: DmsPermissionResource) {
|
await resourceExplorerRef.value?.refresh();
|
createMessage.success(`目录“${folder.name}”已创建到“${parent.name}”`);
|
}
|
|
function handleRevoke(grant: DmsPermissionGrant) {
|
openRevokeModal(true, { grant });
|
}
|
|
function handleRevokeExclusion(exclusion: DmsAccessExclusion) {
|
openExclusionRevokeModal(true, { exclusion });
|
}
|
|
async function handleExclusionReload() {
|
inspectorMode.value = 'exclusions';
|
await loadExclusions();
|
}
|
|
function displayTime(value?: string) {
|
return value ? formatToDateTime(value, 'YYYY-MM-DD HH:mm') : '不限';
|
}
|
|
async function handleRefresh() {
|
await Promise.all([resourceExplorerRef.value?.refresh(), loadGrants(), loadExclusions()]);
|
createMessage.success('已刷新');
|
}
|
|
function prepareFileUpload(file: File) {
|
const folder = currentFolder.value;
|
if (!folder) {
|
createMessage.warning('请先进入一个文件夹');
|
return AUpload.LIST_IGNORE;
|
}
|
openUploadFileModal(true, { file, folder });
|
return AUpload.LIST_IGNORE;
|
}
|
|
async function handleFileUploaded() {
|
await resourceExplorerRef.value?.refresh();
|
createMessage.success('文件已上传并关联文件编号');
|
}
|
|
onMounted(checkCapability);
|
</script>
|
|
<template>
|
<div class="permission-page" v-loading="checkingCapability">
|
<Result v-if="!checkingCapability && capabilityError" status="error" title="权限管理能力检查失败" sub-title="请检查 DMS 服务连接后重试。">
|
<template #extra><a-button type="primary" @click="checkCapability">重试</a-button></template>
|
</Result>
|
<Result
|
v-else-if="!checkingCapability && !permissionAdministrator"
|
status="403"
|
title="无权访问文件夹权限管理"
|
sub-title="请联系系统管理员授予 DMS.PERMISSION.ADMIN 角色。" />
|
|
<div v-else-if="!checkingCapability" class="dms-workspace" :class="{ 'folder-navigation-collapsed': folderNavigationCollapsed }">
|
<div class="workspace-body">
|
<main class="explorer-pane">
|
<ResourceExplorer
|
ref="resourceExplorerRef"
|
@folder-change="handleFolderChange"
|
@select="handleResourceSelect"
|
@tree-collapse="folderNavigationCollapsed = $event">
|
<template #actions>
|
<div class="workspace-actions">
|
<a-button :icon="h(FolderAddOutlined)" :disabled="!currentFolder" @click="handleCreateFolder">新建目录</a-button>
|
<AUpload :before-upload="prepareFileUpload" :disabled="!currentFolder" :show-upload-list="false">
|
<a-button :icon="h(UploadOutlined)" :disabled="!currentFolder">上传</a-button>
|
</AUpload>
|
<a-button type="primary" :icon="h(PlusOutlined)" :disabled="!selectedResource" @click="handleGrant">授权</a-button>
|
<a-tooltip :title="exclusionSupported ? '对指定用户排除文件读取权限' : '排除拒绝仅支持文件和文件版本'">
|
<span class="workspace-action-wrapper">
|
<a-button danger :icon="h(UserDeleteOutlined)" :disabled="!exclusionSupported" @click="handleExclusion">排除拒绝</a-button>
|
</span>
|
</a-tooltip>
|
<a-tooltip title="刷新当前目录和权限">
|
<a-button aria-label="刷新当前目录和权限" :icon="h(ReloadOutlined)" @click="handleRefresh" />
|
</a-tooltip>
|
</div>
|
</template>
|
</ResourceExplorer>
|
</main>
|
|
<aside class="permission-inspector">
|
<template v-if="selectedResource">
|
<header class="inspector-header">
|
<span class="selected-resource-icon" :class="selectedResource.resourceType.toLowerCase()">
|
<FolderOutlined v-if="selectedResource.resourceType === 'FOLDER'" />
|
<FileTextOutlined v-else-if="selectedResource.resourceType === 'DOCUMENT'" />
|
<FileOutlined v-else />
|
</span>
|
<div class="selected-resource-heading">
|
<div>
|
<h2 :title="selectedResource.fileName || selectedResource.name">{{ selectedResource.fileName || selectedResource.name }}</h2>
|
<a-tag>{{ resourceTypeLabel }}</a-tag>
|
</div>
|
<span :title="selectedResource.id">{{ selectedResource.id }}</span>
|
</div>
|
</header>
|
|
<div class="permission-overview">
|
<div>
|
<strong>{{ activeGrantCount }}</strong
|
><span>生效中</span>
|
</div>
|
<div>
|
<strong>{{ directGrantCount }}</strong
|
><span>直接授权</span>
|
</div>
|
<div>
|
<strong>{{ inheritedGrantCount }}</strong
|
><span>继承授权</span>
|
</div>
|
<div>
|
<strong>{{ activeExclusionCount }}</strong
|
><span>生效排除</span>
|
</div>
|
</div>
|
|
<div class="inspector-toolbar">
|
<div class="inspector-title"><SafetyCertificateOutlined /><span>权限规则</span></div>
|
<a-radio-group v-model:value="inspectorMode" size="small" button-style="solid">
|
<a-radio-button value="grants">访问权限</a-radio-button>
|
<a-radio-button value="exclusions" :disabled="!exclusionSupported">排除拒绝</a-radio-button>
|
</a-radio-group>
|
</div>
|
|
<div class="permission-search">
|
<a-input
|
v-model:value="permissionKeyword"
|
allow-clear
|
:placeholder="inspectorMode === 'grants' ? '搜索主体、操作或来源' : '搜索用户、原因或来源'" />
|
</div>
|
|
<div class="permission-list" v-loading="inspectorMode === 'grants' ? grantsLoading : exclusionsLoading">
|
<template v-if="inspectorMode === 'grants'">
|
<article v-for="grant in filteredGrants" :key="grant.id" class="permission-item">
|
<div class="permission-item-heading">
|
<div class="principal-title">
|
<a-tag :bordered="false">{{ principalTypeLabel(grant.principalType) }}</a-tag>
|
<strong :title="principalDisplayTitle(grant.principalName, grant.principalId)">
|
{{ principalDisplayName(grant.principalName, grant.principalId) }}
|
</strong>
|
</div>
|
<a-tag :color="statusColor(grant.status)">{{ statusLabel(grant.status) }}</a-tag>
|
</div>
|
|
<dl class="permission-facts">
|
<div>
|
<dt>操作</dt>
|
<dd>{{ actionLabel(grant.actionCode) }}</dd>
|
</div>
|
<div>
|
<dt>范围</dt>
|
<dd>{{ scopeLabel(grant.resourceScope) }}</dd>
|
</div>
|
<div>
|
<dt>方式</dt>
|
<dd>{{ grant.inherited ? '继承' : '直接' }}</dd>
|
</div>
|
<div>
|
<dt>次数</dt>
|
<dd>{{ grant.usedCount }} / {{ grant.maxUseCount ?? '不限' }}</dd>
|
</div>
|
</dl>
|
|
<div class="permission-validity">{{ displayTime(grant.validFrom) }} 至 {{ displayTime(grant.validTo) }}</div>
|
|
<div class="permission-item-actions">
|
<a-tooltip title="授权详情">
|
<a-button aria-label="授权详情" type="text" size="small" :icon="h(EyeOutlined)" @click="openDetailDrawer(true, { grantId: grant.id })" />
|
</a-tooltip>
|
<a-tooltip title="使用记录">
|
<a-button aria-label="使用记录" type="text" size="small" :icon="h(HistoryOutlined)" @click="openUsageDrawer(true, { grant })" />
|
</a-tooltip>
|
<span class="permission-source" :title="grant.sourceId">{{ grant.sourceType }} · {{ grant.sourceId }}</span>
|
<a-tooltip v-if="!grant.inherited && grant.status !== 'REVOKED'" title="撤销授权">
|
<a-button aria-label="撤销授权" danger type="text" size="small" :icon="h(StopOutlined)" @click="handleRevoke(grant)" />
|
</a-tooltip>
|
</div>
|
</article>
|
|
<Empty
|
v-if="!grantsLoading && !filteredGrants.length"
|
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
:description="permissionKeyword ? '没有匹配的权限' : '暂无相关权限'" />
|
</template>
|
|
<template v-else>
|
<article v-for="exclusion in filteredExclusions" :key="exclusion.id" class="permission-item exclusion-item">
|
<div class="permission-item-heading">
|
<div class="principal-title">
|
<a-tag :bordered="false">用户</a-tag>
|
<strong :title="principalDisplayTitle(exclusion.userName, exclusion.userId)">
|
{{ principalDisplayName(exclusion.userName, exclusion.userId) }}
|
</strong>
|
</div>
|
<a-tag :color="statusColor(exclusion.status)">{{ statusLabel(exclusion.status) }}</a-tag>
|
</div>
|
|
<dl class="permission-facts exclusion-facts">
|
<div>
|
<dt>裁决</dt>
|
<dd>拒绝读取</dd>
|
</div>
|
<div>
|
<dt>优先级</dt>
|
<dd>排除优先</dd>
|
</div>
|
<div>
|
<dt>覆盖范围</dt>
|
<dd>{{ exclusion.resourceType === 'DOCUMENT' ? '文件全部版本' : '当前版本' }}</dd>
|
</div>
|
</dl>
|
|
<div class="exclusion-reason" :title="exclusion.excludeReason || '未填写排除原因'">
|
{{ exclusion.excludeReason || '未填写排除原因' }}
|
</div>
|
<div class="permission-validity">{{ displayTime(exclusion.validFrom) }} 至 {{ displayTime(exclusion.validTo) }}</div>
|
|
<div class="permission-item-actions">
|
<span class="permission-source" :title="exclusion.sourceId">{{ exclusion.sourceType }} · {{ exclusion.sourceId }}</span>
|
<a-tooltip v-if="exclusion.status !== 'REVOKED'" title="撤销排除拒绝">
|
<a-button aria-label="撤销排除拒绝" danger type="text" size="small" :icon="h(StopOutlined)" @click="handleRevokeExclusion(exclusion)" />
|
</a-tooltip>
|
</div>
|
</article>
|
|
<a-button v-if="exclusionsHasMore && !permissionKeyword" block :loading="exclusionsLoadingMore" @click="loadExclusions(true)">
|
加载更多排除记录
|
</a-button>
|
<Empty
|
v-if="!exclusionsLoading && !filteredExclusions.length"
|
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
:description="permissionKeyword ? '没有匹配的排除记录' : '暂无排除拒绝'" />
|
</template>
|
</div>
|
</template>
|
<Empty v-else class="inspector-empty" description="选择一个文件或目录" />
|
</aside>
|
</div>
|
</div>
|
|
<GrantDrawer @register="registerGrantDrawer" @reload="loadGrants" />
|
<GrantDetailDrawer @register="registerDetailDrawer" />
|
<UsageDrawer @register="registerUsageDrawer" />
|
<RevokeModal @register="registerRevokeModal" @reload="loadGrants" />
|
<ExclusionDrawer @register="registerExclusionDrawer" @reload="handleExclusionReload" />
|
<ExclusionRevokeModal @register="registerExclusionRevokeModal" @reload="handleExclusionReload" />
|
<CreateFolderModal @register="registerCreateFolderModal" @created="handleFolderCreated" />
|
<UploadFileModal @register="registerUploadFileModal" @uploaded="handleFileUploaded" />
|
</div>
|
</template>
|
|
<style lang="scss" scoped>
|
.permission-page {
|
width: 100%;
|
height: 100%;
|
min-height: 520px;
|
}
|
|
.dms-workspace {
|
--folder-navigation-width: 224px;
|
|
display: grid;
|
grid-template-columns: var(--folder-navigation-width) minmax(440px, 1fr) 390px;
|
grid-template-rows: minmax(0, 1fr);
|
width: 100%;
|
height: 100%;
|
overflow: hidden;
|
background: var(--component-background);
|
|
&.folder-navigation-collapsed {
|
--folder-navigation-width: 40px;
|
}
|
}
|
|
.workspace-actions {
|
display: flex;
|
flex: 0 0 auto;
|
flex-wrap: wrap;
|
gap: 7px;
|
align-items: center;
|
}
|
|
.workspace-action-wrapper {
|
display: inline-flex;
|
}
|
|
.workspace-body {
|
display: contents;
|
}
|
|
.explorer-pane {
|
display: contents;
|
}
|
|
.permission-inspector {
|
display: flex;
|
flex-direction: column;
|
grid-row: 1;
|
grid-column: 3;
|
min-width: 0;
|
min-height: 0;
|
background: color-mix(in srgb, var(--component-background) 97%, #697386 3%);
|
border-left: 1px solid var(--border-color-base1);
|
}
|
|
.inspector-header {
|
display: flex;
|
flex: 0 0 auto;
|
gap: 12px;
|
align-items: center;
|
min-height: 76px;
|
padding: 12px 14px;
|
border-bottom: 1px solid var(--border-color-base1);
|
}
|
|
.selected-resource-icon {
|
display: flex;
|
flex: 0 0 44px;
|
align-items: center;
|
justify-content: center;
|
width: 44px;
|
height: 44px;
|
font-size: 27px;
|
color: #667085;
|
background: var(--component-background);
|
border: 1px solid var(--border-color-base1);
|
border-radius: 6px;
|
|
&.folder {
|
color: #e6a700;
|
}
|
|
&.document {
|
color: #2468c9;
|
}
|
}
|
|
.selected-resource-heading {
|
min-width: 0;
|
|
> div {
|
display: flex;
|
gap: 7px;
|
align-items: center;
|
min-width: 0;
|
}
|
|
h2 {
|
min-width: 0;
|
margin: 0;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
font-size: 14px;
|
font-weight: 600;
|
white-space: nowrap;
|
}
|
|
> span {
|
display: block;
|
margin-top: 4px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
font-size: 11px;
|
color: var(--text-color-secondary);
|
white-space: nowrap;
|
}
|
}
|
|
.permission-overview {
|
display: grid;
|
flex: 0 0 auto;
|
grid-template-columns: repeat(4, 1fr);
|
min-height: 68px;
|
border-bottom: 1px solid var(--border-color-base1);
|
|
> div {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
min-width: 0;
|
border-right: 1px solid var(--border-color-base1);
|
|
&:last-child {
|
border-right: 0;
|
}
|
}
|
|
strong {
|
font-size: 18px;
|
font-weight: 600;
|
}
|
|
span {
|
margin-top: 2px;
|
font-size: 11px;
|
color: var(--text-color-secondary);
|
}
|
}
|
|
.inspector-toolbar {
|
display: flex;
|
flex: 0 0 42px;
|
align-items: center;
|
justify-content: space-between;
|
padding: 5px 12px 3px 14px;
|
}
|
|
.inspector-title {
|
display: flex;
|
gap: 7px;
|
align-items: center;
|
font-size: 13px;
|
font-weight: 600;
|
}
|
|
.permission-search {
|
flex: 0 0 auto;
|
padding: 0 12px 8px;
|
}
|
|
.permission-list {
|
flex: 1;
|
min-height: 0;
|
padding: 0 10px 12px;
|
overflow: auto;
|
}
|
|
.permission-item {
|
padding: 10px;
|
margin-bottom: 8px;
|
background: var(--component-background);
|
border: 1px solid var(--border-color-base1);
|
border-radius: 6px;
|
}
|
|
.permission-item-heading,
|
.permission-item-actions,
|
.principal-title {
|
display: flex;
|
align-items: center;
|
}
|
|
.permission-item-heading {
|
gap: 8px;
|
justify-content: space-between;
|
}
|
|
.principal-title {
|
gap: 6px;
|
min-width: 0;
|
|
strong {
|
min-width: 0;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
font-size: 12px;
|
font-weight: 600;
|
white-space: nowrap;
|
}
|
}
|
|
.permission-facts {
|
display: grid;
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
gap: 6px;
|
margin: 10px 0 0;
|
|
> div {
|
min-width: 0;
|
}
|
|
dt {
|
font-size: 10px;
|
color: var(--text-color-secondary);
|
}
|
|
dd {
|
margin: 1px 0 0;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
font-size: 12px;
|
white-space: nowrap;
|
}
|
}
|
|
.permission-validity {
|
margin-top: 9px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
font-size: 11px;
|
color: var(--text-color-secondary);
|
white-space: nowrap;
|
}
|
|
.exclusion-facts {
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
}
|
|
.exclusion-reason {
|
display: -webkit-box;
|
margin-top: 9px;
|
overflow: hidden;
|
-webkit-box-orient: vertical;
|
-webkit-line-clamp: 2;
|
font-size: 12px;
|
line-height: 1.5;
|
color: var(--text-color-secondary);
|
}
|
|
.permission-item-actions {
|
min-width: 0;
|
min-height: 30px;
|
padding-top: 5px;
|
margin-top: 6px;
|
border-top: 1px solid var(--border-color-base1);
|
}
|
|
.permission-source {
|
flex: 1;
|
min-width: 0;
|
margin: 0 5px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
font-size: 10px;
|
color: var(--text-color-secondary);
|
text-align: right;
|
white-space: nowrap;
|
}
|
|
.inspector-empty {
|
margin-top: min(24vh, 180px);
|
}
|
|
@media (max-width: 1100px) {
|
.dms-workspace {
|
grid-template-columns: var(--folder-navigation-width) minmax(400px, 1fr) 340px;
|
}
|
}
|
|
@media (max-width: 900px) {
|
.permission-page {
|
min-height: 760px;
|
overflow: auto;
|
}
|
|
.dms-workspace {
|
grid-template-rows: 500px 420px;
|
grid-template-columns: var(--folder-navigation-width) minmax(0, 1fr);
|
min-height: 760px;
|
overflow: visible;
|
}
|
|
:deep(.folder-navigation) {
|
grid-row: 1 / 3;
|
grid-column: 1;
|
}
|
|
:deep(.explorer-main) {
|
grid-row: 1;
|
grid-column: 2;
|
}
|
|
.permission-inspector {
|
grid-row: 2;
|
grid-column: 2;
|
width: 100%;
|
border-top: 1px solid var(--border-color-base1);
|
border-left: 0;
|
}
|
}
|
|
@media (max-width: 560px) {
|
.workspace-actions {
|
width: 100%;
|
}
|
|
.permission-facts {
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
}
|
}
|
</style>
|