Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: backups.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: backupschedules.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: clusters.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: gameservers.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: gametemplates.gameplane.local
spec:
Expand Down Expand Up @@ -222,6 +222,15 @@ spec:
description: ActionParamSpec is a single input collected
before running an action.
properties:
currentFrom:
description: |-
CurrentFrom names a status metric (spec.capabilities.status.metrics[].id)
whose live value pre-fills this input in the dashboard dialog, so a
"set" action opens on the current setting. Default applies when the
metric has no reading or (for Type=enum) no option matches it.
maxLength: 63
pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
type: string
default:
description: Default is the pre-filled value (as a
string).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: modules.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: modulesources.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: networkcaptures.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: restores.gameplane.local
spec:
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_backups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: backups.gameplane.local
spec:
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_backupschedules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: backupschedules.gameplane.local
spec:
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: clusters.gameplane.local
spec:
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_gameservers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: gameservers.gameplane.local
spec:
Expand Down
11 changes: 10 additions & 1 deletion charts/gameplane/crds/gameplane.local_gametemplates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: gametemplates.gameplane.local
spec:
Expand Down Expand Up @@ -222,6 +222,15 @@ spec:
description: ActionParamSpec is a single input collected
before running an action.
properties:
currentFrom:
description: |-
CurrentFrom names a status metric (spec.capabilities.status.metrics[].id)
whose live value pre-fills this input in the dashboard dialog, so a
"set" action opens on the current setting. Default applies when the
metric has no reading or (for Type=enum) no option matches it.
maxLength: 63
pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
type: string
default:
description: Default is the pre-filled value (as a
string).
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_modules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: modules.gameplane.local
spec:
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_modulesources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: modulesources.gameplane.local
spec:
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_networkcaptures.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: networkcaptures.gameplane.local
spec:
Expand Down
2 changes: 1 addition & 1 deletion charts/gameplane/crds/gameplane.local_restores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 13361941435c18767a393a107f2a80242b0e84339630ad0db002d46d00e1e62d
gameplane.local/crd-bundle-sha256: 5fa59369664cfab2e0c8ccedfd665ca4dfbd1f3f933e778bd8e1d6bae52c6eea
controller-gen.kubebuilder.io/version: v0.20.1
name: restores.gameplane.local
spec:
Expand Down
9 changes: 9 additions & 0 deletions docs/module-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,15 @@ capabilities:
agent — the shared `gameaction` module — so neither is trusted blind.
- A command template that fails to parse disables only that one action
(logged), never the whole panel.
- **`currentFrom`** (optional, on a param) names a
`capabilities.status.metrics[].id`. When the dialog opens, the dashboard
pre-fills the param with that metric's live value, so a "set" action
starts on the current setting. An `enum` param takes the option that
matches case-insensitively (a reading of `Easy` selects `easy`), a `bool`
takes `true`/`false` and an `int` takes an integer. With no reading, or
no valid match, the param falls back to `default`; the user's own choice
always wins. Readings come from the Status metrics below, so they need
RCON. Minecraft's `set-difficulty` uses `currentFrom: difficulty`.

#### Status

