Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3dfd04b
[cueweb] Add Services (Facility Service Defaults) page (CueCommander …
ramonfigueiredo Jun 12, 2026
311cfb1
[cueweb] Add Stuck Frames page (CueCommander parity)
ramonfigueiredo Jun 12, 2026
e5502de
[cueweb] Stuck Frames: full StuckFramePlugin parity (CueCommander)
ramonfigueiredo Jun 12, 2026
791ddde
[cueweb] Monitor Hosts: Full CueCommander parity
ramonfigueiredo Jun 12, 2026
6015a08
[cueweb] Add Monitor Cue page (CueCommander parity)
ramonfigueiredo Jun 12, 2026
912cf57
[cueweb] Job/Layer/Frame context-menu parity + frame log viewer enhan…
ramonfigueiredo Jun 13, 2026
fc32257
Merge branch 'cueweb/job-layer-frame-actions-and-log-viewer' into cue…
ramonfigueiredo Jun 13, 2026
42c8846
[cueweb] Monitor Cue: CueGUI parity for menu, columns, booking bar, s…
ramonfigueiredo Jun 13, 2026
72bee10
Merge branch 'master' into cueweb-monitor-hosts-full
ramonfigueiredo Jun 13, 2026
3cedab5
Merge branch 'master' into cueweb/job-layer-frame-actions-and-log-viewer
ramonfigueiredo Jun 13, 2026
46edae4
Merge branch 'master' into cueweb-monitor-cue
ramonfigueiredo Jun 13, 2026
15ce44e
[cueweb] Address Monitor Hosts review: route cleanup, races, a11y, fi…
ramonfigueiredo Jun 13, 2026
53a59a2
[cueweb] Monitor Hosts: ownership, comments parity, tag refinements, …
ramonfigueiredo Jun 13, 2026
4ac4247
Merge branch 'master' into cueweb-facility-service-defaults
ramonfigueiredo Jun 13, 2026
a15f4bd
Merge branch 'master' into cueweb-stuck-frames
ramonfigueiredo Jun 13, 2026
8245516
[cueweb] Services: surface load/save/delete failures instead of maski…
ramonfigueiredo Jun 15, 2026
e57ab7f
[cueweb] Stuck Frames: bound per-job fan-out, paginate frames, surfac…
ramonfigueiredo Jun 15, 2026
5870b61
[cueweb] Stuck Frames: address review feedback
ramonfigueiredo Jun 15, 2026
74f55e2
[cueweb] Monitor Hosts: gate comment save on action success
ramonfigueiredo Jun 15, 2026
f7de7fb
[cueweb] Stuck Frames: address review feedback
ramonfigueiredo Jun 15, 2026
8260178
[cueweb] Monitor Cue: address review feedback
ramonfigueiredo Jun 15, 2026
dd59f8a
[cueweb] frame/preview: canonicalize with realpath before the root check
ramonfigueiredo Jun 15, 2026
0fc75ae
[cueweb] Stuck Frames: address review feedback
ramonfigueiredo Jun 15, 2026
2cf2f6f
[cueweb] Layer/frame actions & log viewer: address review feedback
ramonfigueiredo Jun 15, 2026
7d4a516
Merge branch 'cueweb/job-layer-frame-actions-and-log-viewer' of https…
ramonfigueiredo Jun 15, 2026
9d72247
Merge branch 'cueweb-monitor-cue' into cueweb/job-layer-frame-actions…
ramonfigueiredo Jun 15, 2026
e13790e
[cueweb] Stuck Frames: address review feedback
ramonfigueiredo Jun 16, 2026
956df07
[cueweb/docs] Document Facility Service Defaults (Services) page
ramonfigueiredo Jun 16, 2026
11d20e0
[cueweb] Monitor Cue: Add show right-click menu (Properties, Create G…
ramonfigueiredo Jun 17, 2026
8457de4
Merge branch 'cueweb-facility-service-defaults' into cueweb-monitor-cue
ramonfigueiredo Jun 17, 2026
cf7b48c
[cueweb] Monitor Cue: group folder tree + Service Properties (show ov…
ramonfigueiredo Jun 17, 2026
69f2737
[cueweb] Monitor Cue: harden API routes and dialogs per review feedback
ramonfigueiredo Jun 17, 2026
6c98a2e
[cueweb] Frame log viewer: version metadata, download, and error jumper
ramonfigueiredo Jun 18, 2026
9dfcd27
[cueweb] Attributes panel: frame selection + copy key/value buttons
ramonfigueiredo Jun 18, 2026
567e905
Merge branch 'cueweb-monitor-hosts-full' into cueweb/job-layer-frame-…
ramonfigueiredo Jun 18, 2026
0e211f2
Merge branch 'cueweb-monitor-cue' into cueweb/job-layer-frame-actions…
ramonfigueiredo Jun 18, 2026
bc964e0
Merge branch 'cueweb-stuck-frames' into cueweb/job-layer-frame-action…
ramonfigueiredo Jun 18, 2026
bae451b
[cueweb] Wire Attributes panel to all entity views (CueGUI parity)
ramonfigueiredo Jun 18, 2026
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
6 changes: 6 additions & 0 deletions cueweb/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
NEXT_PUBLIC_OPENCUE_ENDPOINT=http://your-rest-gateway-url.com

# Optional allow-list for the frame preview route (/api/frame/preview), as a
# colon-separated list of absolute path prefixes. When set, the server only
# serves preview images located under one of these roots. When unset, preview
# reads are not restricted to a root (set this to harden the deployment).
# CUEWEB_PREVIEW_ROOTS=/mnt/render:/shows

# Sentry values
SENTRY_ENVIRONMENT='development'
SENTRY_DSN = sentrydsn
Expand Down
23 changes: 23 additions & 0 deletions cueweb/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,29 @@ ENV NEXT_PUBLIC_OPENCUE_ENDPOINT=${NEXT_PUBLIC_OPENCUE_ENDPOINT}
ARG NEXT_PUBLIC_LOG_EDITOR_URL=
ENV NEXT_PUBLIC_LOG_EDITOR_URL=${NEXT_PUBLIC_LOG_EDITOR_URL}

# CueProgBar launcher (Monitor Jobs "Show Progress Bar"). CueProgBar is a
# desktop tool (python -m cuegui.cueguiplugin.cueprogbar <job>); a browser
# can only reach it via a registered URL scheme. {job} is replaced with the
# job name. Leave empty to fall back to the in-table Progress column.
ARG NEXT_PUBLIC_CUEPROGBAR_URL=
ENV NEXT_PUBLIC_CUEPROGBAR_URL=${NEXT_PUBLIC_CUEPROGBAR_URL}

# The CueProgBar command shown in the "Show Progress Bar" dialog. {job} is
# replaced with the job name. Sites override this with their own launcher,
# e.g. "spawn launch cueprogbar {job}".
ARG NEXT_PUBLIC_CUEPROGBAR_COMMAND=python -m cuegui.cueguiplugin.cueprogbar {job}
ENV NEXT_PUBLIC_CUEPROGBAR_COMMAND=${NEXT_PUBLIC_CUEPROGBAR_COMMAND}

# "Preview All" (Frames menu) opens rendered output in an external image
# viewer. PREVIEW_COMMAND is the command shown/copied; PREVIEW_URL is an
# optional registered URL scheme the Launch button hands off to a local
# handler. Placeholders {paths} (output paths), {job}, {layer}, {frame} are
# substituted. Override per site, e.g. a viewer scheme "openrv://{paths}".
ARG NEXT_PUBLIC_PREVIEW_COMMAND=rv {paths}
ENV NEXT_PUBLIC_PREVIEW_COMMAND=${NEXT_PUBLIC_PREVIEW_COMMAND}
ARG NEXT_PUBLIC_PREVIEW_URL=
ENV NEXT_PUBLIC_PREVIEW_URL=${NEXT_PUBLIC_PREVIEW_URL}

# Authentication providers - use ARG to allow override at build time
# Set to empty string to disable authentication (sandbox mode)
# Set to comma-separated list for production (e.g., "google,okta,github,ldap")
Expand Down
36 changes: 36 additions & 0 deletions cueweb/app/api/department/getdepartmentnames/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { handleRoute } from '@/app/utils/api_utils';
import { NextRequest, NextResponse } from "next/server";

// List the known department names (for the Group dialog's Department dropdown).
// Request: {} (empty). RPC: /department.DepartmentInterface/GetDepartmentNames.
export async function POST(request: NextRequest) {
const endpoint = "/department.DepartmentInterface/GetDepartmentNames";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
}

const response = await handleRoute(method, endpoint, JSON.stringify({}));
const responseData = await response.json();

if (!response.ok) {
return NextResponse.json({ error: responseData?.error ?? "Failed to fetch department names" }, { status: response.status });
}
return NextResponse.json({ data: responseData.data?.names ?? [] }, { status: response.status });
}
39 changes: 39 additions & 0 deletions cueweb/app/api/department/gettasks/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { handleRoute } from '@/app/utils/api_utils';
import { NextRequest, NextResponse } from "next/server";

// List a department's tasks. Request: { department }.
// RPC: /department.DepartmentInterface/GetTasks (nested under tasks.tasks).
export async function POST(request: NextRequest) {
const endpoint = "/department.DepartmentInterface/GetTasks";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
}

const body = JSON.stringify(await request.json());
const jsonBody = JSON.parse(body);
if (!jsonBody || typeof jsonBody !== 'object' || !jsonBody.department?.id) {
Comment thread
ramonfigueiredo marked this conversation as resolved.
Outdated
return NextResponse.json({ error: 'Invalid request body: department is required' }, { status: 400 });
}

const response = await handleRoute(method, endpoint, body);
const responseData = await response.json();
if (!response.ok) return NextResponse.json({ error: responseData.error }, { status: response.status });
return NextResponse.json({ data: responseData.data?.tasks?.tasks ?? [] }, { status: response.status });
}
39 changes: 39 additions & 0 deletions cueweb/app/api/filter/getactions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { handleRoute } from '@/app/utils/api_utils';
import { NextRequest, NextResponse } from "next/server";

// List a filter's actions. Request: { filter }.
// RPC: /filter.FilterInterface/GetActions (nested under actions.actions).
export async function POST(request: NextRequest) {
const endpoint = "/filter.FilterInterface/GetActions";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
}

const body = JSON.stringify(await request.json());
const jsonBody = JSON.parse(body);
if (!jsonBody || typeof jsonBody !== 'object' || !jsonBody.filter?.id) {
Comment thread
ramonfigueiredo marked this conversation as resolved.
Outdated
return NextResponse.json({ error: 'Invalid request body: filter is required' }, { status: 400 });
}

const response = await handleRoute(method, endpoint, body);
const responseData = await response.json();
if (!response.ok) return NextResponse.json({ error: responseData.error }, { status: response.status });
return NextResponse.json({ data: responseData.data?.actions?.actions ?? [] }, { status: response.status });
}
39 changes: 39 additions & 0 deletions cueweb/app/api/filter/getmatchers/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { handleRoute } from '@/app/utils/api_utils';
import { NextRequest, NextResponse } from "next/server";

