Wire Import and Audit Log pages, add Create User action
- Import page: schedule (XLSX/CSV) and entity import with dry-run toggle - Audit Log page: paginated, filterable by object type - Nav items now clickable (removed stubs) - StartOS Action: Create User with name/email/password/role inputs - Default first-boot creds: jonathan@ten31.xyz
This commit is contained in:
@@ -1,4 +1,129 @@
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
// No custom actions for v1. Config is handled at install time.
|
||||
export const actions = sdk.Actions.of()
|
||||
const { InputSpec, Value, Action } = sdk
|
||||
|
||||
// ============================================
|
||||
// Action: Create User
|
||||
// ============================================
|
||||
const createUserInputSpec = InputSpec.of({
|
||||
name: Value.text({
|
||||
name: 'Full Name',
|
||||
description: 'Display name for the user',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'Jane Smith',
|
||||
}),
|
||||
email: Value.text({
|
||||
name: 'Email',
|
||||
description: 'Login email address',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'jane@ten31.com',
|
||||
patterns: [
|
||||
{
|
||||
regex: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
|
||||
description: 'Must be a valid email address',
|
||||
},
|
||||
],
|
||||
}),
|
||||
password: Value.text({
|
||||
name: 'Password',
|
||||
description: 'Initial password (user should change after first login)',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'minimum 4 characters',
|
||||
}),
|
||||
role: Value.select({
|
||||
name: 'Role',
|
||||
description:
|
||||
'approver: full access including sign-off. cfo: read + edit + submit. fund_admin: read + edit + submit. viewer: read only.',
|
||||
default: 'viewer',
|
||||
values: {
|
||||
approver: 'Approver',
|
||||
cfo: 'CFO',
|
||||
fund_admin: 'Fund Admin',
|
||||
viewer: 'Viewer',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const createUserAction = Action.withInput(
|
||||
'create-user',
|
||||
{
|
||||
name: 'Create User',
|
||||
description: 'Add a new user account with a role',
|
||||
warning: null,
|
||||
allowedStatuses: 'only-running',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
},
|
||||
createUserInputSpec,
|
||||
async () => ({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
role: 'viewer' as const,
|
||||
}),
|
||||
async ({ input, effects }) => {
|
||||
try {
|
||||
const sub = await sdk.SubContainer.of(
|
||||
effects,
|
||||
{ imageId: 'main' },
|
||||
sdk.Mounts.of().mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: null,
|
||||
mountpoint: '/data',
|
||||
readonly: false,
|
||||
}),
|
||||
'create-user-task',
|
||||
)
|
||||
|
||||
const result = await sub.exec(
|
||||
[
|
||||
'python3',
|
||||
'-m',
|
||||
'ten31portal.cli',
|
||||
'create-user',
|
||||
'--name',
|
||||
input.name,
|
||||
'--email',
|
||||
input.email,
|
||||
'--role',
|
||||
input.role,
|
||||
'--password',
|
||||
input.password,
|
||||
],
|
||||
{
|
||||
env: { TEN31_DB_PATH: '/data/portal.db' },
|
||||
},
|
||||
30000,
|
||||
)
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
const stderr = result.stderr?.toString() || 'Unknown error'
|
||||
return {
|
||||
version: '1' as const,
|
||||
title: 'Error',
|
||||
message: `Failed to create user: ${stderr}`,
|
||||
result: null,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
version: '1' as const,
|
||||
title: 'User Created',
|
||||
message: `Created user ${input.name} (${input.email}) with role: ${input.role}`,
|
||||
result: null,
|
||||
}
|
||||
} catch (e: any) {
|
||||
return {
|
||||
version: '1' as const,
|
||||
title: 'Error',
|
||||
message: `Failed to create user: ${e.message || e}`,
|
||||
result: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const actions = sdk.Actions.of().addAction(createUserAction)
|
||||
|
||||
Reference in New Issue
Block a user