Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import CreateWorkflow from './pages/CreateWorkflow';
import WorkflowDetails from './pages/WorkflowDetails';
import WorkflowList from './pages/WorkflowList';
import TemplateList from './pages/TemplateList';
import TemplateRunHistory from './pages/TemplateRunHistory';
import Navigation from './components/Navigation';

const queryClient = new QueryClient();
Expand All @@ -23,6 +25,8 @@ function App() {
<Route path="/" element={<CreateWorkflow />} />
<Route path="/workflows" element={<WorkflowList />} />
<Route path="/workflow/:id" element={<WorkflowDetails />} />
<Route path="/templates" element={<TemplateList />} />
<Route path="/templates/:templateId/runs" element={<TemplateRunHistory />} />
</Routes>
</Container>
</Box>
Expand Down
79 changes: 79 additions & 0 deletions frontend/src/api/grpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ import {
WorkflowListResponse,
CancelWorkflowRequest,
SignalWorkflowRequest,
CreateWorkflowTemplateRequest,
GetWorkflowTemplateRequest,
ListWorkflowTemplatesRequest,
UpdateWorkflowTemplateRequest,
DeleteWorkflowTemplateRequest,
WorkflowTemplateResponse,
WorkflowTemplate,
WorkflowTemplateListResponse,
ExecuteWorkflowTemplateRequest,
GetTemplateRunHistoryRequest,
TemplateRunHistoryResponse,
} from "../gen/proto/ironbird_pb.js";

console.log("VITE_IRONBIRD_GRPC_ADDRESS:", import.meta.env.VITE_IRONBIRD_GRPC_ADDRESS);
Expand Down Expand Up @@ -85,4 +96,72 @@ export const grpcWorkflowApi = {
});
return await client.signalWorkflow(request) as WorkflowResponse;
},

// Template management methods
createWorkflowTemplate: async (request: CreateWorkflowTemplateRequest): Promise<WorkflowTemplateResponse> => {
try {
console.log("Calling createWorkflowTemplate with:", request);
const response = await client.createWorkflowTemplate(request) as WorkflowTemplateResponse;
console.log("createWorkflowTemplate response:", response);
return response;
} catch (error) {
console.error("createWorkflowTemplate error:", error);
throw error;
}
},

getWorkflowTemplate: async (templateId: string): Promise<WorkflowTemplate> => {
const request = new GetWorkflowTemplateRequest({
id: templateId
});
return await client.getWorkflowTemplate(request) as WorkflowTemplate;
},

listWorkflowTemplates: async (limit?: number, offset?: number): Promise<WorkflowTemplateListResponse> => {
const request = new ListWorkflowTemplatesRequest({
limit: limit || 50,
offset: offset || 0,
});
return await client.listWorkflowTemplates(request) as WorkflowTemplateListResponse;
},

updateWorkflowTemplate: async (request: UpdateWorkflowTemplateRequest): Promise<WorkflowTemplateResponse> => {
try {
console.log("Calling updateWorkflowTemplate with:", request);
const response = await client.updateWorkflowTemplate(request) as WorkflowTemplateResponse;
console.log("updateWorkflowTemplate response:", response);
return response;
} catch (error) {
console.error("updateWorkflowTemplate error:", error);
throw error;
}
},

deleteWorkflowTemplate: async (templateId: string): Promise<WorkflowTemplateResponse> => {
const request = new DeleteWorkflowTemplateRequest({
id: templateId
});
return await client.deleteWorkflowTemplate(request) as WorkflowTemplateResponse;
},

executeWorkflowTemplate: async (request: ExecuteWorkflowTemplateRequest): Promise<WorkflowResponse> => {
try {
console.log("Calling executeWorkflowTemplate with:", request);
const response = await client.executeWorkflowTemplate(request) as WorkflowResponse;
console.log("executeWorkflowTemplate response:", response);
return response;
} catch (error) {
console.error("executeWorkflowTemplate error:", error);
throw error;
}
},

getTemplateRunHistory: async (templateId: string, limit?: number, offset?: number): Promise<TemplateRunHistoryResponse> => {
const request = new GetTemplateRunHistoryRequest({
id: templateId,
limit: limit || 50,
offset: offset || 0
});
return await client.getTemplateRunHistory(request) as TemplateRunHistoryResponse;
},
};
166 changes: 166 additions & 0 deletions frontend/src/api/templateApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { grpcWorkflowApi } from './grpcClient';
import type {
WorkflowTemplate,
WorkflowTemplateSummary,
CreateWorkflowTemplateRequest,
WorkflowTemplateResponse,
ExecuteWorkflowTemplateRequest,
TemplateRunHistoryResponse,
TestnetWorkflowRequest
} from '../types/workflow';
import {
CreateWorkflowTemplateRequest as ProtoCreateRequest,
ExecuteWorkflowTemplateRequest as ProtoExecuteRequest,
} from '../gen/proto/ironbird_pb.js';
import { convertToGrpcCreateWorkflowRequest } from './workflowApi';

// Helper function to safely parse JSON strings
const safeJSONParse = (jsonString: string): any => {
try {
return JSON.parse(jsonString);
} catch (error) {
console.warn('Failed to parse JSON config:', jsonString, error);
return undefined;
}
};