// List a filter's matchers. Request: { filter }.
// RPC: /filter.FilterInterface/GetMatchers (nested under matchers.matchers).
export async function POST(request: NextRequest) {
const endpoint = "/filter.FilterInterface/GetMatchers";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
}

const body = JSON.stringify(await request.json());
const jsonBody = JSON.parse(body);
if (!jsonBody || typeof jsonBody !== 'object' || !jsonBody.filter?.id) {
Comment thread
ramonfigueiredo marked this conversation as resolved.
Outdated
return NextResponse.json({ error: 'Invalid request body: filter is required' }, { status: 400 });
}

const response = await handleRoute(method, endpoint, body);
const responseData = await response.json();
if (!response.ok) return NextResponse.json({ error: responseData.error }, { status: response.status });
return NextResponse.json({ data: responseData.data?.matchers?.matchers ?? [] }, { status: response.status });
}
71 changes: 71 additions & 0 deletions cueweb/app/api/filter/mutate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { handleRoute } from '@/app/utils/api_utils';
import { NextRequest, NextResponse } from "next/server";

// Consolidated proxy for the View Filters dialog mutations. The body is
// { op, ...payload }; `op` selects one of the allowlisted Filter / Matcher /
// Action RPCs and the rest of the body is forwarded verbatim. Returns the
// gateway response data (the created object for create ops).
const ENDPOINTS: Record<string, string> = {
// ShowInterface
"show.createfilter": "/show.ShowInterface/CreateFilter",
// FilterInterface
"filter.setname": "/filter.FilterInterface/SetName",
"filter.settype": "/filter.FilterInterface/SetType",
"filter.setenabled": "/filter.FilterInterface/SetEnabled",
"filter.setorder": "/filter.FilterInterface/SetOrder",
"filter.raiseorder": "/filter.FilterInterface/RaiseOrder",
"filter.lowerorder": "/filter.FilterInterface/LowerOrder",
"filter.orderfirst": "/filter.FilterInterface/OrderFirst",
"filter.orderlast": "/filter.FilterInterface/OrderLast",
"filter.delete": "/filter.FilterInterface/Delete",
"filter.creatematcher": "/filter.FilterInterface/CreateMatcher",
"filter.createaction": "/filter.FilterInterface/CreateAction",
// MatcherInterface
"matcher.commit": "/filter.MatcherInterface/Commit",
"matcher.delete": "/filter.MatcherInterface/Delete",
// ActionInterface
"action.commit": "/filter.ActionInterface/Commit",
"action.delete": "/filter.ActionInterface/Delete",
};

