This commit is contained in:
liosha84
2025-08-22 18:57:24 +03:00
parent bc18200640
commit fac4e2d4df
41 changed files with 3401 additions and 7 deletions
@@ -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),
@@ -0,0 +1 @@
<app-file-browser-with-tree-component></app-file-browser-with-tree-component>
@@ -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();
});
});
@@ -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 {
}
@@ -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',
@@ -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">&times;</button>
</div>
<div class="modal-body">
<pre class="logs-content">{{selectedContainerLogs.join('\n')}}</pre>
</div>
</div>
</div>
</div>
@@ -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();
});
});
@@ -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}
}
]
}