WIP - permissions progress

This commit is contained in:
Francisco Gaona
2025-12-30 03:26:50 +01:00
parent f4143ab106
commit b4bdeeb9f6
22 changed files with 1565 additions and 99 deletions

View File

@@ -0,0 +1,116 @@
<template>
<div class="space-y-6">
<Card>
<CardHeader>
<CardTitle>Org-Wide Default</CardTitle>
<CardDescription>
Control the baseline visibility for records of this object
</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<div class="space-y-2">
<Label for="orgWideDefault">Record Visibility</Label>
<Select v-model="localOrgWideDefault" @update:model-value="handleOrgWideDefaultChange">
<SelectTrigger id="orgWideDefault">
<SelectValue placeholder="Select visibility level" />
</SelectTrigger>
<SelectContent>
<SelectItem value="private">
<div>
<div class="font-semibold">Private</div>
<div class="text-xs text-muted-foreground">Only record owner can see</div>
</div>
</SelectItem>
<SelectItem value="public_read">
<div>
<div class="font-semibold">Public Read Only</div>
<div class="text-xs text-muted-foreground">Everyone can read, only owner can edit/delete</div>
</div>
</SelectItem>
<SelectItem value="public_read_write">
<div>
<div class="font-semibold">Public Read/Write</div>
<div class="text-xs text-muted-foreground">Everyone can read, edit, and delete all records</div>
</div>
</SelectItem>
</SelectContent>
</Select>
<p class="text-sm text-muted-foreground">
This setting controls who can see records by default. Individual user permissions are granted through roles.
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Field-Level Security</CardTitle>
<CardDescription>
Control field visibility and editability by role (coming soon)
</CardDescription>
</CardHeader>
<CardContent>
<div class="text-sm text-muted-foreground">
Field-level permissions will be managed through role configuration.
Navigate to Setup Roles to configure field access for each role.
</div>
</CardContent>
</Card>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '~/components/ui/card';
import { Label } from '~/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select';
const props = defineProps<{
objectApiName: string;
orgWideDefault?: string;
}>();
const emit = defineEmits<{
update: [orgWideDefault: string];
}>();
const { $api } = useNuxtApp();
const { showToast } = useToast();
const localOrgWideDefault = ref(props.orgWideDefault || 'private');
// Watch for prop changes
watch(() => props.orgWideDefault, (newValue) => {
if (newValue) {
localOrgWideDefault.value = newValue;
}
});
const handleOrgWideDefaultChange = async (value: string) => {
try {
// Update object definition
await $api(`/api/setup/objects/${props.objectApiName}`, {
method: 'PATCH',
body: {
orgWideDefault: value
}
});
showToast({
title: 'Success',
description: 'Org-Wide Default saved successfully',
variant: 'default'
});
emit('update', value);
} catch (error: any) {
console.error('Failed to update org-wide default:', error);
showToast({
title: 'Error',
description: error.data?.message || 'Failed to save changes',
variant: 'destructive'
});
}
};
</script>

View File

@@ -137,7 +137,12 @@ const validateForm = (): boolean => {
const handleSave = () => {
if (validateForm()) {
emit('save', { ...formData.value })
// Start with props.data to preserve system fields like id, then override with user edits
const dataToSave = {
...props.data,
...formData.value,
}
emit('save', dataToSave)
}
}

View File

@@ -160,11 +160,10 @@ const validateForm = (): boolean => {
const handleSave = () => {
if (validateForm()) {
// Filter out system fields from save data
const saveData = { ...formData.value }
const systemFields = ['id', 'tenantId', 'ownerId', 'created_at', 'updated_at', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']
for (const field of systemFields) {
delete saveData[field]
// Start with props.data to preserve system fields like id, then override with user edits
const saveData = {
...props.data,
...formData.value,
}
emit('save', saveData)
}

View File

@@ -16,8 +16,9 @@
<!-- Tabs -->
<div class="mb-8">
<Tabs v-model="activeTab" default-value="fields" class="w-full">
<TabsList class="grid w-full grid-cols-2 max-w-md">
<TabsList class="grid w-full grid-cols-3 max-w-2xl">
<TabsTrigger value="fields">Fields</TabsTrigger>
<TabsTrigger value="access">Access</TabsTrigger>
<TabsTrigger value="layouts">Page Layouts</TabsTrigger>
</TabsList>
@@ -55,6 +56,15 @@
</div>
</TabsContent>
<!-- Access Tab -->
<TabsContent value="access" class="mt-6">
<ObjectAccessSettings
:object-api-name="object.apiName"
:org-wide-default="object.orgWideDefault"
@update="handleAccessUpdate"
/>
</TabsContent>
<!-- Page Layouts Tab -->
<TabsContent value="layouts" class="mt-6">
<div v-if="!selectedLayout" class="space-y-4">
@@ -138,6 +148,7 @@ import { Plus, Trash2, ArrowLeft } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import PageLayoutEditor from '@/components/PageLayoutEditor.vue'
import ObjectAccessSettings from '@/components/ObjectAccessSettings.vue'
import type { PageLayout, FieldLayoutItem } from '~/types/page-layout'
const route = useRoute()
@@ -247,7 +258,11 @@ watch(activeTab, (newTab) => {
fetchLayouts()
}
})
const handleAccessUpdate = (orgWideDefault: string) => {
if (object.value) {
object.value.orgWideDefault = orgWideDefault
}
}
onMounted(async () => {
await fetchObject()
// If we start on layouts tab, load them