mirror of
https://github.com/dev-claw/vripper-project.git
synced 2026-08-19 08:35:41 +02:00
@@ -2,10 +2,12 @@
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>me.mnlr</groupId>
|
||||
|
||||
<groupId>me.vripper</groupId>
|
||||
<artifactId>vripper-web-ui</artifactId>
|
||||
<version>4.4.0</version>
|
||||
<version>5.0.0</version>
|
||||
<name>vripper-web-ui</name>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export class DownloadSpeed {
|
||||
constructor(public speed: number) {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class ErrorCount {
|
||||
constructor(public count: number) {}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export class GlobalState {
|
||||
constructor(
|
||||
public running: number,
|
||||
public remaining: number,
|
||||
public error: number,
|
||||
public loggedUser: string,
|
||||
public downloadSpeed: string
|
||||
) {}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
export class Image {
|
||||
constructor(
|
||||
public postId: string,
|
||||
public postId: number,
|
||||
public url: string,
|
||||
public status: string,
|
||||
public index: number,
|
||||
public current: number,
|
||||
public total: number
|
||||
public downloaded: number,
|
||||
public size: number
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export class PostItem {
|
||||
public title: string,
|
||||
public url: string,
|
||||
public hosts: [{ first: string; second: number }],
|
||||
public postId: string,
|
||||
public threadId: string
|
||||
public postId: number,
|
||||
public threadId: number
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export class Post {
|
||||
constructor(
|
||||
public postId: string,
|
||||
public postId: number,
|
||||
public postTitle: string,
|
||||
public status: string,
|
||||
public url: string,
|
||||
@@ -9,6 +9,7 @@ export class Post {
|
||||
public hosts: string[],
|
||||
public addedOn: string,
|
||||
public rank: number,
|
||||
public downloadDirectory: string
|
||||
public downloadDirectory: string,
|
||||
public downloaded: number
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export class QueueState {
|
||||
constructor(public running: number, public remaining: number) {}
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
export interface Settings {
|
||||
maxEventLog: number;
|
||||
connectionSettings: ConnectionSettings;
|
||||
downloadSettings: DownloadSettings;
|
||||
viperSettings: ViperSettings;
|
||||
systemSettings: SystemSettings;
|
||||
}
|
||||
|
||||
export interface ConnectionSettings {
|
||||
maxThreads: number;
|
||||
maxTotalThreads: number;
|
||||
maxConcurrentPerHost: number;
|
||||
maxGlobalConcurrent: number;
|
||||
timeout: number;
|
||||
maxAttempts: number;
|
||||
}
|
||||
|
||||
export interface DownloadSettings {
|
||||
downloadPath: string;
|
||||
tempPath: string;
|
||||
autoStart: boolean;
|
||||
autoQueueThreshold: number;
|
||||
forceOrder: boolean;
|
||||
@@ -31,3 +30,9 @@ export interface ViperSettings {
|
||||
thanks: boolean;
|
||||
proxy: string;
|
||||
}
|
||||
|
||||
export interface SystemSettings {
|
||||
tempPath: string;
|
||||
cachePath: string;
|
||||
maxEventLog: number;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export class Thread {
|
||||
constructor(
|
||||
public link: string,
|
||||
public threadId: string,
|
||||
public title: string,
|
||||
public threadId: number,
|
||||
public total: number
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
import { ProgressCellComponent } from '../progress-cell/progress-cell.component';
|
||||
import { Image } from '../domain/image.model';
|
||||
import { ImageDialogData, ImagesComponent } from '../images/images.component';
|
||||
import { formatBytes } from '../utils/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-download-table',
|
||||
@@ -130,8 +131,13 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
{
|
||||
headerName: 'Total',
|
||||
tooltipValueGetter: params =>
|
||||
`${params.data?.done}/${params.data?.total}`,
|
||||
valueGetter: params => `${params.data?.done}/${params.data?.total}`,
|
||||
`${params.data?.done}/${params.data?.total} (${formatBytes(
|
||||
params.data?.downloaded
|
||||
)})`,
|
||||
valueGetter: params =>
|
||||
`${params.data?.done}/${params.data?.total} (${formatBytes(
|
||||
params.data?.downloaded
|
||||
)})`,
|
||||
},
|
||||
{
|
||||
headerName: 'Hosts',
|
||||
@@ -253,33 +259,38 @@ export class DownloadTableComponent implements OnDestroy {
|
||||
|
||||
private connect() {
|
||||
this.subscriptions.push(
|
||||
this.applicationEndpoint.posts$.subscribe((e: Post[]) => {
|
||||
const toAdd: Post[] = [];
|
||||
const toUpdate: Post[] = [];
|
||||
e.forEach(v => {
|
||||
if (this.agGrid.api.getRowNode(v.postId) == null) {
|
||||
toAdd.push(v);
|
||||
} else {
|
||||
toUpdate.push(v);
|
||||
}
|
||||
});
|
||||
this.agGrid.api.applyTransaction({ update: toUpdate, add: toAdd });
|
||||
this.applicationEndpoint.newPosts$.subscribe((newPosts: Post[]) => {
|
||||
this.agGrid.api.applyTransaction({ add: newPosts });
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.applicationEndpoint.postsRemove$.subscribe((e: string[]) => {
|
||||
this.applicationEndpoint.deletedPosts$.subscribe((e: string[]) => {
|
||||
const toRemove: string[] = [];
|
||||
e.forEach(v => {
|
||||
const rowNode: IRowNode | undefined = this.agGrid.api.getRowNode(v);
|
||||
if (rowNode != null) {
|
||||
toRemove.push(rowNode.data);
|
||||
}
|
||||
return;
|
||||
});
|
||||
this.agGrid.api.applyTransaction({ remove: toRemove });
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.applicationEndpoint.updatedPosts$.subscribe((e: Post[]) => {
|
||||
const toUpdate: Post[] = [];
|
||||
e.forEach(v => {
|
||||
const rowNode: IRowNode | undefined = this.agGrid.api.getRowNode(
|
||||
String(v.postId)
|
||||
);
|
||||
if (rowNode != null) {
|
||||
toUpdate.push(v);
|
||||
}
|
||||
});
|
||||
this.agGrid.api.applyTransaction({ update: toUpdate });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private disconnect() {
|
||||
|
||||
@@ -9,9 +9,10 @@ import { Image } from '../domain/image.model';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ValueGetterParams } from 'ag-grid-community/dist/lib/entities/colDef';
|
||||
import { ProgressCellComponent } from '../progress-cell/progress-cell.component';
|
||||
import { ITooltipParams } from 'ag-grid-community/dist/lib/rendering/tooltipComponent';
|
||||
|
||||
export interface ImageDialogData {
|
||||
postId: string;
|
||||
postId: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -24,7 +25,7 @@ export interface ImageDialogData {
|
||||
export class ImagesComponent implements OnDestroy {
|
||||
@ViewChild('agGrid') agGrid!: AgGridAngular;
|
||||
|
||||
gridOptions: GridOptions;
|
||||
gridOptions: GridOptions<Image>;
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
@@ -48,21 +49,21 @@ export class ImagesComponent implements OnDestroy {
|
||||
{
|
||||
headerName: 'Progress',
|
||||
field: 'progress',
|
||||
tooltipValueGetter: params => {
|
||||
tooltipValueGetter: (params: ITooltipParams<Image>) => {
|
||||
if (!params.data) {
|
||||
return 0;
|
||||
}
|
||||
return params.data.current === 0 || params.data.total === 0
|
||||
return params.data.downloaded === 0 || params.data.size === 0
|
||||
? 0
|
||||
: (params.data.current / params.data.total) * 100;
|
||||
: (params.data.downloaded / params.data.size) * 100;
|
||||
},
|
||||
valueGetter: (params: ValueGetterParams<Image>) => {
|
||||
if (!params.data) {
|
||||
return 0;
|
||||
}
|
||||
return params.data.current === 0 || params.data.total === 0
|
||||
return params.data.downloaded === 0 || params.data.size === 0
|
||||
? 0
|
||||
: (params.data.current / params.data.total) * 100;
|
||||
: (params.data.downloaded / params.data.size) * 100;
|
||||
},
|
||||
cellRenderer: ProgressCellComponent,
|
||||
},
|
||||
|
||||
@@ -101,17 +101,20 @@ export class LogTableComponent implements OnInit, OnDestroy {
|
||||
|
||||
private connect() {
|
||||
this.subscriptions.push(
|
||||
this.applicationEndpoint.logs$.subscribe((e: Log[]) => {
|
||||
const toAdd: Log[] = [];
|
||||
this.applicationEndpoint.newLogs$.subscribe((e: Log[]) => {
|
||||
this.agGrid.api.applyTransaction({ add: e });
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.applicationEndpoint.updatedLogs$.subscribe((e: Log[]) => {
|
||||
const toUpdate: Log[] = [];
|
||||
e.forEach(v => {
|
||||
if (this.agGrid.api.getRowNode(v.id.toString()) == null) {
|
||||
toAdd.push(v);
|
||||
} else {
|
||||
if (this.agGrid.api.getRowNode(v.id.toString()) != null) {
|
||||
toUpdate.push(v);
|
||||
}
|
||||
});
|
||||
this.agGrid.api.applyTransaction({ update: toUpdate, add: toAdd });
|
||||
this.agGrid.api.applyTransaction({ update: toUpdate });
|
||||
})
|
||||
);
|
||||
|
||||
@@ -125,7 +128,6 @@ export class LogTableComponent implements OnInit, OnDestroy {
|
||||
if (rowNode != null) {
|
||||
toRemove.push(rowNode.data);
|
||||
}
|
||||
return;
|
||||
});
|
||||
this.agGrid.api.applyTransaction({ remove: toRemove });
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { formatBytes } from '../utils/utils';
|
||||
|
||||
@Pipe({
|
||||
standalone: true,
|
||||
name: 'downloadSpeed',
|
||||
})
|
||||
export class DownloadSpeedPipe implements PipeTransform {
|
||||
transform(value: number): string {
|
||||
return formatBytes(value);
|
||||
}
|
||||
}
|
||||
@@ -10,27 +10,42 @@ import { Image } from '../domain/image.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { PostItem } from '../domain/post-item.model';
|
||||
import { Settings } from '../domain/settings.model';
|
||||
import { GlobalState } from '../domain/global-state.model';
|
||||
import { QueueState } from '../domain/queue-state.model';
|
||||
import { DownloadSpeed } from '../domain/download.speed';
|
||||
import { ErrorCount } from '../domain/error.count';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ApplicationEndpointService {
|
||||
private rxStomp!: RxStomp;
|
||||
|
||||
private posts!: Observable<Post[]>;
|
||||
|
||||
private globalState!: Observable<GlobalState>;
|
||||
connectionState = signal(RxStompState.CLOSED);
|
||||
|
||||
get posts$(): Observable<Post[]> {
|
||||
return this.posts;
|
||||
constructor(
|
||||
@Inject(WS_BASE_URL) private wsBaseUrl: string,
|
||||
@Inject(BASE_URL) private baseUrl: string,
|
||||
private httpClient: HttpClient
|
||||
) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private postsRemove!: Observable<string[]>;
|
||||
private rxStomp!: RxStomp;
|
||||
|
||||
get postsRemove$(): Observable<string[]> {
|
||||
return this.postsRemove;
|
||||
connectionState = signal(RxStompState.CLOSED);
|
||||
|
||||
private newPosts!: Observable<Post[]>;
|
||||
|
||||
get newPosts$(): Observable<Post[]> {
|
||||
return this.newPosts;
|
||||
}
|
||||
|
||||
private updatedPosts!: Observable<Post[]>;
|
||||
|
||||
get updatedPosts$(): Observable<Post[]> {
|
||||
return this.updatedPosts;
|
||||
}
|
||||
|
||||
private deletedPosts!: Observable<string[]>;
|
||||
|
||||
get deletedPosts$(): Observable<string[]> {
|
||||
return this.deletedPosts;
|
||||
}
|
||||
|
||||
private threads!: Observable<Thread[]>;
|
||||
@@ -51,10 +66,16 @@ export class ApplicationEndpointService {
|
||||
return this.threadRemoveAll;
|
||||
}
|
||||
|
||||
private logs!: Observable<Log[]>;
|
||||
private newLogs!: Observable<Log[]>;
|
||||
|
||||
get logs$(): Observable<Log[]> {
|
||||
return this.logs;
|
||||
get newLogs$(): Observable<Log[]> {
|
||||
return this.newLogs;
|
||||
}
|
||||
|
||||
private updatedLogs!: Observable<Log[]>;
|
||||
|
||||
get updatedLogs$(): Observable<Log[]> {
|
||||
return this.updatedLogs;
|
||||
}
|
||||
|
||||
private logsRemove!: Observable<number[]>;
|
||||
@@ -63,7 +84,7 @@ export class ApplicationEndpointService {
|
||||
return this.logsRemove;
|
||||
}
|
||||
|
||||
postDetails$(postId: string): Observable<Image[]> {
|
||||
postDetails$(postId: number): Observable<Image[]> {
|
||||
return this.rxStomp.watch('/topic/images/' + postId).pipe(
|
||||
map(e => {
|
||||
return JSON.parse(e.body).map((element: any) => {
|
||||
@@ -72,8 +93,8 @@ export class ApplicationEndpointService {
|
||||
element.url,
|
||||
element.status,
|
||||
element.index + 1,
|
||||
element.current,
|
||||
element.total
|
||||
element.downloaded,
|
||||
element.size
|
||||
);
|
||||
});
|
||||
}),
|
||||
@@ -81,14 +102,6 @@ export class ApplicationEndpointService {
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(WS_BASE_URL) private wsBaseUrl: string,
|
||||
@Inject(BASE_URL) private baseUrl: string,
|
||||
private httpClient: HttpClient
|
||||
) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.connect();
|
||||
this.prepareTopics();
|
||||
@@ -98,8 +111,28 @@ export class ApplicationEndpointService {
|
||||
this.rxStomp.deactivate();
|
||||
}
|
||||
|
||||
get globalState$(): Observable<GlobalState> {
|
||||
return this.globalState;
|
||||
private queueState!: Observable<QueueState>;
|
||||
|
||||
get queueState$(): Observable<QueueState> {
|
||||
return this.queueState;
|
||||
}
|
||||
|
||||
private downloadSpeed!: Observable<DownloadSpeed>;
|
||||
|
||||
get downloadSpeed$(): Observable<DownloadSpeed> {
|
||||
return this.downloadSpeed;
|
||||
}
|
||||
|
||||
private vgUsername!: Observable<string>;
|
||||
|
||||
get vgUsername$(): Observable<string> {
|
||||
return this.vgUsername;
|
||||
}
|
||||
|
||||
private errorCount!: Observable<ErrorCount>;
|
||||
|
||||
get errorCount$(): Observable<ErrorCount> {
|
||||
return this.errorCount;
|
||||
}
|
||||
|
||||
connect() {
|
||||
@@ -115,7 +148,28 @@ export class ApplicationEndpointService {
|
||||
prepareTopics() {
|
||||
this.rxStomp.connectionState$.subscribe(e => this.connectionState.set(e));
|
||||
|
||||
this.postsRemove = this.rxStomp.watch('/topic/posts/deleted').pipe(
|
||||
this.downloadSpeed = this.rxStomp.watch('/topic/download-speed').pipe(
|
||||
map(e => {
|
||||
return JSON.parse(e.body);
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.vgUsername = this.rxStomp.watch('/topic/vg-username').pipe(
|
||||
map(e => {
|
||||
return e.body;
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.errorCount = this.rxStomp.watch('/topic/error-count').pipe(
|
||||
map(e => {
|
||||
return JSON.parse(e.body);
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.deletedPosts = this.rxStomp.watch('/topic/posts/deleted').pipe(
|
||||
map(e => {
|
||||
return JSON.parse(e.body);
|
||||
}),
|
||||
@@ -143,15 +197,15 @@ export class ApplicationEndpointService {
|
||||
share()
|
||||
);
|
||||
|
||||
this.globalState = this.rxStomp.watch('/topic/state').pipe(
|
||||
this.queueState = this.rxStomp.watch('/topic/queue-state').pipe(
|
||||
map(e => {
|
||||
const state: GlobalState = JSON.parse(e.body);
|
||||
const state: QueueState = JSON.parse(e.body);
|
||||
return state;
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.posts = this.rxStomp.watch('/topic/posts').pipe(
|
||||
this.newPosts = this.rxStomp.watch('/topic/posts/new').pipe(
|
||||
map(e => {
|
||||
// const posts: Array<Post> = [];
|
||||
return JSON.parse(e.body).map((element: any) => {
|
||||
@@ -165,15 +219,52 @@ export class ApplicationEndpointService {
|
||||
element.hosts,
|
||||
element.addedOn,
|
||||
element.rank + 1,
|
||||
element.downloadDirectory
|
||||
element.downloadDirectory,
|
||||
element.downloaded
|
||||
);
|
||||
});
|
||||
// return posts;
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.logs = this.rxStomp.watch('/topic/logs').pipe(
|
||||
this.updatedPosts = this.rxStomp.watch('/topic/posts/updated').pipe(
|
||||
map(e => {
|
||||
// const posts: Array<Post> = [];
|
||||
return JSON.parse(e.body).map((element: any) => {
|
||||
return new Post(
|
||||
element.postId,
|
||||
element.postTitle,
|
||||
element.status,
|
||||
element.url,
|
||||
element.done,
|
||||
element.total,
|
||||
element.hosts,
|
||||
element.addedOn,
|
||||
element.rank + 1,
|
||||
element.downloadDirectory,
|
||||
element.downloaded
|
||||
);
|
||||
});
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.newLogs = this.rxStomp.watch('/topic/logs/new').pipe(
|
||||
map(e => {
|
||||
return JSON.parse(e.body).map((element: any) => {
|
||||
return new Log(
|
||||
element.id,
|
||||
element.type,
|
||||
element.status,
|
||||
element.time,
|
||||
element.message
|
||||
);
|
||||
});
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
this.updatedLogs = this.rxStomp.watch('/topic/logs/updated').pipe(
|
||||
map(e => {
|
||||
return JSON.parse(e.body).map((element: any) => {
|
||||
return new Log(
|
||||
@@ -191,7 +282,12 @@ export class ApplicationEndpointService {
|
||||
this.threads = this.rxStomp.watch('/topic/threads').pipe(
|
||||
map(e => {
|
||||
return JSON.parse(e.body).map((element: any) => {
|
||||
return new Thread(element.link, element.threadId, element.total);
|
||||
return new Thread(
|
||||
element.link,
|
||||
element.title,
|
||||
element.threadId,
|
||||
element.total
|
||||
);
|
||||
});
|
||||
}),
|
||||
share()
|
||||
@@ -212,7 +308,7 @@ export class ApplicationEndpointService {
|
||||
);
|
||||
}
|
||||
|
||||
getThreadPosts(threadId: string) {
|
||||
getThreadPosts(threadId: number) {
|
||||
return this.httpClient
|
||||
.get<PostItem[]>(this.baseUrl + `/api/grab/${threadId}`)
|
||||
.pipe(map(v => v.map(p => ({ ...p, hosts: p.hosts }))));
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
name="downloadPath"
|
||||
required />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Temporary Path</mat-label>
|
||||
<input formControlName="tempPath" matInput name="tempPath" required />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label
|
||||
@@ -132,22 +128,22 @@
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Global concurrent downloads</mat-label>
|
||||
<input
|
||||
formControlName="maxTotalThreads"
|
||||
formControlName='maxConcurrentPerHost'
|
||||
matInput
|
||||
max="12"
|
||||
min="0"
|
||||
name="maxTotalThreads"
|
||||
name='maxConcurrentPerHost'
|
||||
required
|
||||
type="number" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Concurrent downloads per host</mat-label>
|
||||
<input
|
||||
formControlName="maxThreads"
|
||||
formControlName='maxGlobalConcurrent'
|
||||
matInput
|
||||
max="4"
|
||||
min="1"
|
||||
name="maxThreads"
|
||||
name='maxGlobalConcurrent'
|
||||
required
|
||||
type="number" />
|
||||
</mat-form-field>
|
||||
@@ -233,9 +229,9 @@
|
||||
</div>
|
||||
</form>
|
||||
</mat-tab>
|
||||
<mat-tab label="Event Log">
|
||||
<mat-tab label='System'>
|
||||
<form
|
||||
[formGroup]="eventLogSettingsForm"
|
||||
[formGroup]='systemSettingsForm'
|
||||
autocomplete="off"
|
||||
style="
|
||||
display: flex;
|
||||
@@ -245,7 +241,19 @@
|
||||
padding-right: 20px;
|
||||
">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Maximum records</mat-label>
|
||||
<mat-label>Temporary Path</mat-label>
|
||||
<input formControlName='tempPath' matInput name='tempPath' required />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance='outline'>
|
||||
<mat-label>Cache Path</mat-label>
|
||||
<input
|
||||
formControlName='cachePath'
|
||||
matInput
|
||||
name='cachePath'
|
||||
required />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance='outline'>
|
||||
<mat-label>Maximum log entries</mat-label>
|
||||
<input
|
||||
formControlName="maxEventLog"
|
||||
matInput
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ConnectionSettings,
|
||||
DownloadSettings,
|
||||
Settings,
|
||||
SystemSettings,
|
||||
ViperSettings,
|
||||
} from '../domain/settings.model';
|
||||
import { ApplicationEndpointService } from '../services/application-endpoint.service';
|
||||
@@ -62,7 +63,6 @@ export class SettingsComponent {
|
||||
|
||||
downloadSettingsForm = new FormGroup({
|
||||
downloadPath: new FormControl(''),
|
||||
tempPath: new FormControl(''),
|
||||
autoStart: new FormControl(false),
|
||||
autoQueueThreshold: new FormControl(0),
|
||||
forceOrder: new FormControl(false),
|
||||
@@ -73,13 +73,15 @@ export class SettingsComponent {
|
||||
});
|
||||
|
||||
connectionSettingsForm = new FormGroup({
|
||||
maxThreads: new FormControl(0),
|
||||
maxTotalThreads: new FormControl(0),
|
||||
maxConcurrentPerHost: new FormControl(0),
|
||||
maxGlobalConcurrent: new FormControl(0),
|
||||
timeout: new FormControl(0),
|
||||
maxAttempts: new FormControl(0),
|
||||
});
|
||||
|
||||
eventLogSettingsForm = new FormGroup({
|
||||
systemSettingsForm = new FormGroup({
|
||||
tempPath: new FormControl(''),
|
||||
cachePath: new FormControl(''),
|
||||
maxEventLog: new FormControl(0),
|
||||
});
|
||||
|
||||
@@ -91,7 +93,7 @@ export class SettingsComponent {
|
||||
this.viperGirlsSettingsForm.reset(data.viperSettings);
|
||||
this.downloadSettingsForm.reset(data.downloadSettings);
|
||||
this.connectionSettingsForm.reset(data.connectionSettings);
|
||||
this.eventLogSettingsForm.reset(data);
|
||||
this.systemSettingsForm.reset(data.systemSettings);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
@@ -106,7 +108,9 @@ export class SettingsComponent {
|
||||
downloadSettings: {
|
||||
...(this.downloadSettingsForm.value as DownloadSettings),
|
||||
},
|
||||
...this.eventLogSettingsForm.value,
|
||||
systemSettings: {
|
||||
...(this.systemSettingsForm.value as SystemSettings),
|
||||
},
|
||||
} as Settings)
|
||||
.subscribe(() => this.dialogRef.close());
|
||||
};
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
<ng-container *ngIf="globalState$ | async as state">
|
||||
<div class="status-bar" style="display: flex">
|
||||
<span style="padding-left: 10px; flex-grow: 1">
|
||||
<ng-container *ngIf="state.loggedUser.length > 0"
|
||||
>Logged in as: {{ state.loggedUser }}</ng-container
|
||||
>
|
||||
</span>
|
||||
<span>{{ state.downloadSpeed || '0' + '/s' }}</span>
|
||||
<div class='status-bar' style='display: flex'>
|
||||
<span style='padding-left: 10px; flex-grow: 1'>
|
||||
<ng-container *ngIf='vgUsername$ | async as vgUsername'>
|
||||
<span>{{
|
||||
vgUsername.length > 0 ? 'Logged in as: ' + vgUsername : ''
|
||||
}}</span>
|
||||
</ng-container>
|
||||
</span>
|
||||
<span *ngIf='downloadSpeed$ | async as downloadSpeed'>{{
|
||||
(downloadSpeed.speed | downloadSpeed) + '/s'
|
||||
}}</span>
|
||||
<mat-divider [vertical]='true' style='height: 20px'></mat-divider>
|
||||
<ng-container *ngIf='queueState$ | async as queueState'>
|
||||
<span>Downloading: {{ queueState.running }}</span>
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
<span>Downloading: {{ state.running }}</span>
|
||||
<span>Pending: {{ queueState.remaining }}</span>
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
<span>Pending: {{ state.remaining }}</span>
|
||||
<mat-divider [vertical]="true" style="height: 20px"></mat-divider>
|
||||
<span style="padding-right: 10px">Error: {{ state.error }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<span *ngIf='errorCount$ | async as errorCount' style='padding-right: 10px'
|
||||
>Error: {{ errorCount.count }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -2,16 +2,20 @@ import { Component } from '@angular/core';
|
||||
import { CommonModule, NgIf } from '@angular/common';
|
||||
import { ApplicationEndpointService } from '../services/application-endpoint.service';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { DownloadSpeedPipe } from '../pipes/download-speed.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status-bar',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgIf, MatDividerModule],
|
||||
imports: [CommonModule, NgIf, MatDividerModule, DownloadSpeedPipe],
|
||||
templateUrl: './status-bar.component.html',
|
||||
styleUrls: ['./status-bar.component.scss'],
|
||||
})
|
||||
export class StatusBarComponent {
|
||||
constructor(private applicationEndpoint: ApplicationEndpointService) {}
|
||||
|
||||
globalState$ = this.applicationEndpoint.globalState$;
|
||||
queueState$ = this.applicationEndpoint.queueState$;
|
||||
downloadSpeed$ = this.applicationEndpoint.downloadSpeed$;
|
||||
vgUsername$ = this.applicationEndpoint.vgUsername$;
|
||||
errorCount$ = this.applicationEndpoint.errorCount$;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { PostItem } from '../domain/post-item.model';
|
||||
import { ValueGetterParams } from 'ag-grid-community/dist/lib/entities/colDef';
|
||||
|
||||
export interface ThreadDialogData {
|
||||
threadId: string;
|
||||
threadId: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -3,9 +3,7 @@ import {
|
||||
Component,
|
||||
ComponentRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
@@ -18,7 +16,7 @@ import {
|
||||
RowDataUpdatedEvent,
|
||||
RowDoubleClickedEvent,
|
||||
} from 'ag-grid-community';
|
||||
import { filter, fromEvent, merge, Observable, Subscription, take } from 'rxjs';
|
||||
import { fromEvent, merge, Subscription, take } from 'rxjs';
|
||||
import { ApplicationEndpointService } from '../services/application-endpoint.service';
|
||||
import { Thread } from '../domain/thread.model';
|
||||
import { ComponentPortal, PortalModule } from '@angular/cdk/portal';
|
||||
@@ -73,6 +71,12 @@ export class ThreadTableComponent implements OnDestroy {
|
||||
) {
|
||||
this.gridOptions = <GridOptions>{
|
||||
columnDefs: [
|
||||
{
|
||||
headerName: 'Title',
|
||||
field: 'title',
|
||||
tooltipField: 'title',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
headerName: 'Url',
|
||||
field: 'link',
|
||||
@@ -184,7 +188,7 @@ export class ThreadTableComponent implements OnDestroy {
|
||||
const toAdd: Thread[] = [];
|
||||
const toUpdate: Thread[] = [];
|
||||
e.forEach(v => {
|
||||
if (this.agGrid.api.getRowNode(v.threadId) == null) {
|
||||
if (this.agGrid.api.getRowNode(String(v.threadId)) == null) {
|
||||
toAdd.push(v);
|
||||
} else {
|
||||
toUpdate.push(v);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export function formatBytes(bytes: number, decimals = 2) {
|
||||
if (!+bytes) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = [
|
||||
'Bytes',
|
||||
'KiB',
|
||||
'MiB',
|
||||
'GiB',
|
||||
'TiB',
|
||||
'PiB',
|
||||
'EiB',
|
||||
'ZiB',
|
||||
'YiB',
|
||||
];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||
}
|
||||
Reference in New Issue
Block a user