@zeyos/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +458 -0
- package/agents/README.md +66 -0
- package/agents/shared/business-app-benchmarks.md +111 -0
- package/agents/shared/zeyos-entity-map.md +142 -0
- package/agents/shared/zeyos-entity-reference.md +570 -0
- package/agents/shared/zeyos-query-patterns.md +89 -0
- package/agents/zeyos-account-intelligence/SKILL.md +34 -0
- package/agents/zeyos-account-intelligence/agents/openai.yaml +4 -0
- package/agents/zeyos-account-intelligence/references/workflows.md +84 -0
- package/agents/zeyos-billing-insights/SKILL.md +41 -0
- package/agents/zeyos-billing-insights/agents/openai.yaml +4 -0
- package/agents/zeyos-billing-insights/references/workflows.md +106 -0
- package/agents/zeyos-campaign-and-outreach/SKILL.md +44 -0
- package/agents/zeyos-campaign-and-outreach/agents/openai.yaml +4 -0
- package/agents/zeyos-campaign-and-outreach/references/workflows.md +100 -0
- package/agents/zeyos-collaboration-and-activity/SKILL.md +37 -0
- package/agents/zeyos-collaboration-and-activity/agents/openai.yaml +4 -0
- package/agents/zeyos-collaboration-and-activity/references/workflows.md +104 -0
- package/agents/zeyos-collections-and-dunning/SKILL.md +46 -0
- package/agents/zeyos-collections-and-dunning/agents/openai.yaml +4 -0
- package/agents/zeyos-collections-and-dunning/references/workflows.md +132 -0
- package/agents/zeyos-commerce-and-inventory/SKILL.md +38 -0
- package/agents/zeyos-commerce-and-inventory/agents/openai.yaml +4 -0
- package/agents/zeyos-commerce-and-inventory/references/workflows.md +101 -0
- package/agents/zeyos-mail-operations/SKILL.md +35 -0
- package/agents/zeyos-mail-operations/agents/openai.yaml +4 -0
- package/agents/zeyos-mail-operations/references/workflows.md +110 -0
- package/agents/zeyos-notes-and-sops/SKILL.md +31 -0
- package/agents/zeyos-notes-and-sops/agents/openai.yaml +4 -0
- package/agents/zeyos-notes-and-sops/references/workflows.md +85 -0
- package/agents/zeyos-platform-and-schema/SKILL.md +37 -0
- package/agents/zeyos-platform-and-schema/agents/openai.yaml +4 -0
- package/agents/zeyos-platform-and-schema/references/workflows.md +97 -0
- package/agents/zeyos-work-management/SKILL.md +45 -0
- package/agents/zeyos-work-management/agents/openai.yaml +4 -0
- package/agents/zeyos-work-management/references/workflows.md +148 -0
- package/docs/01-api-reference/01-data-retrieval.md +601 -0
- package/docs/01-api-reference/02-authentication.md +288 -0
- package/docs/01-api-reference/03-resources.md +270 -0
- package/docs/01-api-reference/04-schema.md +539 -0
- package/docs/01-api-reference/_category_.json +9 -0
- package/docs/02-javascript-client/01-getting-started.md +146 -0
- package/docs/02-javascript-client/02-authentication.md +287 -0
- package/docs/02-javascript-client/03-making-requests.md +572 -0
- package/docs/02-javascript-client/04-practical-guide.md +348 -0
- package/docs/02-javascript-client/_category_.json +9 -0
- package/docs/03-cli/01-getting-started.md +219 -0
- package/docs/03-cli/02-commands.md +407 -0
- package/docs/03-cli/03-configuration.md +220 -0
- package/docs/03-cli/_category_.json +9 -0
- package/docs/04-agent-workflows/00-coding-agents.md +35 -0
- package/docs/04-agent-workflows/01-agent-quickstart.md +147 -0
- package/docs/04-agent-workflows/02-agent-recipes.md +109 -0
- package/docs/04-agent-workflows/03-cli-coverage-and-escalation.md +65 -0
- package/docs/04-agent-workflows/_category_.json +9 -0
- package/docs/04-sample-apps/01-kanban.md +89 -0
- package/docs/04-sample-apps/02-crm.md +81 -0
- package/docs/04-sample-apps/03-dashboard.md +80 -0
- package/docs/04-sample-apps/_category_.json +9 -0
- package/docs/05-tutorials/00-application-developers.md +43 -0
- package/docs/05-tutorials/01-integration-architecture.md +60 -0
- package/docs/05-tutorials/02-build-your-own-zeyos-frontend.md +517 -0
- package/docs/05-tutorials/03-server-side-integrations.md +185 -0
- package/docs/05-tutorials/_category_.json +9 -0
- package/docs/intro.md +197 -0
- package/openapi/api.json +24308 -0
- package/openapi/auth.json +415 -0
- package/openapi/dbref.json +56223 -0
- package/openapi/oauth2.json +781 -0
- package/openapi/sdk.json +949 -0
- package/openapi/views.txt +642 -0
- package/package.json +49 -0
- package/samples/crm/README.md +28 -0
- package/samples/crm/index.html +327 -0
- package/samples/crm/js/api.js +208 -0
- package/samples/crm/js/auth.js +61 -0
- package/samples/crm/js/main.js +545 -0
- package/samples/crm/js/state.js +90 -0
- package/samples/crm/js/ui.js +51 -0
- package/samples/dashboard/README.md +28 -0
- package/samples/dashboard/index.html +280 -0
- package/samples/dashboard/js/api.js +197 -0
- package/samples/dashboard/js/auth.js +59 -0
- package/samples/dashboard/js/main.js +382 -0
- package/samples/dashboard/js/state.js +81 -0
- package/samples/dashboard/js/ui.js +48 -0
- package/samples/kanban/README.md +28 -0
- package/samples/kanban/index.html +263 -0
- package/samples/kanban/js/api.js +152 -0
- package/samples/kanban/js/auth.js +59 -0
- package/samples/kanban/js/constants.js +40 -0
- package/samples/kanban/js/kanban.js +246 -0
- package/samples/kanban/js/main.js +362 -0
- package/samples/kanban/js/modals.js +474 -0
- package/samples/kanban/js/settings.js +82 -0
- package/samples/kanban/js/state.js +118 -0
- package/samples/kanban/js/ui.js +49 -0
- package/scripts/generate-client.mjs +344 -0
- package/src/generated/operations.js +9772 -0
- package/src/generated/schema.js +8982 -0
- package/src/index.js +85 -0
- package/src/runtime/client.js +1208 -0
- package/src/runtime/error.js +29 -0
- package/src/runtime/http.js +174 -0
- package/src/runtime/request-shape.js +35 -0
- package/src/runtime/schema.js +206 -0
- package/src/runtime/suggest.js +74 -0
- package/src/runtime/token-store.js +105 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard application entry point.
|
|
3
|
+
* Bootstraps the client, handles auth, wires up all UI events,
|
|
4
|
+
* and orchestrates data loading + rendering.
|
|
5
|
+
*
|
|
6
|
+
* Authentication: reads config from <body> data attributes + localStorage.
|
|
7
|
+
* 1. If tokens are available -> token mode
|
|
8
|
+
* 2. If URL only -> try session detection via /oauth2/v1/userinfo
|
|
9
|
+
* 3. Otherwise -> connection screen with troubleshooting
|
|
10
|
+
*
|
|
11
|
+
* Exposes a global ZeyOS console API for debugging / configuration.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
initTokenClient, initSessionClient,
|
|
15
|
+
countTickets, fetchRecentTickets, fetchTicketsByStatus,
|
|
16
|
+
countAccounts, fetchRecentAccounts,
|
|
17
|
+
STATUS_LABELS, STATUS_COLORS,
|
|
18
|
+
} from './api.js';
|
|
19
|
+
import { trySessionAuth, logout } from './auth.js';
|
|
20
|
+
import { runtime, resolveConfig, saveUrl, saveTokens, clearTokens, clearUrl } from './state.js';
|
|
21
|
+
import { showToast, showLoading, hideLoading } from './ui.js';
|
|
22
|
+
|
|
23
|
+
// -- Priority labels ----------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const PRIORITY_LABELS = {
|
|
26
|
+
0: 'Lowest',
|
|
27
|
+
1: 'Low',
|
|
28
|
+
2: 'Medium',
|
|
29
|
+
3: 'High',
|
|
30
|
+
4: 'Highest',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const PRIORITY_CLASSES = {
|
|
34
|
+
0: 'bg-slate-100 text-slate-600',
|
|
35
|
+
1: 'bg-blue-100 text-blue-700',
|
|
36
|
+
2: 'bg-amber-100 text-amber-700',
|
|
37
|
+
3: 'bg-orange-100 text-orange-700',
|
|
38
|
+
4: 'bg-red-100 text-red-700',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// -- Boot ---------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
44
|
+
const config = resolveConfig();
|
|
45
|
+
|
|
46
|
+
// 1. No URL configured -> show connection screen immediately
|
|
47
|
+
if (!config.url) {
|
|
48
|
+
_showConnectionScreen('No ZeyOS URL configured.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
runtime.url = config.url;
|
|
53
|
+
|
|
54
|
+
// 2. Tokens available -> initialize in token mode
|
|
55
|
+
if (config.accessToken) {
|
|
56
|
+
saveTokens({
|
|
57
|
+
accessToken: config.accessToken,
|
|
58
|
+
refreshToken: config.refreshToken ?? null,
|
|
59
|
+
});
|
|
60
|
+
initTokenClient(config.url);
|
|
61
|
+
runtime.authMode = 'token';
|
|
62
|
+
await _bootApp();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. No tokens -> try session detection
|
|
67
|
+
showLoading();
|
|
68
|
+
const userInfo = await trySessionAuth(config.url);
|
|
69
|
+
hideLoading();
|
|
70
|
+
|
|
71
|
+
if (userInfo) {
|
|
72
|
+
initSessionClient(config.url);
|
|
73
|
+
runtime.authMode = 'session';
|
|
74
|
+
await _bootApp();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 4. Nothing works -> connection screen
|
|
79
|
+
_showConnectionScreen('Could not connect. Set a token or log into ZeyOS first.');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// -- Connection Screen --------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
function _showConnectionScreen(message) {
|
|
85
|
+
document.getElementById('connection-screen')?.classList.remove('hidden');
|
|
86
|
+
document.getElementById('app-shell')?.classList.add('hidden');
|
|
87
|
+
|
|
88
|
+
const msgEl = document.getElementById('connection-message');
|
|
89
|
+
if (msgEl) msgEl.textContent = message;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// -- Main App Boot ------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
async function _bootApp() {
|
|
95
|
+
document.getElementById('connection-screen')?.classList.add('hidden');
|
|
96
|
+
document.getElementById('app-shell')?.classList.remove('hidden');
|
|
97
|
+
|
|
98
|
+
_wireNavbar();
|
|
99
|
+
|
|
100
|
+
await _loadDashboard();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// -- Data Loading & Rendering -------------------------------------------------
|
|
104
|
+
|
|
105
|
+
async function _loadDashboard() {
|
|
106
|
+
showLoading();
|
|
107
|
+
try {
|
|
108
|
+
// Fire all queries in parallel for speed
|
|
109
|
+
const [
|
|
110
|
+
totalTickets,
|
|
111
|
+
activeTickets,
|
|
112
|
+
overdueTickets,
|
|
113
|
+
totalAccounts,
|
|
114
|
+
statusDistribution,
|
|
115
|
+
recentTickets,
|
|
116
|
+
recentAccounts,
|
|
117
|
+
] = await Promise.all([
|
|
118
|
+
countTickets(),
|
|
119
|
+
countTickets({ status: 4 }),
|
|
120
|
+
_countOverdueTickets(),
|
|
121
|
+
countAccounts(),
|
|
122
|
+
fetchTicketsByStatus(),
|
|
123
|
+
fetchRecentTickets(10),
|
|
124
|
+
fetchRecentAccounts(10),
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
_renderKPIs(totalTickets, activeTickets, overdueTickets, totalAccounts);
|
|
128
|
+
_renderStatusChart(statusDistribution);
|
|
129
|
+
_renderRecentTickets(recentTickets);
|
|
130
|
+
_renderRecentAccounts(recentAccounts);
|
|
131
|
+
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if (err?.status === 401) {
|
|
134
|
+
showToast('Session expired. Please log in again.', 'error');
|
|
135
|
+
setTimeout(async () => { await logout(); location.reload(); }, 2000);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
showToast(`Failed to load data: ${err.message}`, 'error');
|
|
139
|
+
} finally {
|
|
140
|
+
hideLoading();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Count overdue tickets: duedate < now AND status < 8 (not cancelled/completed/failed/booked).
|
|
146
|
+
*/
|
|
147
|
+
async function _countOverdueTickets() {
|
|
148
|
+
const now = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
|
|
149
|
+
const promises = [];
|
|
150
|
+
for (let status = 0; status < 8; status++) {
|
|
151
|
+
promises.push(
|
|
152
|
+
countTickets({ status, duedate: { '<': now } })
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const counts = await Promise.all(promises);
|
|
156
|
+
return counts.reduce((sum, c) => sum + c, 0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// -- KPI Cards ----------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
function _renderKPIs(totalTickets, activeTickets, overdueTickets, totalAccounts) {
|
|
162
|
+
_setKpi('kpi-total-tickets', totalTickets);
|
|
163
|
+
_setKpi('kpi-active-tickets', activeTickets);
|
|
164
|
+
_setKpi('kpi-overdue-tickets', overdueTickets);
|
|
165
|
+
_setKpi('kpi-total-accounts', totalAccounts);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function _setKpi(id, value) {
|
|
169
|
+
const el = document.getElementById(id);
|
|
170
|
+
if (el) el.textContent = typeof value === 'number' ? value.toLocaleString() : value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// -- Status Distribution Chart ------------------------------------------------
|
|
174
|
+
|
|
175
|
+
function _renderStatusChart(distribution) {
|
|
176
|
+
const container = document.getElementById('status-chart');
|
|
177
|
+
if (!container) return;
|
|
178
|
+
|
|
179
|
+
const withCount = distribution.filter(d => d.count > 0);
|
|
180
|
+
|
|
181
|
+
if (withCount.length === 0) {
|
|
182
|
+
container.innerHTML = '<p class="text-sm text-slate-400 italic">No ticket data available.</p>';
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const maxCount = Math.max(...withCount.map(d => d.count));
|
|
187
|
+
|
|
188
|
+
const bars = withCount.map(d => {
|
|
189
|
+
const pct = maxCount > 0 ? (d.count / maxCount) * 100 : 0;
|
|
190
|
+
return `
|
|
191
|
+
<div class="flex items-center gap-3">
|
|
192
|
+
<span class="text-xs text-slate-600 w-36 text-right truncate flex-shrink-0">${_esc(d.label)}</span>
|
|
193
|
+
<div class="flex-1 h-7 bg-slate-100 rounded-md overflow-hidden relative">
|
|
194
|
+
<div class="h-full rounded-md transition-all duration-500"
|
|
195
|
+
style="width:${pct}%; background:${d.color}; min-width:${d.count > 0 ? '2px' : '0'}"></div>
|
|
196
|
+
</div>
|
|
197
|
+
<span class="text-sm font-semibold text-slate-700 w-10 text-right flex-shrink-0">${d.count}</span>
|
|
198
|
+
</div>
|
|
199
|
+
`;
|
|
200
|
+
}).join('');
|
|
201
|
+
|
|
202
|
+
container.innerHTML = bars;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// -- Recent Tickets Table -----------------------------------------------------
|
|
206
|
+
|
|
207
|
+
function _renderRecentTickets(tickets) {
|
|
208
|
+
const tbody = document.getElementById('recent-tickets-body');
|
|
209
|
+
if (!tbody) return;
|
|
210
|
+
|
|
211
|
+
if (tickets.length === 0) {
|
|
212
|
+
tbody.innerHTML = '<tr><td colspan="6" class="px-4 py-8 text-center text-sm text-slate-400 italic">No tickets found.</td></tr>';
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
tbody.innerHTML = tickets.map(t => {
|
|
217
|
+
const statusNum = Number(t.status ?? 0);
|
|
218
|
+
const statusLabel = STATUS_LABELS[statusNum] ?? `Status ${statusNum}`;
|
|
219
|
+
const statusColor = STATUS_COLORS[statusNum] ?? '#94a3b8';
|
|
220
|
+
|
|
221
|
+
const priorityNum = Number(t.priority ?? 1);
|
|
222
|
+
const priorityLabel = PRIORITY_LABELS[priorityNum] ?? 'Normal';
|
|
223
|
+
const priorityClass = PRIORITY_CLASSES[priorityNum] ?? PRIORITY_CLASSES[1];
|
|
224
|
+
|
|
225
|
+
const duedate = t.duedate ? _formatDate(t.duedate) : '--';
|
|
226
|
+
const isOverdue = t.duedate && t.duedate < Math.floor(Date.now() / 1000) && statusNum < 8;
|
|
227
|
+
|
|
228
|
+
return `
|
|
229
|
+
<tr class="border-b border-slate-100 hover:bg-slate-50/50 transition-colors">
|
|
230
|
+
<td class="px-4 py-2.5 text-xs text-slate-400 font-mono">${_esc(t.ticketnum ?? t.ID)}</td>
|
|
231
|
+
<td class="px-4 py-2.5 text-sm text-slate-800 font-medium max-w-xs truncate">${_esc(t.name ?? '')}</td>
|
|
232
|
+
<td class="px-4 py-2.5">
|
|
233
|
+
<span class="inline-flex items-center gap-1.5 text-xs font-medium">
|
|
234
|
+
<span class="w-2 h-2 rounded-full flex-shrink-0" style="background:${statusColor}"></span>
|
|
235
|
+
${_esc(statusLabel)}
|
|
236
|
+
</span>
|
|
237
|
+
</td>
|
|
238
|
+
<td class="px-4 py-2.5">
|
|
239
|
+
<span class="px-2 py-0.5 rounded-full text-xs font-medium ${priorityClass}">${_esc(priorityLabel)}</span>
|
|
240
|
+
</td>
|
|
241
|
+
<td class="px-4 py-2.5 text-xs ${isOverdue ? 'text-red-600 font-semibold' : 'text-slate-500'}">${duedate}</td>
|
|
242
|
+
</tr>
|
|
243
|
+
`;
|
|
244
|
+
}).join('');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// -- Recent Accounts Table ----------------------------------------------------
|
|
248
|
+
|
|
249
|
+
function _renderRecentAccounts(accounts) {
|
|
250
|
+
const tbody = document.getElementById('recent-accounts-body');
|
|
251
|
+
if (!tbody) return;
|
|
252
|
+
|
|
253
|
+
if (accounts.length === 0) {
|
|
254
|
+
tbody.innerHTML = '<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-slate-400 italic">No accounts found.</td></tr>';
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const typeLabels = {
|
|
259
|
+
0: 'Prospect',
|
|
260
|
+
1: 'Customer',
|
|
261
|
+
2: 'Supplier',
|
|
262
|
+
3: 'Cust. & Suppl.',
|
|
263
|
+
4: 'Competitor',
|
|
264
|
+
5: 'Employee',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
tbody.innerHTML = accounts.map(a => {
|
|
268
|
+
const typeNum = Number(a.type ?? 0);
|
|
269
|
+
const typeLabel = typeLabels[typeNum] ?? `Type ${typeNum}`;
|
|
270
|
+
const name = [a.firstname, a.lastname].filter(Boolean).join(' ') || '--';
|
|
271
|
+
const city = a['contact.city'] ?? '--';
|
|
272
|
+
const email = a['contact.email'] ?? '--';
|
|
273
|
+
const assignee = a['assigneduser.name'] ?? '--';
|
|
274
|
+
|
|
275
|
+
return `
|
|
276
|
+
<tr class="border-b border-slate-100 hover:bg-slate-50/50 transition-colors">
|
|
277
|
+
<td class="px-4 py-2.5 text-xs text-slate-400 font-mono">${_esc(a.ID)}</td>
|
|
278
|
+
<td class="px-4 py-2.5 text-sm text-slate-800 font-medium max-w-xs truncate">${_esc(name)}</td>
|
|
279
|
+
<td class="px-4 py-2.5 text-xs text-slate-600">${_esc(typeLabel)}</td>
|
|
280
|
+
<td class="px-4 py-2.5 text-xs text-slate-500">${_esc(city)}</td>
|
|
281
|
+
<td class="px-4 py-2.5 text-xs text-slate-500 truncate max-w-[180px]">${_esc(assignee)}</td>
|
|
282
|
+
</tr>
|
|
283
|
+
`;
|
|
284
|
+
}).join('');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// -- Navbar Wiring ------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
function _wireNavbar() {
|
|
290
|
+
document.getElementById('btn-reload')?.addEventListener('click', () => _loadDashboard());
|
|
291
|
+
|
|
292
|
+
document.getElementById('btn-logout')?.addEventListener('click', async () => {
|
|
293
|
+
if (!confirm('Log out of ZeyOS Dashboard?')) return;
|
|
294
|
+
await logout();
|
|
295
|
+
clearUrl();
|
|
296
|
+
location.reload();
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// -- Helpers ------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
function _formatDate(unixSeconds) {
|
|
303
|
+
if (!unixSeconds) return '--';
|
|
304
|
+
const d = new Date(Number(unixSeconds) * 1000);
|
|
305
|
+
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function _esc(str) {
|
|
309
|
+
return String(str)
|
|
310
|
+
.replace(/&/g, '&').replace(/</g, '<')
|
|
311
|
+
.replace(/>/g, '>').replace(/"/g, '"');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// -- Console API --------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
globalThis.ZeyOS = {
|
|
317
|
+
/**
|
|
318
|
+
* Set the ZeyOS instance URL.
|
|
319
|
+
* @param {string} url
|
|
320
|
+
*/
|
|
321
|
+
setUrl(url) {
|
|
322
|
+
if (!url || typeof url !== 'string') {
|
|
323
|
+
console.error('[ZeyOS] Usage: ZeyOS.setUrl("https://cloud.zeyos.com/demo/")');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
saveUrl(url.trim());
|
|
327
|
+
console.log(`%c[ZeyOS]%c URL set to: ${url}`, 'color:#2563eb;font-weight:bold', '');
|
|
328
|
+
console.log('%c[ZeyOS]%c Call ZeyOS.reconnect() to apply.', 'color:#2563eb;font-weight:bold', '');
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Set access (and optionally refresh) token.
|
|
333
|
+
* @param {string} accessToken
|
|
334
|
+
* @param {string} [refreshToken]
|
|
335
|
+
*/
|
|
336
|
+
setToken(accessToken, refreshToken) {
|
|
337
|
+
if (!accessToken || typeof accessToken !== 'string') {
|
|
338
|
+
console.error('[ZeyOS] Usage: ZeyOS.setToken("access-token", "optional-refresh-token")');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
saveTokens({
|
|
342
|
+
accessToken: accessToken.trim(),
|
|
343
|
+
refreshToken: refreshToken?.trim() ?? null,
|
|
344
|
+
});
|
|
345
|
+
console.log('%c[ZeyOS]%c Token saved.', 'color:#2563eb;font-weight:bold', '');
|
|
346
|
+
console.log('%c[ZeyOS]%c Call ZeyOS.reconnect() to apply.', 'color:#2563eb;font-weight:bold', '');
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Print the current connection status to the console.
|
|
351
|
+
*/
|
|
352
|
+
status() {
|
|
353
|
+
const config = resolveConfig();
|
|
354
|
+
const lines = [
|
|
355
|
+
'',
|
|
356
|
+
` URL: ${config.url ?? '(not set)'}`,
|
|
357
|
+
` Access Token: ${config.accessToken ? config.accessToken.slice(0, 16) + '...' : '(not set)'}`,
|
|
358
|
+
` Refresh Token: ${config.refreshToken ? 'yes' : 'no'}`,
|
|
359
|
+
` Auth Mode: ${runtime.authMode ?? '(not connected)'}`,
|
|
360
|
+
'',
|
|
361
|
+
];
|
|
362
|
+
console.log(`%c[ZeyOS] Status%c\n${lines.join('\n')}`, 'color:#2563eb;font-weight:bold', 'color:inherit');
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Clear all stored config (URL + tokens) and reload.
|
|
367
|
+
*/
|
|
368
|
+
logout() {
|
|
369
|
+
clearTokens();
|
|
370
|
+
clearUrl();
|
|
371
|
+
console.log('%c[ZeyOS]%c Config cleared. Reloading...', 'color:#2563eb;font-weight:bold', '');
|
|
372
|
+
location.reload();
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Reload the page to re-run the boot sequence with current config.
|
|
377
|
+
*/
|
|
378
|
+
reconnect() {
|
|
379
|
+
console.log('%c[ZeyOS]%c Reconnecting...', 'color:#2563eb;font-weight:bold', '');
|
|
380
|
+
location.reload();
|
|
381
|
+
},
|
|
382
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application state -- persistence via localStorage, in-memory for runtime data.
|
|
3
|
+
* Config resolution merges <body> data attributes with localStorage overrides.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const KEYS = {
|
|
7
|
+
URL: 'zeyos_dashboard_url',
|
|
8
|
+
TOKENS: 'zeyos_dashboard_tokens',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// -- helpers ------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
function readJson(key, fallback) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = localStorage.getItem(key);
|
|
16
|
+
return raw != null ? JSON.parse(raw) : fallback;
|
|
17
|
+
} catch {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeJson(key, value) {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
25
|
+
} catch {
|
|
26
|
+
// storage full or unavailable -- silently ignore
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// -- URL ----------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export function loadUrl() { return localStorage.getItem(KEYS.URL) ?? null; }
|
|
33
|
+
export function saveUrl(url) { localStorage.setItem(KEYS.URL, url); }
|
|
34
|
+
export function clearUrl() { localStorage.removeItem(KEYS.URL); }
|
|
35
|
+
|
|
36
|
+
// -- tokens -------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
export function loadTokens() {
|
|
39
|
+
return readJson(KEYS.TOKENS, null);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function saveTokens(tokens) {
|
|
43
|
+
writeJson(KEYS.TOKENS, tokens);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function clearTokens() {
|
|
47
|
+
localStorage.removeItem(KEYS.TOKENS);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// -- config resolution --------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve effective config by merging <body> data attributes with localStorage.
|
|
54
|
+
* localStorage values (set via ZeyOS console API) override body attributes.
|
|
55
|
+
* Returns { url, accessToken, refreshToken }.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveConfig() {
|
|
58
|
+
const body = document.body;
|
|
59
|
+
|
|
60
|
+
// Body attributes (defaults)
|
|
61
|
+
const bodyUrl = body?.dataset.zeyosUrl?.trim() || null;
|
|
62
|
+
const bodyAccess = body?.dataset.zeyosAccesstoken?.trim() || null;
|
|
63
|
+
const bodyRefresh = body?.dataset.zeyosRefreshtoken?.trim() || null;
|
|
64
|
+
|
|
65
|
+
// localStorage overrides
|
|
66
|
+
const storedUrl = loadUrl();
|
|
67
|
+
const storedTokens = loadTokens();
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
url: storedUrl || bodyUrl,
|
|
71
|
+
accessToken: storedTokens?.accessToken || bodyAccess,
|
|
72
|
+
refreshToken: storedTokens?.refreshToken || bodyRefresh,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// -- in-memory runtime state (not persisted) ----------------------------------
|
|
77
|
+
|
|
78
|
+
export const runtime = {
|
|
79
|
+
url: null, // resolved ZeyOS instance URL
|
|
80
|
+
authMode: null, // 'token' | 'session' | null
|
|
81
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared UI utilities: toast notifications + loading overlay.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// -- Toast Notifications ------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} message
|
|
9
|
+
* @param {'success'|'error'|'info'} type
|
|
10
|
+
*/
|
|
11
|
+
export function showToast(message, type = 'info') {
|
|
12
|
+
const container = document.getElementById('toast-container');
|
|
13
|
+
if (!container) return;
|
|
14
|
+
|
|
15
|
+
const colors = {
|
|
16
|
+
success: 'bg-emerald-600',
|
|
17
|
+
error: 'bg-red-600',
|
|
18
|
+
info: 'bg-slate-700',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const toast = document.createElement('div');
|
|
22
|
+
toast.className =
|
|
23
|
+
`pointer-events-auto flex items-center gap-3 px-4 py-3 rounded-xl text-white text-sm shadow-xl ` +
|
|
24
|
+
`${colors[type] ?? colors.info} translate-y-2 opacity-0 transition-all duration-200`;
|
|
25
|
+
toast.textContent = message;
|
|
26
|
+
|
|
27
|
+
container.appendChild(toast);
|
|
28
|
+
// Trigger transition on next frame
|
|
29
|
+
requestAnimationFrame(() => {
|
|
30
|
+
requestAnimationFrame(() => toast.classList.remove('translate-y-2', 'opacity-0'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const duration = type === 'error' ? 5000 : 3000;
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
toast.classList.add('opacity-0', 'translate-y-2');
|
|
36
|
+
toast.addEventListener('transitionend', () => toast.remove(), { once: true });
|
|
37
|
+
}, duration);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// -- Loading Overlay ----------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export function showLoading() {
|
|
43
|
+
document.getElementById('loading-overlay')?.classList.remove('hidden');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function hideLoading() {
|
|
47
|
+
document.getElementById('loading-overlay')?.classList.add('hidden');
|
|
48
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# ZeyOS Kanban Sample
|
|
2
|
+
|
|
3
|
+
This directory contains the static Kanban sample application.
|
|
4
|
+
|
|
5
|
+
The canonical documentation is:
|
|
6
|
+
|
|
7
|
+
- [Kanban sample docs](../../docs/04-sample-apps/01-kanban.md)
|
|
8
|
+
|
|
9
|
+
## Quick Run
|
|
10
|
+
|
|
11
|
+
Serve the repository root with any static file server:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd /path/to/zeyos/client
|
|
15
|
+
python3 -m http.server 8080
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then open:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
http://localhost:8080/samples/kanban/
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Configure the sample via `data-zeyos-*` attributes in [`index.html`](./index.html) or through the `window.ZeyOS` console API described in the docs.
|
|
25
|
+
|
|
26
|
+
For localhost, token mode is usually the reliable path. Session mode only works from the same origin or when the ZeyOS instance allows credentialed CORS.
|
|
27
|
+
|
|
28
|
+
> **Note:** The sample must be served from the **repository root** (the directory containing `src/`), because the client is imported via `../../../src/index.js`. Copying the `samples/` folder in isolation will break that import. Also, do **not** open `index.html` directly via the `file://` protocol — browsers block ES module relative imports under `file://`. Always use a local static server as shown above.
|