Skip to content

Enterprise Components

import { Tabs, TabItem } from ‘@astrojs/starlight/components’;

svadmin ships with a comprehensive suite of enterprise-grade components designed for real-world admin systems. All components follow the Headless + Wrapper pattern:

  • Layer A (Headless): Pure UI components driven by Props + Callbacks — zero backend coupling
  • Layer B (Wrapper): Optional integration with AuthProvider for out-of-the-box experience

A fully decoupled RBAC permission matrix for managing (Role × Resource × Action) relationships.

PropTypeDescription
rolesRoleInfo[]Role list ({ code, name })
resourcesResourceInfo[]Resource list, supports section grouping
actionsActionInfo[]Action definitions (CRUD, etc.)
isGranted(role, resource, action) => booleanExternal authorization check function
selectedRole$bindable<string>Currently selected role (two-way binding)
onToggle(role, resource, action, grant) => voidPermission change callback
sidebarExtraSnippetLeft sidebar extension slot
headerExtraSnippetTop toolbar extension slot
loadingbooleanDisable interactions while loading
<script>
import { PermissionMatrix } from '@svadmin/ui';
let policies = $state([]);
function checkGrant(role, resource, action) {
return policies.some(p => p.role === role && p.res === resource && p.act === action);
}
async function handleToggle(role, res, act, grant) {
await fetch('/api/permissions', {
method: 'POST',
body: JSON.stringify({ role, res, act, grant })
});
}
</script>
<PermissionMatrix
roles={[{ code: 'admin', name: 'Administrator' }, { code: 'editor', name: 'Editor' }]}
resources={[{ code: 'posts', name: 'Posts', section: 'Content' }]}
actions={[{ code: 'read', name: 'Read' }, { code: 'write', name: 'Write' }]}
isGranted={checkGrant}
onToggle={handleToggle}
>
{#snippet sidebarExtra()}
<p>Tenant: Acme Corp</p>
{/snippet}
</PermissionMatrix>

The <RolesSettings /> component wraps PermissionMatrix with AuthProvider integration. It is automatically available at /settings/roles when you use <SettingsPage />.

To enable it, implement these optional methods on your AuthProvider:

const authProvider: AuthProvider = {
// ... login, logout, check, getIdentity ...
getRoles: async () => [{ id: '1', name: 'Admin' }],
getRolePermissions: async (roleId) => ({ posts: ['create', 'read'] }),
updateRolePermissions: async (roleId, permissions) => ({ success: true }),
};

A compliance-ready audit log viewer with search filtering, action badges, and an integrated snapshot drawer for viewing JSON diffs.

const authProvider: AuthProvider = {
getAuditLogs: async ({ page, pageSize }) => ({
data: [
{ id: '1', userName: 'admin', action: 'update', resource: 'users',
createdAt: new Date(), ipAddress: '192.168.1.1',
details: { before: { name: 'Old' }, after: { name: 'New' } } }
],
total: 100,
}),
};

Available at /settings/audit automatically in <SettingsPage />.


A zero-refresh multi-tenant workspace switcher built for Sidebar integration.

<script>
import { TenantSwitcher } from '@svadmin/ui';
const tenants = [
{ id: '1', name: 'Acme Corporation', logo: '/acme.png' },
{ id: '2', name: 'Beta Industries' },
];
let currentTenantId = $state('1');
function handleSwitch(id: string) {
// Reload data for new tenant context
location.href = `?tenant=${id}`;
}
</script>
<!-- Place at the top of Sidebar -->
<TenantSwitcher
{tenants}
bind:currentTenantId
onSwitch={handleSwitch}
/>
PropTypeDescription
tenantsTenant[]List of available tenants
currentTenantId$bindable<string>Active tenant ID
collapsedbooleanAdapts to sidebar collapsed state
onSwitch(tenantId) => voidSwitch callback
headerSnippetSnippetDropdown header slot (for search/create)

A background task center showing real-time progress, status indicators, and download/retry actions.

<script>
import { TaskQueueDrawer } from '@svadmin/ui';
import { createClient } from '@supabase/supabase-js';
import { createSupaCloudClient } from '@supacloud/js';
import { createSupaCloudTaskProvider } from '@svadmin/supabase/supacloud';
const supabase = createClient(import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_ANON_KEY);
const supacloud = createSupaCloudClient({
supabase,
managementApiUrl: import.meta.env.VITE_SUPACLOUD_API_URL,
projectRef: import.meta.env.VITE_SUPACLOUD_PROJECT_REF,
});
const taskProvider = createSupaCloudTaskProvider({ supacloud });
let drawerOpen = $state(false);
</script>
<TaskQueueDrawer
bind:open={drawerOpen}
{taskProvider}
/>

TaskQueueDrawer is now a first-class task center powered directly by TaskProvider. It includes task and DLQ tabs, filtering, auto-refresh, inline task submission, and a synced detail panel out of the box.


A zero-dependency drag-and-drop grid for customizable dashboard layouts using native HTML5 Drag & Drop.

<script>
import { DraggableGrid, type GridModule } from '@svadmin/ui';
let modules = $state<GridModule[]>([
{ id: 'revenue', title: 'Revenue' },
{ id: 'users', title: 'Active Users' },
{ id: 'orders', title: 'Recent Orders' },
]);
function saveOrder(ids: string[]) {
localStorage.setItem('dashboard_order', JSON.stringify(ids));
}
</script>
<DraggableGrid bind:modules onOrderSave={saveOrder}>
{#snippet renderItem({ module, dragging })}
<div class="p-4 border rounded-lg bg-card {dragging ? 'opacity-50' : ''}">
<h3>{module.title}</h3>
</div>
{/snippet}
</DraggableGrid>

Declarative permission gate component. Renders children only if the current user has access.

<script>
import { CanAccess } from '@svadmin/ui';
</script>
<CanAccess resource="posts" action="delete">
<button>Delete Post</button>
{#snippet fallback()}
<button disabled>No Permission</button>
{/snippet}
</CanAccess>

Can is an alias for CanAccess for shorter syntax.


Dynamic nested form field for one-to-many relationships (e.g., order items, contact details).

const fields: FieldDefinition[] = [
{
key: 'items',
label: 'Order Items',
type: 'array',
subFields: [
{ key: 'name', label: 'Product', type: 'text', required: true },
{ key: 'quantity', label: 'Qty', type: 'number', required: true },
{ key: 'price', label: 'Price', type: 'number' },
],
},
];

ArrayField is automatically rendered by AutoForm when type: 'array' is specified.