const axios = require('axios');
const BASE_URL = 'http://localhost:13000/api';
const EMAIL = 'admin@nocobase.com';
const PASSWORD = 'admin123'; // Standard Default
// --- CONFIGURATION ---
const MENU_STRUCTURE = [
{
title: 'Dashboard',
icon: 'DashboardOutlined',
type: 'page',
collection: null
},
{
title: 'Accounts',
icon: 'BankOutlined',
type: 'collection_page',
collection: 'accounts',
blockType: 'table'
},
{
title: 'Contacts',
icon: 'UserOutlined',
type: 'collection_page',
collection: 'contacts',
blockType: 'table'
},
{
title: 'Leads',
icon: 'UsergroupAddOutlined',
type: 'folder',
children: [
{ title: 'My Leads', icon: 'UserOutlined', collection: 'leads', type: 'collection_page', params: { filter: { owner_id: '{{$user.id}}' } } },
{ title: 'All Leads', icon: 'TeamOutlined', collection: 'leads', type: 'collection_page' },
{ title: 'Exhibition Leads', icon: 'ShopOutlined', collection: 'leads', type: 'collection_page', params: { filter: { business_line: 'EXHIBITION' } } },
{ title: 'Agency Leads', icon: 'ToolOutlined', collection: 'leads', type: 'collection_page', params: { filter: { business_line: 'AGENCY' } } }
]
},
{
title: 'Opportunities',
icon: 'DollarOutlined',
type: 'folder',
children: [
{
title: 'Exhibition Pipeline',
icon: 'ProjectOutlined',
collection: 'opportunities',
type: 'collection_page',
blockType: 'kanban',
groupField: 'stage',
params: { filter: { business_line: 'EXHIBITION', status: 'open' } }
},
{
title: 'Agency Pipeline',
icon: 'ProjectOutlined',
collection: 'opportunities',
type: 'collection_page',
blockType: 'kanban',
groupField: 'stage',
params: { filter: { business_line: 'AGENCY', status: 'open' } }
},
{ title: 'My Opportunities', icon: 'UserOutlined', collection: 'opportunities', type: 'collection_page' },
{ title: 'All Opportunities', icon: 'TeamOutlined', collection: 'opportunities', type: 'collection_page' }
]
},
{
title: 'Events & Booths',
icon: 'ShopOutlined',
type: 'folder',
children: [
{ title: 'Events', icon: 'CalendarOutlined', collection: 'events', type: 'collection_page' },
{ title: 'Booth Inventory', icon: 'AppstoreOutlined', collection: 'booth_inventory', type: 'collection_page' }
]
},
{
title: 'Agency',
icon: 'ToolOutlined',
type: 'folder',
children: [
{ title: 'Services Catalog', icon: 'UnorderedListOutlined', collection: 'agency_services', type: 'collection_page' },
{ title: 'SOWs', icon: 'FileTextOutlined', collection: 'sows', type: 'collection_page' }
]
},
{
title: 'Activities',
icon: 'ScheduleOutlined',
type: 'folder',
children: [
{ title: 'My Tasks', icon: 'CheckSquareOutlined', collection: 'activities', type: 'collection_page', params: { filter: { activity_type: 'task', assigned_to_id: '{{$user.id}}' } } },
{ title: 'All Activities', icon: 'OrderedListOutlined', collection: 'activities', type: 'collection_page' }
]
}
];
// --- HELPERS ---
function generateUid() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 6);
}
function createTableBlockSchema(collectionName, params = {}) {
const dataSource = 'main';
const blockUid = generateUid();
const tableUid = generateUid();
const actionColumnUid = generateUid();
return {
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': `${collectionName}:list`,
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: collectionName,
dataSource,
action: 'list',
params: {
pageSize: 20,
...params
},
rowKey: 'id',
showIndex: true,
dragSort: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-uid': blockUid,
properties: {
actions: {
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': { style: { marginBottom: 'var(--nb-spacing)' } },
properties: {},
},
[tableUid]: {
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': { rowKey: 'id', rowSelection: { type: 'checkbox' } },
properties: {
actions: {
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-initializer': 'table:configureItemActions',
'x-settings': 'fieldSettings:TableColumn',
'x-component-props': { width: 100, align: 'center' },
properties: {
[actionColumnUid]: {
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': { split: '|' },
properties: {}
},
},
},
},
},
},
};
}
function createKanbanBlockSchema(collectionName, groupField, params = {}) {
const dataSource = 'main';
const blockUid = generateUid();
const kanbanUid = generateUid();
return {
type: 'void',
'x-acl-action': `${collectionName}:list`,
'x-decorator': 'KanbanBlockProvider',
'x-decorator-props': {
collection: collectionName,
dataSource,
action: 'list',
groupField,
params: { paginate: false, ...params }
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:kanban',
'x-component': 'CardItem',
'x-uid': blockUid,
properties: {
actions: {
type: 'void',
'x-initializer': 'kanban:configureActions',
'x-component': 'ActionBar',
'x-component-props': { style: { marginBottom: 'var(--nb-spacing)' } },
properties: {},
},
[kanbanUid]: {
type: 'array',
'x-component': 'Kanban',
'x-use-component-props': 'useKanbanBlockProps',
properties: {
card: {
type: 'void',
'x-read-pretty': true,
'x-label-disabled': true,
'x-decorator': 'BlockItem',
'x-component': 'Kanban.Card',
'x-component-props': { openMode: 'drawer' },
'x-designer': 'Kanban.Card.Designer',
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-component-props': { dndContext: false },
},
},
},
cardViewer: {
type: 'void',
title: '{{ t("View") }}',
'x-component': 'Kanban.CardViewer',
'x-action': 'view',
'x-component-props': { openMode: 'drawer' },
properties: {
drawer: {
type: 'void',
title: '{{ t("View record") }}',
'x-component': 'Action.Container',
'x-component-props': { className: 'nb-action-popup' },
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-initializer': 'popup:addTab',
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
properties: {}
}
}
}
}
}
}
}
}
}
},
},
},
};
}
async function createMenuItem(client, item, parentId) {
console.log(`Creating ${item.title}...`);
// 1. Prepare Schema
const pageUid = generateUid(); // Schema UID for Page
const tabUid = generateUid(); // Schema UID for Grid/Tab
const tabName = generateUid();
let fullSchema;
if (item.type === 'collection_page') {
// Create Grid with Block
const gridRowUid = generateUid();
const gridColUid = generateUid();
let blockSchema;
if (item.blockType === 'kanban') {
blockSchema = createKanbanBlockSchema(item.collection, item.groupField, item.params);
} else {
// Default Table
blockSchema = createTableBlockSchema(item.collection, item.params);
}
fullSchema = {
type: 'void',
'x-component': 'Page',
'x-uid': pageUid,
properties: {
[tabName]: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-uid': tabUid,
'x-async': true,
properties: {
[gridRowUid]: {
type: 'void',
'x-component': 'Grid.Row',
'x-uid': gridRowUid,
properties: {
[gridColUid]: {
type: 'void',
'x-component': 'Grid.Col',
'x-uid': gridColUid,
properties: {
[blockSchema['x-uid']]: blockSchema
}
}
}
}
}
}
}
};
} else {
// Folder or Empty Page (Dashboard)
fullSchema = {
type: 'void',
'x-component': 'Page',
'x-uid': pageUid,
properties: {
[tabName]: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-uid': tabUid,
'x-async': true,
properties: {}
}
}
};
}
// 2. Insert Schema
try {
await client.post('/uiSchemas:insert', fullSchema);
} catch (e) {
console.error(`Error inserting schema for ${item.title}:`, e.response?.data || e.message);
return;
}
// 3. Create Route
const routePayload = {
type: 'page',
title: item.title,
icon: item.icon,
parentId: parentId || null,
schemaUid: pageUid,
menuSchemaUid: generateUid(),
enableTabs: false,
children: [{
type: 'tabs',
schemaUid: tabUid,
tabSchemaName: tabName,
hidden: true
}]
};
let routeId;
try {
const routeRes = await client.post('/desktopRoutes:create', routePayload);
routeId = routeRes.data.data.id;
console.log(`✅ Created ${item.title} (ID: ${routeId})`);
} catch (e) {
console.error(`Error creating route for ${item.title}:`, e.response?.data || e.message);
return;
}
// 4. Process Children
if (item.children && item.children.length > 0) {
for (const child of item.children) {
await createMenuItem(client, child, routeId);
}
}
}
async function main() {
console.log('🚀 Starting CRM Setup Script...');
// 1. Try Login with admin/admin first, then try admin/admin123
let token;
let password = 'admin'; // Try "admin" first
try {
const res = await axios.post(`${BASE_URL}/users:signin`, { email: EMAIL, password: password });
token = res.data.data.token;
console.log('✅ Login successful with password: admin');
} catch (e) {
console.log('⚠️ Login failed with "admin", trying "admin123"...');
try {
password = 'admin123';
const res = await axios.post(`${BASE_URL}/users:signin`, { email: EMAIL, password: password });
token = res.data.data.token;
console.log('✅ Login successful with password: admin123');
} catch (e2) {
console.error('❌ Login failed completely. Please check credentials.');
return;
}
}
const client = axios.create({
baseURL: BASE_URL,
headers: { Authorization: `Bearer ${token}` }
});
// 2. Execute
for (const item of MENU_STRUCTURE) {
await createMenuItem(client, item, null);
}
console.log('🎉 ALL DONE!');
}
main();