mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-17 02:00:56 +03:00
Added UI for managing tasks
Added ability to schedule tasks based on timestamp Fixed mismatched types between frontend and openapi yaml Simplified imports for several backend components
This commit is contained in:
77
src/app/components/tasks/tasks.component.html
Normal file
77
src/app/components/tasks/tasks.component.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<div [hidden]="!(tasks && tasks.length > 0)">
|
||||
<div style="overflow: hidden;" [ngClass]="'mat-elevation-z8'">
|
||||
<mat-table style="overflow: hidden" matSort [dataSource]="dataSource">
|
||||
<!-- Title Column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<span class="one-line" [matTooltip]="element.title ? element.title : null">
|
||||
{{element.title}}
|
||||
</span>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Last Ran Column -->
|
||||
<ng-container matColumnDef="last_ran">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Last ran">Last ran</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.last_ran">{{element.last_ran*1000 | date: 'short'}}</ng-container>
|
||||
<ng-container i18n="N/A" *ngIf="!element.last_ran">N/A</ng-container>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Last Confirmed Column -->
|
||||
<ng-container matColumnDef="last_confirmed">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Last confirmed">Last confirmed</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.last_confirmed">{{element.last_confirmed*1000 | date: 'short'}}</ng-container>
|
||||
<ng-container i18n="N/A" *ngIf="!element.last_confirmed">N/A</ng-container>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Status Column -->
|
||||
<ng-container matColumnDef="status">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Status">Status</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<span *ngIf="element.running || element.confirming"><mat-spinner matTooltip="Busy" i18n-matTooltip="Busy" [diameter]="25"></mat-spinner></span>
|
||||
<span *ngIf="!(element.running || element.confirming) && element.schedule">
|
||||
<ng-container i18n="Scheduled">Scheduled for</ng-container>
|
||||
{{element.next_invocation | date: 'short'}}<mat-icon style="font-size: 16px; text-align: center;" *ngIf="element.schedule.type === 'recurring'">repeat</mat-icon>
|
||||
</span>
|
||||
<span *ngIf="!(element.running || element.confirming) && !element.schedule">
|
||||
<ng-container i18n="Not scheduled">Not scheduled</ng-container>
|
||||
</span>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Actions">Actions</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<div>
|
||||
<ng-container *ngIf="element.data?.uids?.length > 0">
|
||||
<button (click)="confirmTask(element.key)" [disabled]="element.running || element.confirming" mat-stroked-button>
|
||||
<ng-container *ngIf="element.key == 'missing_files_check'" i18n="Clear missing files from DB">Clear missing files from DB:</ng-container>
|
||||
<ng-container *ngIf="element.key == 'duplicate_files_check'" i18n="Clear duplicate files from DB">Clear duplicate files from DB:</ng-container> {{element.data.uids.length}}
|
||||
</button>
|
||||
</ng-container>
|
||||
<button (click)="runTask(element.key)" [disabled]="element.running || element.confirming" mat-icon-button matTooltip="Run" i18n-matTooltip="Run"><mat-icon>play_arrow</mat-icon></button>
|
||||
<button (click)="scheduleTask(element)" mat-icon-button matTooltip="Schedule" i18n-matTooltip="Schedule"><mat-icon>schedule</mat-icon></button>
|
||||
</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator [pageSizeOptions]="[5, 10, 20]"
|
||||
showFirstLastButtons
|
||||
aria-label="Select page of tasks">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(!tasks || tasks.length === 0) && tasks_retrieved">
|
||||
<h4 style="text-align: center; margin-top: 10px;" i18n="No tasks label">No tasks available!</h4>
|
||||
</div>
|
||||
32
src/app/components/tasks/tasks.component.scss
Normal file
32
src/app/components/tasks/tasks.component.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
mat-header-cell, mat-cell {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.one-line {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.icon-button-spinner {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.downloads-action-button-div {
|
||||
margin-top: 10px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.rounded-top {
|
||||
border-radius: 16px 16px 0px 0px !important;
|
||||
}
|
||||
|
||||
.rounded-bottom {
|
||||
border-radius: 0px 0px 16px 16px !important;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 16px 16px 16px 16px !important;
|
||||
}
|
||||
25
src/app/components/tasks/tasks.component.spec.ts
Normal file
25
src/app/components/tasks/tasks.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TasksComponent } from './tasks.component';
|
||||
|
||||
describe('TasksComponent', () => {
|
||||
let component: TasksComponent;
|
||||
let fixture: ComponentFixture<TasksComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TasksComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TasksComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
109
src/app/components/tasks/tasks.component.ts
Normal file
109
src/app/components/tasks/tasks.component.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { UpdateTaskScheduleDialogComponent } from 'app/dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tasks',
|
||||
templateUrl: './tasks.component.html',
|
||||
styleUrls: ['./tasks.component.scss']
|
||||
})
|
||||
export class TasksComponent implements OnInit {
|
||||
|
||||
interval_id = null;
|
||||
tasks_check_interval = 1500;
|
||||
tasks = null;
|
||||
tasks_retrieved = false;
|
||||
|
||||
displayedColumns: string[] = ['title', 'last_ran', 'last_confirmed', 'status', 'actions'];
|
||||
dataSource = null;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
constructor(private postsService: PostsService, private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.postsService.initialized) {
|
||||
this.getTasksRecurring();
|
||||
} else {
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
this.getTasksRecurring();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.interval_id) { clearInterval(this.interval_id) }
|
||||
}
|
||||
|
||||
getTasksRecurring(): void {
|
||||
this.getTasks();
|
||||
this.interval_id = setInterval(() => {
|
||||
this.getTasks();
|
||||
}, this.tasks_check_interval);
|
||||
}
|
||||
|
||||
getTasks(): void {
|
||||
this.postsService.getTasks().subscribe(res => {
|
||||
if (this.tasks) {
|
||||
if (JSON.stringify(this.tasks) === JSON.stringify(res['tasks'])) return;
|
||||
for (const task of res['tasks']) {
|
||||
const task_index = this.tasks.map(t => t.key).indexOf(task['key']);
|
||||
this.tasks[task_index] = task;
|
||||
}
|
||||
this.dataSource = new MatTableDataSource<Task>(this.tasks);
|
||||
} else {
|
||||
this.tasks = res['tasks'];
|
||||
this.dataSource = new MatTableDataSource<Task>(this.tasks);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runTask(task_key: string): void {
|
||||
this.postsService.runTask(task_key).subscribe(res => {
|
||||
this.getTasks();
|
||||
});
|
||||
}
|
||||
|
||||
confirmTask(task_key: string): void {
|
||||
this.postsService.confirmTask(task_key).subscribe(res => {
|
||||
this.getTasks();
|
||||
});
|
||||
}
|
||||
|
||||
scheduleTask(task: any): void {
|
||||
// open dialog
|
||||
const dialogRef = this.dialog.open(UpdateTaskScheduleDialogComponent, {
|
||||
data: {
|
||||
task: task
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(schedule => {
|
||||
if (schedule || schedule === null) {
|
||||
this.postsService.updateTaskSchedule(task['key'], schedule).subscribe(res => {
|
||||
this.getTasks();
|
||||
console.log(res);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
key: string;
|
||||
title: string;
|
||||
last_ran: number;
|
||||
last_confirmed: number;
|
||||
running: boolean;
|
||||
confirming: boolean;
|
||||
data: unknown;
|
||||
}
|
||||
Reference in New Issue
Block a user