// Helper function to convert frontend template request to protobuf
const convertToProtoTemplateRequest = (request: CreateWorkflowTemplateRequest): ProtoCreateRequest => {
const protoRequest = new ProtoCreateRequest({
id: request.templateId,
description: request.description,
templateConfig: convertToGrpcCreateWorkflowRequest(request.templateConfig),
});

return protoRequest;
};

// Helper function to convert protobuf template to frontend type
const convertFromProtoTemplate = (protoTemplate: any): WorkflowTemplate => {
return {
templateId: protoTemplate.id,
description: protoTemplate.description,
templateConfig: convertFromProtoWorkflowRequest(protoTemplate.templateConfig),
createdAt: protoTemplate.createdAt,
createdBy: protoTemplate.createdBy,
};
};

// Helper function to convert protobuf workflow request to frontend type
const convertFromProtoWorkflowRequest = (protoConfig: any): TestnetWorkflowRequest => {
// Convert genesis modifications from protobuf format
const genesisModifications = (protoConfig.chainConfig?.genesisModifications || []).map((gm: any) => ({
key: gm.key || '',
value: gm.value || '',
}));

// Convert region configs from protobuf format
const regionConfigs = (protoConfig.chainConfig?.regionConfigs || []).map((rc: any) => ({
name: rc.name || '',
numOfNodes: Number(rc.numOfNodes) || 0,
numOfValidators: Number(rc.numOfValidators) || 0,
}));

return {
Repo: protoConfig.repo || '',
SHA: protoConfig.sha || '',
IsEvmChain: protoConfig.isEvmChain || false,
ChainConfig: {
Name: protoConfig.chainConfig?.name || '',
Image: protoConfig.chainConfig?.image || '',
Version: protoConfig.chainConfig?.version || '',
NumOfNodes: Number(protoConfig.chainConfig?.numOfNodes) || 0,
NumOfValidators: Number(protoConfig.chainConfig?.numOfValidators) || 0,
GenesisModifications: genesisModifications,
RegionConfigs: regionConfigs,
AppConfig: protoConfig.chainConfig?.customAppConfig ?
safeJSONParse(protoConfig.chainConfig.customAppConfig) : undefined,
ConsensusConfig: protoConfig.chainConfig?.customConsensusConfig ?
safeJSONParse(protoConfig.chainConfig.customConsensusConfig) : undefined,
ClientConfig: protoConfig.chainConfig?.customClientConfig ?
safeJSONParse(protoConfig.chainConfig.customClientConfig) : undefined,
SetSeedNode: protoConfig.chainConfig?.setSeedNode || false,
SetPersistentPeers: protoConfig.chainConfig?.setPersistentPeers || false,
},
RunnerType: protoConfig.runnerType || '',
EncodedLoadTestSpec: protoConfig.encodedLoadTestSpec || '',
LongRunningTestnet: protoConfig.longRunningTestnet || false,
LaunchLoadBalancer: protoConfig.launchLoadBalancer || false,
TestnetDuration: protoConfig.testnetDuration || '',
NumWallets: Number(protoConfig.numWallets) || 2500,
CatalystVersion: protoConfig.catalystVersion || '',
};
};

export const templateApi = {
createTemplate: async (request: CreateWorkflowTemplateRequest): Promise<WorkflowTemplateResponse> => {
const protoRequest = convertToProtoTemplateRequest(request);
const response = await grpcWorkflowApi.createWorkflowTemplate(protoRequest);
return {
templateId: response.id,
};
},

getTemplate: async (templateId: string): Promise<WorkflowTemplate> => {
const response = await grpcWorkflowApi.getWorkflowTemplate(templateId);
return convertFromProtoTemplate(response);
},

listTemplates: async (limit?: number, offset?: number): Promise<{templates: WorkflowTemplateSummary[], count: number}> => {
const response = await grpcWorkflowApi.listWorkflowTemplates(limit, offset);

const templates: WorkflowTemplateSummary[] = (response.templates || []).map((template: any) => ({
templateId: template.id,
description: template.description,
createdAt: template.createdAt,
runCount: template.runCount || 0,
}));

return {
templates,
count: response.count || 0,
};
},

deleteTemplate: async (templateId: string): Promise<WorkflowTemplateResponse> => {
const response = await grpcWorkflowApi.deleteWorkflowTemplate(templateId);
return {
templateId: response.id,
};
},

executeTemplate: async (request: ExecuteWorkflowTemplateRequest): Promise<{workflowId: string}> => {
const protoRequest = new ProtoExecuteRequest({
id: request.templateId,
sha: request.sha,
runName: request.runName || '',
});

const response = await grpcWorkflowApi.executeWorkflowTemplate(protoRequest);
return {
workflowId: response.workflowId,
};
},

getTemplateRunHistory: async (templateId: string, limit?: number, offset?: number): Promise<TemplateRunHistoryResponse> => {
const response = await grpcWorkflowApi.getTemplateRunHistory(templateId, limit, offset);

const runs = (response.runs || []).map((run: any) => ({
runId: run.runId,
workflowId: run.workflowId,
templateId: run.templateId,
sha: run.sha,
runName: run.runName,
status: run.status,
startedAt: run.startedAt,
completedAt: run.completedAt,
monitoringLinks: run.monitoringLinks || {},
provider: run.provider || '',
}));

return {
runs,
count: response.count || 0,
};
},
};
Loading
Loading