zebflow 0.10.3

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
import { cx, useState } from "zeb/react";
import { requestJson } from "@/components/lib/http";
import Button from "@/components/ui/button";
import CommitDialog from "@/components/ui/commit-dialog";
import { settingsStatusToneClass } from "@/pages/project-studio/settings/components/settings-lib";
import SettingsSection from "@/pages/project-studio/settings/components/settings-section";

/** Defaults every pipeline run starts from. */
export default function RuntimeDefaultsPanel({ api, initialConfig }) {
  const [maxMb, setMaxMb] = useState(Number(initialConfig?.max_asset_size_mb ?? 10));
  const [fileMaxMb, setFileMaxMb] = useState(Number(initialConfig?.max_file_size_mb ?? 1024));
  const [webhookMaxMb, setWebhookMaxMb] = useState(Number(initialConfig?.webhook_body_max_mb ?? 100));
  const [nodeTimeoutSecs, setNodeTimeoutSecs] = useState(Number(initialConfig?.pipeline_node_timeout_secs ?? 30));
  const [statusMsg, setStatusMsg] = useState("Ready.");
  const [statusTone, setStatusTone] = useState("info");
  const [saving, setSaving] = useState(false);
  const [commitOpen, setCommitOpen] = useState(false);
  const [pendingData, setPendingData] = useState(null);

  function handleSubmit(e) {
    e.preventDefault();
    setPendingData({
      max_asset_size_mb: maxMb,
      max_file_size_mb: fileMaxMb,
      webhook_body_max_mb: webhookMaxMb,
      pipeline_node_timeout_secs: nodeTimeoutSecs,
    });
    setCommitOpen(true);
  }

  async function handleCommit(commitMessage) {
    setCommitOpen(false);
    setSaving(true);
    setStatusMsg("Saving...");
    setStatusTone("info");
    try {
      const resp = await requestJson(api, {
        method: "PUT",
        body: JSON.stringify({ commit_message: commitMessage, data: pendingData }),
      });
      if (resp?.committed) {
        setStatusMsg("Saved & committed.");
        setStatusTone("ok");
      } else if (resp?.git_error) {
        setStatusMsg(`Saved (git: ${resp.git_error})`);
        setStatusTone("info");
      } else {
        setStatusMsg("Saved.");
        setStatusTone("ok");
      }
    } catch (err) {
      setStatusMsg(`Failed: ${err?.message || String(err)}`);
      setStatusTone("error");
    } finally {
      setSaving(false);
      setPendingData(null);
    }
  }

  return (
    <SettingsSection
      title="Runtime Defaults"
      description="Project-level upload limits and node execution timeout defaults."
      tag="Assets"
    >
      <CommitDialog
        open={commitOpen}
        section="assets"
        defaultMessage="settings(assets): update runtime defaults"
        onConfirm={handleCommit}
        onCancel={() => { setCommitOpen(false); setPendingData(null); }}
      />
      <form className="flex flex-col gap-[0.65rem]" onSubmit={handleSubmit}>
        <label className="pipeline-editor-field">
          <span>Max asset upload size: <strong>{maxMb} MB</strong></span>
          <input
            type="range"
            name="max_asset_size_mb"
            min={5}
            max={1024}
            step={1}
            value={maxMb}
            onInput={(e) => setMaxMb(Number((e.target as HTMLInputElement).value))}
            className="w-full cursor-pointer accent-dark-accent1"
          />
          <small className="pipeline-editor-field-help">
            Maximum file size per uploaded project asset (5–1024 MB).
          </small>
        </label>
        <label className="pipeline-editor-field">
          <span>Max FS upload size: <strong>{fileMaxMb} MB</strong></span>
          <input
            type="range"
            name="max_file_size_mb"
            min={5}
            max={1024}
            step={1}
            value={fileMaxMb}
            onInput={(e) => setFileMaxMb(Number((e.target as HTMLInputElement).value))}
            className="w-full cursor-pointer accent-dark-accent1"
          />
          <small className="pipeline-editor-field-help">
            Per-file limit for Project Files uploads at <code>/api/projects/.../files/upload</code> (5–1024 MB).
          </small>
        </label>
        <label className="pipeline-editor-field">
          <span>Webhook body max: <strong>{webhookMaxMb} MB</strong></span>
          <input
            type="range"
            name="webhook_body_max_mb"
            min={100}
            max={1024}
            step={1}
            value={webhookMaxMb}
            onInput={(e) => setWebhookMaxMb(Number((e.target as HTMLInputElement).value))}
            className="w-full cursor-pointer accent-dark-accent1"
          />
          <small className="pipeline-editor-field-help">
            Per-project logical request limit for <code>/wh/...</code> uploads (100–1024 MB). This should be at least as large as the documents your webhook pipelines need to accept.
          </small>
        </label>
        <label className="pipeline-editor-field">
          <span>Pipeline node timeout: <strong>{nodeTimeoutSecs} sec</strong></span>
          <input
            type="range"
            name="pipeline_node_timeout_secs"
            min={5}
            max={3600}
            step={5}
            value={nodeTimeoutSecs}
            onInput={(e) => setNodeTimeoutSecs(Number((e.target as HTMLInputElement).value))}
            className="w-full cursor-pointer accent-dark-accent1"
          />
          <small className="pipeline-editor-field-help">
            Maximum runtime per node before timeout (5–3600 sec effective). Raise this for heavy transforms like PDF conversion.
          </small>
        </label>
        <div className="flex items-center gap-[0.7rem]">
          <Button
            type="submit"
            variant="primary"
            size="sm"
            disabled={saving}
            label={saving ? "Saving..." : "Save Defaults"}
          />
          <span className={cx("text-[0.72rem]", settingsStatusToneClass(statusTone))}>{statusMsg}</span>
        </div>
      </form>
    </SettingsSection>
  );
}