Skip to content

Commit 01ce49b

Browse files
authored
bug: the practice panel's Server console opens onto its server for the staff the api lets in (#955)
1 parent bf3cee8 commit 01ce49b

23 files changed

Lines changed: 413 additions & 57 deletions

‎components/utility/UtilityPracticeSessionPanel.vue‎

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Button } from "~/components/ui/button";
1414
import AnimatedFilters from "~/components/common/AnimatedFilters.vue";
1515
import { Spinner } from "~/components/ui/spinner";
1616
import { Separator } from "~/components/ui/separator";
17+
import { Fold, HeightSwap } from "~/components/ui/transitions";
1718
import ClipBoard from "~/components/ClipBoard.vue";
1819
import PlayerSearch from "~/components/PlayerSearch.vue";
1920
import RconCommander from "~/components/servers/RconCommander.vue";
@@ -31,8 +32,6 @@ import {
3132
stopUtilityPracticeMutation,
3233
} from "~/graphql/utilityGraphql";
3334
import { readUtilityPracticeSession } from "~/types/utility";
34-
import { useAuthStore } from "~/stores/AuthStore";
35-
import { e_player_roles_enum } from "~/generated/zeus";
3635
import type {
3736
UtilityPracticeSession,
3837
UtilityPracticeSessionOutput,
@@ -161,15 +160,8 @@ const canDrive = computed(() => practice.value.isLive && canManage.value);
161160
const matchId = computed(() => practice.value.matchId);
162161
const serverId = computed(() => practice.value.serverId);
163162
164-
// RCON is refused by the API for anyone below these roles, so showing it lower
165-
// would only ever render a console that answers with an error. It takes an
166-
// administrator, or the moderator running the match, which the practice host is.
167-
const isAdmin = computed(() =>
168-
useAuthStore().isRoleAbove(e_player_roles_enum.administrator),
169-
);
170-
171-
const canOperate = computed(
172-
() => isAdmin.value || useAuthStore().isRoleAbove(e_player_roles_enum.moderator),
163+
const staffCanStop = computed(
164+
() => practice.value.canStop && !canManage.value,
173165
);
174166
175167
const operatorOpen = ref(false);
@@ -214,6 +206,10 @@ onBeforeUnmount(() => {
214206
const invitees = ref<Invitee[]>([]);
215207
const inviting = ref(false);
216208
const ending = ref(false);
209+
const confirmingStop = ref(false);
210+
const stopping = ref(false);
211+
212+
const hostName = computed(() => props.session?.host?.name ?? null);
217213
218214
const inviteeSteamIds = computed(() =>
219215
invitees.value.map((entry) => entry.steamId),
@@ -306,6 +302,35 @@ async function end() {
306302
}
307303
}
308304
305+
async function staffStop() {
306+
const id = sessionId.value;
307+
308+
if (!id || !staffCanStop.value) {
309+
return;
310+
}
311+
312+
stopping.value = true;
313+
314+
try {
315+
await getGraphqlClient().mutate({
316+
mutation: stopUtilityPracticeMutation,
317+
variables: { session_id: id },
318+
});
319+
320+
confirmingStop.value = false;
321+
invitees.value = [];
322+
emit("ended");
323+
} catch (error: any) {
324+
toast({
325+
title: t("pages.utility.practice.stop_failed"),
326+
description: error?.message,
327+
variant: "destructive",
328+
});
329+
} finally {
330+
stopping.value = false;
331+
}
332+
}
333+
309334
const sectionLabel =
310335
"flex items-center gap-1.5 font-mono text-[0.62rem] uppercase tracking-[0.16em] text-muted-foreground";
311336
@@ -463,12 +488,12 @@ const panelCta = "w-full font-bold uppercase tracking-[0.22em]";
463488
</template>
464489

465490
<!-- Operator tools. A practice pod is a game server like any other, and
466-
when one refuses connections its console is what answers why. It
467-
is staff-gated here because RCON is staff-gated on the server. -->
468-
<template v-if="canOperate">
491+
when one refuses connections its console is what answers why. The
492+
api only hands the server id to viewers RCON lets in. -->
493+
<Fold :open="!!serverId">
469494
<Separator />
470495

471-
<Collapsible v-model:open="operatorOpen">
496+
<Collapsible v-model:open="operatorOpen" class="mt-3">
472497
<CollapsibleTrigger
473498
class="flex w-full items-center justify-between gap-2 text-left"
474499
>
@@ -494,26 +519,75 @@ const panelCta = "w-full font-bold uppercase tracking-[0.22em]";
494519
/>
495520
</CollapsibleContent>
496521
</Collapsible>
497-
</template>
522+
</Fold>
498523
</template>
499524

500525
<!-- Whatever the surface wants between the session and the way out of it. -->
501526
<slot name="extra" />
502527

503528
<Separator />
504529

505-
<Button
506-
:variant="canManage ? 'destructive' : 'outline'"
507-
size="lg"
508-
:class="panelCta"
509-
:loading="ending"
510-
@click="end()"
511-
>
512-
{{
513-
canManage
514-
? $t("pages.utility.practice.stop")
515-
: $t("pages.utility.practice.leave")
516-
}}
517-
</Button>
530+
<!-- Staff who joined are still offered Leave: Stop sits beside it, and
531+
asks first, because it ends the server for everyone on it. -->
532+
<HeightSwap>
533+
<div
534+
v-if="confirmingStop && staffCanStop"
535+
key="confirm"
536+
class="flex flex-col gap-2 rounded-md border border-destructive/50 bg-destructive/[0.06] p-2.5"
537+
>
538+
<p class="text-xs text-foreground/90">
539+
{{
540+
hostName
541+
? $t("pages.utility.practice.staff_stop_ask", { host: hostName })
542+
: $t("pages.utility.practice.staff_stop_ask_unnamed")
543+
}}
544+
</p>
545+
<div class="flex items-center justify-end gap-2">
546+
<Button
547+
variant="outline"
548+
size="sm"
549+
class="h-7"
550+
:disabled="stopping"
551+
@click="confirmingStop = false"
552+
>
553+
{{ $t("common.cancel") }}
554+
</Button>
555+
<Button
556+
variant="destructive"
557+
size="sm"
558+
class="h-7"
559+
:loading="stopping"
560+
@click="staffStop()"
561+
>
562+
{{ $t("pages.utility.practice.stop") }}
563+
</Button>
564+
</div>
565+
</div>
566+
<div v-else key="way-out" class="flex items-center gap-1.5">
567+
<Button
568+
:variant="canManage ? 'destructive' : 'outline'"
569+
size="lg"
570+
:class="[panelCta, 'min-w-0 flex-1']"
571+
:loading="ending"
572+
@click="end()"
573+
>
574+
{{
575+
canManage
576+
? $t("pages.utility.practice.stop")
577+
: $t("pages.utility.practice.leave")
578+
}}
579+
</Button>
580+
<Button
581+
v-if="staffCanStop"
582+
variant="outline"
583+
size="lg"
584+
class="shrink-0 font-bold uppercase tracking-[0.22em] text-destructive hover:text-destructive"
585+
:disabled="ending"
586+
@click="confirmingStop = true"
587+
>
588+
{{ $t("pages.utility.practice.staff_stop") }}
589+
</Button>
590+
</div>
591+
</HeightSwap>
518592
</div>
519593
</template>

‎graphql/utilityGraphql.ts‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,10 @@ export const utilityPracticeSessionFields = {
133133
// Named, not just numbered: the only useful thing to tell a guest whose
134134
// server is on the wrong map is who can move it.
135135
host: { name: true },
136-
// Only for the staff console and the pod log tail below the panel; nothing
137-
// player-facing reads it.
138-
match: { server_id: true },
136+
// The staff console's server. Not match.server_id: practice matches are
137+
// hidden from every role, so that is always null. Null too for anyone RCON
138+
// would refuse, which is what decides whether the console is offered.
139+
server_id: true,
139140
team_id: true,
140141
map_name: true,
141142
map_changing_at: true,
@@ -153,6 +154,7 @@ export const utilityPracticeSessionFields = {
153154
is_member: true,
154155
can_view: true,
155156
can_manage: true,
157+
can_stop: true,
156158
} as const;
157159

158160
// A drill record belongs to a player, so signed out there is none to read --
@@ -736,9 +738,9 @@ export const inviteToUtilityPracticeMutation = generateMutation({
736738
});
737739

738740
/**
739-
* The other half of stopUtilityPractice. Stopping is gated on `can_manage`, and
740-
* without this a joiner has no way out of a session at all — they are on the
741-
* roster of a server they cannot end and cannot leave.
741+
* The other half of stopUtilityPractice. Stopping is the host's (and staff's,
742+
* through `can_stop`), and without this a joiner has no way out of a session at
743+
* all — they are on the roster of a server they cannot end and cannot leave.
742744
*/
743745
export const leaveUtilityPracticeMutation = generateMutation({
744746
leaveUtilityPractice: [

‎i18n/locales/ar_SA.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "اطلب من {host} تبديلها.",
69356935
"ask_the_host": "اطلب من المضيف تبديلها.",
69366936
"nav_switching": "قيد التبديل",
6937-
"operator": "وحدة تحكم الخادم"
6937+
"operator": "وحدة تحكم الخادم",
6938+
"staff_stop": "إيقاف",
6939+
"staff_stop_ask": "إيقاف خادم {host} لكل من عليه؟",
6940+
"staff_stop_ask_unnamed": "إيقاف هذا الخادم لكل من عليه؟"
69386941
},
69396942
"collections": {
69406943
"tab": "المجموعات",

‎i18n/locales/da_DK.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "Bed {host} om at skifte den.",
69356935
"ask_the_host": "Bed værten om at skifte den.",
69366936
"nav_switching": "Skifter",
6937-
"operator": "Serverkonsol"
6937+
"operator": "Serverkonsol",
6938+
"staff_stop": "Stop",
6939+
"staff_stop_ask": "Stop serveren, som {host} har startet, for alle på den?",
6940+
"staff_stop_ask_unnamed": "Stop denne server for alle på den?"
69386941
},
69396942
"collections": {
69406943
"tab": "Samlinger",

‎i18n/locales/de_DE.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "Bitte {host}, sie zu wechseln.",
69356935
"ask_the_host": "Bitte den Host, sie zu wechseln.",
69366936
"nav_switching": "Wechselt",
6937-
"operator": "Server-Konsole"
6937+
"operator": "Server-Konsole",
6938+
"staff_stop": "Stoppen",
6939+
"staff_stop_ask": "Server von {host} für alle darauf stoppen?",
6940+
"staff_stop_ask_unnamed": "Diesen Server für alle darauf stoppen?"
69386941
},
69396942
"collections": {
69406943
"tab": "Sammlungen",

‎i18n/locales/en.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "Ask {host} to switch it.",
69356935
"ask_the_host": "Ask the host to switch it.",
69366936
"nav_switching": "Switching",
6937-
"operator": "Server console"
6937+
"operator": "Server console",
6938+
"staff_stop": "Stop",
6939+
"staff_stop_ask": "Stop {host}'s server for everyone on it?",
6940+
"staff_stop_ask_unnamed": "Stop this server for everyone on it?"
69386941
},
69396942
"collections": {
69406943
"tab": "Collections",

‎i18n/locales/es_ES.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "Pídele a {host} que lo cambie.",
69356935
"ask_the_host": "Pídele al anfitrión que lo cambie.",
69366936
"nav_switching": "Cambiando",
6937-
"operator": "Consola del servidor"
6937+
"operator": "Consola del servidor",
6938+
"staff_stop": "Detener",
6939+
"staff_stop_ask": "¿Detener el servidor de {host} para todos los que están en él?",
6940+
"staff_stop_ask_unnamed": "¿Detener este servidor para todos los que están en él?"
69386941
},
69396942
"collections": {
69406943
"tab": "Colecciones",

‎i18n/locales/fr_FR.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "Demandez à {host} de la changer.",
69356935
"ask_the_host": "Demandez à l'hôte de la changer.",
69366936
"nav_switching": "Changement",
6937-
"operator": "Console du serveur"
6937+
"operator": "Console du serveur",
6938+
"staff_stop": "Arrêter",
6939+
"staff_stop_ask": "Arrêter le serveur de {host} pour tous ceux qui y sont ?",
6940+
"staff_stop_ask_unnamed": "Arrêter ce serveur pour tous ceux qui y sont ?"
69386941
},
69396942
"collections": {
69406943
"tab": "Collections",

‎i18n/locales/it_IT.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "Chiedi a {host} di cambiarla.",
69356935
"ask_the_host": "Chiedi all'host di cambiarla.",
69366936
"nav_switching": "Cambio mappa",
6937-
"operator": "Console del server"
6937+
"operator": "Console del server",
6938+
"staff_stop": "Ferma",
6939+
"staff_stop_ask": "Fermare il server di {host} per tutti quelli che ci sono?",
6940+
"staff_stop_ask_unnamed": "Fermare questo server per tutti quelli che ci sono?"
69386941
},
69396942
"collections": {
69406943
"tab": "Raccolte",

‎i18n/locales/ja_JP.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6934,7 +6934,10 @@
69346934
"ask_host": "{host} に切り替えを頼んでください。",
69356935
"ask_the_host": "ホストに切り替えを頼んでください。",
69366936
"nav_switching": "切り替え中",
6937-
"operator": "サーバーコンソール"
6937+
"operator": "サーバーコンソール",
6938+
"staff_stop": "停止",
6939+
"staff_stop_ask": "{host} のサーバーを、参加中の全員に対して停止しますか?",
6940+
"staff_stop_ask_unnamed": "このサーバーを、参加中の全員に対して停止しますか?"
69386941
},
69396942
"collections": {
69406943
"tab": "コレクション",

0 commit comments

Comments
 (0)