mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-08 18:05:10 +00:00
feat(stacks): per-stack action tracking, optimistic status, and bulk status endpoint (#362)
* feat(stacks): per-stack action tracking, optimistic status, and bulk status endpoint Replace global loadingAction mutex with per-stack tracking so users can fire actions on multiple stacks concurrently. Add optimistic status updates to fix sidebar showing "--" after stop/start. Add bulk GET /api/stacks/statuses endpoint using a single docker.listContainers call instead of N docker compose ps invocations (~21s → ~110ms for 3 stacks). Falls back to per-stack queries for remote nodes on older versions. * fix(stacks): remove stale 'start' action check from deploy button label
This commit is contained in:
@@ -2917,6 +2917,25 @@ app.get('/api/stacks', async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/stacks/statuses', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stacks = await FileSystemService.getInstance(req.nodeId).getStacks();
|
||||
const stackNames = stacks.map((s: string) => s.replace(/\.(yml|yaml)$/, ''));
|
||||
const dockerController = DockerController.getInstance(req.nodeId);
|
||||
const statuses = await dockerController.getBulkStackStatuses(stackNames);
|
||||
// Map back to filenames to match frontend expectations
|
||||
const result: Record<string, 'running' | 'exited' | 'unknown'> = {};
|
||||
for (const stack of stacks) {
|
||||
const name = stack.replace(/\.(yml|yaml)$/, '');
|
||||
result[stack] = statuses[name] ?? 'unknown';
|
||||
}
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch stack statuses:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch stack statuses' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/stacks/:stackName', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
|
||||
@@ -364,6 +364,29 @@ class DockerController {
|
||||
return this.validateApiData<any[]>(containers);
|
||||
}
|
||||
|
||||
public async getBulkStackStatuses(stackNames: string[]): Promise<Record<string, 'running' | 'exited' | 'unknown'>> {
|
||||
const allContainers = await this.docker.listContainers({ all: true });
|
||||
const knownSet = new Set(stackNames);
|
||||
|
||||
const statuses: Record<string, 'running' | 'exited' | 'unknown'> = {};
|
||||
for (const name of stackNames) {
|
||||
statuses[name] = 'unknown';
|
||||
}
|
||||
|
||||
for (const container of allContainers as any[]) {
|
||||
const project: string | undefined = container.Labels?.['com.docker.compose.project'];
|
||||
if (project && knownSet.has(project)) {
|
||||
if (container.State === 'running') {
|
||||
statuses[project] = 'running';
|
||||
} else if (statuses[project] !== 'running') {
|
||||
statuses[project] = 'exited';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statuses;
|
||||
}
|
||||
|
||||
public async getContainersByStack(stackName: string) {
|
||||
const stackDir = path.join(COMPOSE_DIR, stackName);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user