Skip to content

Expose ObjectOS kernel capabilities through admin UI (Phase 2)#212

Merged
hotlong merged 3 commits intomainfrom
copilot/expose-kernel-capabilities-admin-ui
Feb 8, 2026
Merged

Expose ObjectOS kernel capabilities through admin UI (Phase 2)#212
hotlong merged 3 commits intomainfrom
copilot/expose-kernel-capabilities-admin-ui

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 7, 2026

Implements system administration pages to surface kernel capabilities: permissions, audit logging, plugin management, background jobs, metrics, and notifications.

Backend: REST APIs in Plugin Lifecycle

Added HTTP endpoints in plugin start() methods via http.server service. All APIs follow consistent JSON structure {success, data, error}:

Endpoints:

  • /api/v1/permissions/* - Permission sets, object permissions, permission checks
  • /api/v1/audit/* - Event log queries with filters (user, object, event type, date range)
  • /api/v1/admin/plugins - Loaded plugins with health status and metadata
  • /api/v1/jobs/* - Queue stats, job queries, retry/cancel actions
  • /api/v1/metrics/* - Counters, gauges, histograms + Prometheus export
  • /api/v1/notifications/* - Channel config and queue status

Pattern:

// packages/jobs/src/plugin.ts
async start(context: PluginContext): Promise<void> {
  const httpServer = context.getService('http.server') as any;
  const rawApp = httpServer?.getRawApp?.();
  
  rawApp.get('/api/v1/jobs', async (c: any) => {
    const jobs = await this.queryJobs(options);
    return c.json({ success: true, data: jobs });
  });
}

Frontend: Admin Pages with TanStack Query

Created 6 pages under /settings/* using React 19, TanStack Query for data fetching, and shadcn/ui components:

  1. Jobs Monitor - Queue statistics, filterable jobs table, retry/cancel actions
  2. Plugin Management - Plugin list with health checks, uptime, capabilities
  3. Metrics Dashboard - Separate tables for counters/gauges/histograms, Prometheus link
  4. Audit Log - Event filtering by object/user/type/date, inline field changes
  5. Permissions - Permission sets table, object-level CRUD breakdown
  6. Notifications - Channel configuration, queue status

All pages follow pattern: stats cards → filterable table → auto-refresh (5-10s intervals).

Integration

  • Added @tanstack/react-query dependency and configured QueryClientProvider
  • Updated App.tsx with lazy-loaded routes
  • Extended SettingsLayout sidebar navigation
  • Build passes without errors

Technical Notes

  • Backend changes are minimal additions to existing plugin files (no refactoring)
  • Frontend uses existing UI components and patterns from auth/org pages
  • Auto-refresh intervals vary by data sensitivity (jobs: 5s, plugins: 10s)
  • Empty states and loading spinners for all data fetching
Original prompt

Phase 2 — System Administration Pages (2 weeks)

Goal: Expose all ObjectOS Kernel capabilities through the admin UI.

# Task Details
2.1 Roles & Permissions UI View/edit Permission Sets, Object Permissions, Field-level Security
2.2 Audit Log Viewer Filterable table of audit events, user/object/action filters
2.3 Plugin Management List loaded plugins, health status, enable/disable (future)
2.6 Jobs Monitor Active/completed/failed jobs, retry actions
2.7 Metrics Dashboard System metrics display (from @objectos/metrics export)
2.8 Notification Settings Channel configuration (email/SMS/webhook templates)

The user has attached the following file paths as relevant context:

  • .github/copilot-instructions.md

创建自 VS Code


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectos Ready Ready Preview, Comment Feb 7, 2026 7:09pm

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ions, and permissions

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Add System Administration pages to expose ObjectOS Kernel capabilities Expose ObjectOS kernel capabilities through admin UI (Phase 2) Feb 7, 2026
Copilot AI requested a review from hotlong February 7, 2026 19:09
@hotlong hotlong marked this pull request as ready for review February 8, 2026 02:17
Copilot AI review requested due to automatic review settings February 8, 2026 02:17
@hotlong hotlong merged commit 7376125 into main Feb 8, 2026
4 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Phase 2 system administration surfaces to the ObjectOS admin console by exposing kernel/plugin capabilities via new REST endpoints and corresponding /settings/* UI pages.

Changes:

  • Added admin REST endpoints in multiple kernel plugins (permissions, audit, jobs, metrics, notification) using http.server.getRawApp() in start().
  • Introduced new settings pages (Jobs, Plugins, Metrics, Notifications) and upgraded existing Permissions/Audit pages to fetch and render real data.
  • Integrated TanStack Query across apps/web via QueryClientProvider and added navigation/routes for the new pages.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks @tanstack/react-query dependency tree.
apps/web/package.json Adds @tanstack/react-query.
apps/web/src/main.tsx Configures QueryClient and wraps app in QueryClientProvider.
apps/web/src/App.tsx Adds lazy routes for new settings pages.
apps/web/src/components/layouts/SettingsLayout.tsx Extends Settings sidebar navigation to new pages.
apps/web/src/pages/settings/jobs.tsx Jobs monitor UI (list + stats + retry/cancel actions).
apps/web/src/pages/settings/plugins.tsx Plugin management UI consuming /api/v1/admin/plugins.
apps/web/src/pages/settings/metrics.tsx Metrics dashboard UI consuming /api/v1/metrics + Prometheus link.
apps/web/src/pages/settings/notifications.tsx Notification settings UI consuming channels + queue status.
apps/web/src/pages/settings/audit.tsx Audit log viewer UI with filters consuming /api/v1/audit/events.
apps/web/src/pages/settings/permissions.tsx Permissions UI consuming /api/v1/permissions/sets.
packages/permissions/src/plugin.ts Adds Permissions REST endpoints (sets/object/check).
packages/audit/src/plugin.ts Adds Audit REST endpoints (events/trail/field-history).
packages/jobs/src/plugin.ts Adds Jobs REST endpoints (query/stats/retry/cancel) and tweaks health message.
packages/metrics/src/plugin.ts Adds Metrics REST endpoints + admin plugins listing endpoint.
packages/notification/src/plugin.ts Adds Notification REST endpoints (channels/queue status/send).
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +136 to +153
'@objectos/metrics',
'@objectos/cache',
'@objectos/storage',
'@objectos/auth',
'@objectos/permissions',
'@objectos/audit',
'@objectos/workflow',
'@objectos/automation',
'@objectos/jobs',
'@objectos/notification',
'@objectos/i18n',
'@objectos/realtime',
];

for (const pluginName of knownPlugins) {
try {
// Try to get the service
const serviceName = pluginName.replace('@objectos/', '');
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/api/v1/admin/plugins builds serviceName via pluginName.replace('@objectos/', ''), but @objectos/audit registers as service audit-log (not audit), so it will never be listed. Consider enumerating context.getKernel().services (or maintaining an explicit pluginName→serviceName map) to avoid silently missing plugins.

Suggested change
'@objectos/metrics',
'@objectos/cache',
'@objectos/storage',
'@objectos/auth',
'@objectos/permissions',
'@objectos/audit',
'@objectos/workflow',
'@objectos/automation',
'@objectos/jobs',
'@objectos/notification',
'@objectos/i18n',
'@objectos/realtime',
];
for (const pluginName of knownPlugins) {
try {
// Try to get the service
const serviceName = pluginName.replace('@objectos/', '');
{ pluginName: '@objectos/metrics', serviceName: 'metrics' },
{ pluginName: '@objectos/cache', serviceName: 'cache' },
{ pluginName: '@objectos/storage', serviceName: 'storage' },
{ pluginName: '@objectos/auth', serviceName: 'auth' },
{ pluginName: '@objectos/permissions', serviceName: 'permissions' },
{ pluginName: '@objectos/audit', serviceName: 'audit-log' },
{ pluginName: '@objectos/workflow', serviceName: 'workflow' },
{ pluginName: '@objectos/automation', serviceName: 'automation' },
{ pluginName: '@objectos/jobs', serviceName: 'jobs' },
{ pluginName: '@objectos/notification', serviceName: 'notification' },
{ pluginName: '@objectos/i18n', serviceName: 'i18n' },
{ pluginName: '@objectos/realtime', serviceName: 'realtime' },
];
for (const { pluginName, serviceName } of knownPlugins) {
try {
// Try to get the service

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +112
// GET /api/v1/metrics/prometheus - Prometheus format export
rawApp.get('/api/v1/metrics/prometheus', async (c: any) => {
try {
const prometheusText = this.exportPrometheus();
return c.text(prometheusText);
} catch (error: any) {
context.logger.error('[Metrics API] Prometheus export error:', error);
return c.json({ success: false, error: error.message }, 500);
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says the admin REST APIs follow { success, data, error }, but this endpoint returns plain text via c.text(). Either update the PR description to document this exception, or wrap the Prometheus payload in the standard JSON envelope (and set an explicit content-type if staying with plain text).

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +133
// GET /api/v1/admin/plugins - List all loaded plugins with health status
rawApp.get('/api/v1/admin/plugins', async (c: any) => {
try {
// Get all registered services (plugins register themselves as services)
const plugins: any[] = [];

// Try to get plugin information from the kernel context
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This admin plugins endpoint exposes detailed health/manifests for services but does not enforce authentication/authorization. Since it reveals system topology and status, it should require an authenticated admin session (or equivalent middleware) before returning data.

Copilot uses AI. Check for mistakes.
Comment on lines +193 to +203
// POST /api/v1/notifications/send - Send notification
rawApp.post('/api/v1/notifications/send', async (c: any) => {
try {
const request = await c.req.json();
const result = await this.send(request);
return c.json({ success: true, data: result });
} catch (error: any) {
context.logger.error('[Notification API] Send error:', error);
return c.json({ success: false, error: error.message }, 500);
}
});
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Notification API exposes a /send endpoint that can trigger outbound email/SMS/webhooks, but the handler does not enforce authentication/authorization. This is a high-impact endpoint and should require admin privileges (and ideally rate limiting) to prevent abuse and external-call amplification.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +17
status: 'healthy' | 'degraded' | 'error' | 'unknown';
uptime: number;
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend health status values commonly include 'unhealthy', but this page’s Plugin['status'] union and statusColors map do not include it (only error/unknown). This can lead to inconsistent rendering and makes TypeScript lie about possible values; include 'unhealthy' (or align backend to 'error') and map it to a Badge variant.

Copilot uses AI. Check for mistakes.
Comment on lines +155 to +161
const hasPermission = await this.engine.checkPermission(
permissionContext,
objectName,
action as any
);

return c.json({ success: true, data: { hasPermission } });
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PermissionEngine.checkPermission() returns a PermissionCheckResult object, but this handler stores it in hasPermission and returns it under { hasPermission }, which implies a boolean. This will return an object to clients and can break consumers; return result.allowed (and optionally result.reason/filters) or rename the field to reflect the actual shape.

Suggested change
const hasPermission = await this.engine.checkPermission(
permissionContext,
objectName,
action as any
);
return c.json({ success: true, data: { hasPermission } });
const result = await this.engine.checkPermission(
permissionContext,
objectName,
action as any
);
return c.json({
success: true,
data: {
// Maintain boolean semantics for hasPermission
hasPermission: result.allowed,
// Optionally expose full permission check details
permission: result,
},
});

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +101
// GET /api/v1/permissions/sets - List all permission sets
rawApp.get('/api/v1/permissions/sets', async (c: any) => {
try {
const sets = await this.storage.getAllPermissionSets();
return c.json({ success: true, data: sets });
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These Permissions API routes are mounted directly on the raw Hono app and do not perform any authentication/authorization checks. Since the plugin manifest declares requiredPermissions: ['admin'], the routes should enforce admin access explicitly (e.g., via auth session + permissions service) to avoid exposing permission sets to unauthenticated callers.

Copilot uses AI. Check for mistakes.
try {
const id = c.req.param('id');
await this.cancel(id);
return c.json({ success: true, message: 'Job cancelled' });
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cancel endpoint returns { success: true, message: 'Job cancelled' }, which diverges from the documented { success, data, error } envelope. To keep API consumers consistent, return the message under data (or return the updated job) and avoid introducing a new top-level field.

Suggested change
return c.json({ success: true, message: 'Job cancelled' });
return c.json({ success: true, data: 'Job cancelled' });

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +312
// POST /api/v1/jobs/:id/retry - Retry failed job
rawApp.post('/api/v1/jobs/:id/retry', async (c: any) => {
try {
const id = c.req.param('id');
const job = await this.getJob(id);
if (!job) {
return c.json({ success: false, error: 'Job not found' }, 404);
}
if (job.status !== 'failed') {
return c.json({ success: false, error: 'Only failed jobs can be retried' }, 400);
}
// Re-enqueue the job
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/api/v1/jobs/:id/retry re-enqueues jobs and /api/v1/jobs/:id/cancel mutates queue state, but the handlers do not enforce any authentication/authorization. These are privileged operations and should be restricted (e.g., admin-only) to prevent untrusted callers from cancelling or replaying background work.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +99
// GET /api/v1/audit/events - Query audit events
rawApp.get('/api/v1/audit/events', async (c: any) => {
try {
const query = c.req.query();
const options: AuditQueryOptions = {
objectName: query.objectName,
recordId: query.recordId,
userId: query.userId,
eventType: query.eventType as AuditEventType,
startDate: query.startDate,
endDate: query.endDate,
limit: query.limit ? parseInt(query.limit) : undefined,
offset: query.offset ? parseInt(query.offset) : undefined,
};
const events = await this.queryEvents(options);
return c.json({ success: true, data: events });
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET /api/v1/audit/events exposes potentially sensitive audit data but the handler does not enforce authentication/authorization. The plugin manifest indicates requiredPermissions: ['admin']; the route should explicitly require an authenticated admin session before returning logs.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants