fix server creation run binding
This commit is contained in:
@@ -621,7 +621,7 @@ describe("PlatformApiClient AI providers", () => {
|
||||
await expect(client.listArtifacts({ ownerKind: "job", ownerId: job.id, state: "available" })).resolves.toMatchObject({ count: 1, items: [{ id: artifact.id }] });
|
||||
await expect(client.openArtifactDownload(artifact.id)).resolves.toMatchObject({ downloadUrl: "/api/v1/artifacts/artifact-1/content", rangeSupported: true });
|
||||
await expect(client.readArtifactContent(artifact.id, 0, 8)).resolves.toMatchObject({ contentLength: 8, contentRange: "bytes 0-7/18", checksum: artifact.checksum });
|
||||
await expect(client.createServerWorkflow({ id: "server-2", pluginId: plugin.id, runEndpointId: endpoint.id, name: "Server 2", idempotencyKey: "idem-create", profileKey: "local", bindings: {} })).resolves.toMatchObject({
|
||||
await expect(client.createServerWorkflow({ id: "server-2", pluginId: plugin.id, name: "Server 2", idempotencyKey: "idem-create" })).resolves.toMatchObject({
|
||||
action: "create"
|
||||
});
|
||||
await expect(client.startServerInstance(server.id, { expectedConfigVersion: 1, idempotencyKey: "idem-start" })).resolves.toMatchObject({ action: "start" });
|
||||
|
||||
@@ -22,7 +22,7 @@ Normal browser login uses the platform's HttpOnly SameSite cookie and `credentia
|
||||
|
||||
## Server Management Workflows
|
||||
|
||||
- `createServerWorkflow` posts `ServerLifecycleCreateRequest` with the create-wizard deployment definition to `/server-instances/workflows/create`, including deployment mode, plugin create inputs, and custom startup fields when provided. It does not require deployment target, run endpoint, or runtime profile selection during creation.
|
||||
- `createServerWorkflow` posts `ServerLifecycleCreateRequest` with the create-wizard deployment definition to `/server-instances/workflows/create`, including deployment mode, plugin create inputs, and custom startup fields when provided. It must not include deployment target, run endpoint, runtime profile, or runtime bindings during creation; those are established only after creation through generated Run registration, runtime binding, or deployment update flows.
|
||||
- `getServerRuntimeBinding` reads `/server-instances/{id}/runtime-binding`; `updateServerRuntimeBinding` patches the selected profile and logical refs. Responses contain only profile metadata, logical key names, configured/secret-backed flags, missing keys, and safe reasons. They never contain stored refs or secret values.
|
||||
- `startServerInstance` and `stopServerInstance` post `ServerLifecycleCommandRequest` with the current config version and receive the lifecycle job response.
|
||||
- `listServerAdministratorCandidates`, `addServerAdministrator`, and `removeServerAdministrator` call server membership endpoints so server owners can invite or remove active non-platform-admin server administrators.
|
||||
|
||||
@@ -505,12 +505,8 @@ export interface ServerInstanceListResponse {
|
||||
export interface ServerLifecycleCreateRequest {
|
||||
id: string;
|
||||
pluginId: string;
|
||||
deploymentTargetId?: string;
|
||||
runEndpointId?: string;
|
||||
name: string;
|
||||
idempotencyKey: string;
|
||||
profileKey?: string;
|
||||
bindings?: Record<string, string>;
|
||||
deployment?: ServerDeploymentRequest;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,13 +116,17 @@ describe("ServerDeploymentWorkflow", () => {
|
||||
pluginId: "game.runtime",
|
||||
name: "Custom Runtime Server",
|
||||
idempotencyKey: "web:create:server-custom-runtime-server-17:17",
|
||||
runEndpointId: undefined,
|
||||
deployment: {
|
||||
mode: "custom-command",
|
||||
serverRoot: "/srv/custom-runtime",
|
||||
startCommand: "./start-runtime.sh"
|
||||
}
|
||||
});
|
||||
expect(submitted && "runEndpointId" in submitted).toBe(false);
|
||||
expect(submitted && "profileKey" in submitted).toBe(false);
|
||||
expect(submitted && "bindings" in submitted).toBe(false);
|
||||
expect(submitted?.deployment && "profileKey" in submitted.deployment).toBe(false);
|
||||
expect(submitted?.deployment && "runtimeBindings" in submitted.deployment).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { UsersPage } from "./UsersPage";
|
||||
import runtimeTaskProgressSource from "../components/RuntimeTaskProgress.tsx?raw";
|
||||
import serverDeploymentWorkflowSource from "../components/ServerDeploymentWorkflow.tsx?raw";
|
||||
import serverLiveOperationsSource from "../components/ServerLiveOperations.tsx?raw";
|
||||
import serverCreateSchemaSource from "../schemas/serverManagement.ts?raw";
|
||||
import serversPageSource from "./ServersPage.tsx?raw";
|
||||
import serverDetailPageSource from "./ServerDetailPage.tsx?raw";
|
||||
import type { PageComponentProps } from "../contracts/page";
|
||||
@@ -239,6 +240,10 @@ describe("first-party console pages", () => {
|
||||
expect(serverDeploymentWorkflowSource).toContain('const configurationStep = kind === "create" ? 2 : needsTargetSelection ? 1 : 0;');
|
||||
expect(serverDeploymentWorkflowSource).toContain('const needsTargetSelection = kind === "edit" && !initialForm.runEndpointId;');
|
||||
expect(serversPageSource).toContain("serverCreateRequestFromForm(nextForm)");
|
||||
expect(serverCreateSchemaSource).not.toContain("runEndpointId: form.runEndpointId");
|
||||
expect(serverCreateSchemaSource).not.toContain("deploymentTargetId: form.deploymentTargetId");
|
||||
expect(serverCreateSchemaSource).not.toContain("profileKey: form.profileKey");
|
||||
expect(serverCreateSchemaSource).not.toContain("runtimeBindings:");
|
||||
});
|
||||
|
||||
it("renders server runtime actions as a compact popover trigger instead of an in-card details stack", () => {
|
||||
|
||||
@@ -107,9 +107,7 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
pluginId: plugin?.id ?? "",
|
||||
profileKey,
|
||||
bindings: plugin?.id === current.pluginId && profileKey === current.profileKey ? current.bindings : {},
|
||||
runEndpointId: endpointResponse.items.some((endpoint) => endpoint.id === current.runEndpointId)
|
||||
? current.runEndpointId
|
||||
: endpointResponse.items[0]?.id || ""
|
||||
runEndpointId: endpointResponse.items.some((endpoint) => endpoint.id === current.runEndpointId) ? current.runEndpointId : ""
|
||||
};
|
||||
});
|
||||
if (showLoading) {
|
||||
|
||||
@@ -53,7 +53,7 @@ describe("runtime profile server creation contracts", () => {
|
||||
expect(runtimeBindingFields(plugin, "local").some((field) => field.key === "ftp.profile")).toBe(false);
|
||||
});
|
||||
|
||||
it("selects the plugin profile and submits real profile bindings", () => {
|
||||
it("submits deployment inputs without binding a Run or runtime profile", () => {
|
||||
const form = defaultServerCreateForm([plugin], []);
|
||||
expect(form.profileKey).toBe("local");
|
||||
expect(
|
||||
@@ -69,15 +69,10 @@ describe("runtime profile server creation contracts", () => {
|
||||
).toEqual({
|
||||
id: "server-1",
|
||||
pluginId: "game.runtime",
|
||||
runEndpointId: undefined,
|
||||
name: "Runtime Server",
|
||||
idempotencyKey: "web:create:server-1:17",
|
||||
profileKey: "local",
|
||||
bindings: { "server-root": "runtime.server-root", "rcon.password": "secret://runtime/server-1/rcon" },
|
||||
deployment: {
|
||||
mode: "guided-install",
|
||||
profileKey: "local",
|
||||
runtimeBindings: { "server-root": "runtime.server-root", "rcon.password": "secret://runtime/server-1/rcon" },
|
||||
createInputs: { gamePort: "7777", maxPlayers: "64" }
|
||||
}
|
||||
});
|
||||
@@ -117,7 +112,11 @@ describe("runtime profile server creation contracts", () => {
|
||||
it("keeps complete paths and commands in a write-only deployment payload", () => {
|
||||
const form = defaultServerCreateForm([plugin], []);
|
||||
const request = serverCreateRequestFromForm({ ...form, name: "Venv Server", deploymentMode: "custom-command", serverRoot: "/srv/venv-server", workingDirectory: "/srv/venv-server", startCommand: "/srv/venv-server/.venv/bin/python server.py", shell: "" }, 19);
|
||||
expect(request.runEndpointId).toBeUndefined();
|
||||
expect("runEndpointId" in request).toBe(false);
|
||||
expect("profileKey" in request).toBe(false);
|
||||
expect("bindings" in request).toBe(false);
|
||||
expect(request.deployment).toMatchObject({ mode: "custom-command", serverRoot: "/srv/venv-server", workingDirectory: "/srv/venv-server", startCommand: "/srv/venv-server/.venv/bin/python server.py" });
|
||||
expect("profileKey" in (request.deployment ?? {})).toBe(false);
|
||||
expect("runtimeBindings" in (request.deployment ?? {})).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,16 +26,10 @@ export function serverCreateRequestFromForm(form: ServerCreateFormState, sequenc
|
||||
return {
|
||||
id,
|
||||
pluginId: form.pluginId.trim(),
|
||||
deploymentTargetId: form.deploymentTargetId.trim() || undefined,
|
||||
runEndpointId: form.runEndpointId.trim() || undefined,
|
||||
name: form.name.trim(),
|
||||
idempotencyKey: lifecycleIdempotencyKey("create", id, sequence),
|
||||
profileKey: form.profileKey.trim() || undefined,
|
||||
bindings: Object.fromEntries(Object.entries(form.bindings).map(([key, value]) => [key, value.trim()]).filter(([, value]) => value !== "")),
|
||||
deployment: {
|
||||
mode: form.deploymentMode,
|
||||
profileKey: form.profileKey.trim() || undefined,
|
||||
runtimeBindings: Object.fromEntries(Object.entries(form.bindings).map(([key, value]) => [key, value.trim()]).filter(([, value]) => value !== "")),
|
||||
createInputs: Object.fromEntries(Object.entries(form.createInputs).map(([key, value]) => [key, value.trim()])),
|
||||
serverRoot: form.serverRoot.trim() || undefined,
|
||||
workingDirectory: form.workingDirectory.trim() || undefined,
|
||||
|
||||
Reference in New Issue
Block a user