Added statistics
- Added total file size statistic - Added total uploads statistic - Added total downloads statistic - Added icons to btns
This commit is contained in:
parent
95c9b2082a
commit
299cc565d7
@ -1 +1,146 @@
|
|||||||
<p>adminui works!</p>
|
<div class="container mx-auto p-4 mt-4">
|
||||||
|
<h1 class="text-5xl font-bold text-center text-gray-800 mb-10">Admin Dashboard</h1>
|
||||||
|
|
||||||
|
<!-- Statistics Section -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 mb-10">
|
||||||
|
<div class="shadow stats bg-white">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Total Files Uploaded</div>
|
||||||
|
<div class="stat-value">{{ fileUploads.length + expiredFileUploads.length }}</div>
|
||||||
|
<div class="stat-desc">Since launch</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow stats bg-white">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Total File Size on Disk</div>
|
||||||
|
<div class="stat-value">{{ totalFileSizeOnDisk | formatFileSizePipe }}</div>
|
||||||
|
<div class="stat-desc">Across all files</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow stats bg-white">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Operational For</div>
|
||||||
|
<div class="stat-value">2 Years</div>
|
||||||
|
<div class="stat-desc">Since launch</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow stats bg-white">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Total Downloads</div>
|
||||||
|
<div class="stat-value">{{ totalFileDownloads }}</div>
|
||||||
|
<div class="stat-desc">All time</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow stats bg-white">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Last Admin Login</div>
|
||||||
|
<div class="stat-value">2 days ago</div>
|
||||||
|
<div class="stat-desc">Most recent login</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Buttons Section -->
|
||||||
|
<div class="flex justify-center mt-10 space-x-4">
|
||||||
|
<button class="btn btn-primary">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person" viewBox="0 0 16 16">
|
||||||
|
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10s-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664z"/>
|
||||||
|
</svg>
|
||||||
|
Change Administrator Login
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary">Delete All Uploaded Files</button>
|
||||||
|
<button class="btn btn-accent">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people" viewBox="0 0 16 16">
|
||||||
|
<path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1zm-7.978-1L7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002-.014.002zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0M6.936 9.28a6 6 0 0 0-1.23-.247A7 7 0 0 0 5 9c-4 0-5 3-5 4q0 1 1 1h4.216A2.24 2.24 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816M4.92 10A5.5 5.5 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275ZM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0m3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4"/>
|
||||||
|
</svg>
|
||||||
|
User Management
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-neutral">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-left" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v2a.5.5 0 0 1-1 0v-2A1.5 1.5 0 0 1 6.5 2h8A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 5 12.5v-2a.5.5 0 0 1 1 0z"/>
|
||||||
|
<path fill-rule="evenodd" d="M.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L1.707 7.5H10.5a.5.5 0 0 1 0 1H1.707l2.147 2.146a.5.5 0 0 1-.708.708z"/>
|
||||||
|
</svg>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="mt-10 mb-10">
|
||||||
|
|
||||||
|
<!-- Table Section -->
|
||||||
|
<h2>Active file uploads</h2>
|
||||||
|
<div class="w-full overflow-x-auto mt-10 mb-10">
|
||||||
|
<table class="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Entity ID</th>
|
||||||
|
<th>File ID</th>
|
||||||
|
<th>File Name</th>
|
||||||
|
<th>File Size</th>
|
||||||
|
<th>Single Download</th>
|
||||||
|
<th>Disabled</th>
|
||||||
|
<th>Upload Date</th>
|
||||||
|
<th>Uploaded By IP</th>
|
||||||
|
<th>Download Count</th>
|
||||||
|
<!--<th>File Description</th>-->
|
||||||
|
<th>Password Protected</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let file of fileUploads">
|
||||||
|
<td>{{ file.id }}</td>
|
||||||
|
<td>{{ file.fileId }}</td>
|
||||||
|
<td>{{ file.fileName }}</td>
|
||||||
|
<td>{{ file.fileSize | formatFileSizePipe }}</td>
|
||||||
|
<td>{{ file.singleDownload ? 'true' : 'false' }}</td>
|
||||||
|
<td>{{ file.disabled ? 'true' : 'false' }}</td>
|
||||||
|
<td>{{ file.uploadDate | date: 'medium' }}</td>
|
||||||
|
<td>{{ file.uploadedByIpAddress }}</td>
|
||||||
|
<td>{{ file.downloadCount }}</td>
|
||||||
|
<!--<td>{{ file.fileDescription }}</td>-->
|
||||||
|
<td>{{ file.passwordProtected ? 'true' : 'false' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Expired file uploads</h2>
|
||||||
|
<div class="w-full overflow-x-auto mt-10">
|
||||||
|
<table class="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Entity ID</th>
|
||||||
|
<th>File ID</th>
|
||||||
|
<th>File Name</th>
|
||||||
|
<th>File Size</th>
|
||||||
|
<th>Single Download</th>
|
||||||
|
<th>Disabled</th>
|
||||||
|
<th>Upload Date</th>
|
||||||
|
<th>Uploaded By IP</th>
|
||||||
|
<th>Download Count</th>
|
||||||
|
<!--<th>File Description</th>-->
|
||||||
|
<th>Password Protected</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let file of expiredFileUploads">
|
||||||
|
<td>{{ file.id }}</td>
|
||||||
|
<td>{{ file.fileId }}</td>
|
||||||
|
<td>{{ file.fileName }}</td>
|
||||||
|
<td>{{ file.fileSize | number }}</td>
|
||||||
|
<td>{{ file.singleDownload ? 'true' : 'false' }}</td>
|
||||||
|
<td>{{ file.disabled ? 'true' : 'false' }}</td>
|
||||||
|
<td>{{ file.uploadDate | date: 'medium' }}</td>
|
||||||
|
<td>{{ file.uploadedByIpAddress }}</td>
|
||||||
|
<td>{{ file.downloadCount }}</td>
|
||||||
|
<!--<td>{{ file.fileDescription }}</td>-->
|
||||||
|
<td>{{ file.passwordProtected ? 'true' : 'false' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -1,12 +1,89 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import {DatePipe, DecimalPipe, NgForOf} from "@angular/common";
|
||||||
|
import axios from "axios";
|
||||||
|
import {firstValueFrom} from "rxjs";
|
||||||
|
import {DevelopmentStore} from "../../store/DevelopmentStore";
|
||||||
|
import {AuthStore} from "../../store/authStore";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
import {FormatFileSizePipePipe} from "../format-file-size-pipe.pipe";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-adminui',
|
selector: 'app-adminui',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [
|
||||||
|
DatePipe,
|
||||||
|
DecimalPipe,
|
||||||
|
NgForOf,
|
||||||
|
FormatFileSizePipePipe
|
||||||
|
],
|
||||||
templateUrl: './adminui.component.html',
|
templateUrl: './adminui.component.html',
|
||||||
styleUrl: './adminui.component.scss'
|
styleUrl: './adminui.component.scss'
|
||||||
})
|
})
|
||||||
export class AdminuiComponent {
|
export class AdminuiComponent {
|
||||||
|
|
||||||
|
fileUploads: any[] = [];
|
||||||
|
expiredFileUploads: any[] = [];
|
||||||
|
totalFileSizeOnDisk: number = 0;
|
||||||
|
totalFileDownloads = 0;
|
||||||
|
|
||||||
|
constructor(private developmentStore: DevelopmentStore, private authStore: AuthStore, private router: Router) {
|
||||||
|
this.verifyToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyToken() {
|
||||||
|
if(await firstValueFrom(this.authStore.token$) === "") {
|
||||||
|
console.log("No token present, redirecting to login...");
|
||||||
|
await this.router.navigate(['/login']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.fetchFileUploads();
|
||||||
|
await this.fetchExpiredFileUploads();
|
||||||
|
await this.calculateStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateStatistics() {
|
||||||
|
console.log("Calculating statistics...");
|
||||||
|
console.log(this.fileUploads)
|
||||||
|
for(let fileUpload of this.fileUploads) {
|
||||||
|
this.totalFileSizeOnDisk += fileUpload.fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let fileUpload of this.expiredFileUploads) {
|
||||||
|
this.totalFileDownloads += fileUpload.downloadCount;
|
||||||
|
}
|
||||||
|
for(let fileUpload of this.fileUploads) {
|
||||||
|
this.totalFileDownloads += fileUpload.downloadCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchFileUploads() {
|
||||||
|
try {
|
||||||
|
const response = await axios({
|
||||||
|
method: 'get',
|
||||||
|
url: this.developmentStore.getBaseUrl() + 'api/v1/secure/upload-history',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + await firstValueFrom(this.authStore.token$)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.fileUploads = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fetchExpiredFileUploads() {
|
||||||
|
try {
|
||||||
|
const response = await axios({
|
||||||
|
method: 'get',
|
||||||
|
url: this.developmentStore.getBaseUrl() + 'api/v1/secure/expired-upload-history',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + await firstValueFrom(this.authStore.token$)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.expiredFileUploads = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import {DownloadComponent} from "./download/download.component";
|
|||||||
import {CreditsComponent} from "./credits/credits.component";
|
import {CreditsComponent} from "./credits/credits.component";
|
||||||
import {LicensesComponent} from "./credits/licenses/licenses.component";
|
import {LicensesComponent} from "./credits/licenses/licenses.component";
|
||||||
import {LoginComponent} from "./login/login.component";
|
import {LoginComponent} from "./login/login.component";
|
||||||
|
import {AdminuiComponent} from "./adminui/adminui.component";
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
||||||
@ -14,6 +15,7 @@ export const routes: Routes = [
|
|||||||
{ path: 'credits', component: CreditsComponent },
|
{ path: 'credits', component: CreditsComponent },
|
||||||
{ path: 'licenses', component: LicensesComponent },
|
{ path: 'licenses', component: LicensesComponent },
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent },
|
||||||
|
{ path: 'secure/administration', component: AdminuiComponent},
|
||||||
// { path: 'download/:id', component: DownloadComponent }
|
// { path: 'download/:id', component: DownloadComponent }
|
||||||
{ path: '**', redirectTo: 'home' }
|
{ path: '**', redirectTo: 'home' }
|
||||||
];
|
];
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<input class="input w-full shadow text-center" id="username" type="text" placeholder="Username"
|
<input class="input w-full shadow text-center" id="username" type="text" placeholder="Username"
|
||||||
[(ngModel)]="inputUsername" name="username"
|
[(ngModel)]="inputUsername" name="username"
|
||||||
[ngClass]="{'input-error': loginFailed}">
|
[ngClass]="{'input-error': loginFailed}" autofocus >
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<label class="block text-gray-700 text-sm font-bold mb-2 text-center" for="password">
|
<label class="block text-gray-700 text-sm font-bold mb-2 text-center" for="password">
|
||||||
|
@ -5,6 +5,7 @@ import axios from "axios";
|
|||||||
import {NgClass, NgIf} from "@angular/common";
|
import {NgClass, NgIf} from "@angular/common";
|
||||||
import {AuthStore} from "../../store/authStore";
|
import {AuthStore} from "../../store/authStore";
|
||||||
import {firstValueFrom} from "rxjs";
|
import {firstValueFrom} from "rxjs";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
@ -23,7 +24,7 @@ export class LoginComponent {
|
|||||||
loginFailed: boolean = false;
|
loginFailed: boolean = false;
|
||||||
loginSuccessful: boolean = false;
|
loginSuccessful: boolean = false;
|
||||||
|
|
||||||
constructor(private developmentStore: DevelopmentStore, private authStore: AuthStore) {
|
constructor(private developmentStore: DevelopmentStore, private authStore: AuthStore, private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tryToLogin() {
|
tryToLogin() {
|
||||||
@ -51,6 +52,11 @@ export class LoginComponent {
|
|||||||
console.log("Login successful");
|
console.log("Login successful");
|
||||||
console.log("Token: " + await firstValueFrom(this.authStore.token$));
|
console.log("Token: " + await firstValueFrom(this.authStore.token$));
|
||||||
console.log("Username: " + await firstValueFrom(this.authStore.username$));
|
console.log("Username: " + await firstValueFrom(this.authStore.username$));
|
||||||
|
|
||||||
|
//timeout
|
||||||
|
setTimeout(() => {
|
||||||
|
this.router.navigate(['/secure/administration']);
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user