-
+
Access Denied
-
You don't have permission to access the data explorer. This section is only available to viewers and managers.
+
You don't have permission to access the data explorer. This section is only available to project managers.
@@ -143,10 +143,10 @@ import AssetThumbnail from '@/components/project/AssetThumbnail.vue';
import Button from '@/components/common/Button.vue';
import UploadImagesModal from '@/components/project/UploadImagesModal.vue';
import {assetService, dataSourceService} from '@/services/project';
+import { PERMISSIONS } from '@/services/auth/permissions.types';
import {useAlert} from '@/composables/useAlert';
import {AppLogger} from '@/core/logger/logger';
import {usePermissions} from '@/composables/usePermissions';
-import {PERMISSIONS} from '@/services/auth/permissions.types';
import { AssetStatus, type Asset } from '@/core/asset/asset.types';
import type { AssetListParams } from '@/services/project/asset';
@@ -207,9 +207,8 @@ const hasActiveFilters = computed(() =>
Boolean(filterStatus.value || (searchQuery.value && searchQuery.value.trim()))
);
-const canAccessDataExplorer = computed(() =>
- hasProjectPermission(PERMISSIONS.DATA_EXPLORER.READ)
-);
+// Manager-only for now
+const canAccessDataExplorer = computed(() => hasProjectPermission(PERMISSIONS.DATA_SOURCE.UPDATE));
// Methods
const navigateToProject = () => {
@@ -600,4 +599,4 @@ onMounted(async () => {
line-height: 1.5;
}
}
-
\ No newline at end of file
+
diff --git a/frontend/src/views/project/DataSourcesView.vue b/frontend/src/views/project/DataSourcesView.vue
index a70c855f..d002780d 100644
--- a/frontend/src/views/project/DataSourcesView.vue
+++ b/frontend/src/views/project/DataSourcesView.vue
@@ -74,10 +74,8 @@ const isModalOpen = ref(false);
const isLoading = ref(false);
const isCreating = ref(false);
-// Permission checks - READ permission required as baseline to view data sources page
-const canManageDataSources = computed(() =>
- hasProjectPermission(PERMISSIONS.DATA_SOURCE.READ)
-);
+// Manager-only access: require update permission
+const canManageDataSources = computed(() => hasProjectPermission(PERMISSIONS.DATA_SOURCE.UPDATE));
const openModal = () => isModalOpen.value = true;
const closeModal = () => isModalOpen.value = false;
@@ -196,4 +194,4 @@ onMounted(() => {
}
}
-
\ No newline at end of file
+
diff --git a/frontend/src/views/project/ProjectDetailView.vue b/frontend/src/views/project/ProjectDetailView.vue
index 27e82b3b..08cfb651 100644
--- a/frontend/src/views/project/ProjectDetailView.vue
+++ b/frontend/src/views/project/ProjectDetailView.vue
@@ -26,7 +26,7 @@
Dashboard
-
+
-
+
-
+
{
opacity: 0;
transform: translateY(20px);
}
-
\ No newline at end of file
+
diff --git a/frontend/src/views/project/TasksView.vue b/frontend/src/views/project/TasksView.vue
index 3abda2bd..9531cee3 100644
--- a/frontend/src/views/project/TasksView.vue
+++ b/frontend/src/views/project/TasksView.vue
@@ -3,6 +3,7 @@
-
+
@@ -51,6 +54,7 @@
variant="primary"
@click="handleRefresh"
:disabled="isLoading"
+ v-if="canManageTasks"
>
Refresh
@@ -61,6 +65,7 @@
-
+
-
+
('Processing...');
// Computed properties for reactive props
const currentSelectionCount = computed(() => selection.selectionCount.value);
-const canManageTasks = computed(() => {
- return hasProjectPermission(PERMISSIONS.PROJECT.UPDATE);
-});
+const canManageTasks = computed(() => isManager(projectId.value));
-const canAssignTasks = computed(() => {
- return hasProjectPermission(PERMISSIONS.TASK.ASSIGN);
-});
+const canAssignTasks = computed(() => hasProjectPermission(PERMISSIONS.TASK.ASSIGN));
-const canUpdateTaskStatus = computed(() => {
- return hasProjectPermission(PERMISSIONS.TASK.UPDATE_STATUS);
-});
+const canUpdateTaskStatus = computed(() => hasProjectPermission(PERMISSIONS.TASK.UPDATE_STATUS));
-const canReviewTasks = computed(() => {
- return hasProjectPermission(PERMISSIONS.ANNOTATION.REVIEW);
-});
+const canReviewTasks = computed(() => hasProjectPermission(PERMISSIONS.ANNOTATION.REVIEW));
-const isUserManager = computed(() => {
- return canManageTasks.value;
-});
+const isUserManager = computed(() => canManageTasks.value);
const currentUserEmail = computed(() => {
const authStore = useAuthStore();
@@ -340,30 +335,30 @@ const isTaskClickable = (task: TaskTableRow): boolean => {
// Table configuration
-const tableColumns: TableColumn[] = [
- { key: 'select', label: '☐', sortable: false, width: '4%', align: 'center' },
- { key: 'assetName', label: 'Asset', sortable: true, width: '26%' },
- { key: 'priority', label: 'Priority', sortable: true, width: '8%', align: 'center' },
- { key: 'status', label: 'Status', sortable: true, width: '12%', align: 'center' },
- { key: 'assignedTo', label: 'Assigned To', sortable: true, width: '16%' },
- { key: 'dueDate', label: 'Due Date', sortable: true, width: '12%', format: 'date' },
- { key: 'createdAt', label: 'Created', sortable: true, width: '11%', format: 'datetime' },
- { key: 'completedAt', label: 'Completed', sortable: true, width: '11%', format: 'datetime' },
-];
+const tableColumns = computed(() => {
+ const cols: TableColumn[] = [
+ { key: 'assetName', label: 'Asset', sortable: true, width: '26%' },
+ { key: 'priority', label: 'Priority', sortable: true, width: '8%', align: 'center' },
+ { key: 'status', label: 'Status', sortable: true, width: '12%', align: 'center' },
+ { key: 'assignedTo', label: 'Assigned To', sortable: true, width: '16%' },
+ { key: 'dueDate', label: 'Due Date', sortable: true, width: '12%', format: 'date' },
+ { key: 'createdAt', label: 'Created', sortable: true, width: '11%', format: 'datetime' },
+ { key: 'completedAt', label: 'Completed', sortable: true, width: '11%', format: 'datetime' },
+ ];
+
+ if (canManageTasks.value) {
+ cols.unshift({ key: 'select', label: '☐', sortable: false, width: '4%', align: 'center' });
+ }
+ return cols;
+});
const rowActions = computed((): TableRowAction[] => {
+ if (!canManageTasks.value) return [];
+
const actions: TableRowAction[] = [
+ { key: 'edit', label: 'Edit', icon: faEdit, variant: 'secondary' },
{
- key: 'edit',
- label: 'Edit',
- icon: faEdit,
- variant: 'secondary'
- },
- {
- key: 'assign',
- label: 'Assign',
- icon: faUserCog,
- variant: 'secondary',
+ key: 'assign', label: 'Assign', icon: faUserCog, variant: 'secondary',
disabled: (row: TaskTableRow) =>
row.status === TaskStatus.SUSPENDED ||
row.status === TaskStatus.DEFERRED ||
@@ -372,30 +367,18 @@ const rowActions = computed((): TableRowAction[] => {
row.status === TaskStatus.VETOED
},
{
- key: 'priority',
- label: 'Change Priority',
- icon: faSort,
- variant: 'secondary',
+ key: 'priority', label: 'Change Priority', icon: faSort, variant: 'secondary',
+ disabled: (row: TaskTableRow) => row.status === TaskStatus.ARCHIVED || row.status === TaskStatus.VETOED
+ },
+ {
+ key: 'change-status', label: 'Change Status', icon: faBolt, variant: 'secondary',
disabled: (row: TaskTableRow) =>
+ row.status === TaskStatus.COMPLETED ||
row.status === TaskStatus.ARCHIVED ||
row.status === TaskStatus.VETOED
}
];
- // Add single status change button for managers only
- if (canManageTasks.value) {
- actions.push({
- key: 'change-status',
- label: 'Change Status',
- icon: faBolt,
- variant: 'secondary',
- disabled: (row: TaskTableRow) =>
- row.status === TaskStatus.COMPLETED ||
- row.status === TaskStatus.ARCHIVED ||
- row.status === TaskStatus.VETOED
- });
- }
-
return actions;
});
diff --git a/frontend/src/views/project/WorkflowPipelineView.vue b/frontend/src/views/project/WorkflowPipelineView.vue
index 352f5a52..eb641341 100644
--- a/frontend/src/views/project/WorkflowPipelineView.vue
+++ b/frontend/src/views/project/WorkflowPipelineView.vue
@@ -1,5 +1,5 @@
-