mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(wiki): gate write controls behind the manage:wiki capability
This commit is contained in:
parent
c9f73bbbd5
commit
71aba2649e
@ -45,7 +45,7 @@
|
||||
<!-- Error state with actions -->
|
||||
<div v-if="status === 'failed'" class="stage-error">
|
||||
<span class="error-badge">❌ {{ t('wiki.jobStage.failed') }} — {{ errorCode }}</span>
|
||||
<div class="error-actions">
|
||||
<div v-if="canManageWiki" class="error-actions">
|
||||
<button class="btn-mini" @click="$emit('reprocess')">{{ t('wiki.reprocess') }}</button>
|
||||
<button class="btn-mini btn-mini-alt" @click="$emit('repair')">{{ t('wiki.page.repair') }}</button>
|
||||
</div>
|
||||
@ -56,8 +56,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
const workspace = useWorkspaceStore()
|
||||
|
||||
// Reprocess / repair are write actions — viewers see job status without them.
|
||||
const canManageWiki = computed(() => workspace.can('manage:wiki'))
|
||||
|
||||
const props = defineProps<{
|
||||
stage: string
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<el-icon :size="12"><Link /></el-icon>
|
||||
{{ t('wiki.page.enriched') }}
|
||||
</span>
|
||||
<button v-else class="btn-mini-enrich" @click="$emit('enrich')">
|
||||
<button v-else-if="canManageWiki" class="btn-mini-enrich" @click="$emit('enrich')">
|
||||
<el-icon :size="12"><Link /></el-icon>
|
||||
{{ t('wiki.page.enrich') }}
|
||||
</button>
|
||||
@ -41,8 +41,13 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Link } from '@element-plus/icons-vue'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
const workspace = useWorkspaceStore()
|
||||
|
||||
// Enriching a page (adding cross-links) is a write action — viewers only read.
|
||||
const canManageWiki = computed(() => workspace.can('manage:wiki'))
|
||||
|
||||
const props = defineProps<{
|
||||
page: {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="raw-panel">
|
||||
<!-- Upload + Add text row -->
|
||||
<div class="upload-row">
|
||||
<div v-if="canManageWiki" class="upload-row">
|
||||
<div
|
||||
class="upload-zone"
|
||||
:class="{ 'is-dragging': isDragging, 'is-uploading': uploadingFiles.length > 0 }"
|
||||
@ -45,7 +45,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Directory scan -->
|
||||
<div class="dir-scan-row">
|
||||
<div v-if="canManageWiki" class="dir-scan-row">
|
||||
<div class="dir-input-wrap">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
|
||||
@ -163,7 +163,7 @@
|
||||
{{ raw.errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="raw-item-actions">
|
||||
<div v-if="canManageWiki" class="raw-item-actions">
|
||||
<button
|
||||
v-if="raw.processingStatus === 'processing' && !cancellingIds.has(raw.id)"
|
||||
class="btn-icon btn-icon-danger" :title="t('wiki.cancel')"
|
||||
@ -290,12 +290,18 @@ import { mcToast } from '@/composables/useMcToast'
|
||||
import { useFileDrop } from '@/composables/useFileDrop'
|
||||
import { Download } from '@element-plus/icons-vue'
|
||||
import { useWikiStore } from '@/stores/useWikiStore'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
import { wikiApi } from '@/api/index'
|
||||
import JobStageBar from './JobStageBar.vue'
|
||||
import type { WikiProcessingJob } from '@/composables/useWikiJobPoller'
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useWikiStore()
|
||||
const workspace = useWorkspaceStore()
|
||||
|
||||
// Uploading, scanning and (re)processing raw material all require manage:wiki.
|
||||
// Viewers can still see the material list but get no write controls.
|
||||
const canManageWiki = computed(() => workspace.can('manage:wiki'))
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// While raw materials are active, subscribe to the backend SSE progress stream.
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
@keydown.space.prevent="$emit('open', kb.id)"
|
||||
>
|
||||
<button
|
||||
v-if="canManageWiki"
|
||||
type="button"
|
||||
class="kb-card-delete"
|
||||
:title="t('wiki.library.deleteKB')"
|
||||
@ -61,6 +62,7 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { WikiKB } from '@/stores/useWikiStore'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
import { kbAccent, kbAccentFg, kbInitial, relativeTime } from '../utils/kbVisual'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -74,6 +76,10 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const workspace = useWorkspaceStore()
|
||||
|
||||
// Deleting a knowledge base requires manage:wiki; hide it from read-only viewers.
|
||||
const canManageWiki = computed(() => workspace.can('manage:wiki'))
|
||||
|
||||
const failedJobCount = computed(() => props.failedJobCount ?? 0)
|
||||
const initial = computed(() => kbInitial(props.kb))
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<h1 class="mc-page-title">{{ t('nav.wiki') }}</h1>
|
||||
<p class="mc-page-desc">{{ t('wiki.desc') }}</p>
|
||||
</div>
|
||||
<button class="btn-primary page-cta" @click="$emit('create')">
|
||||
<button v-if="canManageWiki" class="btn-primary page-cta" @click="$emit('create')">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</svg>
|
||||
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
<h3 class="library-empty-title">{{ t('wiki.library.empty') }}</h3>
|
||||
<p class="library-empty-hint">{{ t('wiki.library.emptyHint') }}</p>
|
||||
<button class="btn-primary" @click="$emit('create')">
|
||||
<button v-if="canManageWiki" class="btn-primary" @click="$emit('create')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||
{{ t('wiki.createKB') }}
|
||||
</button>
|
||||
@ -87,6 +87,7 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { WikiKB } from '@/stores/useWikiStore'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
import McPagination from '@/components/common/McPagination.vue'
|
||||
import WikiKBCard from './WikiKBCard.vue'
|
||||
|
||||
@ -108,6 +109,10 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const workspace = useWorkspaceStore()
|
||||
|
||||
// view:wiki users without manage:wiki may browse but not create knowledge bases.
|
||||
const canManageWiki = computed(() => workspace.can('manage:wiki'))
|
||||
|
||||
const searchInput = ref('')
|
||||
type SortMode = 'recent' | 'name' | 'pages'
|
||||
|
||||
@ -13,8 +13,8 @@
|
||||
<p class="summary-text">{{ store.currentPage.summary }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions bar -->
|
||||
<div class="page-actions-bar">
|
||||
<!-- Actions bar — write actions, hidden for read-only viewers -->
|
||||
<div v-if="canManageWiki" class="page-actions-bar">
|
||||
<button class="btn-secondary btn-sm" @click="editing = !editing">
|
||||
{{ editing ? t('common.cancel') : t('common.edit') }}
|
||||
</button>
|
||||
@ -99,6 +99,7 @@
|
||||
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useWikiStore, isProtectedPage, type WikiPage } from '@/stores/useWikiStore'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
import { wikiApi } from '@/api/index'
|
||||
import { useMarkdownRenderer } from '@/composables/useMarkdownRenderer'
|
||||
import { Link, SetUp } from '@element-plus/icons-vue'
|
||||
@ -109,8 +110,13 @@ import ImageLightbox from './ImageLightbox.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useWikiStore()
|
||||
const workspace = useWorkspaceStore()
|
||||
const { renderMarkdown } = useMarkdownRenderer()
|
||||
|
||||
// Editing, enriching, repairing and deleting pages all require manage:wiki.
|
||||
// Read-only viewers get the rendered page without the action bar.
|
||||
const canManageWiki = computed(() => workspace.can('manage:wiki'))
|
||||
|
||||
const editing = ref(false)
|
||||
const editContent = ref('')
|
||||
const backlinks = ref<WikiPage[]>([])
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useWikiStore, type WikiKB } from '@/stores/useWikiStore'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
import RawMaterialPanel from './RawMaterialPanel.vue'
|
||||
import WikiPageViewer from './WikiPageViewer.vue'
|
||||
import WikiConfig from './WikiConfig.vue'
|
||||
@ -69,17 +70,27 @@ defineProps<{ kb: WikiKB }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useWikiStore()
|
||||
const workspace = useWorkspaceStore()
|
||||
|
||||
// Read-only viewers (view:wiki without manage:wiki) only get the browsing tabs;
|
||||
// the processing-config and transformations tabs are management surfaces.
|
||||
const canManageWiki = computed(() => workspace.can('manage:wiki'))
|
||||
|
||||
const activeTab = ref('raw')
|
||||
|
||||
const tabs = computed(() => [
|
||||
{ key: 'raw', label: t('wiki.rawMaterials') },
|
||||
{ key: 'pages', label: t('wiki.pages') },
|
||||
{ key: 'graph', label: t('wiki.graph.tab') },
|
||||
{ key: 'config', label: t('wiki.config') },
|
||||
{ key: 'transformations', label: t('wiki.transformations.tab') },
|
||||
{ key: 'hotCache', label: t('wiki.hotCache.tab') },
|
||||
])
|
||||
const tabs = computed(() => {
|
||||
const list = [
|
||||
{ key: 'raw', label: t('wiki.rawMaterials') },
|
||||
{ key: 'pages', label: t('wiki.pages') },
|
||||
{ key: 'graph', label: t('wiki.graph.tab') },
|
||||
]
|
||||
if (canManageWiki.value) {
|
||||
list.push({ key: 'config', label: t('wiki.config') })
|
||||
list.push({ key: 'transformations', label: t('wiki.transformations.tab') })
|
||||
}
|
||||
list.push({ key: 'hotCache', label: t('wiki.hotCache.tab') })
|
||||
return list
|
||||
})
|
||||
|
||||
// When the user picks a different KB from the library, snap back to the
|
||||
// raw-materials tab so they don't land on stale state from the previous KB.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user