fix
This commit is contained in:
+7
@@ -1,6 +1,13 @@
|
||||
<p>admin-welcome.component works!</p>
|
||||
|
||||
<p>admin-welcome.component works!</p>
|
||||
|
||||
<p>admin-welcome.component works!</p>
|
||||
|
||||
<p>admin-welcome.component works!</p><p>admin-welcome.component works!</p>
|
||||
|
||||
|
||||
<p>admin-welcome.component works!</p>
|
||||
<p>admin-welcome.component works!</p>
|
||||
<p>admin-welcome.component works!</p>
|
||||
<p>admin-welcome.component works!</p>
|
||||
|
||||
@@ -2,12 +2,21 @@ import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {AdminComponent} from './admin.component/admin.component';
|
||||
import {adminRouting} from './admin.routing';
|
||||
import {MatTreeModule} from '@angular/material/tree';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
||||
],
|
||||
imports: [adminRouting, AdminComponent],
|
||||
imports: [adminRouting, AdminComponent,
|
||||
MatTreeModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
exports: [RouterModule],
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
|
||||
@@ -28,6 +28,11 @@ export const ADMIN_ROUTES: Routes = [
|
||||
loadComponent: () => import('../admin-module/users.component/users.component').then((c) => c.UsersComponent),
|
||||
data:{title: 'Users', icon:'', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'hard-drive-browser',
|
||||
loadComponent: () => import('../admin-module/hard-drive-browser.component/hard-drive-browser.component').then((c)=> c.HardDriveBrowserComponent),
|
||||
data:{title: 'Hard Drive', icon:'hard_drive', nav:true}
|
||||
},
|
||||
{
|
||||
path:"data-base",
|
||||
loadComponent: () => import('../admin-module/data-base.component/data-base.component').then((c) => c.DataBaseComponent),
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
<app-file-browser-with-tree-component></app-file-browser-with-tree-component>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HardDriveBrowserComponent } from './hard-drive-browser.component';
|
||||
|
||||
describe('HardDriveBrowserComponent', () => {
|
||||
let component: HardDriveBrowserComponent;
|
||||
let fixture: ComponentFixture<HardDriveBrowserComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HardDriveBrowserComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HardDriveBrowserComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
FileBrowserWithTreeComponent
|
||||
} from '../../../components/file-browser-with-tree-component/file-browser-with-tree-component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hard-drive-browser.component',
|
||||
imports: [
|
||||
FileBrowserWithTreeComponent
|
||||
],
|
||||
templateUrl: './hard-drive-browser.component.html',
|
||||
styleUrl: './hard-drive-browser.component.scss'
|
||||
})
|
||||
export class HardDriveBrowserComponent {
|
||||
|
||||
}
|
||||
+2
-1
@@ -19,7 +19,7 @@ import {
|
||||
MatDialogRef,
|
||||
MatDialogTitle
|
||||
} from '@angular/material/dialog';
|
||||
import {MatFormField, MatInput, MatLabel, MatSuffix} from '@angular/material/input';
|
||||
import {MatError, MatFormField, MatInput, MatLabel, MatSuffix} from '@angular/material/input';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {DialogSignupData} from '../main.component';
|
||||
@@ -41,6 +41,7 @@ import {ErrorStateMatcher} from '@angular/material/core';
|
||||
MatSuffix,
|
||||
MatFormField,
|
||||
ReactiveFormsModule,
|
||||
MatError,
|
||||
|
||||
],
|
||||
templateUrl: './dialog-signup.component.html',
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<div class="docker-management">
|
||||
<h2>Docker Container Management</h2>
|
||||
|
||||
<!-- Container List -->
|
||||
<div class="containers-section">
|
||||
<h3>Running Containers</h3>
|
||||
<button (click)="refreshContainers()" class="btn btn-secondary">Refresh</button>
|
||||
|
||||
<div class="container-list" *ngIf="containers.length > 0; else noContainers">
|
||||
<div class="container-item" *ngFor="let container of containers">
|
||||
<div class="container-info">
|
||||
<strong>{{container.name}}</strong>
|
||||
<span class="image">{{container.image}}</span>
|
||||
<span class="status" [class.running]="container.status.includes('Up')">
|
||||
{{container.status}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="container-actions">
|
||||
<button (click)="stopContainer(container.name)"
|
||||
[disabled]="!container.status.includes('Up')"
|
||||
class="btn btn-warning">Stop</button>
|
||||
<button (click)="startContainer(container.name)"
|
||||
[disabled]="container.status.includes('Up')"
|
||||
class="btn btn-success">Start</button>
|
||||
<button (click)="removeContainer(container.name)"
|
||||
class="btn btn-danger">Remove</button>
|
||||
<button (click)="viewLogs(container.name)"
|
||||
class="btn btn-info">Logs</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #noContainers>
|
||||
<p>No containers found</p>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<!-- Run New Container -->
|
||||
<div class="run-container-section">
|
||||
<h3>Run New Container</h3>
|
||||
<form [formGroup]="runContainerForm" (ngSubmit)="runContainer()">
|
||||
<div class="form-group">
|
||||
<label>Image Name:</label>
|
||||
<input type="text" formControlName="imageName"
|
||||
placeholder="e.g., nginx:latest" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Container Name:</label>
|
||||
<input type="text" formControlName="containerName"
|
||||
placeholder="Optional container name" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Ports:</label>
|
||||
<input type="text" formControlName="ports"
|
||||
placeholder="e.g., 8080:80" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Environment Variables:</label>
|
||||
<input type="text" formControlName="environmentVars"
|
||||
placeholder="e.g., -e NODE_ENV=production" class="form-control">
|
||||
</div>
|
||||
|
||||
<button type="submit" [disabled]="runContainerForm.invalid || loading"
|
||||
class="btn btn-primary">
|
||||
{{loading ? 'Starting...' : 'Run Container'}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Execute Command -->
|
||||
<div class="execute-command-section">
|
||||
<h3>Execute Docker Command</h3>
|
||||
<form [formGroup]="commandForm" (ngSubmit)="executeCommand()">
|
||||
<div class="form-group">
|
||||
<label>Docker Command:</label>
|
||||
<input type="text" formControlName="command"
|
||||
placeholder="e.g., docker ps -a" class="form-control">
|
||||
</div>
|
||||
<button type="submit" [disabled]="commandForm.invalid || loading"
|
||||
class="btn btn-primary">Execute</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Command Output -->
|
||||
<div class="output-section" *ngIf="lastCommandResult">
|
||||
<h3>Command Output</h3>
|
||||
<div class="output-box"
|
||||
[class.success]="lastCommandResult.success"
|
||||
[class.error]="!lastCommandResult.success">
|
||||
<pre>{{getOutputText(lastCommandResult.output)}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Container Logs Modal -->
|
||||
<div class="logs-modal" *ngIf="selectedContainerLogs">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>Container Logs: {{selectedContainer}}</h3>
|
||||
<button (click)="closeLogs()" class="close-btn">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre class="logs-content">{{selectedContainerLogs.join('\n')}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DockerManagementComponent } from './docker-management-component';
|
||||
|
||||
describe('DockerManagementComponent', () => {
|
||||
let component: DockerManagementComponent;
|
||||
let fixture: ComponentFixture<DockerManagementComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DockerManagementComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DockerManagementComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {NgForOf, NgIf} from '@angular/common';
|
||||
import {Subject, takeUntil} from 'rxjs';
|
||||
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {ContainerInfo, DockerResult, DockerService} from '../docker.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-docker-management-component',
|
||||
imports: [
|
||||
NgIf,
|
||||
ReactiveFormsModule,
|
||||
NgForOf
|
||||
],
|
||||
templateUrl: './docker-management-component.html',
|
||||
styleUrl: './docker-management-component.scss'
|
||||
})
|
||||
export class DockerManagementComponent implements OnInit, OnDestroy {
|
||||
containers: ContainerInfo[] = [];
|
||||
runContainerForm: FormGroup;
|
||||
commandForm: FormGroup;
|
||||
lastCommandResult: DockerResult | null = null;
|
||||
selectedContainer: string = '';
|
||||
selectedContainerLogs: string[] | null = null;
|
||||
loading = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private dockerService: DockerService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.runContainerForm = this.fb.group({
|
||||
imageName: ['', Validators.required],
|
||||
containerName: [''],
|
||||
ports: [''],
|
||||
environmentVars: ['']
|
||||
});
|
||||
|
||||
this.commandForm = this.fb.group({
|
||||
command: ['docker ps', Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// Subscribe to containers updates
|
||||
this.dockerService.containers$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(containers => {
|
||||
this.containers = containers;
|
||||
});
|
||||
|
||||
// Initial load
|
||||
this.refreshContainers();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
refreshContainers() {
|
||||
this.dockerService.refreshContainers();
|
||||
}
|
||||
|
||||
runContainer() {
|
||||
if (this.runContainerForm.valid) {
|
||||
this.loading = true;
|
||||
const formValue = this.runContainerForm.value;
|
||||
|
||||
this.dockerService.runContainer(formValue)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.lastCommandResult = result;
|
||||
this.loading = false;
|
||||
if (result.success) {
|
||||
this.runContainerForm.reset();
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error running container:', error);
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
stopContainer(containerName: string) {
|
||||
this.dockerService.stopContainer(containerName)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.lastCommandResult = result;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error stopping container:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startContainer(containerName: string) {
|
||||
this.dockerService.startContainer(containerName)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.lastCommandResult = result;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error starting container:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeContainer(containerName: string) {
|
||||
if (confirm(`Are you sure you want to remove container "${containerName}"?`)) {
|
||||
this.dockerService.removeContainer(containerName)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.lastCommandResult = result;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error removing container:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
viewLogs(containerName: string) {
|
||||
this.selectedContainer = containerName;
|
||||
this.dockerService.getContainerLogs(containerName, 100)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (logs) => {
|
||||
this.selectedContainerLogs = logs;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error getting logs:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeLogs() {
|
||||
this.selectedContainerLogs = null;
|
||||
this.selectedContainer = '';
|
||||
}
|
||||
|
||||
executeCommand() {
|
||||
if (this.commandForm.valid) {
|
||||
this.loading = true;
|
||||
const command = this.commandForm.get('command')?.value;
|
||||
|
||||
this.dockerService.executeCommand(command)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.lastCommandResult = result;
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error executing command:', error);
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getOutputText(output: string[] | string): string {
|
||||
return Array.isArray(output) ? output.join('\n') : output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DockerService } from './docker.service';
|
||||
|
||||
describe('DockerService', () => {
|
||||
let service: DockerService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(DockerService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,471 @@
|
||||
// docker.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
import { throwError } from 'rxjs';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
|
||||
// Interfaces for type safety
|
||||
export interface DockerResult {
|
||||
success: boolean;
|
||||
exitCode?: number;
|
||||
output: string[] | string;
|
||||
command?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ContainerRunRequest {
|
||||
imageName: string;
|
||||
containerName?: string;
|
||||
ports?: string;
|
||||
environmentVars?: string;
|
||||
}
|
||||
|
||||
export interface ContainerInfo {
|
||||
containerId: string;
|
||||
name: string;
|
||||
image: string;
|
||||
status: string;
|
||||
ports: string;
|
||||
created: string;
|
||||
}
|
||||
|
||||
export interface CommandRequest {
|
||||
command: string;
|
||||
}
|
||||
|
||||
export interface ExecCommandRequest {
|
||||
command: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DockerService {
|
||||
baseUrl = `${GlobalConstants.API_URL}/user/docker`;
|
||||
|
||||
private readonly httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
};
|
||||
|
||||
// Observable for real-time container status updates
|
||||
private containersSubject = new BehaviorSubject<ContainerInfo[]>([]);
|
||||
public containers$ = this.containersSubject.asObservable();
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
// Auto-refresh containers every 30 seconds
|
||||
//this.startAutoRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute arbitrary Docker command
|
||||
*/
|
||||
executeCommand(command: string): Observable<DockerResult> {
|
||||
const request: CommandRequest = { command };
|
||||
|
||||
return this.http.post<DockerResult>(`${this.baseUrl}/execute`, request, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => this.normalizeDockerResult(response)),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute Docker command asynchronously
|
||||
*/
|
||||
executeCommandAsync(command: string): Observable<DockerResult> {
|
||||
const request: CommandRequest = { command };
|
||||
|
||||
return this.http.post<DockerResult>(`${this.baseUrl}/execute/async`, request, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => this.normalizeDockerResult(response)),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a Docker container
|
||||
*/
|
||||
runContainer(request: ContainerRunRequest): Observable<DockerResult> {
|
||||
return this.http.post<DockerResult>(`${this.baseUrl}/container/run`, request, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => {
|
||||
// Refresh containers list after successful container creation
|
||||
if (response.success) {
|
||||
this.refreshContainers();
|
||||
}
|
||||
return this.normalizeDockerResult(response);
|
||||
}),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a Docker container
|
||||
*/
|
||||
stopContainer(containerName: string): Observable<DockerResult> {
|
||||
return this.http.post<DockerResult>(`${this.baseUrl}/container/${containerName}/stop`, {}, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => {
|
||||
// Refresh containers list after stopping
|
||||
if (response.success) {
|
||||
this.refreshContainers();
|
||||
}
|
||||
return this.normalizeDockerResult(response);
|
||||
}),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a Docker container
|
||||
*/
|
||||
startContainer(containerName: string): Observable<DockerResult> {
|
||||
return this.executeCommand(`docker start ${containerName}`)
|
||||
.pipe(
|
||||
map(response => {
|
||||
if (response.success) {
|
||||
this.refreshContainers();
|
||||
}
|
||||
return response;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a Docker container
|
||||
*/
|
||||
removeContainer(containerName: string, force: boolean = false): Observable<DockerResult> {
|
||||
const command = force ? `docker rm -f ${containerName}` : `docker rm ${containerName}`;
|
||||
|
||||
return this.executeCommand(command)
|
||||
.pipe(
|
||||
map(response => {
|
||||
if (response.success) {
|
||||
this.refreshContainers();
|
||||
}
|
||||
return response;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of all containers
|
||||
*/
|
||||
listContainers(): Observable<ContainerInfo[]> {
|
||||
return this.http.get<{success: boolean, containers: string[]}>(`${this.baseUrl}/containers`)
|
||||
.pipe(
|
||||
map(response => {
|
||||
if (response.success && response.containers) {
|
||||
const containers = this.parseContainerList(response.containers);
|
||||
this.containersSubject.next(containers);
|
||||
return containers;
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get running containers only
|
||||
*/
|
||||
listRunningContainers(): Observable<ContainerInfo[]> {
|
||||
return this.listContainers().pipe(
|
||||
map(containers => containers.filter(c => c.status.includes('Up')))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get container logs
|
||||
*/
|
||||
getContainerLogs(containerName: string, lines: number = 100): Observable<string[]> {
|
||||
const params = new HttpParams().set('lines', lines.toString());
|
||||
|
||||
return this.http.get<{success: boolean, logs: string[]}>(`${this.baseUrl}/container/${containerName}/logs`, { params })
|
||||
.pipe(
|
||||
map(response => response.success ? response.logs : []),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute command inside container
|
||||
*/
|
||||
execInContainer(containerName: string, command: string): Observable<DockerResult> {
|
||||
const request: ExecCommandRequest = { command };
|
||||
|
||||
return this.http.post<DockerResult>(`${this.baseUrl}/container/${containerName}/exec`, request, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => this.normalizeDockerResult(response)),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Docker image
|
||||
*/
|
||||
buildImage(imageName: string, dockerfilePath: string = '.', tag?: string): Observable<DockerResult> {
|
||||
const fullTag = tag ? `${imageName}:${tag}` : imageName;
|
||||
const command = `docker build -t ${fullTag} ${dockerfilePath}`;
|
||||
|
||||
return this.executeCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull Docker image
|
||||
*/
|
||||
pullImage(imageName: string, tag: string = 'latest'): Observable<DockerResult> {
|
||||
const command = `docker pull ${imageName}:${tag}`;
|
||||
return this.executeCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Docker images
|
||||
*/
|
||||
listImages(): Observable<DockerResult> {
|
||||
return this.executeCommand('docker images');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Docker image
|
||||
*/
|
||||
removeImage(imageName: string, force: boolean = false): Observable<DockerResult> {
|
||||
const command = force ? `docker rmi -f ${imageName}` : `docker rmi ${imageName}`;
|
||||
return this.executeCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Docker system info
|
||||
*/
|
||||
getSystemInfo(): Observable<DockerResult> {
|
||||
return this.executeCommand('docker system info');
|
||||
}
|
||||
|
||||
/**
|
||||
* Docker version info
|
||||
*/
|
||||
getVersion(): Observable<DockerResult> {
|
||||
return this.executeCommand('docker --version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Docker is running
|
||||
*/
|
||||
isDockerRunning(): Observable<boolean> {
|
||||
return this.executeCommand('docker ps').pipe(
|
||||
map(result => result.success),
|
||||
catchError(() => [false])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Docker Compose up
|
||||
*/
|
||||
dockerComposeUp(composePath: string = 'docker-compose.yml'): Observable<DockerResult> {
|
||||
return this.executeCommand(`docker-compose -f ${composePath} up -d`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Docker Compose down
|
||||
*/
|
||||
dockerComposeDown(composePath: string = 'docker-compose.yml'): Observable<DockerResult> {
|
||||
return this.executeCommand(`docker-compose -f ${composePath} down`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Docker Compose services status
|
||||
*/
|
||||
dockerComposePs(composePath: string = 'docker-compose.yml'): Observable<DockerResult> {
|
||||
return this.executeCommand(`docker-compose -f ${composePath} ps`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh containers list
|
||||
*/
|
||||
refreshContainers(): void {
|
||||
this.listContainers().subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start auto-refresh of containers
|
||||
*/
|
||||
private startAutoRefresh(): void {
|
||||
setInterval(() => {
|
||||
this.refreshContainers();
|
||||
}, 30000); // Refresh every 30 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse container list output into structured data
|
||||
*/
|
||||
private parseContainerList(containerLines: string[]): ContainerInfo[] {
|
||||
const containers: ContainerInfo[] = [];
|
||||
|
||||
// Skip header line
|
||||
const dataLines = containerLines.slice(1);
|
||||
|
||||
dataLines.forEach(line => {
|
||||
if (line.trim()) {
|
||||
const parts = line.split(/\s{2,}/); // Split by 2 or more spaces
|
||||
if (parts.length >= 6) {
|
||||
containers.push({
|
||||
containerId: parts[0],
|
||||
image: parts[1],
|
||||
name: parts[6] || parts[parts.length - 1],
|
||||
status: parts[4],
|
||||
ports: parts[5] || '',
|
||||
created: parts[3]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return containers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize Docker result response
|
||||
*/
|
||||
private normalizeDockerResult(response: any): DockerResult {
|
||||
return {
|
||||
success: response.success || false,
|
||||
exitCode: response.exitCode,
|
||||
output: response.output || response.logs || [],
|
||||
command: response.command,
|
||||
error: response.error
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle HTTP errors
|
||||
*/
|
||||
private handleError = (error: any): Observable<never> => {
|
||||
console.error('Docker Service Error:', error);
|
||||
|
||||
let errorMessage = 'An unknown error occurred';
|
||||
if (error.error instanceof ErrorEvent) {
|
||||
// Client-side error
|
||||
errorMessage = error.error.message;
|
||||
} else {
|
||||
// Server-side error
|
||||
errorMessage = error.error?.message || error.message || `Error Code: ${error.status}`;
|
||||
}
|
||||
|
||||
return throwError(() => new Error(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// docker-models.ts - Additional models file
|
||||
export interface DockerStats {
|
||||
containerId: string;
|
||||
name: string;
|
||||
cpuPercentage: string;
|
||||
memoryUsage: string;
|
||||
memoryPercentage: string;
|
||||
networkIO: string;
|
||||
blockIO: string;
|
||||
pids: string;
|
||||
}
|
||||
|
||||
export interface DockerImage {
|
||||
repository: string;
|
||||
tag: string;
|
||||
imageId: string;
|
||||
created: string;
|
||||
size: string;
|
||||
}
|
||||
|
||||
export interface DockerVolume {
|
||||
driver: string;
|
||||
name: string;
|
||||
mountpoint: string;
|
||||
created: string;
|
||||
}
|
||||
|
||||
// Extended service methods
|
||||
export class DockerExtendedService extends DockerService {
|
||||
|
||||
/**
|
||||
* Get container statistics
|
||||
*/
|
||||
getContainerStats(containerName: string): Observable<DockerStats[]> {
|
||||
return this.executeCommand(`docker stats ${containerName} --no-stream --format "table {{.Container}}\\t{{.Name}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\\t{{.MemPerc}}\\t{{.NetIO}}\\t{{.BlockIO}}\\t{{.PIDs}}"`)
|
||||
.pipe(
|
||||
map(result => {
|
||||
if (result.success && Array.isArray(result.output)) {
|
||||
return this.parseStatsOutput(result.output);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Docker volumes
|
||||
*/
|
||||
listVolumes(): Observable<DockerVolume[]> {
|
||||
return this.executeCommand('docker volume ls --format "table {{.Driver}}\\t{{.Name}}"')
|
||||
.pipe(
|
||||
map(result => {
|
||||
if (result.success && Array.isArray(result.output)) {
|
||||
return this.parseVolumeOutput(result.output);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up Docker system
|
||||
*/
|
||||
systemPrune(force: boolean = false): Observable<DockerResult> {
|
||||
const command = force ? 'docker system prune -f' : 'docker system prune';
|
||||
return this.executeCommand(command);
|
||||
}
|
||||
|
||||
private parseStatsOutput(output: string[]): DockerStats[] {
|
||||
const stats: DockerStats[] = [];
|
||||
const dataLines = output.slice(1); // Skip header
|
||||
|
||||
dataLines.forEach(line => {
|
||||
const parts = line.split('\t');
|
||||
if (parts.length >= 8) {
|
||||
stats.push({
|
||||
containerId: parts[0],
|
||||
name: parts[1],
|
||||
cpuPercentage: parts[2],
|
||||
memoryUsage: parts[3],
|
||||
memoryPercentage: parts[4],
|
||||
networkIO: parts[5],
|
||||
blockIO: parts[6],
|
||||
pids: parts[7]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private parseVolumeOutput(output: string[]): DockerVolume[] {
|
||||
const volumes: DockerVolume[] = [];
|
||||
const dataLines = output.slice(1); // Skip header
|
||||
|
||||
dataLines.forEach(line => {
|
||||
const parts = line.split(/\s+/);
|
||||
if (parts.length >= 2) {
|
||||
volumes.push({
|
||||
driver: parts[0],
|
||||
name: parts[1],
|
||||
mountpoint: '',
|
||||
created: ''
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return volumes;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,11 @@ export const USER_ROUTS: Routes = [
|
||||
path: 'images',
|
||||
loadComponent: () => import('../user-module/images.component/images.component').then((c) => c.ImagesComponent),
|
||||
data:{title: 'Images', icon: 'imagesmode', nav:true}
|
||||
},
|
||||
{
|
||||
path:'docker',
|
||||
loadComponent: () => import('../user-module/docker-management-component/docker-management-component').then((c)=>c.DockerManagementComponent),
|
||||
data:{title: 'Docker', icon: 'docker', nav:true}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user