Expand Down
9 changes: 9 additions & 0 deletions operator/api/v1alpha1/gametemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,15 @@ type ActionParamSpec struct {
// +optional
Default string `json:"default,omitempty"`

// CurrentFrom names a status metric (spec.capabilities.status.metrics[].id)
// whose live value pre-fills this input in the dashboard dialog, so a
// "set" action opens on the current setting. Default applies when the
// metric has no reading or (for Type=enum) no option matches it.
// +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`
// +kubebuilder:validation:MaxLength=63
// +optional
CurrentFrom string `json:"currentFrom,omitempty"`

// Enum restricts valid values when Type=enum.
// +optional
Enum []string `json:"enum,omitempty"`
Expand Down
9 changes: 9 additions & 0 deletions operator/config/crd/gameplane.local_gametemplates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,15 @@ spec:
description: ActionParamSpec is a single input collected
before running an action.
properties:
currentFrom:
description: |-
CurrentFrom names a status metric (spec.capabilities.status.metrics[].id)
whose live value pre-fills this input in the dashboard dialog, so a
"set" action opens on the current setting. Default applies when the
metric has no reading or (for Type=enum) no option matches it.
maxLength: 63
pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
type: string
default:
description: Default is the pre-filled value (as a
string).
Expand Down
2 changes: 1 addition & 1 deletion web/specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ Console input/output and Logs streaming use existing bidirectional WebSocket (co
- **WipeServerDialog.tsx** (123 lines) — Wipe-world confirmation from the HeroUI `AlertDialog` family plus `Checkbox` (import lines 3–14) — not ui/ConfirmDialog
- **DeleteServerDialog.tsx** (52 lines) — Delete server confirmation via ui/ ConfirmDialog (line 2)
- **ServerStatusCard.tsx** (73 lines) — Status summary card in Overview tab, built on HeroUI Card (line 3); displays "—" when the agent reports no game version instead of falling back to template name
- **ServerActionsCard.tsx** (487 lines) — Lifecycle action card in Overview tab; HeroUI Button/Card/CardHeader/CardContent/Modal family/Input/Label/Select/ListBox/ListBoxItem/Checkbox/Description/FieldError (import lines 20–39)
- **ServerActionsCard.tsx** (487 lines) — Lifecycle action card in Overview tab; HeroUI Button/Card/CardHeader/CardContent/Modal family/Input/Label/Select/ListBox/ListBoxItem/Checkbox/Description/FieldError (import lines 20–39). A param with `currentFrom` is pre-filled from that status metric's live reading (shared `server-status` query; enum options match case-insensitively), falling back to `default`; the user's edits always take precedence
- **ServerSleepCard.tsx** (170 lines) — Server sleep/idle state summary; HeroUI Card/Alert (line 2) plus a `Chip` re-exported from ui/PhaseChip (line 6)
- **EventList.tsx** (43 lines) — Kubernetes event list renderer, used from both Events.tsx (line 6) and Overview.tsx (line 12)
- **PortOverridesEditor.tsx** (80 lines) — Port configuration helper; HeroUI Input/Button (line 1) (Settings tab, deferred)
Expand Down
86 changes: 86 additions & 0 deletions web/src/components/server/ServerActionsCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,92 @@ describe("ServerActionsCard", () => {
);
});

describe("currentFrom", () => {
const difficultyAction: ServerActionDecl = {
id: "set-difficulty",
displayName: "Set difficulty",
params: [
{
name: "level",
displayName: "Difficulty",
type: "enum",
enum: ["peaceful", "easy", "normal", "hard"],
default: "normal",
required: true,
currentFrom: "difficulty",
},
],
};

// routeStatus layers a /servers/s1/status answer over routeFetch.
function routeStatus(readings: unknown) {
const base = fetchMock.getMockImplementation();
fetchMock.mockImplementation((url: string, opts?: { method?: string; body?: string }) =>
url.endsWith("/servers/s1/status")
? Promise.resolve(jsonRes(readings))
: base?.(url, opts),
);
}

it("pre-fills an enum param from its status metric, matching case-insensitively", async () => {
const runs: RunCall[] = [];
routeFetch("operator", runs);
routeStatus([{ id: "difficulty", value: "Easy" }]);
renderWithQuery(<ServerActionsCard name="s1" tmpl={tmpl([difficultyAction])} />);
const open = await screen.findByRole("button", { name: /set difficulty/i });
await waitFor(() => expect(open).not.toBeDisabled());
fireEvent.click(open);

// The Select trigger is a button named by its label (see the enum test above).
const trigger = screen.getByRole("button", { name: /difficulty/i });
await waitFor(() => expect(trigger).toHaveTextContent("easy"));
fireEvent.click(screen.getByRole("button", { name: "Run" }));
await waitFor(() =>
expect(runs).toEqual([{ id: "set-difficulty", params: { level: "easy" } }]),
);
});

it("keeps the user's choice over the live reading", async () => {
const runs: RunCall[] = [];
routeFetch("operator", runs);
routeStatus([{ id: "difficulty", value: "Easy" }]);
renderWithQuery(<ServerActionsCard name="s1" tmpl={tmpl([difficultyAction])} />);
const open = await screen.findByRole("button", { name: /set difficulty/i });
await waitFor(() => expect(open).not.toBeDisabled());
fireEvent.click(open);

const trigger = screen.getByRole("button", { name: /difficulty/i });
await waitFor(() => expect(trigger).toHaveTextContent("easy"));
fireEvent.click(trigger);
fireEvent.click(await screen.findByRole("option", { name: "hard" }));
await waitFor(() => expect(trigger).toHaveTextContent("hard"));
fireEvent.click(screen.getByRole("button", { name: "Run" }));
await waitFor(() =>
expect(runs).toEqual([{ id: "set-difficulty", params: { level: "hard" } }]),
);
});

it("falls back to the default when the reading matches no option", async () => {
const runs: RunCall[] = [];
routeFetch("operator", runs);
routeStatus([{ id: "difficulty", value: "Extreme" }]);
renderWithQuery(<ServerActionsCard name="s1" tmpl={tmpl([difficultyAction])} />);
await waitFor(() =>
expect(
fetchMock.mock.calls.some(([u]) => String(u).endsWith("/servers/s1/status")),
).toBe(true),
);
const open = await screen.findByRole("button", { name: /set difficulty/i });
await waitFor(() => expect(open).not.toBeDisabled());
fireEvent.click(open);

fireEvent.click(await screen.findByRole("button", { name: "Run" }));
await waitFor(() =>
expect(runs).toEqual([{ id: "set-difficulty", params: { level: "normal" } }]),
);
});
});

it("disables actions for a viewer", async () => {
routeFetch("viewer", []);
renderWithQuery(
Expand Down
Loading
Loading