AI Assistant c7f037c58a feat: Complete OpenClaw Dashboard with all features
- Dashboard Overview with real-time status display
- Live Log Viewer (scrollable, filterable)
- Config Editor with JSON syntax highlighting
- Model Switcher for provider management
- Provider Manager for API key configuration
- Quick Actions for common tasks
- API Routes: status, logs, config, actions, models, providers

Tech Stack:
- Next.js 14 (App Router)
- TypeScript
- Tailwind CSS
- shadcn/ui components
- CodeMirror for JSON editing
- Docker support with docker-compose
2026-02-27 05:55:23 +00:00

388 lines
9.4 KiB
TypeScript

import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs/promises';
import path from 'path';
const execAsync = promisify(exec);
const OPENCLAW_CONFIG_PATH = '/home/ai/.openclaw/openclaw.json';
const OPENCLAW_BIN = 'openclaw';
export interface OpenClawConfig {
meta?: {
lastTouchedVersion?: string;
lastTouchedAt?: string;
};
auth?: {
profiles?: Record<string, any>;
};
models?: {
mode?: string;
providers?: Record<string, any>;
defaults?: {
model?: {
primary?: string;
};
};
};
gateway?: {
port?: number;
mode?: string;
bind?: string;
controlUi?: {
enabled?: boolean;
};
auth?: {
token?: string;
mode?: string;
};
};
plugins?: {
entries?: Record<string, any>;
};
tools?: {
web?: {
search?: {
apiKey?: string;
};
};
};
[key: string]: any;
}
export interface GatewayStatus {
running: boolean;
pid?: number;
port?: number;
uptime?: number;
mode?: string;
}
export interface InstanceStatus {
id: string;
name: string;
type: 'openclaw' | 'opencode' | 'gateway';
status: 'online' | 'offline' | 'unknown' | 'starting' | 'stopping';
endpoint?: string;
model?: string;
lastCheck: number;
}
/**
* Get the OpenClaw configuration
*/
export async function getOpenClawConfig(): Promise<OpenClawConfig> {
try {
const content = await fs.readFile(OPENCLAW_CONFIG_PATH, 'utf-8');
return JSON.parse(content);
} catch (error) {
throw new Error(`Failed to read OpenClaw config: ${(error as Error).message}`);
}
}
/**
* Save the OpenClaw configuration
*/
export async function saveOpenClawConfig(config: OpenClawConfig): Promise<void> {
try {
await fs.writeFile(
OPENCLAW_CONFIG_PATH,
JSON.stringify(config, null, 2),
'utf-8'
);
} catch (error) {
throw new Error(`Failed to save OpenClaw config: ${(error as Error).message}`);
}
}
/**
* Get Gateway status
*/
export async function getGatewayStatus(): Promise<GatewayStatus> {
try {
const { stdout } = await execAsync('ps aux | grep "[o]penclaw gateway"');
const running = stdout.trim().length > 0;
if (!running) {
return { running: false };
}
// Try to get more details from the running process
try {
const { stdout: statusOutput } = await execAsync('openclaw gateway status --json', {
timeout: 5000,
});
const statusData = JSON.parse(statusOutput);
return {
running: true,
pid: statusData.pid,
port: statusData.port,
uptime: statusData.uptime,
mode: statusData.mode,
};
} catch {
return { running: true };
}
} catch {
return { running: false };
}
}
/**
* Start the Gateway
*/
export async function startGateway(): Promise<{ success: boolean; message: string }> {
try {
await execAsync('openclaw gateway start', { timeout: 10000 });
return { success: true, message: 'Gateway started successfully' };
} catch (error) {
return { success: false, message: `Failed to start Gateway: ${(error as Error).message}` };
}
}
/**
* Stop the Gateway
*/
export async function stopGateway(): Promise<{ success: boolean; message: string }> {
try {
await execAsync('openclaw gateway stop', { timeout: 10000 });
return { success: true, message: 'Gateway stopped successfully' };
} catch (error) {
return { success: false, message: `Failed to stop Gateway: ${(error as Error).message}` };
}
}
/**
* Restart the Gateway
*/
export async function restartGateway(): Promise<{ success: boolean; message: string }> {
try {
await execAsync('openclaw gateway restart', { timeout: 15000 });
return { success: true, message: 'Gateway restarted successfully' };
} catch (error) {
return { success: false, message: `Failed to restart Gateway: ${(error as Error).message}` };
}
}
/**
* Get recent logs from Gateway
*/
export async function getGatewayLogs(lines: number = 100): Promise<string[]> {
try {
const { stdout } = await execAsync(`journalctl -u openclaw -n ${lines} --no-pager --output=cat`, {
timeout: 5000,
});
return stdout.trim().split('\n');
} catch {
// Fallback: Try to read from common log locations
const logPaths = [
'/var/log/openclaw/gateway.log',
'/root/.openclaw/logs/gateway.log',
'/tmp/openclaw-gateway.log',
];
for (const logPath of logPaths) {
try {
const content = await fs.readFile(logPath, 'utf-8');
return content.trim().split('\n').slice(-lines);
} catch {
continue;
}
}
return [];
}
}
/**
* Get current active model
*/
export async function getActiveModel(): Promise<string | null> {
try {
const config = await getOpenClawConfig();
return config.models?.defaults?.model?.primary || null;
} catch {
return null;
}
}
/**
* Set active model
*/
export async function setActiveModel(modelId: string): Promise<{ success: boolean; message: string }> {
try {
const config = await getOpenClawConfig();
if (!config.models) config.models = {};
if (!config.models.defaults) config.models.defaults = {};
if (!config.models.defaults.model) config.models.defaults.model = {};
config.models.defaults.model.primary = modelId;
await saveOpenClawConfig(config);
// Restart gateway to apply changes
await restartGateway();
return { success: true, message: `Model set to ${modelId}` };
} catch (error) {
return { success: false, message: `Failed to set model: ${(error as Error).message}` };
}
}
/**
* Get available models
*/
export async function getAvailableModels(): Promise<Array<{ id: string; name: string }>> {
try {
const config = await getOpenClawConfig();
const models: Array<{ id: string; name: string }> = [];
// Collect models from all providers
if (config.models?.providers) {
for (const [providerName, providerData] of Object.entries(config.models.providers)) {
if (providerData && typeof providerData === 'object' && 'models' in providerData) {
const providerModels = (providerData as any).models || [];
for (const model of providerModels) {
models.push({
id: `${providerName}/${model.id}`,
name: model.name || model.id,
});
}
}
}
}
return models;
} catch {
return [];
}
}
/**
* Get all providers with their API keys
*/
export async function getProviders(): Promise<Array<{
id: string;
name: string;
type: string;
hasKey: boolean;
key?: string;
}>> {
try {
const config = await getOpenClawConfig();
const providers: Array<{
id: string;
name: string;
type: string;
hasKey: boolean;
key?: string;
}> = [];
if (config.auth?.profiles) {
for (const [id, profile] of Object.entries(config.auth.profiles)) {
const providerData = profile as any;
providers.push({
id,
name: providerData.email || id,
type: providerData.provider || id.split(':')[0],
hasKey: !!providerData.apiKey,
key: providerData.apiKey,
});
}
}
return providers;
} catch {
return [];
}
}
/**
* Add or update a provider's API key
*/
export async function setProviderApiKey(
providerId: string,
apiKey: string
): Promise<{ success: boolean; message: string }> {
try {
const config = await getOpenClawConfig();
if (!config.auth) config.auth = {};
if (!config.auth.profiles) config.auth.profiles = {};
if (!config.auth.profiles[providerId]) {
// Create new provider profile
const [providerName, ...rest] = providerId.split(':');
config.auth.profiles[providerId] = {
provider: providerName,
mode: 'api_key',
apiKey,
};
} else {
config.auth.profiles[providerId].apiKey = apiKey;
}
await saveOpenClawConfig(config);
return { success: true, message: `API key updated for ${providerId}` };
} catch (error) {
return { success: false, message: `Failed to update API key: ${(error as Error).message}` };
}
}
/**
* Get system status summary
*/
export async function getSystemStatus(): Promise<{
gateway: GatewayStatus;
activeModel: string | null;
instanceCount: number;
uptime: number;
}> {
const [gatewayStatus, activeModel] = await Promise.all([
getGatewayStatus(),
getActiveModel(),
]);
// Get system uptime
const uptime = process.uptime();
return {
gateway: gatewayStatus,
activeModel,
instanceCount: 1, // Will be expanded for multiple instances
uptime,
};
}
/**
* Apply config changes and restart gateway
*/
export async function applyConfig(config: OpenClawConfig): Promise<{
success: boolean;
message: string;
}> {
try {
// Validate config (basic)
if (!config || typeof config !== 'object') {
throw new Error('Invalid configuration');
}
await saveOpenClawConfig(config);
// Restart gateway to apply changes
const restartResult = await restartGateway();
if (!restartResult.success) {
return {
success: false,
message: `Config saved but failed to restart Gateway: ${restartResult.message}`,
};
}
return { success: true, message: 'Configuration applied and Gateway restarted' };
} catch (error) {
return { success: false, message: `Failed to apply config: ${(error as Error).message}` };
}
}