ci/cd + any fixes

This commit is contained in:
2026-04-24 19:58:29 +03:00
parent 857dbce662
commit 82cae4e556
17 changed files with 3369 additions and 8049 deletions
+32
View File
@@ -0,0 +1,32 @@
hetzner
user:root
password:Lio$ha84
domen name:
https://www.spaceship.com/application/domain-list-application/
login:liosha84
password:Lio$ha84
----smtp-----
SMTP Server
smtp-relay.brevo.com
Port
587
Login
a8cae8001@smtp-brevo.com
Сервис,Адрес,Роль в созидании
Caddy,jambotron.com,Вратарь и защитник (SSL)
Portainer,manage.jambotron.com,Твои «руки» (управление Docker)
Grafana,jambotron.com/monitor,Твои «глаза» (метрики ресурсов)
Gitea,gitea.jambotron.com,Твоя «память» (хранилище кода)
---------
portainer
user:admin
password:Z*N3p8&iI,a70"ER45GT9}fBAu2/^Q)C
+9 -7
View File
@@ -15,11 +15,12 @@
"prefix": "app", "prefix": "app",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular/build:application",
"options": { "options": {
"outputPath": "dist/jambotron-ui", "outputPath": {
"base": "dist/jambotron-ui"
},
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts",
"polyfills": [ "polyfills": [
"zone.js" "zone.js"
], ],
@@ -67,7 +68,8 @@
"node_modules/jquery/dist/jquery.js", "node_modules/jquery/dist/jquery.js",
"node_modules/bootstrap-markdown/js/bootstrap-markdown.js" "node_modules/bootstrap-markdown/js/bootstrap-markdown.js"
] ],
"browser": "src/main.ts"
}, },
"configurations": { "configurations": {
@@ -108,20 +110,20 @@
"defaultConfiguration": "production" "defaultConfiguration": "production"
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular/build:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"buildTarget": "jambotron-ui:build:production" "buildTarget": "jambotron-ui:build:production"
}, },
"development": { "development": {
"disableHostCheck": true, "allowedHosts": ["all"],
"buildTarget": "jambotron-ui:build:development" "buildTarget": "jambotron-ui:build:development"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n" "builder": "@angular/build:extract-i18n"
}, },
"test": { "test": {
"builder": "@angular/build:karma", "builder": "@angular/build:karma",
+3009 -7726
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -13,17 +13,17 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^20.0.4", "@angular/animations": "^21.2.9",
"@angular/cdk": "^20.1.6", "@angular/cdk": "^20.1.6",
"@angular/common": "^20.0.0", "@angular/common": "^21.2.9",
"@angular/compiler": "^20.0.0", "@angular/compiler": "^21.2.9",
"@angular/core": "^20.0.0", "@angular/core": "^21.2.9",
"@angular/forms": "^20.0.0", "@angular/forms": "^21.2.9",
"@angular/material": "^20.1.6", "@angular/material": "^20.1.6",
"@angular/platform-browser": "^20.0.0", "@angular/platform-browser": "^21.2.9",
"@angular/platform-server": "^20.0.0", "@angular/platform-server": "^21.2.9",
"@angular/router": "^20.0.0", "@angular/router": "^21.2.9",
"@angular/ssr": "^20.0.2", "@angular/ssr": "^21.2.7",
"@material/material-color-utilities": "^0.3.0", "@material/material-color-utilities": "^0.3.0",
"@ng-bootstrap/ng-bootstrap": "19.0.0", "@ng-bootstrap/ng-bootstrap": "19.0.0",
"@popperjs/core": "2.11.8", "@popperjs/core": "2.11.8",
@@ -40,9 +40,9 @@
"zone.js": "~0.15.0" "zone.js": "~0.15.0"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^20.0.2", "@angular/build": "^21.2.7",
"@angular/cli": "^20.0.2", "@angular/cli": "^21.2.7",
"@angular/compiler-cli": "^20.0.0", "@angular/compiler-cli": "^21.2.9",
"@types/express": "^5.0.1", "@types/express": "^5.0.1",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
@@ -53,6 +53,6 @@
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.8.2" "typescript": "~5.9.3"
} }
} }
@@ -3,15 +3,15 @@
<mat-sidenav-container class="sidenav-container"> <mat-sidenav-container class="sidenav-container">
<!-- Folder Tree Sidenav --> <!-- Folder Tree Sidenav -->
<mat-sidenav #sidenav <mat-sidenav #sidenav
mode="side" mode="side"
[opened]="showTreeView" [opened]="showTreeView"
class="tree-sidenav"> class="tree-sidenav">
<app-file-system-tree-component <app-file-system-tree-component
(folderSelected)="onFolderSelected($event)" (folderSelected)="onFolderSelected($event)"
[baseUrl]="baseUrl" [baseUrl]="baseUrl"
> >
</app-file-system-tree-component> </app-file-system-tree-component>
</mat-sidenav> </mat-sidenav>
<!-- Main Content --> <!-- Main Content -->
@@ -22,7 +22,7 @@
<div class="title-section"> <div class="title-section">
<button mat-icon-button (click)="toggleView()" <button mat-icon-button (click)="toggleView()"
[matTooltip]="showTreeView ? 'Hide Tree' : 'Show Tree'"> [matTooltip]="showTreeView ? 'Hide Tree' : 'Show Tree'">
<mat-icon>{{ showTreeView ? 'view_list' : 'account_tree' }}</mat-icon> <mat-icon>{{ showTreeView ? 'view_list' : 'account_tree' }}</mat-icon>
</button> </button>
<!-- Breadcrumb Navigation --> <!-- Breadcrumb Navigation -->
@@ -34,29 +34,31 @@
Root Root
</button> </button>
</li> </li>
<li *ngFor="let segment of pathSegments; let i = index" class="breadcrumb-item"> @for (segment of pathSegments; track segment; let i = $index) {
<mat-icon>chevron_right</mat-icon> <li class="breadcrumb-item">
<button mat-button (click)="navigateToPath(i)" class="path-button"> <mat-icon>chevron_right</mat-icon>
{{ segment }} <button mat-button (click)="navigateToPath(i)" class="path-button">
</button> {{ segment }}
</li> </button>
</li>
}
</ol> </ol>
</nav> </nav>
<div class="header-actions"> <div class="header-actions">
<!-- Search Bar --> <!-- Search Bar -->
<!--<div class="search-container"> <!--<div class="search-container">
<mat-form-field appearance="outline" class="search-field"> <mat-form-field appearance="outline" class="search-field">
<mat-label>Search files...</mat-label> <mat-label>Search files...</mat-label>
<input matInput <input matInput
[(ngModel)]="searchQuery" [(ngModel)]="searchQuery"
(input)="onSearchInput($event)" (input)="onSearchInput($event)"
placeholder="Enter filename or extension"> placeholder="Enter filename or extension">
<mat-icon matSuffix>search</mat-icon> <mat-icon matSuffix>search</mat-icon>
<button *ngIf="searchQuery" <button *ngIf="searchQuery"
mat-icon-button mat-icon-button
matSuffix matSuffix
(click)="clearSearch()" (click)="clearSearch()"
matTooltip="Clear search"> matTooltip="Clear search">
<mat-icon>clear</mat-icon> <mat-icon>clear</mat-icon>
</button> </button>
</mat-form-field> </mat-form-field>
@@ -64,134 +66,144 @@
</div>--> </div>-->
<input #fileInput type="file" multiple style="display: none" <input #fileInput type="file" multiple style="display: none"
(change)="onFilesSelected($event)"> (change)="onFilesSelected($event)">
<button mat-icon-button <button mat-icon-button
color="primary" color="primary"
[matTooltip]="'Upload Files'" [matTooltip]="'Upload Files'"
(click)="fileInput.click()"> (click)="fileInput.click()">
<mat-icon>upload</mat-icon> <mat-icon>upload</mat-icon>
</button> </button>
<button mat-icon-button <button mat-icon-button
color="accent" color="accent"
[matTooltip]="'New Folder'" [matTooltip]="'New Folder'"
(click)="openCreateFolderDialog()"> (click)="openCreateFolderDialog()">
<mat-icon>create_new_folder</mat-icon> <mat-icon>create_new_folder</mat-icon>
</button> </button>
</div>
</div> </div>
</div>
</mat-card-title> </mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<!-- Progress Bar --> <!-- Progress Bar -->
<mat-progress-bar *ngIf="loading" mode="indeterminate"></mat-progress-bar> @if (loading) {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
}
<!-- Upload Progress --> <!-- Upload Progress -->
<div *ngIf="uploading" class="upload-progress"> @if (uploading) {
<mat-progress-bar mode="determinate" [value]="uploadProgress"></mat-progress-bar> <div class="upload-progress">
<span class="progress-text">Uploading... {{uploadProgress}}%</span> <mat-progress-bar mode="determinate" [value]="uploadProgress"></mat-progress-bar>
</div> <span class="progress-text">Uploading... {{uploadProgress}}%</span>
</div>
}
<!-- File Table --> <!-- File Table -->
<div class="table-container"> <div class="table-container">
<table mat-table [dataSource]="dataSource" class="file-table"> <table mat-table [dataSource]="dataSource" class="file-table">
<!-- Selection Column --> <!-- Selection Column -->
<ng-container matColumnDef="select"> <ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef> <th mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="masterToggle()" <mat-checkbox (change)="masterToggle()"
[checked]="selection.size > 0 && isAllSelected()" [checked]="selection.size > 0 && isAllSelected()"
[indeterminate]="selection.size > 0 && !isAllSelected()"> [indeterminate]="selection.size > 0 && !isAllSelected()">
</mat-checkbox> </mat-checkbox>
</th> </th>
<td mat-cell *matCellDef="let file"> <td mat-cell *matCellDef="let file">
<mat-checkbox (change)="toggleSelection(file)" <mat-checkbox (change)="toggleSelection(file)"
[checked]="isSelected(file)"> [checked]="isSelected(file)">
</mat-checkbox> </mat-checkbox>
</td> </td>
</ng-container> </ng-container>
<!-- Icon Column --> <!-- Icon Column -->
<ng-container matColumnDef="icon"> <ng-container matColumnDef="icon">
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let file" class="icon-cell"> <td mat-cell *matCellDef="let file" class="icon-cell">
<mat-icon [class.folder-icon]="file.type === 'directory'"> <mat-icon [class.folder-icon]="file.type === 'directory'">
{{ getFileIcon(file) }} {{ getFileIcon(file) }}
</mat-icon> </mat-icon>
</td> </td>
</ng-container> </ng-container>
<!-- Name Column --> <!-- Name Column -->
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th> <th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let file" <td mat-cell *matCellDef="let file"
(dblclick)="onFileDoubleClick(file)" (dblclick)="onFileDoubleClick(file)"
class="name-cell" class="name-cell"
[class.clickable]="file.type === 'directory'"> [class.clickable]="file.type === 'directory'">
{{ file.name }} {{ file.name }}
</td> </td>
</ng-container> </ng-container>
<!-- Size Column --> <!-- Size Column -->
<ng-container matColumnDef="size"> <ng-container matColumnDef="size">
<th mat-header-cell *matHeaderCellDef>Size</th> <th mat-header-cell *matHeaderCellDef>Size</th>
<td mat-cell *matCellDef="let file"> <td mat-cell *matCellDef="let file">
{{ file.type === 'directory' ? '-' : formatFileSize(file.size) }} {{ file.type === 'directory' ? '-' : formatFileSize(file.size) }}
</td> </td>
</ng-container> </ng-container>
<!-- Last Modified Column --> <!-- Last Modified Column -->
<ng-container matColumnDef="lastModified"> <ng-container matColumnDef="lastModified">
<th mat-header-cell *matHeaderCellDef>Last Modified</th> <th mat-header-cell *matHeaderCellDef>Last Modified</th>
<td mat-cell *matCellDef="let file"> <td mat-cell *matCellDef="let file">
{{ file.lastModified | date:'short' }} {{ file.lastModified | date:'short' }}
</td> </td>
</ng-container> </ng-container>
<!-- Actions Column --> <!-- Actions Column -->
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> <th mat-header-cell *matHeaderCellDef>
<button *ngIf="selection.size > 0" @if (selection.size > 0) {
mat-icon-button <button
color="warn" mat-icon-button
(click)="deleteSelected()" color="warn"
matTooltip="Delete Selected"> (click)="deleteSelected()"
<mat-icon>delete</mat-icon> matTooltip="Delete Selected">
</button> <mat-icon>delete</mat-icon>
</th> </button>
<td mat-cell *matCellDef="let file"> }
<button *ngIf="file.type === 'file'" </th>
mat-icon-button <td mat-cell *matCellDef="let file">
(click)="downloadFile(file)" @if (file.type === 'file') {
matTooltip="Download"> <button
<mat-icon>download</mat-icon> mat-icon-button
</button> (click)="downloadFile(file)"
<button mat-icon-button matTooltip="Download">
(click)="deleteFile(file)" <mat-icon>download</mat-icon>
color="warn" </button>
matTooltip="Delete"> }
<mat-icon>delete</mat-icon> <button mat-icon-button
</button> (click)="deleteFile(file)"
</td> color="warn"
</ng-container> matTooltip="Delete">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table> </table>
</div> </div>
<!-- Empty State --> <!-- Empty State -->
<div *ngIf="!loading && dataSource.data.length === 0" class="empty-state"> @if (!loading && dataSource.data.length === 0) {
<mat-icon>{{ searchQuery ? 'search_off' : 'folder_open' }}</mat-icon> <div class="empty-state">
<h3>{{ searchQuery ? 'No results found' : 'This folder is empty' }}</h3> <mat-icon>{{ searchQuery ? 'search_off' : 'folder_open' }}</mat-icon>
<p>{{ searchQuery ? 'Try adjusting your search terms' : 'Upload files or create folders to get started.' }}</p> <h3>{{ searchQuery ? 'No results found' : 'This folder is empty' }}</h3>
</div> <p>{{ searchQuery ? 'Try adjusting your search terms' : 'Upload files or create folders to get started.' }}</p>
</mat-card-content> </div>
</mat-card> }
</mat-sidenav-content> </mat-card-content>
</mat-sidenav-container> </mat-card>
</div> </mat-sidenav-content>
</mat-sidenav-container>
</div>
@@ -1,5 +1,5 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnInit, ViewChild} from '@angular/core'; import {Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnInit, ViewChild} from '@angular/core';
import {DatePipe, NgForOf, NgIf} from '@angular/common'; import { DatePipe } from '@angular/common';
import { import {
MatCell, MatCell,
MatCellDef, MatColumnDef, MatCellDef, MatColumnDef,
@@ -29,7 +29,6 @@ import {FileSystemTreeComponent} from '../file-system-tree-component/file-system
@Component({ @Component({
selector: 'app-file-browser-with-tree-component', selector: 'app-file-browser-with-tree-component',
imports: [ imports: [
NgIf,
MatIcon, MatIcon,
MatHeaderRowDef, MatHeaderRowDef,
MatRowDef, MatRowDef,
@@ -45,10 +44,7 @@ import {FileSystemTreeComponent} from '../file-system-tree-component/file-system
MatCheckbox, MatCheckbox,
MatTable, MatTable,
MatProgressBar, MatProgressBar,
FormsModule, FormsModule,
NgForOf,
MatButton, MatButton,
MatCardContent, MatCardContent,
MatSidenavContent, MatSidenavContent,
@@ -57,12 +53,9 @@ import {FileSystemTreeComponent} from '../file-system-tree-component/file-system
MatCardTitle, MatCardTitle,
MatSidenav, MatSidenav,
MatSidenavContainer, MatSidenavContainer,
DatePipe, DatePipe,
FileSystemTreeComponent, FileSystemTreeComponent
],
],
templateUrl: './file-browser-with-tree-component.html', templateUrl: './file-browser-with-tree-component.html',
styleUrl: './file-browser-with-tree-component.scss', styleUrl: './file-browser-with-tree-component.scss',
schemas:[CUSTOM_ELEMENTS_SCHEMA] schemas:[CUSTOM_ELEMENTS_SCHEMA]
@@ -15,7 +15,7 @@ import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatIconModule} from '@angular/material/icon'; import {MatIconModule} from '@angular/material/icon';
import {MatButtonModule} from '@angular/material/button'; import {MatButtonModule} from '@angular/material/button';
import {MatTreeModule} from '@angular/material/tree'; import {MatTreeModule} from '@angular/material/tree';
import {NgIf} from '@angular/common';
import {FileBrowserTreeService, FileItem} from '../../services/file-browser-tree.service'; import {FileBrowserTreeService, FileItem} from '../../services/file-browser-tree.service';
import {MatTooltip} from '@angular/material/tooltip'; import {MatTooltip} from '@angular/material/tooltip';
@@ -1,4 +1,4 @@
@import "~node_modules/cropperjs/dist/cropper.css"; @import "node_modules/cropperjs/dist/cropper.css";
.ngx-pe-overlay-container { .ngx-pe-overlay-container {
width: 100%; width: 100%;
@@ -11,7 +11,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import {Route, Router, RouterLink, RouterLinkActive} from '@angular/router'; import {Route, Router, RouterLink, RouterLinkActive} from '@angular/router';
import {MatListItem, MatListItemIcon, MatListItemTitle, MatNavList} from '@angular/material/list'; import {MatListItem, MatListItemIcon, MatListItemTitle, MatNavList} from '@angular/material/list';
import {NgForOf} from '@angular/common';
import {MatIconButton} from '@angular/material/button'; import {MatIconButton} from '@angular/material/button';
import {MatTooltip} from '@angular/material/tooltip'; import {MatTooltip} from '@angular/material/tooltip';
import {MatIcon} from '@angular/material/icon'; import {MatIcon} from '@angular/material/icon';
@@ -36,10 +36,8 @@ export interface NavItem {
RouterLink, RouterLink,
MatListItem, MatListItem,
MatNavList, MatNavList,
NgForOf, MatIconButton
MatIconButton, ],
],
templateUrl: './vertical-nav.component.html', templateUrl: './vertical-nav.component.html',
styleUrl: './vertical-nav.component.scss', styleUrl: './vertical-nav.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
@@ -1,8 +1,8 @@
<div class="json-dump-container" *ngIf="!loading; else loadingTpl"> @if (!loading) {
<h2>JSON Dump Management</h2> <div class="json-dump-container">
<h2>JSON Dump Management</h2>
<!-- Entity-specific Export/Import --> <!-- Entity-specific Export/Import -->
<!-- <div class="entity-actions" *ngFor="let entity of entityTypes"> <!-- <div class="entity-actions" *ngFor="let entity of entityTypes">
<h3>{{ entity | titlecase }}</h3> <h3>{{ entity | titlecase }}</h3>
<button mat-raised-button color="primary" (click)="exportEntity(entity)"> <button mat-raised-button color="primary" (click)="exportEntity(entity)">
<mat-icon>download</mat-icon> Export {{ entity }} <mat-icon>download</mat-icon> Export {{ entity }}
@@ -38,51 +38,48 @@
accept=".json" accept=".json"
(change)="onFileSelected($event, entity)" (change)="onFileSelected($event, entity)"
hidden hidden
/> />
</mat-toolbar-row> </mat-toolbar-row>
} }
</mat-toolbar> </mat-toolbar>
<!-- Files Table --> <!-- Files Table -->
<h3>Available JSON Files</h3> <h3>Available JSON Files</h3>
<mat-table [dataSource]="files"> <mat-table [dataSource]="files">
<ng-container matColumnDef="fileName"> <ng-container matColumnDef="fileName">
<mat-header-cell *matHeaderCellDef> File Name </mat-header-cell> <mat-header-cell *matHeaderCellDef> File Name </mat-header-cell>
<mat-cell *matCellDef="let file">{{ file.fileName }}</mat-cell> <mat-cell *matCellDef="let file">{{ file.fileName }}</mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="size">
<ng-container matColumnDef="size"> <mat-header-cell *matHeaderCellDef> Size </mat-header-cell>
<mat-header-cell *matHeaderCellDef> Size </mat-header-cell> <mat-cell *matCellDef="let file">{{ jsonDumpService.formatFileSize(file.size) }}</mat-cell>
<mat-cell *matCellDef="let file">{{ jsonDumpService.formatFileSize(file.size) }}</mat-cell> </ng-container>
</ng-container> <ng-container matColumnDef="lastModified">
<mat-header-cell *matHeaderCellDef> Last Modified </mat-header-cell>
<ng-container matColumnDef="lastModified"> <mat-cell *matCellDef="let file">{{ jsonDumpService.formatDate(file.lastModified) }}</mat-cell>
<mat-header-cell *matHeaderCellDef> Last Modified </mat-header-cell> </ng-container>
<mat-cell *matCellDef="let file">{{ jsonDumpService.formatDate(file.lastModified) }}</mat-cell> <ng-container matColumnDef="actions">
</ng-container> <mat-header-cell *matHeaderCellDef> Actions </mat-header-cell>
<mat-cell *matCellDef="let file">
<ng-container matColumnDef="actions"> <button mat-icon-button (click)="downloadFile(file.fileName)" matTooltip="Download">
<mat-header-cell *matHeaderCellDef> Actions </mat-header-cell> <mat-icon>download</mat-icon>
<mat-cell *matCellDef="let file"> </button>
<button mat-icon-button (click)="downloadFile(file.fileName)" matTooltip="Download"> <button mat-icon-button color="warn" (click)="deleteFile(file.fileName)" matTooltip="Delete">
<mat-icon>download</mat-icon> <mat-icon>delete</mat-icon>
</button> </button>
<button mat-icon-button color="warn" (click)="deleteFile(file.fileName)" matTooltip="Delete"> </mat-cell>
<mat-icon>delete</mat-icon> </ng-container>
</button> <mat-header-row *matHeaderRowDef="['fileName', 'size', 'lastModified', 'actions']"></mat-header-row>
</mat-cell> <mat-row *matRowDef="let row; columns: ['fileName', 'size', 'lastModified', 'actions']"></mat-row>
</ng-container> </mat-table>
<!-- Archives -->
<mat-header-row *matHeaderRowDef="['fileName', 'size', 'lastModified', 'actions']"></mat-header-row> <h3>Archives</h3>
<mat-row *matRowDef="let row; columns: ['fileName', 'size', 'lastModified', 'actions']"></mat-row> <mat-list>
</mat-table> @for (archive of archives; track archive) {
<mat-list-item>{{ archive }}</mat-list-item>
<!-- Archives --> }
<h3>Archives</h3> </mat-list>
<mat-list> </div>
<mat-list-item *ngFor="let archive of archives">{{ archive }}</mat-list-item> } @else {
</mat-list>
</div>
<ng-template #loadingTpl>
<mat-spinner></mat-spinner> <mat-spinner></mat-spinner>
</ng-template> }
@@ -12,7 +12,7 @@ import {
MatRowDef, MatTable MatRowDef, MatTable
} from '@angular/material/table'; } from '@angular/material/table';
import {MatIconButton} from '@angular/material/button'; import {MatIconButton} from '@angular/material/button';
import {NgForOf, NgIf, TitleCasePipe} from '@angular/common'; import { TitleCasePipe } from '@angular/common';
import {MatProgressSpinner} from '@angular/material/progress-spinner'; import {MatProgressSpinner} from '@angular/material/progress-spinner';
import {MatIcon} from '@angular/material/icon'; import {MatIcon} from '@angular/material/icon';
import {MatTooltip} from '@angular/material/tooltip'; import {MatTooltip} from '@angular/material/tooltip';
@@ -41,12 +41,10 @@ import {MatLabel} from '@angular/material/input';
MatCellDef, MatCellDef,
MatTable, MatTable,
TitleCasePipe, TitleCasePipe,
NgIf,
NgForOf,
MatToolbar, MatToolbar,
MatToolbarRow, MatToolbarRow,
MatLabel MatLabel
], ],
styleUrls: ['./json-dump.component.scss'], styleUrls: ['./json-dump.component.scss'],
schemas:[CUSTOM_ELEMENTS_SCHEMA] schemas:[CUSTOM_ELEMENTS_SCHEMA]
}) })
@@ -7,7 +7,7 @@ import {Subscription} from 'rxjs';
import {TokenStorageService} from '../../../services/token-storage.service'; import {TokenStorageService} from '../../../services/token-storage.service';
import {AuthService} from '../../../services/auth.service'; import {AuthService} from '../../../services/auth.service';
import {EventBusService} from '../../../_shared/event-bus.service'; import {EventBusService} from '../../../_shared/event-bus.service';
import {CommonModule} from '@angular/common';
import {MatIcon} from '@angular/material/icon'; import {MatIcon} from '@angular/material/icon';
import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu';
import {MatTooltip} from '@angular/material/tooltip'; import {MatTooltip} from '@angular/material/tooltip';
@@ -26,7 +26,6 @@ import {GlobalConstants} from '../../../global-constants';
MatButton, MatButton,
RouterLink, RouterLink,
RouterOutlet, RouterOutlet,
CommonModule,
MatIcon, MatIcon,
MatMiniFabButton, MatMiniFabButton,
MatMenuTrigger, MatMenuTrigger,
@@ -34,7 +33,7 @@ import {GlobalConstants} from '../../../global-constants';
MatMenuItem, MatMenuItem,
MatTooltip, MatTooltip,
MatLabel MatLabel
], ],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
templateUrl: './main.component.html', templateUrl: './main.component.html',
styleUrl: './main.component.scss' styleUrl: './main.component.scss'
@@ -6,33 +6,36 @@
<h3>Running Containers</h3> <h3>Running Containers</h3>
<button (click)="refreshContainers()" class="btn btn-secondary">Refresh</button> <button (click)="refreshContainers()" class="btn btn-secondary">Refresh</button>
<div class="container-list" *ngIf="containers.length > 0; else noContainers"> @if (containers.length > 0) {
<div class="container-item" *ngFor="let container of containers"> <div class="container-list">
<div class="container-info"> @for (container of containers; track container) {
<strong>{{container.name}}</strong> <div class="container-item">
<span class="image">{{container.image}}</span> <div class="container-info">
<span class="status" [class.running]="container.status.includes('Up')"> <strong>{{container.name}}</strong>
<span class="image">{{container.image}}</span>
<span class="status" [class.running]="container.status.includes('Up')">
{{container.status}} {{container.status}}
</span> </span>
</div> </div>
<div class="container-actions"> <div class="container-actions">
<button (click)="stopContainer(container.name)" <button (click)="stopContainer(container.name)"
[disabled]="!container.status.includes('Up')" [disabled]="!container.status.includes('Up')"
class="btn btn-warning">Stop</button> class="btn btn-warning">Stop</button>
<button (click)="startContainer(container.name)" <button (click)="startContainer(container.name)"
[disabled]="container.status.includes('Up')" [disabled]="container.status.includes('Up')"
class="btn btn-success">Start</button> class="btn btn-success">Start</button>
<button (click)="removeContainer(container.name)" <button (click)="removeContainer(container.name)"
class="btn btn-danger">Remove</button> class="btn btn-danger">Remove</button>
<button (click)="viewLogs(container.name)" <button (click)="viewLogs(container.name)"
class="btn btn-info">Logs</button> class="btn btn-info">Logs</button>
</div> </div>
</div>
}
</div> </div>
</div> } @else {
<ng-template #noContainers>
<p>No containers found</p> <p>No containers found</p>
</ng-template> }
</div> </div>
<!-- Run New Container --> <!-- Run New Container -->
@@ -42,68 +45,72 @@
<div class="form-group"> <div class="form-group">
<label>Image Name:</label> <label>Image Name:</label>
<input type="text" formControlName="imageName" <input type="text" formControlName="imageName"
placeholder="e.g., nginx:latest" class="form-control"> placeholder="e.g., nginx:latest" class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Container Name:</label> <label>Container Name:</label>
<input type="text" formControlName="containerName" <input type="text" formControlName="containerName"
placeholder="Optional container name" class="form-control"> placeholder="Optional container name" class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Ports:</label> <label>Ports:</label>
<input type="text" formControlName="ports" <input type="text" formControlName="ports"
placeholder="e.g., 8080:80" class="form-control"> placeholder="e.g., 8080:80" class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Environment Variables:</label> <label>Environment Variables:</label>
<input type="text" formControlName="environmentVars" <input type="text" formControlName="environmentVars"
placeholder="e.g., -e NODE_ENV=production" class="form-control"> placeholder="e.g., -e NODE_ENV=production" class="form-control">
</div> </div>
<button type="submit" [disabled]="runContainerForm.invalid || loading" <button type="submit" [disabled]="runContainerForm.invalid || loading"
class="btn btn-primary"> class="btn btn-primary">
{{loading ? 'Starting...' : 'Run Container'}} {{loading ? 'Starting...' : 'Run Container'}}
</button> </button>
</form> </form>
</div> </div>
<!-- Execute Command --> <!-- Execute Command -->
<div class="execute-command-section"> <div class="execute-command-section">
<h3>Execute Docker Command</h3> <h3>Execute Docker Command</h3>
<form [formGroup]="commandForm" (ngSubmit)="executeCommand()"> <form [formGroup]="commandForm" (ngSubmit)="executeCommand()">
<div class="form-group"> <div class="form-group">
<label>Docker Command:</label> <label>Docker Command:</label>
<input type="text" formControlName="command" <input type="text" formControlName="command"
placeholder="e.g., docker ps -a" class="form-control"> placeholder="e.g., docker ps -a" class="form-control">
</div> </div>
<button type="submit" [disabled]="commandForm.invalid || loading" <button type="submit" [disabled]="commandForm.invalid || loading"
class="btn btn-primary">Execute</button> class="btn btn-primary">Execute</button>
</form> </form>
</div> </div>
<!-- Command Output --> <!-- Command Output -->
<div class="output-section" *ngIf="lastCommandResult"> @if (lastCommandResult) {
<h3>Command Output</h3> <div class="output-section">
<div class="output-box" <h3>Command Output</h3>
[class.success]="lastCommandResult.success" <div class="output-box"
[class.error]="!lastCommandResult.success"> [class.success]="lastCommandResult.success"
<pre>{{getOutputText(lastCommandResult.output)}}</pre> [class.error]="!lastCommandResult.success">
</div> <pre>{{getOutputText(lastCommandResult.output)}}</pre>
</div> </div>
</div>
}
<!-- Container Logs Modal --> <!-- Container Logs Modal -->
<div class="logs-modal" *ngIf="selectedContainerLogs"> @if (selectedContainerLogs) {
<div class="modal-content"> <div class="logs-modal">
<div class="modal-header"> <div class="modal-content">
<h3>Container Logs: {{selectedContainer}}</h3> <div class="modal-header">
<button (click)="closeLogs()" class="close-btn">&times;</button> <h3>Container Logs: {{selectedContainer}}</h3>
</div> <button (click)="closeLogs()" class="close-btn">&times;</button>
<div class="modal-body"> </div>
<pre class="logs-content">{{selectedContainerLogs.join('\n')}}</pre> <div class="modal-body">
</div> <pre class="logs-content">{{selectedContainerLogs.join('\n')}}</pre>
</div> </div>
</div> </div>
</div> </div>
}
</div>
@@ -1,5 +1,5 @@
import {Component, OnDestroy, OnInit} from '@angular/core'; import {Component, OnDestroy, OnInit} from '@angular/core';
import {NgForOf, NgIf} from '@angular/common';
import {Subject, takeUntil} from 'rxjs'; import {Subject, takeUntil} from 'rxjs';
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {ContainerInfo, DockerResult, DockerService} from '../docker.service'; import {ContainerInfo, DockerResult, DockerService} from '../docker.service';
@@ -7,10 +7,8 @@ import {ContainerInfo, DockerResult, DockerService} from '../docker.service';
@Component({ @Component({
selector: 'app-docker-management-component', selector: 'app-docker-management-component',
imports: [ imports: [
NgIf, ReactiveFormsModule
ReactiveFormsModule, ],
NgForOf
],
templateUrl: './docker-management-component.html', templateUrl: './docker-management-component.html',
styleUrl: './docker-management-component.scss' styleUrl: './docker-management-component.scss'
}) })
@@ -3,6 +3,7 @@ export const environment = {
port: 8081, port: 8081,
production: true, production: true,
fromWeb: true, fromWeb: true,
host_name: 'https://jambotron.run.place', //host_name: 'https://jambotron.run.place',
host_name:'http://localhost',
title: 'JAMBOTRON.RUN.PLACE' title: 'JAMBOTRON.RUN.PLACE'
}; };
@@ -2,7 +2,7 @@ spring.config.import=classpath:/yml_properties/application_production.yml
spring.application.name=jambotron spring.application.name=jambotron
server.port=443 server.port=8081
#============Localhost Configurations======================== #============Localhost Configurations========================
spring.datasource.url= jdbc:postgresql://localhost:5432/jambotronDB spring.datasource.url= jdbc:postgresql://localhost:5432/jambotronDB
+2 -2
View File
@@ -1,4 +1,4 @@
#bootJar with profile prod #bootJar with profile prod
#spring.profiles.active=prod spring.profiles.active=prod
spring.profiles.active=dev #spring.profiles.active=dev