feat(mod_services): Add global service registry and global commands for managing project containers

This commit is contained in:
2025-11-29 17:56:46 +00:00
parent ddf5023ecb
commit 847e679e92
7 changed files with 431 additions and 17 deletions

View File

@@ -0,0 +1,190 @@
import * as plugins from '../plugins.js';
import { DockerContainer } from './classes.dockercontainer.js';
import { logger } from '../gitzone.logging.js';
export interface IRegisteredProject {
projectPath: string;
projectName: string;
containers: {
mongo?: string;
minio?: string;
elasticsearch?: string;
};
ports: {
mongo?: number;
s3?: number;
s3Console?: number;
elasticsearch?: number;
};
enabledServices: string[];
lastActive: number;
}
export interface IGlobalRegistryData {
projects: { [projectPath: string]: IRegisteredProject };
}
export class GlobalRegistry {
private static instance: GlobalRegistry | null = null;
private kvStore: plugins.npmextra.KeyValueStore<IGlobalRegistryData>;
private docker: DockerContainer;
private constructor() {
this.kvStore = new plugins.npmextra.KeyValueStore({
typeArg: 'userHomeDir',
identityArg: 'gitzone-services',
});
this.docker = new DockerContainer();
}
/**
* Get the singleton instance
*/
public static getInstance(): GlobalRegistry {
if (!GlobalRegistry.instance) {
GlobalRegistry.instance = new GlobalRegistry();
}
return GlobalRegistry.instance;
}
/**
* Register or update a project in the global registry
*/
public async registerProject(data: Omit<IRegisteredProject, 'lastActive'>): Promise<void> {
const allData = await this.kvStore.readAll();
const projects = allData.projects || {};
projects[data.projectPath] = {
...data,
lastActive: Date.now(),
};
await this.kvStore.writeKey('projects', projects);
}
/**
* Remove a project from the registry
*/
public async unregisterProject(projectPath: string): Promise<void> {
const allData = await this.kvStore.readAll();
const projects = allData.projects || {};
if (projects[projectPath]) {
delete projects[projectPath];
await this.kvStore.writeKey('projects', projects);
}
}
/**
* Update the lastActive timestamp for a project
*/
public async touchProject(projectPath: string): Promise<void> {
const allData = await this.kvStore.readAll();
const projects = allData.projects || {};
if (projects[projectPath]) {
projects[projectPath].lastActive = Date.now();
await this.kvStore.writeKey('projects', projects);
}
}
/**
* Get all registered projects
*/
public async getAllProjects(): Promise<{ [path: string]: IRegisteredProject }> {
const allData = await this.kvStore.readAll();
return allData.projects || {};
}
/**
* Check if a project is registered
*/
public async isRegistered(projectPath: string): Promise<boolean> {
const projects = await this.getAllProjects();
return !!projects[projectPath];
}
/**
* Get status of all containers across all registered projects
*/
public async getGlobalStatus(): Promise<
Array<{
projectPath: string;
projectName: string;
containers: Array<{ name: string; status: string }>;
lastActive: number;
}>
> {
const projects = await this.getAllProjects();
const result: Array<{
projectPath: string;
projectName: string;
containers: Array<{ name: string; status: string }>;
lastActive: number;
}> = [];
for (const [path, project] of Object.entries(projects)) {
const containerStatuses: Array<{ name: string; status: string }> = [];
for (const containerName of Object.values(project.containers)) {
if (containerName) {
const status = await this.docker.getStatus(containerName);
containerStatuses.push({ name: containerName, status });
}
}
result.push({
projectPath: path,
projectName: project.projectName,
containers: containerStatuses,
lastActive: project.lastActive,
});
}
return result;
}
/**
* Stop all containers across all registered projects
*/
public async stopAll(): Promise<{ stopped: string[]; failed: string[] }> {
const projects = await this.getAllProjects();
const stopped: string[] = [];
const failed: string[] = [];
for (const project of Object.values(projects)) {
for (const containerName of Object.values(project.containers)) {
if (containerName) {
const status = await this.docker.getStatus(containerName);
if (status === 'running') {
if (await this.docker.stop(containerName)) {
stopped.push(containerName);
} else {
failed.push(containerName);
}
}
}
}
}
return { stopped, failed };
}
/**
* Remove stale registry entries (projects that no longer exist on disk)
*/
public async cleanup(): Promise<string[]> {
const projects = await this.getAllProjects();
const removed: string[] = [];
for (const projectPath of Object.keys(projects)) {
const exists = await plugins.smartfs.directory(projectPath).exists();
if (!exists) {
await this.unregisterProject(projectPath);
removed.push(projectPath);
}
}
return removed;
}
}