diff --git a/src/app/home/home.html b/src/app/home/home.html
index b05a17c..2874f72 100644
--- a/src/app/home/home.html
+++ b/src/app/home/home.html
@@ -9,10 +9,12 @@
+
-
- {{ serverIp }}
+
+ {{ serverIp }}
+ {{ version }}
@@ -39,7 +41,11 @@
{{ running ? "Stop" : "Start" }} Server
@if (error) {
- {{ error }}
+ {{ error }}
+ } @if (playerCount !== "undefined/undefined"){
+ Players: {{ playerCount }}
+ } @for (player of playerList; track $index) {
+ {{ $index + ". " + player.name_clean }}
}
diff --git a/src/app/home/home.scss b/src/app/home/home.scss
index 881f2ad..de09519 100644
--- a/src/app/home/home.scss
+++ b/src/app/home/home.scss
@@ -56,4 +56,8 @@
position: absolute;
top: 10px;
left: 80px;
+}
+
+.icon {
+ width: 50px;
}
\ No newline at end of file
diff --git a/src/app/home/home.ts b/src/app/home/home.ts
index 9a8d54c..22df94a 100644
--- a/src/app/home/home.ts
+++ b/src/app/home/home.ts
@@ -7,6 +7,8 @@ import { MatChipsModule } from '@angular/material/chips';
import { ServerService } from '../server.service';
import { MatIconModule } from '@angular/material/icon';
import { AuthService } from '../auth.service';
+import { McstatusService } from '../mcstatus.service';
+import { MinecraftPlayerSample } from '../../mcstatus.model';
@Component({
selector: 'app-home',
@@ -28,18 +30,37 @@ export class Home implements OnInit {
serverIp = localStorage.getItem('serverIp');
error = '';
- private statusInterval?: ReturnType;
+ version: string | undefined = undefined;
+ playerCount: string | undefined = undefined;
+ playerList: MinecraftPlayerSample[] | undefined = undefined;
+ icon: string | null | undefined = undefined
+
+ private infoInterval?: ReturnType;
private serverService: ServerService = inject(ServerService);
private authService: AuthService = inject(AuthService);
+ private mcstatusService: McstatusService = inject(McstatusService);
ngOnInit() {
this.loading = true;
+ this.fetchInfo();
+ this.infoInterval = setInterval(() => this.fetchInfo(), 1000);
+ }
+
+ private fetchInfo() {
+ this.mcstatusService.loadStatusInfo()
this.fetchStatus();
- this.statusInterval = setInterval(() => this.fetchStatus(), 5000);
+ this.fetchMcstatusInfo();
+ }
+
+ private fetchMcstatusInfo() {
+ this.version = this.mcstatusService.getVersion();
+ this.playerCount = this.mcstatusService.getPlayerCount();
+ this.playerList = this.mcstatusService.getPlayerList();
+ this.icon = this.mcstatusService.getServerIcon()
}
- fetchStatus() {
+ private fetchStatus() {
this.serverService.getStatus().subscribe({
next: (res) => {
this.running = res.running;
@@ -55,7 +76,7 @@ export class Home implements OnInit {
toggleServer() {
if (this.running === null) return;
- clearInterval(this.statusInterval);
+ clearInterval(this.infoInterval);
this.loading = true;
const action$ = this.running
@@ -64,7 +85,7 @@ export class Home implements OnInit {
action$.subscribe({
next: () =>
- (this.statusInterval = setInterval(() => this.fetchStatus(), 5000)),
+ (this.infoInterval = setInterval(() => this.fetchStatus(), 5000)),
error: () => (this.loading = false),
});
}
diff --git a/src/app/mcstatus.service.spec.ts b/src/app/mcstatus.service.spec.ts
new file mode 100644
index 0000000..c3a7e60
--- /dev/null
+++ b/src/app/mcstatus.service.spec.ts
@@ -0,0 +1,19 @@
+import { TestBed } from '@angular/core/testing';
+
+import { McstatusService } from './mcstatus.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('McstatusService', () => {
+ let service: McstatusService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpClientTestingModule]
+ });
+ service = TestBed.inject(McstatusService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/mcstatus.service.ts b/src/app/mcstatus.service.ts
new file mode 100644
index 0000000..374ca7a
--- /dev/null
+++ b/src/app/mcstatus.service.ts
@@ -0,0 +1,38 @@
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
+import { Mcstatus } from '../mcstatus.model';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class McstatusService {
+ // https://mcstatus.io/docs
+
+ private readonly http = inject(HttpClient);
+ private readonly URL = 'https://api.mcstatus.io/v2/status/java/';
+ private IP: string | null = null;
+ private status: Mcstatus | null = null;
+
+ public loadStatusInfo() {
+ this.IP = localStorage.getItem('serverIp');
+ this.http
+ .get(`${this.URL}/${this.IP?.replace("https://", "")}`)
+ .subscribe((res) => (this.status = res));
+ }
+
+ public getVersion() {
+ return this.status?.version?.name_clean;
+ }
+
+ public getPlayerCount() {
+ return `${this.status?.players?.online}/${this.status?.players?.max}`;
+ }
+
+ public getPlayerList() {
+ return this.status?.players?.list;
+ }
+
+ public getServerIcon() {
+ return this.status?.icon
+ }
+}
diff --git a/src/mcstatus.model.ts b/src/mcstatus.model.ts
new file mode 100644
index 0000000..73a69bb
--- /dev/null
+++ b/src/mcstatus.model.ts
@@ -0,0 +1,122 @@
+export interface Mcstatus {
+ /** Whether the server is online or offline
+ * Not used since we get the status directly from the machine */
+ online: boolean;
+
+ /** Resolved hostname of the server */
+ host: string;
+
+ /** Resolved port of the server */
+ port: number;
+
+ /** Resolved IP address of the hostname (can be null if resolution failed) */
+ ip_address: string | null;
+
+ /** Whether this address is blocked by Mojang (EULA violation) */
+ eula_blocked: boolean;
+
+ /** Unix ms timestamp when the status was retrieved */
+ retrieved_at: number;
+
+ /** Unix ms timestamp when this status cache expires */
+ expires_at: number;
+
+ /** Server version data (missing if server is offline) */
+ version?: MinecraftVersion;
+
+ /** Player information (missing if server is offline) */
+ players?: MinecraftPlayers;
+
+ /** Message of the day / description (missing if server is offline) */
+ motd?: MinecraftMotd;
+
+ /**
+ * Base64-encoded PNG data of the 64x64 server icon.
+ * May be null if no icon is set. Missing if server is offline.
+ */
+ icon?: string | null;
+
+ /**
+ * Forge mods loaded on the server.
+ * Usually empty if no Forge is installed.
+ */
+ mods: MinecraftMod[];
+
+ /**
+ * Software the server is running (null if query lookup fails).
+ * Missing if server is offline.
+ */
+ software?: string | null;
+
+ /**
+ * Plugins running on the server (missing if server is offline).
+ */
+ plugins?: MinecraftPlugin[];
+
+ /**
+ * Result of SRV record lookup.
+ * Always present but can be null if no SRV record was found.
+ */
+ srv_record: MinecraftSrvRecord | null;
+}
+
+export interface MinecraftVersion {
+ /** Raw version name with formatting codes */
+ name_raw: string;
+ /** Clean version name without formatting codes */
+ name_clean: string;
+ /** HTML-formatted version name */
+ name_html: string;
+ /** Protocol version used to identify supported client versions */
+ protocol: number;
+}
+
+export interface MinecraftPlayers {
+ /** Online player count */
+ online: number;
+ /** Maximum allowed players */
+ max: number;
+ /** Sample list of online players (may be empty) */
+ list: MinecraftPlayerSample[];
+}
+
+export interface MinecraftPlayerSample {
+ /** UUID of the player */
+ uuid: string;
+ /** Username with possible formatting codes */
+ name_raw: string;
+ /** Username without formatting codes */
+ name_clean: string;
+ /** HTML-formatted username */
+ name_html: string;
+}
+
+export interface MinecraftMotd {
+ /** Raw MOTD with formatting codes */
+ raw: string;
+ /** Clean text-only MOTD */
+ clean: string;
+ /** HTML representation of the MOTD */
+ html: string;
+}
+
+export interface MinecraftMod {
+ /** Name of the mod */
+ name: string;
+ /** Version of the mod */
+ version: string;
+}
+
+export interface MinecraftPlugin {
+ /** Name of the plugin */
+ name: string;
+ /** Semantic version of the plugin (can be null) */
+ version: string | null;
+}
+
+export interface MinecraftSrvRecord {
+ /** Hostname returned by the SRV lookup */
+ host: string;
+ /** Port returned by the SRV lookup */
+ port: number;
+}