- 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
130 lines
3.1 KiB
TypeScript
130 lines
3.1 KiB
TypeScript
import { sdk } from '../sdk'
|
|
|
|
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)
|