Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
6.1 KiB
6.1 KiB
DataTable Integration Guide (Iframe UI + UI Kit)
This guide shows how to use the Alga UI Kit DataTable inside an extension’s iframe app, fetching data via the gateway (/api/ext/[extensionId]/[[...path]]) and following best practices for performance, security, and UX. In the v2 architecture:
- All server calls go through the API Gateway and are executed by the Runner (
POST /v1/execute). - UI assets are served by the Runner at
${RUNNER_PUBLIC_BASE}/ext-ui/{extensionId}/{content_hash}/[...]. - The host constructs the iframe URL via buildExtUiSrc() and initializes with bootstrapIframe().
- Reference gateway scaffold: server/src/app/api/ext/[extensionId]/[[...path]]/route.ts
Overview
- UI renders in a sandboxed iframe
- Use
@alga/ui-kitcomponents and@alga/extension-iframe-sdkfor host integration - All server calls go through the gateway and are executed by the Runner
Prerequisites
- An iframe app (React recommended) scaffolded with Vite/Next
- Installed SDKs:
@alga/extension-iframe-sdk@alga/ui-kit
Basic Table
import React from 'react';
import { DataTable } from '@alga/ui-kit';
import { useEffect, useMemo, useState } from 'react';
import { useExtension } from '@alga/extension-iframe-sdk';
export default function AgreementsTable() {
const { context } = useExtension(); // provides tenant/extension context, auth bridge
const [rows, setRows] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(25);
const [total, setTotal] = useState(0);
const columns = useMemo(() => [
{ key: 'name', header: 'Agreement Name', sortable: true },
{ key: 'vendor', header: 'Vendor', sortable: true },
{ key: 'status', header: 'Status', sortable: true },
{ key: 'amount', header: 'Amount' }
], []);
useEffect(() => {
const abort = new AbortController();
async function load() {
setLoading(true);
try {
const qp = new URLSearchParams({ page: String(page), limit: String(pageSize) });
const url = `${context.gatewayBase}/api/ext/${context.extensionId}/agreements?${qp}`;
const res = await fetch(url, { signal: abort.signal, headers: context.authHeaders });
if (!res.ok) throw new Error('Request failed');
const data = await res.json();
const list = Array.isArray(data) ? data : data.data ?? [];
setRows(list);
setTotal((data.meta && data.meta.total) || list.length);
} finally {
setLoading(false);
}
}
load();
return () => abort.abort();
}, [context.extensionId, context.gatewayBase, context.authHeaders, page, pageSize]);
return (
<DataTable
columns={columns}
rows={rows}
loading={loading}
pagination={{
page,
pageSize,
total,
onPageChange: setPage,
onPageSizeChange: setPageSize,
pageSizeOptions: [10, 25, 50, 100]
}}
onSortChange={(sort) => {
// Optionally re‑fetch with sort params
}}
/>
);
}
Notes:
context.gatewayBaseandcontext.authHeadersare provided by the SDK bridge- The gateway applies header allowlists and enforces timeouts; the Runner executes the handler
Custom Cells and Actions
import { Button, Badge } from '@alga/ui-kit';
const columns = [
{
key: 'name',
header: 'Agreement Name',
sortable: true,
cell: (row: any) => (
<a className="text-blue-600 hover:underline" href={`#/agreements/${row.id}`}>{row.name}</a>
)
},
{
key: 'status',
header: 'Status',
cell: (row: any) => (
<Badge variant={row.status === 'active' ? 'success' : row.status === 'pending' ? 'warning' : 'secondary'}>
{row.status}
</Badge>
)
},
{
key: 'amount',
header: 'Amount',
cell: (row: any) => (
<span className="font-medium">{row.currency} {Number(row.amount).toLocaleString()}</span>
)
},
{
key: 'actions',
header: '',
cell: (row: any) => (
<Button variant="ghost" size="sm" onClick={() => console.log('Actions for', row.id)}>Actions</Button>
)
}
];
Data Fetching Patterns
- Always go through the gateway:
/api/ext/${extensionId}/... - Expect either an array or
{ success, data, meta } - Propagate only SDK‑provided auth headers; do not attach end‑user tokens directly
Performance Tips
- Server‑side: perform data transformation in handlers executed by the Runner
- Client‑side: memoize column definitions; avoid heavy computations in cell renderers
- Use pagination and server‑side filtering/sorting when lists are large
Error and Loading States
- Display loading indicators while fetching
- Show concise error messages; avoid leaking internal details
- Consider retry UI for transient errors (e.g., 502 from Runner)
Security Considerations
- Do not attempt cross‑origin requests; route everything through
/api/ext/... - Avoid evaluating code or templates at runtime
- Keep sizes small; large responses may be rejected by gateway caps
Example Handler (Runner)
Manifest v2 declares an endpoint, e.g.: GET /agreements → dist/handlers/http/list_agreements
Handler (conceptual):
export async function list_agreements(ctx) {
// Use host APIs via ctx: storage, http.fetch, secrets, log, metrics
const items = await ctx.storage.list({ namespace: 'agreements' });
return {
status: 200,
headers: { 'content-type': 'application/json' },
body: { data: items }
};
}
Related References
- Gateway route scaffold: server/src/app/api/ext/[extensionId]/[[...path]]/route.ts
- Iframe bootstrap and src builder: server/src/lib/extensions/ui/iframeBridge.ts
- Runner overview: runner.md
- Manifest and signing: manifest_schema.md, security_signing.md