Skip to content

Commit 73924c4

Browse files
Merge branch 'master' into cuegui-fix-preview-bytes-decode
2 parents 60763a3 + fb245dc commit 73924c4

125 files changed

Lines changed: 2956 additions & 51 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cueweb/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ The current CueWeb system offers a robust set of features designed to enhance us
376376
- **LAN access:** the client builds same-origin relative URLs for every API call by default, so the app loads correctly from any host (`http://<lan-ip>:3000` from a phone, `http://localhost:3000` on the dev Mac). Clipboard has an `execCommand("copy")` fallback for plain-HTTP LAN deployments where the modern Clipboard API is unavailable.
377377
- **Auto-reloading:** Real-time updates for tables.
378378
- **Job-finished notifications (two channels):** A per-row **Notify bell** subscribes the browser - a background poller fires a toast (and an optional desktop popup when notification permission is granted) when the job reaches `FINISHED`; subscriptions persist in `localStorage`, sync across tabs, and the notify decision is serialized cross-tab via the Web Locks API so only one tab toasts when several poll the same job. The right-click **Subscribe to Job** menu entry opens a CueGUI-parity dialog that registers an *email* subscriber on Cuebot via the `AddSubscriber` RPC, so Cuebot mails the saved address when the job finishes. The two channels are independent.
379+
- **Job dependencies (CueGUI parity):** the job right-click menu groups four entries together. **View Dependencies...** opens a themed dialog mirroring CueGUI's `DependDialog` - a Type / Target / Active / OnJob / OnLayer / OnFrame table backed by the `GetDepends` RPC, with a **Refresh** button. **Dependency Wizard...** is a multi-step state machine covering every CueGUI `depend.DependType` (Job-On-Job / Layer / Frame, Frame By Frame for all layers / Hard Depend, Layer-On-Job / Layer / Frame, Frame By Frame, Frame-On-Job / Layer / Frame, Layer on Simulation Frame); every picker (source layers / frames, target jobs / layers / frames) is multi-select and Done fires the full source x target cross-product in one bulk batch. The Hard Depend variant pairs source/target layers by `layer.type` and fans out one `CreateFrameByFrameDependency` per matched pair across every picked target job. **Drop External Dependencies** and **Drop Internal Dependencies** call the `DropDepends` RPC with `target = EXTERNAL` / `INTERNAL` respectively; on success they dispatch `cueweb:refresh-now` and `cueweb:depends-changed` so the Jobs table re-polls and the Group-By Dependent tree cache rebuilds immediately.
379380
- **Logs:** View current and previous logs via dropdown.
380381
- **Security:** Use JWT-based authorization and secure headers.
381382
- **Keyboard shortcuts:** Press `?` anywhere in the app to open a cheat-sheet overlay; the same overlay is also reachable from **Other ▸ Show Shortcuts** in the header or the sidebar. An optional **Notify on Shortcut** toggle (also under Other) fires a toast naming the shortcut that just triggered. See [Keyboard shortcuts](#keyboard-shortcuts) below for the full list.

cueweb/__tests__/cuesubmit/builders.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,11 @@ describe("buildJobSpecXml", () => {
341341
// (deplayer) waiting on "preview" (onlayer) to finish.
342342
expect(xml).toContain("<deplayer>final</deplayer>");
343343
expect(xml).toContain("<onlayer>preview</onlayer>");
344+
// depjob / onjob must carry the job name - cuebot's JobSpec runs both
345+
// through conformJobName, which throws on empty strings. See
346+
// pyoutline/outline/backend/cue.py:478-487 for the reference shape.
347+
expect(xml).toContain("<depjob>test_job</depjob>");
348+
expect(xml).toContain("<onjob>test_job</onjob>");
344349
});
345350

346351
test("omits <depends> body when no layer has dependencyType set", () => {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Contributors to the OpenCue Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { handleRoute } from '@/app/utils/api_utils';
18+
import { NextRequest, NextResponse } from "next/server";
19+
20+
// Mirrors Frame.createDependencyOnFrame (pycue) ->
21+
// FrameInterface.CreateDependencyOnFrame. Used by the Frame On Frame
22+
// and Layer on Simulation Frame dependency wizard flows. The latter
23+
// loops this call once per source frame in the source layer.
24+
export async function POST(request: NextRequest) {
25+
const endpoint = "/frame.FrameInterface/CreateDependencyOnFrame";
26+
const method = request.method;
27+
if (method !== 'POST') {
28+
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
29+
}
30+
31+
let jsonBody: any;
32+
try {
33+
jsonBody = await request.json();
34+
} catch {
35+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
36+
}
37+
if (!jsonBody || typeof jsonBody !== 'object' || Array.isArray(jsonBody) || !jsonBody.frame || !jsonBody.depend_on_frame) {
38+
return NextResponse.json({ error: 'Invalid request body (need {frame, depend_on_frame})' }, { status: 400 });
39+
}
40+
41+
const response = await handleRoute(method, endpoint, JSON.stringify(jsonBody), true);
42+
const responseData = await response.json();
43+
if (!response.ok) {
44+
return NextResponse.json({ error: responseData?.error ?? 'Upstream error' }, { status: response.status });
45+
}
46+
return NextResponse.json({ data: responseData.data }, { status: response.status });
47+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright Contributors to the OpenCue Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { handleRoute } from '@/app/utils/api_utils';
18+
import { NextRequest, NextResponse } from "next/server";
19+
20+
// Mirrors Frame.createDependencyOnJob (pycue) ->
21+
// FrameInterface.CreateDependencyOnJob. Used by the Frame On Job
22+
// dependency wizard flow.
23+
export async function POST(request: NextRequest) {
24+
const endpoint = "/frame.FrameInterface/CreateDependencyOnJob";
25+
const method = request.method;
26+
if (method !== 'POST') {
27+
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
28+
}
29+
30+
let jsonBody: any;
31+
try {
32+
jsonBody = await request.json();
33+
} catch {
34+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
35+
}
36+
if (!jsonBody || typeof jsonBody !== 'object' || Array.isArray(jsonBody) || !jsonBody.frame || !jsonBody.job) {
37+
return NextResponse.json({ error: 'Invalid request body (need {frame, job})' }, { status: 400 });
38+
}
39+
40+
const response = await handleRoute(method, endpoint, JSON.stringify(jsonBody), true);
41+
const responseData = await response.json();
42+
if (!response.ok) {
43+
return NextResponse.json({ error: responseData?.error ?? 'Upstream error' }, { status: response.status });
44+
}
45+
return NextResponse.json({ data: responseData.data }, { status: response.status });
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright Contributors to the OpenCue Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { handleRoute } from '@/app/utils/api_utils';
18+
import { NextRequest, NextResponse } from "next/server";
19+
20+
// Mirrors Frame.createDependencyOnLayer (pycue) ->
21+
// FrameInterface.CreateDependencyOnLayer. Used by the Frame On Layer
22+
// dependency wizard flow.
23+
export async function POST(request: NextRequest) {
24+
const endpoint = "/frame.FrameInterface/CreateDependencyOnLayer";
25+
const method = request.method;
26+
if (method !== 'POST') {
27+
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
28+
}
29+
30+
let jsonBody: any;
31+
try {
32+
jsonBody = await request.json();
33+
} catch {
34+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
35+
}
36+
if (!jsonBody || typeof jsonBody !== 'object' || Array.isArray(jsonBody) || !jsonBody.frame || !jsonBody.layer) {
37+
return NextResponse.json({ error: 'Invalid request body (need {frame, layer})' }, { status: 400 });
38+
}
39+
40+
const response = await handleRoute(method, endpoint, JSON.stringify(jsonBody), true);
41+
const responseData = await response.json();
42+
if (!response.ok) {
43+
return NextResponse.json({ error: responseData?.error ?? 'Upstream error' }, { status: response.status });
44+
}
45+
return NextResponse.json({ data: responseData.data }, { status: response.status });
46+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Contributors to the OpenCue Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { handleRoute } from '@/app/utils/api_utils';
18+
import { NextRequest, NextResponse } from "next/server";
19+
20+
// Mirrors Cuedepend.createJobOnFrameDepend (cuegui/cuegui/Cuedepend.py) ->
21+
// JobInterface.CreateDependencyOnFrame. Adds a "this job depends on that
22+
// frame" link so the dependent job stays paused until the target frame
23+
// completes.
24+
export async function POST(request: NextRequest) {
25+
const endpoint = "/job.JobInterface/CreateDependencyOnFrame";
26+
const method = request.method;
27+
if (method !== 'POST') {
28+
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
29+
}
30+
31+
let jsonBody: any;
32+
try {
33+
jsonBody = await request.json();
34+
} catch {
35+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
36+
}
37+
if (!jsonBody || typeof jsonBody !== 'object' || Array.isArray(jsonBody) || !jsonBody.job || !jsonBody.frame) {
38+
return NextResponse.json({ error: 'Invalid request body (need {job, frame})' }, { status: 400 });
39+
}
40+
41+
const response = await handleRoute(method, endpoint, JSON.stringify(jsonBody), true);
42+
const responseData = await response.json();
43+
if (!response.ok) {
44+
return NextResponse.json({ error: responseData?.error ?? 'Upstream error' }, { status: response.status });
45+
}
46+
return NextResponse.json({ data: responseData.data }, { status: response.status });
47+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright Contributors to the OpenCue Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { handleRoute } from '@/app/utils/api_utils';
18+
import { NextRequest, NextResponse } from "next/server";
19+
20+
// Mirrors Cuedepend.createJobOnJobDepend (cuegui/cuegui/Cuedepend.py) ->
21+
// JobInterface.CreateDependencyOnJob. Adds a "this job depends on that
22+
// job" link; Cuebot pauses the dependent job until the on_job satisfies.
23+
export async function POST(request: NextRequest) {
24+
const endpoint = "/job.JobInterface/CreateDependencyOnJob";
25+
const method = request.method;
26+
if (method !== 'POST') {
27+
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
28+
}
29+
30+
let jsonBody: any;
31+
try {
32+
jsonBody = await request.json();
33+
} catch {
34+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
35+
}
36+
if (!jsonBody || typeof jsonBody !== 'object' || Array.isArray(jsonBody) || !jsonBody.job || !jsonBody.on_job) {
37+
return NextResponse.json({ error: 'Invalid request body (need {job, on_job})' }, { status: 400 });
38+
}
39+
40+
const response = await handleRoute(method, endpoint, JSON.stringify(jsonBody), true);
41+
const responseData = await response.json();
42+
if (!response.ok) {
43+
return NextResponse.json({ error: responseData?.error ?? 'Upstream error' }, { status: response.status });
44+
}
45+
return NextResponse.json({ data: responseData.data }, { status: response.status });
46+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Contributors to the OpenCue Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { handleRoute } from '@/app/utils/api_utils';
18+
import { NextRequest, NextResponse } from "next/server";
19+
20+
// Mirrors Cuedepend.createJobOnLayerDepend (cuegui/cuegui/Cuedepend.py) ->
21+
// JobInterface.CreateDependencyOnLayer. Adds a "this job depends on that
22+
// layer" link so the dependent job stays paused until every frame of the
23+
// target layer completes.
24+
export async function POST(request: NextRequest) {
25+
const endpoint = "/job.JobInterface/CreateDependencyOnLayer";
26+
const method = request.method;
27+
if (method !== 'POST') {
28+
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
29+
}
30+
31+
let jsonBody: any;
32+
try {
33+
jsonBody = await request.json();
34+
} catch {
35+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
36+
}
37+
if (!jsonBody || typeof jsonBody !== 'object' || Array.isArray(jsonBody) || !jsonBody.job || !jsonBody.layer) {
38+
return NextResponse.json({ error: 'Invalid request body (need {job, layer})' }, { status: 400 });
39+
}
40+
41+
const response = await handleRoute(method, endpoint, JSON.stringify(jsonBody), true);
42+
const responseData = await response.json();
43+
if (!response.ok) {
44+
return NextResponse.json({ error: responseData?.error ?? 'Upstream error' }, { status: response.status });
45+
}
46+
return NextResponse.json({ data: responseData.data }, { status: response.status });
47+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright Contributors to the OpenCue Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { fetchObjectFromRestGateway } from '@/app/utils/api_utils';
18+
import { NextRequest, NextResponse } from "next/server";
19+
20+
// Mirrors cuegui.JobActions.viewDepends -> DependDialog
21+
// (cuegui/cuegui/MenuActions.py, cuegui/cuegui/DependDialog.py). Returns
22+
// the depend.DependSeq for the job so the View Dependencies dialog can
23+
// render the Type / Target / Active / OnJob / OnLayer / OnFrame table.
24+
export async function POST(request: NextRequest) {
25+
const endpoint = "/job.JobInterface/GetDepends";
26+
const method = request.method;
27+
if (method !== 'POST') {
28+
return NextResponse.json({ error: 'Invalid method. Only POST is allowed.' }, { status: 405 });
29+
}
30+
31+
let jsonBody: any;
32+
try {
33+
jsonBody = await request.json();
34+
} catch {
35+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
36+
}
37+
if (!jsonBody || typeof jsonBody !== 'object' || Array.isArray(jsonBody) || !jsonBody.job) {
38+
return NextResponse.json({ error: 'Invalid request body (need {job})' }, { status: 400 });
39+
}
40+
41+
const response = await fetchObjectFromRestGateway(endpoint, method, JSON.stringify(jsonBody));
42+
const responseData = await response.json();
43+
44+
if (!response.ok) {
45+
return NextResponse.json(
46+
{ error: responseData?.error ?? 'Upstream error' },
47+
{ status: response.status },
48+
);
49+
}
50+
return NextResponse.json({ data: responseData.data }, { status: response.status });
51+
}

0 commit comments

Comments
 (0)