From 7f5a027a1ea3b399727dce6938ce2957564f013b Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Wed, 30 Sep 2026 06:09:44 -0400 Subject: [PATCH 1/2] feature: player management plugin status as an icon beside refresh --- components/servers/ServerPlayerManagement.vue | 366 ++++++++++-------- i18n/locales/ar_SA.json | 2 +- i18n/locales/da_DK.json | 2 +- i18n/locales/de_DE.json | 2 +- i18n/locales/en.json | 2 +- i18n/locales/es_ES.json | 2 +- i18n/locales/fr_FR.json | 2 +- i18n/locales/it_IT.json | 2 +- i18n/locales/ja_JP.json | 2 +- i18n/locales/ko_KR.json | 2 +- i18n/locales/pl_PL.json | 2 +- i18n/locales/pt_BR.json | 2 +- i18n/locales/ru_RU.json | 2 +- i18n/locales/sv_SE.json | 2 +- i18n/locales/tr_TR.json | 2 +- i18n/locales/uk_UA.json | 2 +- i18n/locales/zh_Hans.json | 2 +- i18n/locales/zh_Hant.json | 2 +- pages/dedicated-servers/[id].vue | 1 + .../servers/ServerPlayerManagement.spec.ts | 132 +++++-- 20 files changed, 318 insertions(+), 215 deletions(-) diff --git a/components/servers/ServerPlayerManagement.vue b/components/servers/ServerPlayerManagement.vue index ed12b15f..a8e10137 100644 --- a/components/servers/ServerPlayerManagement.vue +++ b/components/servers/ServerPlayerManagement.vue @@ -6,6 +6,7 @@ import { RefreshCw, Users, TriangleAlert, + ShieldCheck, Download, Eye, EyeOff, @@ -13,6 +14,11 @@ import { import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; import { Tooltip, TooltipContent, @@ -42,6 +48,7 @@ const props = defineProps<{ gameServerNodeId?: string | null; apiPassword?: string | null; pluginRuntime?: string | null; + online?: boolean; }>(); const { t } = useI18n(); @@ -80,7 +87,6 @@ const pluginState = ref<{ player_management_seen_at: string | null; } | null>(null); const now = ref(Date.now()); -const installOpen = ref(false); const showConfig = ref(false); const selectedRuntime = ref(null); let pluginSub: { unsubscribe: () => void } | null = null; @@ -112,6 +118,20 @@ const pluginVersionLabel = computed(() => { return version && /^\d/.test(version) ? `v${version}` : version; }); +// An offline server can't check in either way, and the status pill already +// says it is offline, so a missing plugin is only worth flagging once RCON is up. +const showPluginStatus = computed( + () => isCommunityServer.value && (pluginActive.value || props.online), +); + +const pluginStatusLabel = computed(() => + t( + pluginActive.value + ? "pages.dedicated_servers.detail.player_management_plugin.active" + : "pages.dedicated_servers.detail.player_management_plugin.not_detected_title", + ), +); + const canInstall = computed( () => props.gameServerNodeId === null && authStore.isAdmin, ); @@ -343,190 +363,198 @@ onBeforeUnmount(() => { {{ players.length }} - - - +
+ + - - {{ $t("common.refresh") }} - - -
- - +
diff --git a/tests/components/servers/ServerPlayerManagement.spec.ts b/tests/components/servers/ServerPlayerManagement.spec.ts index cbad5295..d3f69ab4 100644 --- a/tests/components/servers/ServerPlayerManagement.spec.ts +++ b/tests/components/servers/ServerPlayerManagement.spec.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { flushPromises } from "@vue/test-utils"; import { mountSuspended } from "@nuxt/test-utils/runtime"; import ServerPlayerManagement from "~/components/servers/ServerPlayerManagement.vue"; @@ -42,15 +42,49 @@ const community = (seenAt: string | null) => ({ player_management_seen_at: seenAt, }); +let unmount: (() => void) | null = null; + async function mountCard(props: Record = {}) { const wrapper = await mountSuspended(ServerPlayerManagement, { props: { serverId: "server-1", ...props }, + attachTo: document.body, }); + unmount = () => wrapper.unmount(); await flushPromises(); return wrapper; } +function statusButton(wrapper: Awaited>) { + return wrapper.find('button[aria-label^="Player Management plugin"]'); +} + +// The popover portals out of the card, so its content is read off the body. +async function openStatus(wrapper: Awaited>) { + await statusButton(wrapper).trigger("click"); + await flushPromises(); + return document.body.querySelector('[role="dialog"]')!; +} + +function buttonIn(root: HTMLElement, text: string) { + return Array.from(root.querySelectorAll("button")).find( + (button) => button.textContent?.trim() === text, + ); +} + +// A browser can't focus reka's popper wrapper (a plain div), but happy-dom +// can, and the focusin it fires lands outside the popover and dismisses one +// with nothing focusable inside the moment it opens. +const focus = HTMLElement.prototype.focus; + beforeEach(() => { + vi.spyOn(HTMLElement.prototype, "focus").mockImplementation(function ( + this: HTMLElement, + options?: FocusOptions, + ) { + if (!this.hasAttribute("data-reka-popper-content-wrapper")) { + focus.call(this, options); + } + }); state.server = null; vi.spyOn( useApplicationSettingsStore(), @@ -64,16 +98,26 @@ beforeEach(() => { } as any; }); +afterEach(() => { + unmount?.(); + unmount = null; +}); + describe("ServerPlayerManagement plugin status", () => { it("shows a community server whose plugin checked in as active", async () => { state.server = community(new Date().toISOString()); const wrapper = await mountCard({ gameServerNodeId: "node-1" }); - expect(wrapper.text()).toContain("Player Management plugin active"); - expect(wrapper.text()).toContain("v0.0.412"); - expect(wrapper.text()).toContain("SwiftlyS2"); - expect(wrapper.text()).not.toContain("has not checked in"); + expect(statusButton(wrapper).attributes("aria-label")).toBe( + "Player Management plugin active", + ); + + const popover = await openStatus(wrapper); + + expect(popover.textContent).toContain("v0.0.412"); + expect(popover.textContent).toContain("SwiftlyS2"); + expect(popover.textContent).not.toContain("has not checked in"); }); it("treats a heartbeat older than a few minutes as the plugin being gone", async () => { @@ -81,10 +125,28 @@ describe("ServerPlayerManagement plugin status", () => { new Date(Date.now() - 10 * 60 * 1000).toISOString(), ); + const wrapper = await mountCard({ + gameServerNodeId: "node-1", + online: true, + }); + + expect(statusButton(wrapper).attributes("aria-label")).toBe( + "Player Management plugin not detected", + ); + + const popover = await openStatus(wrapper); + + expect(popover.textContent).toContain("switch the server off and back on"); + expect(popover.textContent).not.toContain("Install Steps"); + }); + + it("stays quiet about a missing plugin while the server is offline", async () => { + state.server = community(null); + const wrapper = await mountCard({ gameServerNodeId: "node-1" }); - expect(wrapper.text()).toContain("switch the server off and back on"); - expect(wrapper.text()).not.toContain("Install Plugin"); + expect(statusButton(wrapper).exists()).toBe(false); + expect(wrapper.text()).not.toContain("has not checked in"); }); it("offers an administrator the download and config for a server outside a node", async () => { @@ -93,36 +155,44 @@ describe("ServerPlayerManagement plugin status", () => { const wrapper = await mountCard({ gameServerNodeId: null, apiPassword: "secret-password", + online: true, }); - expect(wrapper.text()).toContain( + let popover = await openStatus(wrapper); + + expect(popover.textContent).toContain( "only enforced live once the 5Stack Player Management plugin is installed", ); - const install = wrapper - .findAll("button") - .find((button) => button.text() === "Install Plugin"); - await install!.trigger("click"); - - const download = wrapper.find('a[href*="PlayerManagement"]'); - expect(download.attributes("href")).toBe( + const download = popover.querySelector('a[href*="PlayerManagement"]'); + expect(download?.getAttribute("href")).toBe( "https://github.com/5stackgg/game-server/releases/download/sw-v0.0.412/PlayerManagement-sw-v0.0.412.zip", ); - expect(wrapper.text()).toContain( + expect(popover.textContent).toContain( "addons/swiftlys2/configs/plugins/PlayerManagement/config.jsonc", ); // The config carries the api password, so it stays hidden like the // page's own plugin config until asked for. - expect(wrapper.html()).not.toContain("secret-password"); + expect(document.body.innerHTML).not.toContain("secret-password"); - const show = wrapper - .findAll("button") - .find((button) => button.text() === "Show Config"); - await show!.trigger("click"); + buttonIn(popover, "Show Config")!.click(); + await flushPromises(); - expect(wrapper.find("pre").text()).toContain('"PlayerManagement"'); - expect(wrapper.find("pre").text()).toContain("secret-password"); + expect(popover.querySelector("pre")?.textContent).toContain( + '"PlayerManagement"', + ); + expect(popover.querySelector("pre")?.textContent).toContain( + "secret-password", + ); + + // Closing the popover hides it again rather than leaving it on screen. + await statusButton(wrapper).trigger("click"); + await flushPromises(); + popover = await openStatus(wrapper); + + expect(popover.querySelector("pre")).toBeNull(); + expect(document.body.innerHTML).not.toContain("secret-password"); }); it("never shows install steps to someone who is not an administrator", async () => { @@ -132,18 +202,22 @@ describe("ServerPlayerManagement plugin status", () => { } as any; state.server = community(null); - const wrapper = await mountCard({ gameServerNodeId: null }); + const wrapper = await mountCard({ gameServerNodeId: null, online: true }); + + const popover = await openStatus(wrapper); - expect(wrapper.text()).toContain("installed on this server"); - expect(wrapper.text()).not.toContain("Install Plugin"); + expect(popover.textContent).toContain("installed on this server"); + expect(popover.textContent).not.toContain("Install Steps"); }); it("says nothing about the plugin on a Ranked server", async () => { state.server = { ...community(null), type: "Ranked" }; - const wrapper = await mountCard({ gameServerNodeId: "node-1" }); + const wrapper = await mountCard({ + gameServerNodeId: "node-1", + online: true, + }); - expect(wrapper.text()).not.toContain("Player Management plugin"); - expect(wrapper.text()).not.toContain("has not checked in"); + expect(statusButton(wrapper).exists()).toBe(false); }); }); From 85880a7f263742e5deee8a3fcb71f0b895e6f018 Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Wed, 30 Sep 2026 06:20:29 -0400 Subject: [PATCH 2/2] feature: show the active plugin version and last check-in beside refresh --- components/servers/ServerPlayerManagement.vue | 53 ++++++++++++++----- i18n/locales/ar_SA.json | 1 + i18n/locales/da_DK.json | 1 + i18n/locales/de_DE.json | 1 + i18n/locales/en.json | 1 + i18n/locales/es_ES.json | 1 + i18n/locales/fr_FR.json | 1 + i18n/locales/it_IT.json | 1 + i18n/locales/ja_JP.json | 1 + i18n/locales/ko_KR.json | 1 + i18n/locales/pl_PL.json | 1 + i18n/locales/pt_BR.json | 1 + i18n/locales/ru_RU.json | 1 + i18n/locales/sv_SE.json | 1 + i18n/locales/tr_TR.json | 1 + i18n/locales/uk_UA.json | 1 + i18n/locales/zh_Hans.json | 1 + i18n/locales/zh_Hant.json | 1 + .../servers/ServerPlayerManagement.spec.ts | 2 + 19 files changed, 58 insertions(+), 14 deletions(-) diff --git a/components/servers/ServerPlayerManagement.vue b/components/servers/ServerPlayerManagement.vue index a8e10137..a776dd2e 100644 --- a/components/servers/ServerPlayerManagement.vue +++ b/components/servers/ServerPlayerManagement.vue @@ -6,7 +6,6 @@ import { RefreshCw, Users, TriangleAlert, - ShieldCheck, Download, Eye, EyeOff, @@ -31,6 +30,7 @@ import SanctionPlayer from "~/components/SanctionPlayer.vue"; import PlayerSanctions from "~/components/PlayerSanctions.vue"; import KickPlayer from "~/components/KickPlayer.vue"; import ClipBoard from "~/components/ClipBoard.vue"; +import TimeAgo from "~/components/TimeAgo.vue"; import getGraphqlClient from "~/graphql/getGraphqlClient"; import { useAuthStore } from "~/stores/AuthStore"; import { useApplicationSettingsStore } from "~/stores/ApplicationSettings"; @@ -118,6 +118,12 @@ const pluginVersionLabel = computed(() => { return version && /^\d/.test(version) ? `v${version}` : version; }); +const runtimeLabel = computed(() => { + const runtime = pluginState.value?.player_management_runtime; + + return runtime ? RUNTIME_LABELS[runtime] : null; +}); + // An offline server can't check in either way, and the status pill already // says it is offline, so a missing plugin is only worth flagging once RCON is up. const showPluginStatus = computed( @@ -371,14 +377,21 @@ onBeforeUnmount(() => { @@ -401,15 +414,27 @@ onBeforeUnmount(() => { {{ pluginStatusLabel }}
-

- {{ pluginVersionLabel }} - -

+

{{ notDetectedMessage }}

diff --git a/i18n/locales/ar_SA.json b/i18n/locales/ar_SA.json index a8f24485..1dd7f175 100644 --- a/i18n/locales/ar_SA.json +++ b/i18n/locales/ar_SA.json @@ -3677,6 +3677,7 @@ "no_players": "لا يوجد لاعبون على هذا السيرفر حاليًا", "player_management_plugin": { "active": "إضافة إدارة اللاعبين نشطة", + "last_check_in": "آخر اتصال", "not_detected": "لم تتصل إضافة إدارة اللاعبين من هذا الخادم بعد، لذلك لا يُطبَّق الكتم والمنع من الكتابة مباشرةً حتى الآن.", "not_detected_node": "لم تتصل إضافة إدارة اللاعبين بعد. تحصل الخوادم التي أُنشئت قبل إصدارها عليها بعد إعادة النشر: أوقف تشغيل الخادم ثم شغّله مجددًا.", "not_detected_external": "لا يُطبَّق الكتم والمنع من الكتابة والحظر مباشرةً إلا بعد تثبيت إضافة إدارة اللاعبين من 5Stack على هذا الخادم.", diff --git a/i18n/locales/da_DK.json b/i18n/locales/da_DK.json index c327f119..18a754cc 100644 --- a/i18n/locales/da_DK.json +++ b/i18n/locales/da_DK.json @@ -3677,6 +3677,7 @@ "no_players": "Ingen spillere på denne server lige nu", "player_management_plugin": { "active": "Player Management-plugin aktivt", + "last_check_in": "Seneste kontakt", "not_detected": "Player Management-pluginet har ikke meldt sig fra denne server, så mutes og gags håndhæves ikke live endnu.", "not_detected_node": "Player Management-pluginet har ikke meldt sig. Servere oprettet før det kom, får det, når de udrulles igen: slå serveren fra og til igen.", "not_detected_external": "Mutes, gags og bans håndhæves først live, når 5Stack Player Management-pluginet er installeret på denne server.", diff --git a/i18n/locales/de_DE.json b/i18n/locales/de_DE.json index 534f767c..9e65a7b9 100644 --- a/i18n/locales/de_DE.json +++ b/i18n/locales/de_DE.json @@ -3677,6 +3677,7 @@ "no_players": "Aktuell keine Spieler auf diesem Server", "player_management_plugin": { "active": "Player-Management-Plugin aktiv", + "last_check_in": "Letzte Rückmeldung", "not_detected": "Das Player-Management-Plugin hat sich von diesem Server noch nicht gemeldet, daher werden Stummschaltungen und Chatsperren noch nicht live durchgesetzt.", "not_detected_node": "Das Player-Management-Plugin hat sich noch nicht gemeldet. Server, die vor seiner Veröffentlichung erstellt wurden, erhalten es nach einer Neubereitstellung: Schalte den Server aus und wieder ein.", "not_detected_external": "Stummschaltungen, Chatsperren und Banns werden erst live durchgesetzt, wenn das 5Stack-Player-Management-Plugin auf diesem Server installiert ist.", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 4c3baf46..29927fc6 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -3677,6 +3677,7 @@ "no_players": "No players currently on this server", "player_management_plugin": { "active": "Player Management plugin active", + "last_check_in": "Last check-in", "not_detected": "The Player Management plugin has not checked in from this server, so mutes and gags are not enforced live yet.", "not_detected_node": "The Player Management plugin has not checked in. Servers created before it shipped get it once redeployed: switch the server off and back on.", "not_detected_external": "Mutes, gags and bans are only enforced live once the 5Stack Player Management plugin is installed on this server.", diff --git a/i18n/locales/es_ES.json b/i18n/locales/es_ES.json index e22d639d..f132938e 100644 --- a/i18n/locales/es_ES.json +++ b/i18n/locales/es_ES.json @@ -3677,6 +3677,7 @@ "no_players": "Ahora mismo no hay jugadores en este servidor", "player_management_plugin": { "active": "Plugin de gestión de jugadores activo", + "last_check_in": "Último contacto", "not_detected": "El plugin de gestión de jugadores aún no se ha comunicado desde este servidor, así que los muteos y silencios todavía no se aplican en directo.", "not_detected_node": "El plugin de gestión de jugadores aún no se ha comunicado. Los servidores creados antes de su lanzamiento lo reciben al volver a desplegarse: desactiva el servidor y vuelve a activarlo.", "not_detected_external": "Los muteos, silencios y baneos solo se aplican en directo cuando el plugin de gestión de jugadores de 5Stack está instalado en este servidor.", diff --git a/i18n/locales/fr_FR.json b/i18n/locales/fr_FR.json index b170fed9..9e3010e6 100644 --- a/i18n/locales/fr_FR.json +++ b/i18n/locales/fr_FR.json @@ -3677,6 +3677,7 @@ "no_players": "Aucun joueur actuellement sur ce serveur", "player_management_plugin": { "active": "Plugin de gestion des joueurs actif", + "last_check_in": "Dernier signal", "not_detected": "Le plugin de gestion des joueurs ne s'est pas encore signalé depuis ce serveur, les mutes et bâillons ne sont donc pas encore appliqués en direct.", "not_detected_node": "Le plugin de gestion des joueurs ne s'est pas encore signalé. Les serveurs créés avant sa sortie le reçoivent une fois redéployés : désactivez puis réactivez le serveur.", "not_detected_external": "Les mutes, bâillons et bannissements ne sont appliqués en direct qu'une fois le plugin de gestion des joueurs 5Stack installé sur ce serveur.", diff --git a/i18n/locales/it_IT.json b/i18n/locales/it_IT.json index 0331ac8b..0bf4dba6 100644 --- a/i18n/locales/it_IT.json +++ b/i18n/locales/it_IT.json @@ -3677,6 +3677,7 @@ "no_players": "Nessun giocatore attualmente su questo server", "player_management_plugin": { "active": "Plugin di gestione giocatori attivo", + "last_check_in": "Ultimo segnale", "not_detected": "Il plugin di gestione giocatori non ha ancora comunicato da questo server, quindi mute e gag non vengono ancora applicati in tempo reale.", "not_detected_node": "Il plugin di gestione giocatori non ha ancora comunicato. I server creati prima del suo rilascio lo ricevono dopo un nuovo deploy: disattiva e riattiva il server.", "not_detected_external": "Mute, gag e ban vengono applicati in tempo reale solo dopo aver installato il plugin di gestione giocatori di 5Stack su questo server.", diff --git a/i18n/locales/ja_JP.json b/i18n/locales/ja_JP.json index f0534b3e..40d406ea 100644 --- a/i18n/locales/ja_JP.json +++ b/i18n/locales/ja_JP.json @@ -3677,6 +3677,7 @@ "no_players": "現在このサーバーにプレイヤーはいません", "player_management_plugin": { "active": "プレイヤー管理プラグイン稼働中", + "last_check_in": "最終応答", "not_detected": "このサーバーからプレイヤー管理プラグインの応答がまだないため、ミュートとチャット禁止はまだリアルタイムで適用されません。", "not_detected_node": "プレイヤー管理プラグインの応答がまだありません。公開前に作成したサーバーは再デプロイで導入されます:サーバーを一度無効にしてから再度有効にしてください。", "not_detected_external": "ミュート、チャット禁止、BANは、このサーバーに5Stackのプレイヤー管理プラグインをインストールするまでリアルタイムで適用されません。", diff --git a/i18n/locales/ko_KR.json b/i18n/locales/ko_KR.json index e67adb32..76ac6fd3 100644 --- a/i18n/locales/ko_KR.json +++ b/i18n/locales/ko_KR.json @@ -3677,6 +3677,7 @@ "no_players": "현재 이 서버에 접속 중인 플레이어가 없습니다", "player_management_plugin": { "active": "플레이어 관리 플러그인 활성", + "last_check_in": "마지막 응답", "not_detected": "이 서버에서 플레이어 관리 플러그인이 아직 응답하지 않아 음성 및 채팅 차단이 아직 실시간으로 적용되지 않습니다.", "not_detected_node": "플레이어 관리 플러그인이 아직 응답하지 않았습니다. 플러그인 출시 전에 만든 서버는 다시 배포해야 적용됩니다: 서버를 껐다가 다시 켜세요.", "not_detected_external": "음성 차단, 채팅 차단, 밴은 이 서버에 5Stack 플레이어 관리 플러그인을 설치해야 실시간으로 적용됩니다.", diff --git a/i18n/locales/pl_PL.json b/i18n/locales/pl_PL.json index 79861520..c424d2a8 100644 --- a/i18n/locales/pl_PL.json +++ b/i18n/locales/pl_PL.json @@ -3677,6 +3677,7 @@ "no_players": "Obecnie brak graczy na tym serwerze", "player_management_plugin": { "active": "Wtyczka zarządzania graczami aktywna", + "last_check_in": "Ostatni kontakt", "not_detected": "Wtyczka zarządzania graczami nie zgłosiła się jeszcze z tego serwera, więc wyciszenia i blokady czatu nie są jeszcze egzekwowane na żywo.", "not_detected_node": "Wtyczka zarządzania graczami jeszcze się nie zgłosiła. Serwery utworzone przed jej wydaniem otrzymają ją po ponownym wdrożeniu: wyłącz serwer i włącz go ponownie.", "not_detected_external": "Wyciszenia, blokady czatu i bany są egzekwowane na żywo dopiero po zainstalowaniu wtyczki zarządzania graczami 5Stack na tym serwerze.", diff --git a/i18n/locales/pt_BR.json b/i18n/locales/pt_BR.json index 8ddf2dfc..d2ec6bb0 100644 --- a/i18n/locales/pt_BR.json +++ b/i18n/locales/pt_BR.json @@ -3677,6 +3677,7 @@ "no_players": "Nenhum jogador atualmente neste servidor", "player_management_plugin": { "active": "Plugin de gerenciamento de jogadores ativo", + "last_check_in": "Último contato", "not_detected": "O plugin de gerenciamento de jogadores ainda não se comunicou a partir deste servidor, então mutes e silenciamentos ainda não são aplicados ao vivo.", "not_detected_node": "O plugin de gerenciamento de jogadores ainda não se comunicou. Servidores criados antes do lançamento o recebem ao serem reimplantados: desative o servidor e ative-o novamente.", "not_detected_external": "Mutes, silenciamentos e banimentos só são aplicados ao vivo depois que o plugin de gerenciamento de jogadores da 5Stack é instalado neste servidor.", diff --git a/i18n/locales/ru_RU.json b/i18n/locales/ru_RU.json index 88b39d90..c47ae4d3 100644 --- a/i18n/locales/ru_RU.json +++ b/i18n/locales/ru_RU.json @@ -3677,6 +3677,7 @@ "no_players": "На этом сервере сейчас нет игроков", "player_management_plugin": { "active": "Плагин управления игроками активен", + "last_check_in": "Последняя связь", "not_detected": "Плагин управления игроками ещё не отозвался с этого сервера, поэтому заглушение и запрет чата пока не применяются в реальном времени.", "not_detected_node": "Плагин управления игроками ещё не отозвался. Серверы, созданные до его выхода, получат его после повторного развёртывания: выключите сервер и включите его снова.", "not_detected_external": "Заглушение, запрет чата и баны применяются в реальном времени только после установки плагина управления игроками 5Stack на этот сервер.", diff --git a/i18n/locales/sv_SE.json b/i18n/locales/sv_SE.json index 0f4f9a42..11cbcd6e 100644 --- a/i18n/locales/sv_SE.json +++ b/i18n/locales/sv_SE.json @@ -3677,6 +3677,7 @@ "no_players": "Inga spelare på servern just nu", "player_management_plugin": { "active": "Plugin för spelarhantering aktivt", + "last_check_in": "Senaste kontakt", "not_detected": "Pluginet för spelarhantering har inte hört av sig från den här servern ännu, så tystningar och chattförbud tillämpas inte live än.", "not_detected_node": "Pluginet för spelarhantering har inte hört av sig ännu. Servrar som skapades innan det släpptes får det när de driftsätts om: stäng av servern och slå på den igen.", "not_detected_external": "Tystningar, chattförbud och avstängningar tillämpas live först när 5Stacks plugin för spelarhantering är installerat på den här servern.", diff --git a/i18n/locales/tr_TR.json b/i18n/locales/tr_TR.json index 99077c6d..1aa0fa03 100644 --- a/i18n/locales/tr_TR.json +++ b/i18n/locales/tr_TR.json @@ -3677,6 +3677,7 @@ "no_players": "Bu sunucuda şu anda oyuncu yok", "player_management_plugin": { "active": "Oyuncu yönetimi eklentisi etkin", + "last_check_in": "Son yanıt", "not_detected": "Oyuncu yönetimi eklentisi bu sunucudan henüz bildirim göndermedi, bu yüzden susturmalar ve sohbet yasakları henüz canlı uygulanmıyor.", "not_detected_node": "Oyuncu yönetimi eklentisi henüz bildirim göndermedi. Eklenti yayınlanmadan önce oluşturulan sunucular yeniden dağıtıldığında onu alır: sunucuyu kapatıp yeniden açın.", "not_detected_external": "Susturmalar, sohbet yasakları ve yasaklamalar ancak 5Stack oyuncu yönetimi eklentisi bu sunucuya kurulduğunda canlı uygulanır.", diff --git a/i18n/locales/uk_UA.json b/i18n/locales/uk_UA.json index 1ba7871c..b8a6bc43 100644 --- a/i18n/locales/uk_UA.json +++ b/i18n/locales/uk_UA.json @@ -3677,6 +3677,7 @@ "no_players": "Наразі на цьому сервері немає гравців", "player_management_plugin": { "active": "Плагін керування гравцями активний", + "last_check_in": "Останній зв'язок", "not_detected": "Плагін керування гравцями ще не відгукнувся з цього сервера, тому заглушення та заборона чату поки не застосовуються в реальному часі.", "not_detected_node": "Плагін керування гравцями ще не відгукнувся. Сервери, створені до його випуску, отримають його після повторного розгортання: вимкніть сервер і ввімкніть його знову.", "not_detected_external": "Заглушення, заборона чату та бани застосовуються в реальному часі лише після встановлення плагіна керування гравцями 5Stack на цей сервер.", diff --git a/i18n/locales/zh_Hans.json b/i18n/locales/zh_Hans.json index 8ef97f16..1fd2bf0c 100644 --- a/i18n/locales/zh_Hans.json +++ b/i18n/locales/zh_Hans.json @@ -3677,6 +3677,7 @@ "no_players": "此服务器上当前无玩家", "player_management_plugin": { "active": "玩家管理插件运行中", + "last_check_in": "最后响应", "not_detected": "玩家管理插件尚未从此服务器报到,因此禁麦和禁言暂时无法实时生效。", "not_detected_node": "玩家管理插件尚未报到。在它发布前创建的服务器需要重新部署才能获得它:请先停用服务器再重新启用。", "not_detected_external": "只有在此服务器上安装 5Stack 玩家管理插件后,禁麦、禁言和封禁才会实时生效。", diff --git a/i18n/locales/zh_Hant.json b/i18n/locales/zh_Hant.json index 46f427bf..e4346f83 100644 --- a/i18n/locales/zh_Hant.json +++ b/i18n/locales/zh_Hant.json @@ -3677,6 +3677,7 @@ "no_players": "此伺服器上目前沒有玩家", "player_management_plugin": { "active": "玩家管理外掛運作中", + "last_check_in": "最後回應", "not_detected": "玩家管理外掛尚未從此伺服器回報,因此禁麥和禁言暫時無法即時生效。", "not_detected_node": "玩家管理外掛尚未回報。在它發布前建立的伺服器需要重新部署才能取得它:請先停用伺服器再重新啟用。", "not_detected_external": "只有在此伺服器上安裝 5Stack 玩家管理外掛後,禁麥、禁言和封鎖才會即時生效。", diff --git a/tests/components/servers/ServerPlayerManagement.spec.ts b/tests/components/servers/ServerPlayerManagement.spec.ts index d3f69ab4..26e62b1b 100644 --- a/tests/components/servers/ServerPlayerManagement.spec.ts +++ b/tests/components/servers/ServerPlayerManagement.spec.ts @@ -112,11 +112,13 @@ describe("ServerPlayerManagement plugin status", () => { expect(statusButton(wrapper).attributes("aria-label")).toBe( "Player Management plugin active", ); + expect(statusButton(wrapper).text()).toContain("v0.0.412"); const popover = await openStatus(wrapper); expect(popover.textContent).toContain("v0.0.412"); expect(popover.textContent).toContain("SwiftlyS2"); + expect(popover.textContent).toContain("Last check-in"); expect(popover.textContent).not.toContain("has not checked in"); });