Compare commits

...

3 commits

Author SHA1 Message Date
Rajbir Singh
21476af436 TD-36: Implement Administration player filter options
All checks were successful
Quality Check / Linting (pull_request) Successful in 26s
Quality Check / Linting (push) Successful in 26s
2025-03-06 11:03:57 +01:00
43bd781b93
NOTICKET: improve Linter rules
All checks were successful
Quality Check / Linting (push) Successful in 22s
2025-03-06 00:52:04 +01:00
dbf2a4707c
NOTICKET: Api Client Generation
All checks were successful
Quality Check / Linting (push) Successful in 24s
Build Application / generate (push) Successful in 24s
Build Application / build (push) Successful in 34s
Build Application / build-docker (push) Successful in 8s
Build Application / release (push) Successful in 4s
2025-02-15 13:06:00 +01:00
9 changed files with 286 additions and 78 deletions

View file

@ -7,29 +7,39 @@ on:
- 'v*'
jobs:
generate:
runs-on: stable
container:
image: git.euph.dev/actions/runner-java-21:1300
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- name: "Generate"
run: just generate
- name: Upload API Client as Artifact
uses: "https://git.euph.dev/actions/upload-artifact@v3"
with:
name: api-client
path: src/app/core/server/*
build:
runs-on: stable
needs:
- generate
container:
image: git.euph.dev/actions/runner-java-21:latest
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- name: Download artifact from previous job
uses: "https://git.euph.dev/actions/download-artifact@v3"
with:
name: api-client
path: src/app/core/server
- name: "Install Angular CLI"
run: sudo npm i -g @angular/cli
- name: "Install Dependencies"
run: npm ci
- name: "Generate API Client"
run: |
API_VERSION="$(cat package.json | jq -r '.api_version')"
API_URL="https://git.euph.dev/TowerDefence/Server/releases/download/$API_VERSION/api.yml"
rm -rf src/app/core/server
mkdir -p src/app/core/server
npx @openapitools/openapi-generator-cli \
generate \
--openapitools src/app/core/openapitools.json \
-i "$API_URL" \
-g typescript-angular \
-o src/app/core/server
- name: Build Bundle
run: ng build
- name: Upload Binary as Artifact

View file

@ -1,11 +1,13 @@
import simpleImportSort from "eslint-plugin-simple-import-sort";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import {fileURLToPath} from "node:url";
import js from "@eslint/js";
import {FlatCompat} from "@eslint/eslintrc";
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import htmlEslint from '@html-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import globals from 'globals';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -17,13 +19,14 @@ const compat = new FlatCompat({
export default [
{
ignores: ["src/app/core/server"],
ignores: ['src/app/core/server'],
},
...compat.extends("eslint:recommended", "plugin:@typescript-eslint/strict"),
...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/strict'),
{
plugins: {
"simple-import-sort": simpleImportSort,
"@typescript-eslint": typescriptEslint,
'simple-import-sort': simpleImportSort,
'@typescript-eslint': typescriptEslint,
'@html-eslint': htmlEslint,
},
languageOptions: {
@ -32,32 +35,33 @@ export default [
},
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
indent: ["error", 4],
"linebreak-style": ["error", "unix"],
quotes: ["error", "single"],
semi: ["error", "always"],
strict: "error",
"array-bracket-newline": "error",
yoda: "error",
indent: ['error', 4],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
strict: 'error',
'array-bracket-newline': 'error',
yoda: 'error',
"@typescript-eslint/array-type": [
"error",
'@typescript-eslint/array-type': [
'error',
{
default: "generic",
default: 'generic',
},
],
"@typescript-eslint/ban-tslint-comment": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-extraneous-class": "off",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"no-mixed-spaces-and-tabs": "off",
'@typescript-eslint/ban-tslint-comment': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-extraneous-class': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'no-mixed-spaces-and-tabs': 'off',
'@html-eslint/no-inline-styles': 'error'
},
},
]
];

100
package-lock.json generated
View file

@ -28,10 +28,12 @@
"@angular/compiler-cli": "^19.0.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@html-eslint/eslint-plugin": "^0.35.1",
"@html-eslint/parser": "^0.35.1",
"@types/jasmine": "~5.1.0",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"eslint": "^9.19.0",
"eslint": "^9.21.0",
"eslint-plugin-autofix": "^2.2.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.14.0",
@ -2820,9 +2822,9 @@
}
},
"node_modules/@eslint/core": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -2833,9 +2835,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
"integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz",
"integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2894,9 +2896,9 @@
"license": "MIT"
},
"node_modules/@eslint/js": {
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"version": "9.21.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz",
"integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==",
"dev": true,
"license": "MIT",
"engines": {
@ -2914,19 +2916,58 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
"integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.10.0",
"@eslint/core": "^0.12.0",
"levn": "^0.4.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@html-eslint/eslint-plugin": {
"version": "0.35.1",
"resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.35.1.tgz",
"integrity": "sha512-RB3fo0r4OzZSMmhzBZBuuvXeku6Q36Z1s9KE3yVXeGXcX/H8LTEq9+44UmkdZXogWS7Rf3EzOvYPXjY8S6wwqg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@html-eslint/template-parser": "^0.35.1",
"@html-eslint/template-syntax-parser": "^0.35.1"
}
},
"node_modules/@html-eslint/parser": {
"version": "0.35.1",
"resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.35.1.tgz",
"integrity": "sha512-NnRnlg2UfJxKZhKgLnGEHwXCkbgiT/jensCUygcys2MmlG8+OJAJxaoEpPEptZuu6AibaV0b680PqXLJ6yQ3bQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@html-eslint/template-syntax-parser": "^0.35.1",
"es-html-parser": "0.1.1"
}
},
"node_modules/@html-eslint/template-parser": {
"version": "0.35.1",
"resolved": "https://registry.npmjs.org/@html-eslint/template-parser/-/template-parser-0.35.1.tgz",
"integrity": "sha512-+w56j9ggVdEsFxRJytDjgo6EYmuNHZz5VhOyWfJznj68AUBc9bdlbMt5dV6Goy4K2xxPIhb8rWNkPo3vRqUbIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-html-parser": "0.1.1"
}
},
"node_modules/@html-eslint/template-syntax-parser": {
"version": "0.35.1",
"resolved": "https://registry.npmjs.org/@html-eslint/template-syntax-parser/-/template-syntax-parser-0.35.1.tgz",
"integrity": "sha512-6ske9dCKn8LA00QADovSUzNPrVGvb6u5E8zp8vbD8yEg5aOsjK4v1lrOc1xzmrxpjUpcSYWbgsZMkObSb44HAg==",
"dev": true,
"license": "MIT"
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -2980,9 +3021,9 @@
}
},
"node_modules/@humanwhocodes/retry": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
"integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
"integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@ -7862,6 +7903,13 @@
"node": ">= 0.4"
}
},
"node_modules/es-html-parser": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/es-html-parser/-/es-html-parser-0.1.1.tgz",
"integrity": "sha512-SNHdEpKkN4nWZ3sFq9AxPlaUzPKJewGh59JrVS2355vELTOFygyf/lbfDDIONuGvYrhvAHoaUd+sK9UGaGrKUg==",
"dev": true,
"license": "MIT"
},
"node_modules/es-module-lexer": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
@ -7967,22 +8015,22 @@
}
},
"node_modules/eslint": {
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
"version": "9.21.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz",
"integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.0",
"@eslint/core": "^0.10.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "9.19.0",
"@eslint/plugin-kit": "^0.2.5",
"@eslint/config-array": "^0.19.2",
"@eslint/core": "^0.12.0",
"@eslint/eslintrc": "^3.3.0",
"@eslint/js": "9.21.0",
"@eslint/plugin-kit": "^0.2.7",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",

View file

@ -32,10 +32,12 @@
"@angular/compiler-cli": "^19.0.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@html-eslint/eslint-plugin": "^0.35.1",
"@html-eslint/parser": "^0.35.1",
"@types/jasmine": "~5.1.0",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"eslint": "^9.19.0",
"eslint": "^9.21.0",
"eslint-plugin-autofix": "^2.2.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.14.0",
@ -50,7 +52,7 @@
"stylelint-scss": "^6.11.0",
"typescript": "~5.6.2"
},
"api_version": "v0.0.0-rc.2",
"api_version": "v0.0.0-rc.7",
"volta": {
"node": "22.13.1"
}

View file

@ -1,5 +1,6 @@
import { provideHttpClient } from '@angular/common/http';
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter } from '@angular/router';
import { BASE_PATH, Configuration} from '@core/server';
@ -28,6 +29,7 @@ export const appConfig: ApplicationConfig = {
}
}),
{ provide: BASE_PATH, useValue: 'http://localhost:8080/api/v1' }
{ provide: BASE_PATH, useValue: 'http://localhost:8080/api/v1' },
{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } }
]
};

View file

@ -1,4 +1,6 @@
import { Routes } from '@angular/router';
import { DashboardComponent } from '@app/views/dashboard/dashboard.component';
export const routes: Routes = [{ path: '', component: DashboardComponent, title: 'Home' }];
export const routes: Routes = [{ path: '', component: DashboardComponent, title: 'Player Overview'}];

View file

@ -1,3 +1,43 @@
<div class="dashboard">
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Facere illo animi quidem repellat perspiciatis, excepturi amet corrupti ipsa sit consequuntur placeat ratione saepe velit asperiores suscipit esse quod minima exercitationem minus, alias laudantium inventore! Beatae cum nobis error suscipit cupiditate, praesentium itaque ut ipsa iusto in doloribus unde quisquam consequuntur.</p>
@if (authService.$user|async) {
<div class="dashboard__search">
<mat-form-field class="dashboard__search__input-field">
<mat-icon matPrefix>search</mat-icon>
<input matInput placeholder="" #searchbar>
</mat-form-field>
<mat-form-field>
<mat-label>Banned</mat-label>
<mat-select>
<mat-option>Reset</mat-option>
<mat-option>Banned</mat-option>
<mat-option>Not Banned</mat-option>
</mat-select>
</mat-form-field>
<button mat-fab class="shadowless" (click)="onSearch(searchbar)">
<mat-icon>check</mat-icon>
</button>
<button mat-fab class="warn shadowless" (click)="resetFilter(searchbar)">
<mat-icon>backspace</mat-icon>
</button>
</div>
<mat-table [dataSource]="usersDataSource" class="dashboard__table">
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef>Username</th>
<td mat-cell *matCellDef="let user"> {{ user.username }}</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let user">
<button mat-mini-fab class="warn shadowless" (click)="banPlayer(user)">
<mat-icon>block</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</mat-table>
} @else {
<p>Du musst eingeloggt sein, um dies zu sehen</p>
}
</div>

View file

@ -0,0 +1,33 @@
.dashboard {
display: flex;
flex-direction: column;
gap: 1rem;
&__search {
display: flex;
gap: 1rem;
&__input-field {
flex-grow: 1;
}
}
&__table {
tr, td {
display: flex;
height: auto;
}
.mat-column-username {
flex-grow: 1;
}
th.mat-column-actions {
display: none;
}
td.mat-column-actions {
padding: 0.25rem 0;
}
}
}

View file

@ -1,11 +1,78 @@
import { Component } from '@angular/core';
import {AsyncPipe} from '@angular/common';
import {Component, OnInit} from '@angular/core';
import {MatFabButton, MatMiniFabButton} from '@angular/material/button';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIcon} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {
MatTableDataSource, MatTableModule
} from '@angular/material/table';
import {AuthService} from '@core/auth/auth.service';
import UserData from '@core/auth/UserData';
import {AdministratablePlayer, AdminService, PlayerFilter} from '@core/server';
@Component({
selector: 'app-dashboard',
imports: [],
imports: [
AsyncPipe,
MatIcon,
MatInputModule,
MatTableModule,
MatFormFieldModule,
MatMiniFabButton,
MatFabButton,
MatSelectModule
],
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.scss'
})
export class DashboardComponent {
export class DashboardComponent implements OnInit {
displayedColumns: Array<string> = ['username', 'actions'];
usersDataSource: MatTableDataSource<AdministratablePlayer> = new MatTableDataSource<AdministratablePlayer>([]);
constructor(private adminService: AdminService, protected authService: AuthService) {
}
ngOnInit(): void {
this.authService.$user.subscribe((user) => {
if (user != undefined) {
return this.fetchPlayers('');
}
});
}
fetchPlayers(querry: string): void {
const filter: PlayerFilter = {
page: 0,
pageSize: 32,
order: PlayerFilter.OrderEnum.Ascending,
username: querry,
sortBy: 'username'
};
this.adminService.getAllPlayers(filter).subscribe(
(players) => {
this.usersDataSource = new MatTableDataSource<AdministratablePlayer>(players);
},
(error) => {
console.error('Error fetching players:', error);
}
);
}
banPlayer(user: UserData): void {
console.log(`Banning user: ${user.username}`);
// TODO: implement banning logic
}
onSearch(searchbar: HTMLInputElement): void {
this.fetchPlayers(searchbar.value);
}
resetFilter(searchbar: HTMLInputElement): void {
searchbar.value = '';
this.fetchPlayers('');
}
}