export async function POST(request: NextRequest) {
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
}

let jsonBody: any;
try {
jsonBody = await request.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 });
}
const { op, ...payload } = jsonBody ?? {};
const endpoint = typeof op === 'string' ? ENDPOINTS[op] : undefined;
if (!endpoint) {
return NextResponse.json({ error: `Unknown filter op: ${op}` }, { status: 400 });
}

const response = await handleRoute(method, endpoint, JSON.stringify(payload), true);
const responseData = await response.json();
if (!response.ok) {
return NextResponse.json({ error: responseData?.error ?? `Failed: ${op}` }, { status: response.status });
}
return NextResponse.json({ data: responseData.data ?? { success: true } }, { status: response.status });
Comment thread
ramonfigueiredo marked this conversation as resolved.
}
2 changes: 1 addition & 1 deletion cueweb/app/api/frame/action/createdependonframe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { NextRequest, NextResponse } from "next/server";
// and Layer on Simulation Frame dependency wizard flows. The latter
// loops this call once per source frame in the source layer.
export async function POST(request: NextRequest) {
const endpoint = "/frame.FrameInterface/CreateDependencyOnFrame";
const endpoint = "/job.FrameInterface/CreateDependencyOnFrame";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
Expand Down
2 changes: 1 addition & 1 deletion cueweb/app/api/frame/action/createdependonjob/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { NextRequest, NextResponse } from "next/server";
// FrameInterface.CreateDependencyOnJob. Used by the Frame On Job
// dependency wizard flow.
export async function POST(request: NextRequest) {
const endpoint = "/frame.FrameInterface/CreateDependencyOnJob";
const endpoint = "/job.FrameInterface/CreateDependencyOnJob";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
Expand Down
2 changes: 1 addition & 1 deletion cueweb/app/api/frame/action/createdependonlayer/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { NextRequest, NextResponse } from "next/server";
// FrameInterface.CreateDependencyOnLayer. Used by the Frame On Layer
// dependency wizard flow.
export async function POST(request: NextRequest) {
const endpoint = "/frame.FrameInterface/CreateDependencyOnLayer";
const endpoint = "/job.FrameInterface/CreateDependencyOnLayer";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
Expand Down
42 changes: 42 additions & 0 deletions cueweb/app/api/frame/action/dropdepends/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { handleRoute } from '@/app/utils/api_utils';
import { NextRequest, NextResponse } from "next/server";

// Drop all dependencies on a frame (CueGUI FrameActions.dropDepends).
// RPC: /job.FrameInterface/DropDepends. Request: { frame, target } where
// target is a depend.DependTarget name (default "ANY_TARGET" = drop all).
export async function POST(request: NextRequest) {
const endpoint = "/job.FrameInterface/DropDepends";
if (request.method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
}
let jsonBody: any;
try {
jsonBody = await request.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
}
if (!jsonBody?.frame) {
return NextResponse.json({ error: 'Invalid request body (need {frame})' }, { status: 400 });
}
if (!jsonBody.target) jsonBody.target = "ANY_TARGET";
const response = await handleRoute(request.method, endpoint, JSON.stringify(jsonBody), true);
const responseData = await response.json();
if (!response.ok) return NextResponse.json({ error: responseData.error }, { status: response.status });
return NextResponse.json({ data: responseData.data }, { status: response.status });
}
41 changes: 41 additions & 0 deletions cueweb/app/api/frame/action/getdepends/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Contributors to the OpenCue Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { handleRoute } from '@/app/utils/api_utils';
import { NextRequest, NextResponse } from "next/server";

// Return the dependencies a frame depends on (CueGUI DependDialog ->
// getWhatThisDependsOn). RPC: /job.FrameInterface/GetWhatThisDependsOn.
// Request: { frame }. Response wraps a depend.DependSeq.
export async function POST(request: NextRequest) {
const endpoint = "/job.FrameInterface/GetWhatThisDependsOn";
const method = request.method;
if (method !== 'POST') {
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
}

const body = JSON.stringify(await request.json());
const jsonBody = JSON.parse(body);
if (!jsonBody || typeof jsonBody !== 'object' || !jsonBody.frame) {
return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
}

const response = await handleRoute(method, endpoint, body, true);
const responseData = await response.json();

if (!response.ok) return NextResponse.json({ error: responseData.error }, { status: response.status });
return NextResponse.json({ data: responseData.data }, { status: response.status });
}
Loading
Loading