mirror of
https://github.com/MikroWizard/mikrofront.git
synced 2025-12-07 10:39:29 +00:00
feat: redesign backups management interface
- Complete UI/UX redesign of backups module - Improved backup viewing and management - Enhanced backup operations interface - Better backup history and restoration
This commit is contained in:
parent
069e53cec6
commit
07808822f7
4 changed files with 298 additions and 92 deletions
|
|
@ -2,58 +2,97 @@
|
|||
<c-col xs>
|
||||
<c-card class="mb-4">
|
||||
<c-card-header>
|
||||
<c-row>
|
||||
<c-col xs [lg]="8">
|
||||
Backups <c-badge color="warning" *ngIf="devid!=0">Filtered Result For Device ID {{devid}}</c-badge>
|
||||
<c-row class="align-items-center">
|
||||
<c-col xs [lg]="6">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="mb-0 me-3"><i class="fa-solid fa-database me-2"></i>Backups</h5>
|
||||
<c-badge color="warning" *ngIf="devid!=0" class="fs-6">
|
||||
<i class="fa-solid fa-filter me-1"></i>Device ID {{devid}}
|
||||
</c-badge>
|
||||
</div>
|
||||
</c-col>
|
||||
<c-col xs [lg]="3">
|
||||
<c-row>
|
||||
<c-col>
|
||||
<ng-container *ngIf="compareitems.length>0">
|
||||
<div>
|
||||
<c-badge color="dark" style="font-size: 0.7rem;"
|
||||
*ngFor="let item of compareitems;index as i">{{item.id}}:{{item.devname}} {{item.createdC}} <span
|
||||
style="cursor: pointer;" (click)="delete_compare(i)">X</span></c-badge>
|
||||
<c-col xs [lg]="6">
|
||||
<div class="d-flex justify-content-end align-items-center gap-2">
|
||||
<!-- Compare Selection Panel -->
|
||||
<div *ngIf="compareitems.length > 0" class="compare-panel me-2">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<c-badge color="info" class="fs-6">
|
||||
<i class="fa-solid fa-code-compare me-1"></i>{{compareitems.length}} Selected
|
||||
</c-badge>
|
||||
<div class="selected-items d-flex gap-1">
|
||||
<c-badge color="secondary" *ngFor="let item of compareitems; index as i"
|
||||
class="selected-item d-flex align-items-center">
|
||||
<span class="me-1">{{item.devname}}</span>
|
||||
<i class="fa-solid fa-times cursor-pointer" (click)="delete_compare(i)"
|
||||
title="Remove from comparison"></i>
|
||||
</c-badge>
|
||||
</div>
|
||||
</ng-container>
|
||||
</c-col>
|
||||
<c-col style="padding: 0;">
|
||||
<button *ngIf="compareitems.length>1" (click)="start_compare()" cButton class="me-1"
|
||||
color="primary">Compare</button>
|
||||
</c-col>
|
||||
</c-row>
|
||||
</c-col>
|
||||
<c-col styyle="border-left: 1px solid #ccc;" xs [lg]="1">
|
||||
<button (click)="toggleCollapse()" cButton class="me-1" color="primary"><i
|
||||
class="fa-solid fa-filter mr-1"></i>Filter</button>
|
||||
<button *ngIf="compareitems.length > 1" (click)="start_compare()"
|
||||
cButton color="success" size="sm">
|
||||
<i class="fa-solid fa-code-compare me-1"></i>Compare
|
||||
</button>
|
||||
<button (click)="clearAllCompare()" cButton color="secondary" size="sm" variant="outline">
|
||||
<i class="fa-solid fa-trash me-1"></i>Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Filter Toggle -->
|
||||
<button (click)="toggleCollapse()" cButton color="primary" variant="outline">
|
||||
<i class="fa-solid fa-filter me-1"></i>Filters
|
||||
<i class="fa-solid" [class]="filters_visible ? 'fa-chevron-up' : 'fa-chevron-down'"
|
||||
style="margin-left: 0.5rem; font-size: 0.8rem;"></i>
|
||||
</button>
|
||||
</div>
|
||||
</c-col>
|
||||
</c-row>
|
||||
</c-card-header>
|
||||
<c-card-body>
|
||||
<c-row>
|
||||
<div [visible]="filters_visible" cCollapse>
|
||||
<c-col xs [lg]="12" class="example-form">
|
||||
<mat-form-field>
|
||||
<mat-label>Start date</mat-label>
|
||||
<input matInput [matDatepicker]="picker1" (dateChange)="reinitgrid('start',$event)"
|
||||
[(ngModel)]="filters['start_time']" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker1"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker1></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>End date</mat-label>
|
||||
<input matInput [matDatepicker]="picker2" (dateChange)="reinitgrid('end',$event)"
|
||||
[(ngModel)]="filters['end_time']" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker2"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker2></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="ispro">
|
||||
<mat-label>Config search</mat-label>
|
||||
<input (ngModelChange)="reinitgrid('search',$event)" [(ngModel)]="filters['search']" matInput>
|
||||
</mat-form-field>
|
||||
</c-col>
|
||||
</div>
|
||||
</c-row>
|
||||
<!-- Enhanced Filter Panel -->
|
||||
<div [visible]="filters_visible" cCollapse class="mb-3">
|
||||
<c-card class="border-0 bg-light">
|
||||
<c-card-body class="py-3">
|
||||
<c-row class="g-3 align-items-end">
|
||||
<c-col xs="12" md="4">
|
||||
<div class="filter-group">
|
||||
<label class="form-label fw-semibold mb-2">
|
||||
<i class="fa-solid fa-calendar-days me-1 text-primary"></i>Start Date
|
||||
</label>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<input matInput [matDatepicker]="picker1" (dateChange)="reinitgrid('start',$event)"
|
||||
[(ngModel)]="filters['start_time']" placeholder="Select start date" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker1"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker1></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</c-col>
|
||||
<c-col xs="12" md="4">
|
||||
<div class="filter-group">
|
||||
<label class="form-label fw-semibold mb-2">
|
||||
<i class="fa-solid fa-calendar-days me-1 text-primary"></i>End Date
|
||||
</label>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<input matInput [matDatepicker]="picker2" (dateChange)="reinitgrid('end',$event)"
|
||||
[(ngModel)]="filters['end_time']" placeholder="Select end date" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker2"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker2></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</c-col>
|
||||
<c-col xs="12" md="4" *ngIf="ispro">
|
||||
<div class="filter-group">
|
||||
<label class="form-label fw-semibold mb-2">
|
||||
<i class="fa-solid fa-magnifying-glass me-1 text-primary"></i>Config Search
|
||||
</label>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<input (ngModelChange)="reinitgrid('search',$event)" [(ngModel)]="filters['search']"
|
||||
matInput placeholder="Search in configurations...">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</c-col>
|
||||
</c-row>
|
||||
</c-card-body>
|
||||
</c-card>
|
||||
</div>
|
||||
<gui-grid [source]="source" [paging]="paging" [columnMenu]="columnMenu" [sorting]="sorting"
|
||||
[infoPanel]="infoPanel" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
|
||||
[autoResizeWidth]=true>
|
||||
|
|
@ -87,13 +126,29 @@
|
|||
{{value}}
|
||||
</ng-template>
|
||||
</gui-grid-column>
|
||||
<gui-grid-column header="Action" field="id">
|
||||
<gui-grid-column header="Actions" field="id" width="280">
|
||||
<ng-template let-value="item.id" let-item="item" let-index="index">
|
||||
<button cButton [disabled]="backuploading" color="info" size="sm" (click)="ShowBackup(item)" class="mx-1">
|
||||
<i *ngIf="backuploading" style="margin: 1px 5px;color:#ffffff;" class="fa-solid fa-spinner fa-spin"></i>
|
||||
<i *ngIf="!backuploading" style="margin: 1px 5px;color:#ffffff;" class="fa-solid fa-eye"></i>Show backup</button>
|
||||
<button *ngIf="ispro" cButton color="info" size="sm" (click)="add_for_compare(item)" class="mx-1"><i
|
||||
style="margin: 1px 5px;color:#ffffff;" class="fa-solid fa-eye"></i>Compare</button>
|
||||
<div class="d-flex gap-1">
|
||||
<button cButton [disabled]="backuploading" color="primary" size="sm"
|
||||
(click)="ShowBackup(item)" variant="outline" title="View backup content">
|
||||
<i *ngIf="backuploading && currentBackup?.id === item.id"
|
||||
class="fa-solid fa-spinner fa-spin me-1"></i>
|
||||
<i *ngIf="!backuploading || currentBackup?.id !== item.id"
|
||||
class="fa-solid fa-eye me-1"></i>
|
||||
View
|
||||
</button>
|
||||
<button *ngIf="ispro" cButton color="info" size="sm" variant="outline"
|
||||
(click)="add_for_compare(item)" title="Add to comparison"
|
||||
[disabled]="isInCompareList(item)">
|
||||
<i class="fa-solid fa-code-compare me-1"></i>
|
||||
{{isInCompareList(item) ? 'Added' : 'Compare'}}
|
||||
</button>
|
||||
<button *ngIf="ispro" cButton color="warning" size="sm" variant="outline"
|
||||
(click)="restore_backup(false, false, item)" title="Restore this backup">
|
||||
<i class="fa-solid fa-rotate-left me-1"></i>
|
||||
Restore
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</gui-grid-column>
|
||||
</gui-grid>
|
||||
|
|
@ -164,31 +219,80 @@
|
|||
</c-modal-footer>
|
||||
</c-modal>
|
||||
|
||||
<!-- Initial Restore Confirmation -->
|
||||
<c-modal #ConfirmModal backdrop="static" [(visible)]="ConfirmModalVisible" id="runConfirmModal">
|
||||
<c-modal-header>
|
||||
<h6 cModalTitle>Please Confirm Action </h6>
|
||||
<c-modal-header class="bg-warning text-dark">
|
||||
<h5 cModalTitle><i class="fa-solid fa-exclamation-triangle me-2"></i>Confirm Backup Restore</h5>
|
||||
<button [cModalToggle]="ConfirmModal.id" cButtonClose></button>
|
||||
</c-modal-header>
|
||||
<c-modal-body>
|
||||
<span>restore backup ?</span>
|
||||
<ng-container>
|
||||
Are you sure that You want to <code style="padding: 0!important;">Restore this configuration</code> on
|
||||
device?<br />
|
||||
<hr>
|
||||
<p class="text-danger">
|
||||
All Current device configuration will be reset:<br /><br />
|
||||
* All state data/history on router will be reset<br />
|
||||
* All other local users on router will be deleted<br />
|
||||
* After restore the password of the local user will be same as configured in MikroWizard<br />
|
||||
</p>
|
||||
</ng-container>
|
||||
<c-modal-body class="p-4">
|
||||
<div class="text-center mb-3">
|
||||
<i class="fa-solid fa-rotate-left fa-3x text-warning mb-3"></i>
|
||||
<h6>Restore Configuration Backup</h6>
|
||||
</div>
|
||||
|
||||
<div class="backup-info bg-light p-3 rounded mb-3">
|
||||
<h6 class="mb-2"><i class="fa-solid fa-info-circle me-2 text-primary"></i>Backup Details</h6>
|
||||
<div class="row">
|
||||
<div class="col-6"><strong>Device:</strong> {{currentBackup?.devname}}</div>
|
||||
<div class="col-6"><strong>IP:</strong> {{currentBackup?.devip}}</div>
|
||||
<div class="col-6"><strong>Date:</strong> {{currentBackup?.createdC}}</div>
|
||||
<div class="col-6"><strong>Size:</strong> {{currentBackup?.filesize}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<c-alert color="danger" class="mb-0">
|
||||
<h6 class="alert-heading"><i class="fa-solid fa-warning me-2"></i>Critical Warning</h6>
|
||||
<p class="mb-2">This action will completely reset the device configuration:</p>
|
||||
<ul class="mb-0">
|
||||
<li>All current device configuration will be overwritten</li>
|
||||
<li>All state data and history on router will be reset</li>
|
||||
<li>All other local users on router will be deleted</li>
|
||||
<li>Device will reboot and apply the restored configuration</li>
|
||||
</ul>
|
||||
</c-alert>
|
||||
</c-modal-body>
|
||||
<c-modal-footer>
|
||||
<button *ngIf="ispro" (click)="restore_backup(true)" cButton color="info">
|
||||
Restore this
|
||||
<c-modal-footer class="d-flex justify-content-between">
|
||||
<button cButton [cModalToggle]="ConfirmModal.id" color="secondary">
|
||||
<i class="fa-solid fa-times me-1"></i>Cancel
|
||||
</button>
|
||||
<button cButton [cModalToggle]="ConfirmModal.id" color="info">
|
||||
Cancel
|
||||
<button *ngIf="ispro" (click)="restore_backup(true, false)" cButton color="warning">
|
||||
<i class="fa-solid fa-arrow-right me-1"></i>Continue to Final Confirmation
|
||||
</button>
|
||||
</c-modal-footer>
|
||||
</c-modal>
|
||||
|
||||
<!-- Critical Double Confirmation -->
|
||||
<c-modal #CriticalConfirmModal backdrop="static" [(visible)]="CriticalConfirmModalVisible" id="CriticalConfirmModal">
|
||||
<c-modal-header class="bg-danger text-white">
|
||||
<h5 cModalTitle><i class="fa-solid fa-skull-crossbones me-2"></i>CRITICAL: Final Confirmation Required</h5>
|
||||
</c-modal-header>
|
||||
<c-modal-body class="p-4">
|
||||
<div class="text-center mb-4">
|
||||
<i class="fa-solid fa-exclamation-triangle fa-4x text-danger mb-3"></i>
|
||||
<h5 class="text-danger">DESTRUCTIVE ACTION</h5>
|
||||
<p class="mb-0">You are about to permanently overwrite device configuration</p>
|
||||
</div>
|
||||
|
||||
<div class="confirmation-box bg-light border border-danger p-3 rounded">
|
||||
<p class="mb-3 fw-bold">To proceed with this critical action, type <code>CONFIRM</code> in the box below:</p>
|
||||
<input cFormControl [(ngModel)]="confirmationText" placeholder="Type CONFIRM to proceed"
|
||||
class="form-control text-center fw-bold" style="letter-spacing: 2px;" />
|
||||
</div>
|
||||
|
||||
<c-alert color="info" class="mt-3 mb-0">
|
||||
<small><i class="fa-solid fa-info-circle me-1"></i>
|
||||
This confirmation ensures you understand the critical nature of this operation.
|
||||
</small>
|
||||
</c-alert>
|
||||
</c-modal-body>
|
||||
<c-modal-footer class="d-flex justify-content-between">
|
||||
<button (click)="cancelCriticalRestore()" cButton color="secondary">
|
||||
<i class="fa-solid fa-times me-1"></i>Cancel
|
||||
</button>
|
||||
<button (click)="restore_backup(true, true)" cButton color="danger"
|
||||
[disabled]="confirmationText !== 'CONFIRM'">
|
||||
<i class="fa-solid fa-rotate-left me-1"></i>RESTORE BACKUP
|
||||
</button>
|
||||
</c-modal-footer>
|
||||
</c-modal>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,72 @@
|
|||
@import 'ngx-diff/styles/default-theme';
|
||||
|
||||
::ng-deep .modal-xl {
|
||||
--cui-modal-width: 90vw!important;
|
||||
}
|
||||
|
||||
::ng-deep pre {
|
||||
display: block!important;
|
||||
}
|
||||
|
||||
// Enhanced UI Styles
|
||||
.compare-panel {
|
||||
background: rgba(13, 110, 253, 0.1);
|
||||
border: 1px solid rgba(13, 110, 253, 0.2);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
font-size: 0.75rem;
|
||||
|
||||
.fa-times {
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
.form-label {
|
||||
font-size: 0.875rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field {
|
||||
.mat-mdc-text-field-wrapper {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirmation-box {
|
||||
input {
|
||||
font-size: 1.1rem;
|
||||
|
||||
&:focus {
|
||||
border-color: #dc3545;
|
||||
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons spacing
|
||||
.d-flex.gap-1 {
|
||||
gap: 0.25rem !important;
|
||||
}
|
||||
|
||||
// Backup info styling
|
||||
.backup-info {
|
||||
.row > div {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,11 +30,13 @@ export class BackupsComponent implements OnInit {
|
|||
public codeForHighlightAuto: string = "";
|
||||
public ispro: boolean = false;
|
||||
public ConfirmModalVisible: boolean = false;
|
||||
public CriticalConfirmModalVisible: boolean = false;
|
||||
public CompareModalVisible: boolean = false;
|
||||
public compareitems:any=[];
|
||||
public comparecontents:any=[];
|
||||
public compare_type="unified";
|
||||
public copy_msg:boolean=false;
|
||||
public confirmationText: string = '';
|
||||
|
||||
constructor(
|
||||
private data_provider: dataProvider,
|
||||
|
|
@ -186,31 +188,55 @@ export class BackupsComponent implements OnInit {
|
|||
this.filters_visible = !this.filters_visible;
|
||||
}
|
||||
|
||||
restore_backup(apply:boolean=false){
|
||||
var _slef=this;
|
||||
if (!apply){
|
||||
restore_backup(apply: boolean = false, doubleConfirmed: boolean = false, backup?: any) {
|
||||
var _self = this;
|
||||
|
||||
// Set current backup if provided
|
||||
if (backup) {
|
||||
this.currentBackup = backup;
|
||||
}
|
||||
|
||||
if (!apply) {
|
||||
// Step 1: Show initial confirmation
|
||||
this.ConfirmModalVisible = true;
|
||||
return;
|
||||
}
|
||||
if (!this.currentBackup)
|
||||
|
||||
if (!this.currentBackup) {
|
||||
return;
|
||||
if(apply){
|
||||
_slef.ConfirmModalVisible = false;
|
||||
_slef.BakcupModalVisible = true;
|
||||
this.show_toast('Success', 'Backup restored successfully', 'success')
|
||||
this.show_toast('Info', 'Wait for the router to reboot and apply config', 'info')
|
||||
}
|
||||
|
||||
if (apply && !doubleConfirmed) {
|
||||
// Step 2: Show critical confirmation
|
||||
this.ConfirmModalVisible = false;
|
||||
this.CriticalConfirmModalVisible = true;
|
||||
this.confirmationText = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (apply && doubleConfirmed) {
|
||||
// Step 3: Execute restore
|
||||
_self.CriticalConfirmModalVisible = false;
|
||||
_self.BakcupModalVisible = false;
|
||||
|
||||
this.data_provider.restore_backup(this.currentBackup.id).then((res) => {
|
||||
if ('status' in res){
|
||||
if(res['status']=='success'){
|
||||
this.show_toast('Success', 'Backup restored successfully', 'success')
|
||||
this.show_toast('Info', 'Wait for the router to reboot and apply config', 'info')
|
||||
if ('status' in res) {
|
||||
if (res['status'] == 'success') {
|
||||
this.show_toast('Success', 'Backup restored successfully', 'success');
|
||||
this.show_toast('Info', 'Wait for the router to reboot and apply config', 'info');
|
||||
} else {
|
||||
this.show_toast('Error', 'Error restoring backup', 'danger');
|
||||
}
|
||||
else
|
||||
this.show_toast('Error', 'Error restoring backup', 'danger')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancelCriticalRestore() {
|
||||
this.CriticalConfirmModalVisible = false;
|
||||
this.confirmationText = '';
|
||||
this.currentBackup = null;
|
||||
}
|
||||
|
||||
start_compare(){
|
||||
var _self=this;
|
||||
|
|
@ -243,10 +269,20 @@ export class BackupsComponent implements OnInit {
|
|||
this.compareitems.push(item);
|
||||
}
|
||||
}
|
||||
delete_compare(i:number){
|
||||
//delete item index i from compareitems
|
||||
this.compareitems.splice(i,1);
|
||||
|
||||
delete_compare(i: number) {
|
||||
// Delete item index i from compareitems
|
||||
this.compareitems.splice(i, 1);
|
||||
}
|
||||
|
||||
clearAllCompare() {
|
||||
// Clear all compare items
|
||||
this.compareitems = [];
|
||||
this.comparecontents = [];
|
||||
}
|
||||
|
||||
isInCompareList(item: any): boolean {
|
||||
// Check if item is already in compare list
|
||||
return this.compareitems.some((compareItem: any) => compareItem.id === item.id);
|
||||
}
|
||||
reinitgrid(field: string, $event: any) {
|
||||
if (field == "start") this.filters["start_time"] = $event.target.value;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
ModalModule,
|
||||
FormModule,
|
||||
ToastModule,
|
||||
AlertModule,
|
||||
} from "@coreui/angular";
|
||||
|
||||
import { BackupsRoutingModule } from "./backups-routing.module";
|
||||
|
|
@ -34,10 +35,10 @@ import { ClipboardModule } from "@angular/cdk/clipboard";
|
|||
FormModule,
|
||||
FormsModule,
|
||||
ButtonModule,
|
||||
ButtonModule,
|
||||
GuiGridModule,
|
||||
CollapseModule,
|
||||
BadgeModule,
|
||||
AlertModule,
|
||||
Highlight,
|
||||
HighlightAuto,
|
||||
HighlightLineNumbers,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue