Skip to content

POC: Scheduler Config & Logs Dashboard Exploration#262

Draft
aryunewaskar77-art wants to merge 5 commits into
volcano-sh:mainfrom
aryunewaskar77-art:feature/config-logs-tab
Draft

POC: Scheduler Config & Logs Dashboard Exploration#262
aryunewaskar77-art wants to merge 5 commits into
volcano-sh:mainfrom
aryunewaskar77-art:feature/config-logs-tab

Conversation

@aryunewaskar77-art
Copy link
Copy Markdown

This proof-of-concept explores the proposed /scheduler section for Volcano Dashboard and focuses on two core areas from issue #197:

  • Config Tab
  • Logs Tab

The goal of this work is not to implement the entire mentorship project, but to validate assumptions around dashboard architecture, Kubernetes integration patterns, and implementation complexity.

Implemented exploration areas:

Config Tab

  • Scheduler ConfigMap retrieval (volcano-scheduler-configmap)
  • YAML parsing and structured UI state transformation
  • YAML ↔ UI workflow exploration
  • Live YAML preview generation
  • ConfigMap update flow investigation
Screenshot 2026-05-18 at 4 27 18 PM

Logs Tab

  • Component selection support
  • Kubernetes pod log retrieval
  • tailLines support
  • Keyword filtering
  • Live refresh behavior
Screenshot 2026-05-18 at 4 27 42 PM

This POC was built to better understand the interaction between Dashboard UI components, tRPC procedures, Kubernetes APIs, and scheduler-specific workflows before proposing a phased implementation plan.

Future exploration:

  • Metrics tab integration
  • SSE-based streaming
  • Plugin schema validation
  • Optimistic locking and diff-aware save workflows

aryunewaskar77-art added 3 commits May 17, 2026 00:03
Signed-off-by: aryunewaskar77-art <aaryaanewaskar@gmail.com>
…Dashboard

- Initialize react-i18next and configure EN/ZH translation infrastructure
- Localize Dashboard, Jobs, Pods, Queues, and PodGroups views
- Add persistent language switcher with robust language detection
- Implement interpolation-based dynamic resource count translations
- Refactor error handling for reactive language updates
- Standardize Kubernetes terminology and dynamic labels
- Localize resource creation dialogs and dynamic placeholders
- Clean up duplicate keys and improve translation consistency

Signed-off-by: aryunewaskar77-art <aaryaanewaskar@gmail.com>
Signed-off-by: aryunewaskar77-art <aaryaanewaskar@gmail.com>
@volcano-sh-bot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign jessestutler for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces new backend endpoints for configuration management and log streaming, along with a comprehensive frontend overhaul that includes internationalization, new configuration editors, and various UI components. The review identified several critical issues, including a bug in log line splitting, missing YAML validation for configuration updates, potential runtime errors in the log stream's error handling, and flaws in the type inference logic for configuration arguments. It is also recommended to remove temporary test scripts from the repository to maintain a clean codebase.

Comment thread backend/src/server.js Outdated
Comment on lines +1090 to +1128
const k8sLog = new Log(kc);
const logStream = new stream.PassThrough();

logStream.on("data", (chunk) => {
const dataStr = chunk.toString();
const lines = dataStr.split("\n");
lines.forEach((line) => {
if (line.trim()) {
res.write(`data: ${line}\n\n`);
}
});
});

const logRequest = await k8sLog.log(
"volcano-system",
podName,
undefined, // containerName
logStream,
{
follow: true,
tailLines,
pretty: false,
}
);

// Handle Disconnection
req.on("close", () => {
console.log(`Client disconnected from log stream for ${podName}`);
try {
if (logRequest) logRequest.destroy();
} catch (err) {
console.error("Error destroying log request:", err);
}
try {
logStream.destroy();
} catch (err) {
console.error("Error destroying log stream:", err);
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current log streaming implementation has several issues:

  1. Line Splitting Bug: It splits chunks by \n and writes them immediately. If a log line is split across two chunks (e.g., part1 and part2\n), it will be sent as two separate SSE messages, breaking the log output.
  2. Empty Line Filtering: if (line.trim()) incorrectly skips legitimate empty log lines or lines containing only whitespace.
  3. Cleanup Timing: The close handler is registered after the await k8sLog.log call. If the client disconnects while the request is being initiated, the cleanup logic won't run, leading to potential resource leaks.

Using the readline module solves the splitting issue and provides a cleaner interface.

        // Initialize Log Stream
        const k8sLog = new Log(kc);
        const logStream = new stream.PassThrough();
        const rl = readline.createInterface({ input: logStream, terminal: false });

        rl.on("line", (line) => {
            if (!res.destroyed) {
                res.write(`data: ${line}\n\n`);
            }
        });

        let logRequest;
        req.on("close", () => {
            console.log(`Client disconnected from log stream for ${podName}`);
            try {
                if (logRequest) logRequest.destroy();
            } catch (err) {
                console.error("Error destroying log request:", err);
            }
            try {
                logStream.destroy();
                rl.close();
            } catch (err) {
                console.error("Error destroying log stream:", err);
            }
        });

        logRequest = await k8sLog.log(
            "volcano-system",
            podName,
            undefined, // containerName
            logStream,
            {
                follow: true,
                tailLines,
                pretty: false,
            }
        );

Comment thread backend/src/server.js Outdated
Log,
} from "@kubernetes/client-node";
import yaml from "js-yaml";
import stream from "stream";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The readline module is required to correctly handle line-by-line streaming of logs from the Kubernetes API, especially when dealing with chunked data that might split a single log line across multiple chunks.

Suggested change
import stream from "stream";
import stream from "stream";
import readline from "readline";

Comment thread backend/src/server.js Outdated
name: "volcano-scheduler-configmap",
namespace: "volcano-system",
});
const conf = cm.data ? cm.data["volcano-scheduler.conf"] : "";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the ConfigMap exists but the volcano-scheduler.conf key is missing from the data object, conf will be undefined. This can cause issues when sending the JSON response or during frontend parsing. It's safer to provide a default empty string.

Suggested change
const conf = cm.data ? cm.data["volcano-scheduler.conf"] : "";
const conf = cm.data?.["volcano-scheduler.conf"] || "";

Comment thread backend/src/server.js Outdated
Comment on lines +1011 to +1014
const { conf } = req.body;
if (typeof conf !== "string") {
return res.status(400).json({ error: "Invalid body, conf must be string" });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The PATCH endpoint should validate that the provided configuration is at least valid YAML before attempting to update the ConfigMap. This prevents malformed data from being saved, which could cause the scheduler to fail upon reload.

        const { conf } = req.body;
        if (typeof conf !== "string") {
            return res.status(400).json({ error: "Invalid body, conf must be string" });
        }
        try {
            yaml.load(conf);
        } catch (e) {
            return res.status(400).json({ error: "Invalid YAML format in configuration" });
        }

Comment thread backend/src/server.js Outdated
Comment on lines +1132 to +1135
res.status(500).json({
error: "Failed to initiate log stream",
details: err.body?.message || err.message,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If an error occurs after res.writeHead has been called (e.g., during k8sLog.log), attempting to call res.status(500) will throw an 'ERR_HTTP_HEADERS_SENT' error, crashing the request handler. You should check res.headersSent before attempting to set the status code.

    } catch (err) {
        console.error("Error initiating log stream:", err);
        if (!res.headersSent) {
            res.status(500).json({
                error: "Failed to initiate log stream",
                details: err.body?.message || err.message,
            });
        } else {
            res.end();
        }
    }

Comment thread backend/test_patch.js
Comment on lines +1 to +22
const k8s = require('@kubernetes/client-node');
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const api = kc.makeApiClient(k8s.CoreV1Api);

async function test() {
try {
const options = { headers: { 'Content-Type': 'application/merge-patch+json' } };
const res = await api.patchNamespacedConfigMap(
{
name: 'volcano-scheduler-configmap',
namespace: 'volcano-system',
body: { data: { "volcano-scheduler.conf": "test" } }
},
options
);
console.log("Success!");
} catch (err) {
console.log("Error:", err.body);
}
}
test();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test script appears to be a temporary development artifact and should be removed from the repository to maintain a clean codebase.

Comment thread frontend/src/pages/Config/yamlUtils.ts Outdated
Comment on lines +31 to +37
let typedVal: string | number | boolean = String(val);
if (val === "true" || val === true) typedVal = true;
else if (val === "false" || val === false)
typedVal = false;
else if (!isNaN(Number(val)) && typeof val !== "boolean")
typedVal = Number(val);
else typedVal = val as string | number | boolean;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for inferring types from plugin arguments is flawed. Specifically, Number("") returns 0, which means an empty string argument will be incorrectly converted to the number 0. Additionally, String(val) followed by checks against boolean literals is redundant if the value is already a boolean. A more robust check for numeric strings is needed.

Suggested change
let typedVal: string | number | boolean = String(val);
if (val === "true" || val === true) typedVal = true;
else if (val === "false" || val === false)
typedVal = false;
else if (!isNaN(Number(val)) && typeof val !== "boolean")
typedVal = Number(val);
else typedVal = val as string | number | boolean;
let typedVal: string | number | boolean = val;
if (val === "true") typedVal = true;
else if (val === "false") typedVal = false;
else if (typeof val === "string" && val.trim() !== "" && !isNaN(Number(val))) {
typedVal = Number(val);
}

Signed-off-by: aryunewaskar77-art <aaryaanewaskar@gmail.com>
@volcano-sh-bot
Copy link
Copy Markdown
Contributor

Adding label do-not-merge/contains-merge-commits because PR contains merge commits, which are not allowed in this repository.
Use git rebase to reapply your commits on top of the target branch. Detailed instructions for doing so can be found here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants