feat: implement Spotlight Krate Creation workflow

- Add type-to-trigger Spotlight with keyboard (any character)
- Add canvas click to open Spotlight
- Implement keyboard navigation (↑↓ Enter Esc)
- Add keyboard shortcut handlers and spotlight store
- Create useSpotlight hook with fuzzy search
- Create mock Kubernetes resources for initial testing
- Implement krate creation with collision detection
- Add Quick Actions (all pods, services, deployments, namespaces)
- Create Spotlight with filter chips and result rendering
- Add Spotlight state management with setQuery, setFilter, setSel
- Include design specs (Krates.dc.html, server.js, support.js)
This commit is contained in:
Hermes Agent
2026-06-16 12:27:47 -04:00
parent adcd8ddd39
commit f55f31a6d9
46 changed files with 45336 additions and 93 deletions

File diff suppressed because one or more lines are too long

View File

@@ -31,7 +31,7 @@
50% { opacity: 0.5; }
}
</style>
<script type="module" crossorigin src="/assets/index-C1XavMtG.js"></script>
<script type="module" crossorigin src="/assets/index-CggNxKq-.js"></script>
</head>
<body>
<div id="root"></div>

79
client/node_modules/.vite/deps/_metadata.json generated vendored Normal file
View File

@@ -0,0 +1,79 @@
{
"hash": "572b8421",
"configHash": "721dceed",
"lockfileHash": "7ea8032f",
"browserHash": "445b3fe2",
"optimized": {
"react": {
"src": "../../react/index.js",
"file": "react.js",
"fileHash": "f493482f",
"needsInterop": true
},
"react-dom": {
"src": "../../react-dom/index.js",
"file": "react-dom.js",
"fileHash": "96ce0925",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "2e4301f2",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "7c54f297",
"needsInterop": true
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "53bfca8f",
"needsInterop": true
},
"xterm": {
"src": "../../xterm/lib/xterm.js",
"file": "xterm.js",
"fileHash": "8c9fc642",
"needsInterop": true
},
"xterm-addon-fit": {
"src": "../../xterm-addon-fit/lib/xterm-addon-fit.js",
"file": "xterm-addon-fit.js",
"fileHash": "aa816989",
"needsInterop": true
},
"zustand": {
"src": "../../zustand/esm/index.mjs",
"file": "zustand.js",
"fileHash": "27b4400f",
"needsInterop": false
},
"zustand/middleware": {
"src": "../../zustand/esm/middleware.mjs",
"file": "zustand_middleware.js",
"fileHash": "54ddb76f",
"needsInterop": false
},
"yjs": {
"src": "../../yjs/dist/yjs.mjs",
"file": "yjs.js",
"fileHash": "be3b3e27",
"needsInterop": false
}
},
"chunks": {
"chunk-BVI7NZOO": {
"file": "chunk-BVI7NZOO.js"
},
"chunk-4HAMFFQC": {
"file": "chunk-4HAMFFQC.js"
},
"chunk-EQCVQC35": {
"file": "chunk-EQCVQC35.js"
}
}
}

1906
client/node_modules/.vite/deps/chunk-4HAMFFQC.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

7
client/node_modules/.vite/deps/chunk-4HAMFFQC.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

21628
client/node_modules/.vite/deps/chunk-BVI7NZOO.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

7
client/node_modules/.vite/deps/chunk-BVI7NZOO.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

34
client/node_modules/.vite/deps/chunk-EQCVQC35.js generated vendored Normal file
View File

@@ -0,0 +1,34 @@
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
export {
__commonJS,
__toESM,
__publicField
};
//# sourceMappingURL=chunk-EQCVQC35.js.map

7
client/node_modules/.vite/deps/chunk-EQCVQC35.js.map generated vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

3
client/node_modules/.vite/deps/package.json generated vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

7
client/node_modules/.vite/deps/react-dom.js generated vendored Normal file
View File

@@ -0,0 +1,7 @@
import {
require_react_dom
} from "./chunk-BVI7NZOO.js";
import "./chunk-4HAMFFQC.js";
import "./chunk-EQCVQC35.js";
export default require_react_dom();
//# sourceMappingURL=react-dom.js.map

7
client/node_modules/.vite/deps/react-dom.js.map generated vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

39
client/node_modules/.vite/deps/react-dom_client.js generated vendored Normal file
View File

@@ -0,0 +1,39 @@
import {
require_react_dom
} from "./chunk-BVI7NZOO.js";
import "./chunk-4HAMFFQC.js";
import {
__commonJS
} from "./chunk-EQCVQC35.js";
// node_modules/react-dom/client.js
var require_client = __commonJS({
"node_modules/react-dom/client.js"(exports) {
var m = require_react_dom();
if (false) {
exports.createRoot = m.createRoot;
exports.hydrateRoot = m.hydrateRoot;
} else {
i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
exports.createRoot = function(c, o) {
i.usingClientEntryPoint = true;
try {
return m.createRoot(c, o);
} finally {
i.usingClientEntryPoint = false;
}
};
exports.hydrateRoot = function(c, h, o) {
i.usingClientEntryPoint = true;
try {
return m.hydrateRoot(c, h, o);
} finally {
i.usingClientEntryPoint = false;
}
};
}
var i;
}
});
export default require_client();
//# sourceMappingURL=react-dom_client.js.map

View File

@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../react-dom/client.js"],
"sourcesContent": ["'use strict';\n\nvar m = require('react-dom');\nif (process.env.NODE_ENV === 'production') {\n exports.createRoot = m.createRoot;\n exports.hydrateRoot = m.hydrateRoot;\n} else {\n var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n exports.createRoot = function(c, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.createRoot(c, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n exports.hydrateRoot = function(c, h, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.hydrateRoot(c, h, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n}\n"],
"mappings": ";;;;;;;;;AAAA;AAAA;AAEA,QAAI,IAAI;AACR,QAAI,OAAuC;AACzC,cAAQ,aAAa,EAAE;AACvB,cAAQ,cAAc,EAAE;AAAA,IAC1B,OAAO;AACD,UAAI,EAAE;AACV,cAAQ,aAAa,SAAS,GAAG,GAAG;AAClC,UAAE,wBAAwB;AAC1B,YAAI;AACF,iBAAO,EAAE,WAAW,GAAG,CAAC;AAAA,QAC1B,UAAE;AACA,YAAE,wBAAwB;AAAA,QAC5B;AAAA,MACF;AACA,cAAQ,cAAc,SAAS,GAAG,GAAG,GAAG;AACtC,UAAE,wBAAwB;AAC1B,YAAI;AACF,iBAAO,EAAE,YAAY,GAAG,GAAG,CAAC;AAAA,QAC9B,UAAE;AACA,YAAE,wBAAwB;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAjBM;AAAA;AAAA;",
"names": []
}

6
client/node_modules/.vite/deps/react.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
import {
require_react
} from "./chunk-4HAMFFQC.js";
import "./chunk-EQCVQC35.js";
export default require_react();
//# sourceMappingURL=react.js.map

7
client/node_modules/.vite/deps/react.js.map generated vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

913
client/node_modules/.vite/deps/react_jsx-dev-runtime.js generated vendored Normal file
View File

@@ -0,0 +1,913 @@
import {
require_react
} from "./chunk-4HAMFFQC.js";
import {
__commonJS
} from "./chunk-EQCVQC35.js";
// node_modules/react/cjs/react-jsx-dev-runtime.development.js
var require_react_jsx_dev_runtime_development = __commonJS({
"node_modules/react/cjs/react-jsx-dev-runtime.development.js"(exports) {
"use strict";
if (true) {
(function() {
"use strict";
var React = require_react();
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
var REACT_MEMO_TYPE = Symbol.for("react.memo");
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
var FAUX_ITERATOR_SYMBOL = "@@iterator";
function getIteratorFn(maybeIterable) {
if (maybeIterable === null || typeof maybeIterable !== "object") {
return null;
}
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
if (typeof maybeIterator === "function") {
return maybeIterator;
}
return null;
}
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function error(format) {
{
{
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
printWarning("error", format, args);
}
}
}
function printWarning(level, format, args) {
{
var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame;
var stack = ReactDebugCurrentFrame2.getStackAddendum();
if (stack !== "") {
format += "%s";
args = args.concat([stack]);
}
var argsWithFormat = args.map(function(item) {
return String(item);
});
argsWithFormat.unshift("Warning: " + format);
Function.prototype.apply.call(console[level], console, argsWithFormat);
}
}
var enableScopeAPI = false;
var enableCacheElement = false;
var enableTransitionTracing = false;
var enableLegacyHidden = false;
var enableDebugTracing = false;
var REACT_MODULE_REFERENCE;
{
REACT_MODULE_REFERENCE = Symbol.for("react.module.reference");
}
function isValidElementType(type) {
if (typeof type === "string" || typeof type === "function") {
return true;
}
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) {
return true;
}
if (typeof type === "object" && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) {
return true;
}
}
return false;
}
function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
if (displayName) {
return displayName;
}
var functionName = innerType.displayName || innerType.name || "";
return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName;
}
function getContextName(type) {
return type.displayName || "Context";
}
function getComponentNameFromType(type) {
if (type == null) {
return null;
}
{
if (typeof type.tag === "number") {
error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.");
}
}
if (typeof type === "function") {
return type.displayName || type.name || null;
}
if (typeof type === "string") {
return type;
}
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_CONTEXT_TYPE:
var context = type;
return getContextName(context) + ".Consumer";
case REACT_PROVIDER_TYPE:
var provider = type;
return getContextName(provider._context) + ".Provider";
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
case REACT_MEMO_TYPE:
var outerName = type.displayName || null;
if (outerName !== null) {
return outerName;
}
return getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return getComponentNameFromType(init(payload));
} catch (x) {
return null;
}
}
}
}
return null;
}
var assign = Object.assign;
var disabledDepth = 0;
var prevLog;
var prevInfo;
var prevWarn;
var prevError;
var prevGroup;
var prevGroupCollapsed;
var prevGroupEnd;
function disabledLog() {
}
disabledLog.__reactDisabledLog = true;
function disableLogs() {
{
if (disabledDepth === 0) {
prevLog = console.log;
prevInfo = console.info;
prevWarn = console.warn;
prevError = console.error;
prevGroup = console.group;
prevGroupCollapsed = console.groupCollapsed;
prevGroupEnd = console.groupEnd;
var props = {
configurable: true,
enumerable: true,
value: disabledLog,
writable: true
};
Object.defineProperties(console, {
info: props,
log: props,
warn: props,
error: props,
group: props,
groupCollapsed: props,
groupEnd: props
});
}
disabledDepth++;
}
}
function reenableLogs() {
{
disabledDepth--;
if (disabledDepth === 0) {
var props = {
configurable: true,
enumerable: true,
writable: true
};
Object.defineProperties(console, {
log: assign({}, props, {
value: prevLog
}),
info: assign({}, props, {
value: prevInfo
}),
warn: assign({}, props, {
value: prevWarn
}),
error: assign({}, props, {
value: prevError
}),
group: assign({}, props, {
value: prevGroup
}),
groupCollapsed: assign({}, props, {
value: prevGroupCollapsed
}),
groupEnd: assign({}, props, {
value: prevGroupEnd
})
});
}
if (disabledDepth < 0) {
error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
}
}
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var prefix;
function describeBuiltInComponentFrame(name, source, ownerFn) {
{
if (prefix === void 0) {
try {
throw Error();
} catch (x) {
var match = x.stack.trim().match(/\n( *(at )?)/);
prefix = match && match[1] || "";
}
}
return "\n" + prefix + name;
}
}
var reentry = false;
var componentFrameCache;
{
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
componentFrameCache = new PossiblyWeakMap();
}
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) {
return "";
}
{
var frame = componentFrameCache.get(fn);
if (frame !== void 0) {
return frame;
}
}
var control;
reentry = true;
var previousPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = void 0;
var previousDispatcher;
{
previousDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = null;
disableLogs();
}
try {
if (construct) {
var Fake = function() {
throw Error();
};
Object.defineProperty(Fake.prototype, "props", {
set: function() {
throw Error();
}
});
if (typeof Reflect === "object" && Reflect.construct) {
try {
Reflect.construct(Fake, []);
} catch (x) {
control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x) {
control = x;
}
fn.call(Fake.prototype);
}
} else {
try {
throw Error();
} catch (x) {
control = x;
}
fn();
}
} catch (sample) {
if (sample && control && typeof sample.stack === "string") {
var sampleLines = sample.stack.split("\n");
var controlLines = control.stack.split("\n");
var s = sampleLines.length - 1;
var c = controlLines.length - 1;
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
c--;
}
for (; s >= 1 && c >= 0; s--, c--) {
if (sampleLines[s] !== controlLines[c]) {
if (s !== 1 || c !== 1) {
do {
s--;
c--;
if (c < 0 || sampleLines[s] !== controlLines[c]) {
var _frame = "\n" + sampleLines[s].replace(" at new ", " at ");
if (fn.displayName && _frame.includes("<anonymous>")) {
_frame = _frame.replace("<anonymous>", fn.displayName);
}
{
if (typeof fn === "function") {
componentFrameCache.set(fn, _frame);
}
}
return _frame;
}
} while (s >= 1 && c >= 0);
}
break;
}
}
}
} finally {
reentry = false;
{
ReactCurrentDispatcher.current = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
}
var name = fn ? fn.displayName || fn.name : "";
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : "";
{
if (typeof fn === "function") {
componentFrameCache.set(fn, syntheticFrame);
}
}
return syntheticFrame;
}
function describeFunctionComponentFrame(fn, source, ownerFn) {
{
return describeNativeComponentFrame(fn, false);
}
}
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
if (type == null) {
return "";
}
if (typeof type === "function") {
{
return describeNativeComponentFrame(type, shouldConstruct(type));
}
}
if (typeof type === "string") {
return describeBuiltInComponentFrame(type);
}
switch (type) {
case REACT_SUSPENSE_TYPE:
return describeBuiltInComponentFrame("Suspense");
case REACT_SUSPENSE_LIST_TYPE:
return describeBuiltInComponentFrame("SuspenseList");
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return describeFunctionComponentFrame(type.render);
case REACT_MEMO_TYPE:
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
} catch (x) {
}
}
}
}
return "";
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var loggedTypeFailures = {};
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame.setExtraStackFrame(null);
}
}
}
function checkPropTypes(typeSpecs, values, location, componentName, element) {
{
var has = Function.call.bind(hasOwnProperty);
for (var typeSpecName in typeSpecs) {
if (has(typeSpecs, typeSpecName)) {
var error$1 = void 0;
try {
if (typeof typeSpecs[typeSpecName] !== "function") {
var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
err.name = "Invariant Violation";
throw err;
}
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
} catch (ex) {
error$1 = ex;
}
if (error$1 && !(error$1 instanceof Error)) {
setCurrentlyValidatingElement(element);
error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1);
setCurrentlyValidatingElement(null);
}
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
loggedTypeFailures[error$1.message] = true;
setCurrentlyValidatingElement(element);
error("Failed %s type: %s", location, error$1.message);
setCurrentlyValidatingElement(null);
}
}
}
}
}
var isArrayImpl = Array.isArray;
function isArray(a) {
return isArrayImpl(a);
}
function typeName(value) {
{
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
return type;
}
}
function willCoercionThrow(value) {
{
try {
testStringCoercion(value);
return false;
} catch (e) {
return true;
}
}
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
{
if (willCoercionThrow(value)) {
error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value));
return testStringCoercion(value);
}
}
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
var RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true
};
var specialPropKeyWarningShown;
var specialPropRefWarningShown;
var didWarnAboutStringRefs;
{
didWarnAboutStringRefs = {};
}
function hasValidRef(config) {
{
if (hasOwnProperty.call(config, "ref")) {
var getter = Object.getOwnPropertyDescriptor(config, "ref").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== void 0;
}
function hasValidKey(config) {
{
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== void 0;
}
function warnIfStringRefCannotBeAutoConverted(config, self) {
{
if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
if (!didWarnAboutStringRefs[componentName]) {
error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
didWarnAboutStringRefs[componentName] = true;
}
}
}
}
function defineKeyPropWarningGetter(props, displayName) {
{
var warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
}
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function() {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type,
key,
ref,
props,
// Record the component responsible for creating this element.
_owner: owner
};
{
element._store = {};
Object.defineProperty(element._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: false
});
Object.defineProperty(element, "_self", {
configurable: false,
enumerable: false,
writable: false,
value: self
});
Object.defineProperty(element, "_source", {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
function jsxDEV(type, config, maybeKey, source, self) {
{
var propName;
var props = {};
var key = null;
var ref = null;
if (maybeKey !== void 0) {
{
checkKeyStringCoercion(maybeKey);
}
key = "" + maybeKey;
}
if (hasValidKey(config)) {
{
checkKeyStringCoercion(config.key);
}
key = "" + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config, self);
}
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === void 0) {
props[propName] = defaultProps[propName];
}
}
}
if (key || ref) {
var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
}
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement$1(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
}
}
}
var propTypesMisspellWarningShown;
{
propTypesMisspellWarningShown = false;
}
function isValidElement(object) {
{
return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
}
function getDeclarationErrorAddendum() {
{
if (ReactCurrentOwner$1.current) {
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
if (name) {
return "\n\nCheck the render method of `" + name + "`.";
}
}
return "";
}
}
function getSourceInfoErrorAddendum(source) {
{
if (source !== void 0) {
var fileName = source.fileName.replace(/^.*[\\\/]/, "");
var lineNumber = source.lineNumber;
return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
}
return "";
}
}
var ownerHasKeyUseWarning = {};
function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
if (!info) {
var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name;
if (parentName) {
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
}
}
return info;
}
}
function validateExplicitKey(element, parentType) {
{
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
var childOwner = "";
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
}
setCurrentlyValidatingElement$1(element);
error('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
setCurrentlyValidatingElement$1(null);
}
}
function validateChildKeys(node, parentType) {
{
if (typeof node !== "object") {
return;
}
if (isArray(node)) {
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
if (node._store) {
node._store.validated = true;
}
} else if (node) {
var iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === "function") {
if (iteratorFn !== node.entries) {
var iterator = iteratorFn.call(node);
var step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
}
function validatePropTypes(element) {
{
var type = element.type;
if (type === null || type === void 0 || typeof type === "string") {
return;
}
var propTypes;
if (typeof type === "function") {
propTypes = type.propTypes;
} else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
var name = getComponentNameFromType(type);
checkPropTypes(propTypes, element.props, "prop", name, element);
} else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
var _name = getComponentNameFromType(type);
error("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown");
}
if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) {
error("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
}
}
}
function validateFragmentProps(fragment) {
{
var keys = Object.keys(fragment.props);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key !== "children" && key !== "key") {
setCurrentlyValidatingElement$1(fragment);
error("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key);
setCurrentlyValidatingElement$1(null);
break;
}
}
if (fragment.ref !== null) {
setCurrentlyValidatingElement$1(fragment);
error("Invalid attribute `ref` supplied to `React.Fragment`.");
setCurrentlyValidatingElement$1(null);
}
}
}
var didWarnAboutKeySpread = {};
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
{
var validType = isValidElementType(type);
if (!validType) {
var info = "";
if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
}
var sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
var typeString;
if (type === null) {
typeString = "null";
} else if (isArray(type)) {
typeString = "array";
} else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />";
info = " Did you accidentally export a JSX literal instead of a component?";
} else {
typeString = typeof type;
}
error("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info);
}
var element = jsxDEV(type, props, key, source, self);
if (element == null) {
return element;
}
if (validType) {
var children = props.children;
if (children !== void 0) {
if (isStaticChildren) {
if (isArray(children)) {
for (var i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
}
} else {
validateChildKeys(children, type);
}
}
}
{
if (hasOwnProperty.call(props, "key")) {
var componentName = getComponentNameFromType(type);
var keys = Object.keys(props).filter(function(k) {
return k !== "key";
});
var beforeExample = keys.length > 0 ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
var afterExample = keys.length > 0 ? "{" + keys.join(": ..., ") + ": ...}" : "{}";
error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
}
var jsxDEV$1 = jsxWithValidation;
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.jsxDEV = jsxDEV$1;
})();
}
}
});
// node_modules/react/jsx-dev-runtime.js
var require_jsx_dev_runtime = __commonJS({
"node_modules/react/jsx-dev-runtime.js"(exports, module) {
if (false) {
module.exports = null;
} else {
module.exports = require_react_jsx_dev_runtime_development();
}
}
});
export default require_jsx_dev_runtime();
/*! Bundled license information:
react/cjs/react-jsx-dev-runtime.development.js:
(**
* @license React
* react-jsx-dev-runtime.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=react_jsx-dev-runtime.js.map

File diff suppressed because one or more lines are too long

925
client/node_modules/.vite/deps/react_jsx-runtime.js generated vendored Normal file
View File

@@ -0,0 +1,925 @@
import {
require_react
} from "./chunk-4HAMFFQC.js";
import {
__commonJS
} from "./chunk-EQCVQC35.js";
// node_modules/react/cjs/react-jsx-runtime.development.js
var require_react_jsx_runtime_development = __commonJS({
"node_modules/react/cjs/react-jsx-runtime.development.js"(exports) {
"use strict";
if (true) {
(function() {
"use strict";
var React = require_react();
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
var REACT_MEMO_TYPE = Symbol.for("react.memo");
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
var FAUX_ITERATOR_SYMBOL = "@@iterator";
function getIteratorFn(maybeIterable) {
if (maybeIterable === null || typeof maybeIterable !== "object") {
return null;
}
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
if (typeof maybeIterator === "function") {
return maybeIterator;
}
return null;
}
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function error(format) {
{
{
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
printWarning("error", format, args);
}
}
}
function printWarning(level, format, args) {
{
var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame;
var stack = ReactDebugCurrentFrame2.getStackAddendum();
if (stack !== "") {
format += "%s";
args = args.concat([stack]);
}
var argsWithFormat = args.map(function(item) {
return String(item);
});
argsWithFormat.unshift("Warning: " + format);
Function.prototype.apply.call(console[level], console, argsWithFormat);
}
}
var enableScopeAPI = false;
var enableCacheElement = false;
var enableTransitionTracing = false;
var enableLegacyHidden = false;
var enableDebugTracing = false;
var REACT_MODULE_REFERENCE;
{
REACT_MODULE_REFERENCE = Symbol.for("react.module.reference");
}
function isValidElementType(type) {
if (typeof type === "string" || typeof type === "function") {
return true;
}
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) {
return true;
}
if (typeof type === "object" && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) {
return true;
}
}
return false;
}
function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
if (displayName) {
return displayName;
}
var functionName = innerType.displayName || innerType.name || "";
return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName;
}
function getContextName(type) {
return type.displayName || "Context";
}
function getComponentNameFromType(type) {
if (type == null) {
return null;
}
{
if (typeof type.tag === "number") {
error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.");
}
}
if (typeof type === "function") {
return type.displayName || type.name || null;
}
if (typeof type === "string") {
return type;
}
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_CONTEXT_TYPE:
var context = type;
return getContextName(context) + ".Consumer";
case REACT_PROVIDER_TYPE:
var provider = type;
return getContextName(provider._context) + ".Provider";
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
case REACT_MEMO_TYPE:
var outerName = type.displayName || null;
if (outerName !== null) {
return outerName;
}
return getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return getComponentNameFromType(init(payload));
} catch (x) {
return null;
}
}
}
}
return null;
}
var assign = Object.assign;
var disabledDepth = 0;
var prevLog;
var prevInfo;
var prevWarn;
var prevError;
var prevGroup;
var prevGroupCollapsed;
var prevGroupEnd;
function disabledLog() {
}
disabledLog.__reactDisabledLog = true;
function disableLogs() {
{
if (disabledDepth === 0) {
prevLog = console.log;
prevInfo = console.info;
prevWarn = console.warn;
prevError = console.error;
prevGroup = console.group;
prevGroupCollapsed = console.groupCollapsed;
prevGroupEnd = console.groupEnd;
var props = {
configurable: true,
enumerable: true,
value: disabledLog,
writable: true
};
Object.defineProperties(console, {
info: props,
log: props,
warn: props,
error: props,
group: props,
groupCollapsed: props,
groupEnd: props
});
}
disabledDepth++;
}
}
function reenableLogs() {
{
disabledDepth--;
if (disabledDepth === 0) {
var props = {
configurable: true,
enumerable: true,
writable: true
};
Object.defineProperties(console, {
log: assign({}, props, {
value: prevLog
}),
info: assign({}, props, {
value: prevInfo
}),
warn: assign({}, props, {
value: prevWarn
}),
error: assign({}, props, {
value: prevError
}),
group: assign({}, props, {
value: prevGroup
}),
groupCollapsed: assign({}, props, {
value: prevGroupCollapsed
}),
groupEnd: assign({}, props, {
value: prevGroupEnd
})
});
}
if (disabledDepth < 0) {
error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
}
}
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var prefix;
function describeBuiltInComponentFrame(name, source, ownerFn) {
{
if (prefix === void 0) {
try {
throw Error();
} catch (x) {
var match = x.stack.trim().match(/\n( *(at )?)/);
prefix = match && match[1] || "";
}
}
return "\n" + prefix + name;
}
}
var reentry = false;
var componentFrameCache;
{
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
componentFrameCache = new PossiblyWeakMap();
}
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) {
return "";
}
{
var frame = componentFrameCache.get(fn);
if (frame !== void 0) {
return frame;
}
}
var control;
reentry = true;
var previousPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = void 0;
var previousDispatcher;
{
previousDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = null;
disableLogs();
}
try {
if (construct) {
var Fake = function() {
throw Error();
};
Object.defineProperty(Fake.prototype, "props", {
set: function() {
throw Error();
}
});
if (typeof Reflect === "object" && Reflect.construct) {
try {
Reflect.construct(Fake, []);
} catch (x) {
control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x) {
control = x;
}
fn.call(Fake.prototype);
}
} else {
try {
throw Error();
} catch (x) {
control = x;
}
fn();
}
} catch (sample) {
if (sample && control && typeof sample.stack === "string") {
var sampleLines = sample.stack.split("\n");
var controlLines = control.stack.split("\n");
var s = sampleLines.length - 1;
var c = controlLines.length - 1;
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
c--;
}
for (; s >= 1 && c >= 0; s--, c--) {
if (sampleLines[s] !== controlLines[c]) {
if (s !== 1 || c !== 1) {
do {
s--;
c--;
if (c < 0 || sampleLines[s] !== controlLines[c]) {
var _frame = "\n" + sampleLines[s].replace(" at new ", " at ");
if (fn.displayName && _frame.includes("<anonymous>")) {
_frame = _frame.replace("<anonymous>", fn.displayName);
}
{
if (typeof fn === "function") {
componentFrameCache.set(fn, _frame);
}
}
return _frame;
}
} while (s >= 1 && c >= 0);
}
break;
}
}
}
} finally {
reentry = false;
{
ReactCurrentDispatcher.current = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
}
var name = fn ? fn.displayName || fn.name : "";
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : "";
{
if (typeof fn === "function") {
componentFrameCache.set(fn, syntheticFrame);
}
}
return syntheticFrame;
}
function describeFunctionComponentFrame(fn, source, ownerFn) {
{
return describeNativeComponentFrame(fn, false);
}
}
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
if (type == null) {
return "";
}
if (typeof type === "function") {
{
return describeNativeComponentFrame(type, shouldConstruct(type));
}
}
if (typeof type === "string") {
return describeBuiltInComponentFrame(type);
}
switch (type) {
case REACT_SUSPENSE_TYPE:
return describeBuiltInComponentFrame("Suspense");
case REACT_SUSPENSE_LIST_TYPE:
return describeBuiltInComponentFrame("SuspenseList");
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return describeFunctionComponentFrame(type.render);
case REACT_MEMO_TYPE:
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
} catch (x) {
}
}
}
}
return "";
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var loggedTypeFailures = {};
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame.setExtraStackFrame(null);
}
}
}
function checkPropTypes(typeSpecs, values, location, componentName, element) {
{
var has = Function.call.bind(hasOwnProperty);
for (var typeSpecName in typeSpecs) {
if (has(typeSpecs, typeSpecName)) {
var error$1 = void 0;
try {
if (typeof typeSpecs[typeSpecName] !== "function") {
var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
err.name = "Invariant Violation";
throw err;
}
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
} catch (ex) {
error$1 = ex;
}
if (error$1 && !(error$1 instanceof Error)) {
setCurrentlyValidatingElement(element);
error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1);
setCurrentlyValidatingElement(null);
}
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
loggedTypeFailures[error$1.message] = true;
setCurrentlyValidatingElement(element);
error("Failed %s type: %s", location, error$1.message);
setCurrentlyValidatingElement(null);
}
}
}
}
}
var isArrayImpl = Array.isArray;
function isArray(a) {
return isArrayImpl(a);
}
function typeName(value) {
{
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
return type;
}
}
function willCoercionThrow(value) {
{
try {
testStringCoercion(value);
return false;
} catch (e) {
return true;
}
}
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
{
if (willCoercionThrow(value)) {
error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value));
return testStringCoercion(value);
}
}
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
var RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true
};
var specialPropKeyWarningShown;
var specialPropRefWarningShown;
var didWarnAboutStringRefs;
{
didWarnAboutStringRefs = {};
}
function hasValidRef(config) {
{
if (hasOwnProperty.call(config, "ref")) {
var getter = Object.getOwnPropertyDescriptor(config, "ref").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== void 0;
}
function hasValidKey(config) {
{
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== void 0;
}
function warnIfStringRefCannotBeAutoConverted(config, self) {
{
if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
if (!didWarnAboutStringRefs[componentName]) {
error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
didWarnAboutStringRefs[componentName] = true;
}
}
}
}
function defineKeyPropWarningGetter(props, displayName) {
{
var warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
}
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function() {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type,
key,
ref,
props,
// Record the component responsible for creating this element.
_owner: owner
};
{
element._store = {};
Object.defineProperty(element._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: false
});
Object.defineProperty(element, "_self", {
configurable: false,
enumerable: false,
writable: false,
value: self
});
Object.defineProperty(element, "_source", {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
function jsxDEV(type, config, maybeKey, source, self) {
{
var propName;
var props = {};
var key = null;
var ref = null;
if (maybeKey !== void 0) {
{
checkKeyStringCoercion(maybeKey);
}
key = "" + maybeKey;
}
if (hasValidKey(config)) {
{
checkKeyStringCoercion(config.key);
}
key = "" + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config, self);
}
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === void 0) {
props[propName] = defaultProps[propName];
}
}
}
if (key || ref) {
var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
}
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement$1(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
}
}
}
var propTypesMisspellWarningShown;
{
propTypesMisspellWarningShown = false;
}
function isValidElement(object) {
{
return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
}
function getDeclarationErrorAddendum() {
{
if (ReactCurrentOwner$1.current) {
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
if (name) {
return "\n\nCheck the render method of `" + name + "`.";
}
}
return "";
}
}
function getSourceInfoErrorAddendum(source) {
{
if (source !== void 0) {
var fileName = source.fileName.replace(/^.*[\\\/]/, "");
var lineNumber = source.lineNumber;
return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
}
return "";
}
}
var ownerHasKeyUseWarning = {};
function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
if (!info) {
var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name;
if (parentName) {
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
}
}
return info;
}
}
function validateExplicitKey(element, parentType) {
{
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
var childOwner = "";
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
}
setCurrentlyValidatingElement$1(element);
error('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
setCurrentlyValidatingElement$1(null);
}
}
function validateChildKeys(node, parentType) {
{
if (typeof node !== "object") {
return;
}
if (isArray(node)) {
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
if (node._store) {
node._store.validated = true;
}
} else if (node) {
var iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === "function") {
if (iteratorFn !== node.entries) {
var iterator = iteratorFn.call(node);
var step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
}
function validatePropTypes(element) {
{
var type = element.type;
if (type === null || type === void 0 || typeof type === "string") {
return;
}
var propTypes;
if (typeof type === "function") {
propTypes = type.propTypes;
} else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
var name = getComponentNameFromType(type);
checkPropTypes(propTypes, element.props, "prop", name, element);
} else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
var _name = getComponentNameFromType(type);
error("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown");
}
if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) {
error("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
}
}
}
function validateFragmentProps(fragment) {
{
var keys = Object.keys(fragment.props);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key !== "children" && key !== "key") {
setCurrentlyValidatingElement$1(fragment);
error("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key);
setCurrentlyValidatingElement$1(null);
break;
}
}
if (fragment.ref !== null) {
setCurrentlyValidatingElement$1(fragment);
error("Invalid attribute `ref` supplied to `React.Fragment`.");
setCurrentlyValidatingElement$1(null);
}
}
}
var didWarnAboutKeySpread = {};
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
{
var validType = isValidElementType(type);
if (!validType) {
var info = "";
if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
}
var sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
var typeString;
if (type === null) {
typeString = "null";
} else if (isArray(type)) {
typeString = "array";
} else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />";
info = " Did you accidentally export a JSX literal instead of a component?";
} else {
typeString = typeof type;
}
error("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info);
}
var element = jsxDEV(type, props, key, source, self);
if (element == null) {
return element;
}
if (validType) {
var children = props.children;
if (children !== void 0) {
if (isStaticChildren) {
if (isArray(children)) {
for (var i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
}
} else {
validateChildKeys(children, type);
}
}
}
{
if (hasOwnProperty.call(props, "key")) {
var componentName = getComponentNameFromType(type);
var keys = Object.keys(props).filter(function(k) {
return k !== "key";
});
var beforeExample = keys.length > 0 ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
var afterExample = keys.length > 0 ? "{" + keys.join(": ..., ") + ": ...}" : "{}";
error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
}
function jsxWithValidationStatic(type, props, key) {
{
return jsxWithValidation(type, props, key, true);
}
}
function jsxWithValidationDynamic(type, props, key) {
{
return jsxWithValidation(type, props, key, false);
}
}
var jsx = jsxWithValidationDynamic;
var jsxs = jsxWithValidationStatic;
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.jsx = jsx;
exports.jsxs = jsxs;
})();
}
}
});
// node_modules/react/jsx-runtime.js
var require_jsx_runtime = __commonJS({
"node_modules/react/jsx-runtime.js"(exports, module) {
if (false) {
module.exports = null;
} else {
module.exports = require_react_jsx_runtime_development();
}
}
});
export default require_jsx_runtime();
/*! Bundled license information:
react/cjs/react-jsx-runtime.development.js:
(**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=react_jsx-runtime.js.map

File diff suppressed because one or more lines are too long

41
client/node_modules/.vite/deps/xterm-addon-fit.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
import {
__commonJS
} from "./chunk-EQCVQC35.js";
// node_modules/xterm-addon-fit/lib/xterm-addon-fit.js
var require_xterm_addon_fit = __commonJS({
"node_modules/xterm-addon-fit/lib/xterm-addon-fit.js"(exports, module) {
!function(e, t) {
"object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.FitAddon = t() : e.FitAddon = t();
}(self, () => (() => {
"use strict";
var e = {};
return (() => {
var t = e;
Object.defineProperty(t, "__esModule", { value: true }), t.FitAddon = void 0, t.FitAddon = class {
activate(e2) {
this._terminal = e2;
}
dispose() {
}
fit() {
const e2 = this.proposeDimensions();
if (!e2 || !this._terminal || isNaN(e2.cols) || isNaN(e2.rows)) return;
const t2 = this._terminal._core;
this._terminal.rows === e2.rows && this._terminal.cols === e2.cols || (t2._renderService.clear(), this._terminal.resize(e2.cols, e2.rows));
}
proposeDimensions() {
if (!this._terminal) return;
if (!this._terminal.element || !this._terminal.element.parentElement) return;
const e2 = this._terminal._core, t2 = e2._renderService.dimensions;
if (0 === t2.css.cell.width || 0 === t2.css.cell.height) return;
const r = 0 === this._terminal.options.scrollback ? 0 : e2.viewport.scrollBarWidth, i = window.getComputedStyle(this._terminal.element.parentElement), o = parseInt(i.getPropertyValue("height")), s = Math.max(0, parseInt(i.getPropertyValue("width"))), n = window.getComputedStyle(this._terminal.element), l = o - (parseInt(n.getPropertyValue("padding-top")) + parseInt(n.getPropertyValue("padding-bottom"))), a = s - (parseInt(n.getPropertyValue("padding-right")) + parseInt(n.getPropertyValue("padding-left"))) - r;
return { cols: Math.max(2, Math.floor(a / t2.css.cell.width)), rows: Math.max(1, Math.floor(l / t2.css.cell.height)) };
}
};
})(), e;
})());
}
});
export default require_xterm_addon_fit();
//# sourceMappingURL=xterm-addon-fit.js.map

View File

@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../xterm-addon-fit/lib/webpack:/FitAddon/webpack/universalModuleDefinition", "../../xterm-addon-fit/lib/webpack:/FitAddon/src/FitAddon.ts"],
"sourcesContent": ["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"FitAddon\"] = factory();\n\telse\n\t\troot[\"FitAddon\"] = factory();\n})(self, () => {\nreturn ", "/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ITerminalAddon } from 'xterm';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\n\ninterface ITerminalDimensions {\n /**\n * The number of rows in the terminal.\n */\n rows: number;\n\n /**\n * The number of columns in the terminal.\n */\n cols: number;\n}\n\nconst MINIMUM_COLS = 2;\nconst MINIMUM_ROWS = 1;\n\nexport class FitAddon implements ITerminalAddon {\n private _terminal: Terminal | undefined;\n\n public activate(terminal: Terminal): void {\n this._terminal = terminal;\n }\n\n public dispose(): void {}\n\n public fit(): void {\n const dims = this.proposeDimensions();\n if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {\n return;\n }\n\n // TODO: Remove reliance on private API\n const core = (this._terminal as any)._core;\n\n // Force a full render\n if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {\n core._renderService.clear();\n this._terminal.resize(dims.cols, dims.rows);\n }\n }\n\n public proposeDimensions(): ITerminalDimensions | undefined {\n if (!this._terminal) {\n return undefined;\n }\n\n if (!this._terminal.element || !this._terminal.element.parentElement) {\n return undefined;\n }\n\n // TODO: Remove reliance on private API\n const core = (this._terminal as any)._core;\n const dims: IRenderDimensions = core._renderService.dimensions;\n\n if (dims.css.cell.width === 0 || dims.css.cell.height === 0) {\n return undefined;\n }\n\n const scrollbarWidth = this._terminal.options.scrollback === 0 ?\n 0 : core.viewport.scrollBarWidth;\n\n const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(this._terminal.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;\n const geometry = {\n cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),\n rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))\n };\n return geometry;\n }\n}\n"],
"mappings": ";;;;;;;KAAA,SAA2CA,GAAMC,GAAAA;AAC1B,kBAAA,OAAZC,WAA0C,YAAA,OAAXC,SACxCA,OAAOD,UAAUD,EAAAA,IACQ,cAAA,OAAXG,UAAyBA,OAAOC,MAC9CD,OAAO,CAAA,GAAIH,CAAAA,IACe,YAAA,OAAZC,UACdA,QAAkB,WAAID,EAAAA,IAEtBD,EAAe,WAAIC,EAAAA;IACpB,EAAEK,MAAM,OAAA,MAAA;AAAA;AAAA,UAAA,IAAA,CAAA;AAAA,cAAA,MAAA;AAAA,YAAA,IAAA;AAAA,eAAA,eAAA,GAAA,cAAA,EAAA,OAAA,KAAA,CAAA,GAAA,EAAA,WAAA,QCcT,EAAA,WAAA,MAAA;UAGS,SAASC,IAAAA;AACdC,iBAAKC,YAAYF;UACnB;UAEO,UAAAG;UAAiB;UAEjB,MAAAC;AACL,kBAAMC,KAAOJ,KAAKK,kBAAAA;AAClB,gBAAA,CAAKD,MAAAA,CAASJ,KAAKC,aAAaK,MAAMF,GAAKG,IAAAA,KAASD,MAAMF,GAAKI,IAAAA,EAC7D;AAIF,kBAAMC,KAAQT,KAAKC,UAAkBS;AAGjCV,iBAAKC,UAAUO,SAASJ,GAAKI,QAAQR,KAAKC,UAAUM,SAASH,GAAKG,SACpEE,GAAKE,eAAeC,MAAAA,GACpBZ,KAAKC,UAAUY,OAAOT,GAAKG,MAAMH,GAAKI,IAAAA;UAE1C;UAEO,oBAAAH;AACL,gBAAA,CAAKL,KAAKC,UACR;AAGF,gBAAA,CAAKD,KAAKC,UAAUa,WAAAA,CAAYd,KAAKC,UAAUa,QAAQC,cACrD;AAIF,kBAAMN,KAAQT,KAAKC,UAAkBS,OAC/BN,KAA0BK,GAAKE,eAAeK;AAEpD,gBAA4B,MAAxBZ,GAAKa,IAAIC,KAAKC,SAAwC,MAAzBf,GAAKa,IAAIC,KAAKE,OAC7C;AAGF,kBAAMC,IAAuD,MAAtCrB,KAAKC,UAAUqB,QAAQC,aAC5C,IAAId,GAAKe,SAASC,gBAEdC,IAAqBC,OAAOC,iBAAiB5B,KAAKC,UAAUa,QAAQC,aAAAA,GACpEc,IAAsBC,SAASJ,EAAmBK,iBAAiB,QAAA,CAAA,GACnEC,IAAqBC,KAAKC,IAAI,GAAGJ,SAASJ,EAAmBK,iBAAiB,OAAA,CAAA,CAAA,GAC9EI,IAAeR,OAAOC,iBAAiB5B,KAAKC,UAAUa,OAAAA,GAStDsB,IAAkBP,KAPjBC,SAASK,EAAaJ,iBAAiB,aAAA,CAAA,IACpCD,SAASK,EAAaJ,iBAAiB,gBAAA,CAAA,IAO3CM,IAAiBL,KANdF,SAASK,EAAaJ,iBAAiB,eAAA,CAAA,IACxCD,SAASK,EAAaJ,iBAAiB,cAAA,CAAA,KAKiBV;AAKhE,mBAJiB,EACfd,MAAM0B,KAAKC,IA/DI,GA+DcD,KAAKK,MAAMD,IAAiBjC,GAAKa,IAAIC,KAAKC,KAAAA,CAAAA,GACvEX,MAAMyB,KAAKC,IA/DI,GA+DcD,KAAKK,MAAMF,IAAkBhC,GAAKa,IAAIC,KAAKE,MAAAA,CAAAA,EAAAA;UAG5E;QAAA;MAAA,GAAA,GAAA;IAAA,GAAA,CAAA;;;",
"names": ["root", "factory", "exports", "module", "define", "amd", "self", "terminal", "this", "_terminal", "dispose", "fit", "dims", "proposeDimensions", "isNaN", "cols", "rows", "core", "_core", "_renderService", "clear", "resize", "element", "parentElement", "dimensions", "css", "cell", "width", "height", "scrollbarWidth", "options", "scrollback", "viewport", "scrollBarWidth", "parentElementStyle", "window", "getComputedStyle", "parentElementHeight", "parseInt", "getPropertyValue", "parentElementWidth", "Math", "max", "elementStyle", "availableHeight", "availableWidth", "floor"]
}

5953
client/node_modules/.vite/deps/xterm.js generated vendored Normal file

File diff suppressed because one or more lines are too long

7
client/node_modules/.vite/deps/xterm.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

9630
client/node_modules/.vite/deps/yjs.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

7
client/node_modules/.vite/deps/yjs.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

270
client/node_modules/.vite/deps/zustand.js generated vendored Normal file
View File

@@ -0,0 +1,270 @@
import {
require_react
} from "./chunk-4HAMFFQC.js";
import {
__commonJS,
__toESM
} from "./chunk-EQCVQC35.js";
// node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js
var require_use_sync_external_store_shim_development = __commonJS({
"node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js"(exports) {
"use strict";
(function() {
function is(x, y) {
return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
}
function useSyncExternalStore$2(subscribe, getSnapshot) {
didWarnOld18Alpha || void 0 === React.startTransition || (didWarnOld18Alpha = true, console.error(
"You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
));
var value = getSnapshot();
if (!didWarnUncachedGetSnapshot) {
var cachedValue = getSnapshot();
objectIs(value, cachedValue) || (console.error(
"The result of getSnapshot should be cached to avoid an infinite loop"
), didWarnUncachedGetSnapshot = true);
}
cachedValue = useState({
inst: { value, getSnapshot }
});
var inst = cachedValue[0].inst, forceUpdate = cachedValue[1];
useLayoutEffect(
function() {
inst.value = value;
inst.getSnapshot = getSnapshot;
checkIfSnapshotChanged(inst) && forceUpdate({ inst });
},
[subscribe, value, getSnapshot]
);
useEffect(
function() {
checkIfSnapshotChanged(inst) && forceUpdate({ inst });
return subscribe(function() {
checkIfSnapshotChanged(inst) && forceUpdate({ inst });
});
},
[subscribe]
);
useDebugValue2(value);
return value;
}
function checkIfSnapshotChanged(inst) {
var latestGetSnapshot = inst.getSnapshot;
inst = inst.value;
try {
var nextValue = latestGetSnapshot();
return !objectIs(inst, nextValue);
} catch (error) {
return true;
}
}
function useSyncExternalStore$1(subscribe, getSnapshot) {
return getSnapshot();
}
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
var React = require_react(), objectIs = "function" === typeof Object.is ? Object.is : is, useState = React.useState, useEffect = React.useEffect, useLayoutEffect = React.useLayoutEffect, useDebugValue2 = React.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
exports.useSyncExternalStore = void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim;
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
})();
}
});
// node_modules/use-sync-external-store/shim/index.js
var require_shim = __commonJS({
"node_modules/use-sync-external-store/shim/index.js"(exports, module) {
"use strict";
if (false) {
module.exports = null;
} else {
module.exports = require_use_sync_external_store_shim_development();
}
}
});
// node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js
var require_with_selector_development = __commonJS({
"node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js"(exports) {
"use strict";
(function() {
function is(x, y) {
return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
}
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
var React = require_react(), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is, useSyncExternalStore = shim.useSyncExternalStore, useRef = React.useRef, useEffect = React.useEffect, useMemo = React.useMemo, useDebugValue2 = React.useDebugValue;
exports.useSyncExternalStoreWithSelector = function(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
var instRef = useRef(null);
if (null === instRef.current) {
var inst = { hasValue: false, value: null };
instRef.current = inst;
} else inst = instRef.current;
instRef = useMemo(
function() {
function memoizedSelector(nextSnapshot) {
if (!hasMemo) {
hasMemo = true;
memoizedSnapshot = nextSnapshot;
nextSnapshot = selector(nextSnapshot);
if (void 0 !== isEqual && inst.hasValue) {
var currentSelection = inst.value;
if (isEqual(currentSelection, nextSnapshot))
return memoizedSelection = currentSelection;
}
return memoizedSelection = nextSnapshot;
}
currentSelection = memoizedSelection;
if (objectIs(memoizedSnapshot, nextSnapshot))
return currentSelection;
var nextSelection = selector(nextSnapshot);
if (void 0 !== isEqual && isEqual(currentSelection, nextSelection))
return memoizedSnapshot = nextSnapshot, currentSelection;
memoizedSnapshot = nextSnapshot;
return memoizedSelection = nextSelection;
}
var hasMemo = false, memoizedSnapshot, memoizedSelection, maybeGetServerSnapshot = void 0 === getServerSnapshot ? null : getServerSnapshot;
return [
function() {
return memoizedSelector(getSnapshot());
},
null === maybeGetServerSnapshot ? void 0 : function() {
return memoizedSelector(maybeGetServerSnapshot());
}
];
},
[getSnapshot, getServerSnapshot, selector, isEqual]
);
var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]);
useEffect(
function() {
inst.hasValue = true;
inst.value = value;
},
[value]
);
useDebugValue2(value);
return value;
};
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
})();
}
});
// node_modules/use-sync-external-store/shim/with-selector.js
var require_with_selector = __commonJS({
"node_modules/use-sync-external-store/shim/with-selector.js"(exports, module) {
"use strict";
if (false) {
module.exports = null;
} else {
module.exports = require_with_selector_development();
}
}
});
// node_modules/zustand/esm/vanilla.mjs
var createStoreImpl = (createState) => {
let state;
const listeners = /* @__PURE__ */ new Set();
const setState = (partial, replace) => {
const nextState = typeof partial === "function" ? partial(state) : partial;
if (!Object.is(nextState, state)) {
const previousState = state;
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
listeners.forEach((listener) => listener(state, previousState));
}
};
const getState = () => state;
const getInitialState = () => initialState;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const destroy = () => {
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
console.warn(
"[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."
);
}
listeners.clear();
};
const api = { setState, getState, getInitialState, subscribe, destroy };
const initialState = state = createState(setState, getState, api);
return api;
};
var createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
// node_modules/zustand/esm/index.mjs
var import_react = __toESM(require_react(), 1);
var import_with_selector = __toESM(require_with_selector(), 1);
var { useDebugValue } = import_react.default;
var { useSyncExternalStoreWithSelector } = import_with_selector.default;
var didWarnAboutEqualityFn = false;
var identity = (arg) => arg;
function useStore(api, selector = identity, equalityFn) {
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && equalityFn && !didWarnAboutEqualityFn) {
console.warn(
"[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"
);
didWarnAboutEqualityFn = true;
}
const slice = useSyncExternalStoreWithSelector(
api.subscribe,
api.getState,
api.getServerState || api.getInitialState,
selector,
equalityFn
);
useDebugValue(slice);
return slice;
}
var createImpl = (createState) => {
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && typeof createState !== "function") {
console.warn(
"[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`."
);
}
const api = typeof createState === "function" ? createStore(createState) : createState;
const useBoundStore = (selector, equalityFn) => useStore(api, selector, equalityFn);
Object.assign(useBoundStore, api);
return useBoundStore;
};
var create = (createState) => createState ? createImpl(createState) : createImpl;
var react = (createState) => {
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
console.warn(
"[DEPRECATED] Default export is deprecated. Instead use `import { create } from 'zustand'`."
);
}
return create(createState);
};
export {
create,
createStore,
react as default,
useStore
};
/*! Bundled license information:
use-sync-external-store/cjs/use-sync-external-store-shim.development.js:
(**
* @license React
* use-sync-external-store-shim.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js:
(**
* @license React
* use-sync-external-store-shim/with-selector.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=zustand.js.map

7
client/node_modules/.vite/deps/zustand.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

589
client/node_modules/.vite/deps/zustand_middleware.js generated vendored Normal file
View File

@@ -0,0 +1,589 @@
import "./chunk-EQCVQC35.js";
// node_modules/zustand/esm/middleware.mjs
var reduxImpl = (reducer, initial) => (set, _get, api) => {
api.dispatch = (action) => {
set((state) => reducer(state, action), false, action);
return action;
};
api.dispatchFromDevtools = true;
return { dispatch: (...a) => api.dispatch(...a), ...initial };
};
var redux = reduxImpl;
var trackedConnections = /* @__PURE__ */ new Map();
var getTrackedConnectionState = (name) => {
const api = trackedConnections.get(name);
if (!api) return {};
return Object.fromEntries(
Object.entries(api.stores).map(([key, api2]) => [key, api2.getState()])
);
};
var extractConnectionInformation = (store, extensionConnector, options) => {
if (store === void 0) {
return {
type: "untracked",
connection: extensionConnector.connect(options)
};
}
const existingConnection = trackedConnections.get(options.name);
if (existingConnection) {
return { type: "tracked", store, ...existingConnection };
}
const newConnection = {
connection: extensionConnector.connect(options),
stores: {}
};
trackedConnections.set(options.name, newConnection);
return { type: "tracked", store, ...newConnection };
};
var devtoolsImpl = (fn, devtoolsOptions = {}) => (set, get, api) => {
const { enabled, anonymousActionType, store, ...options } = devtoolsOptions;
let extensionConnector;
try {
extensionConnector = (enabled != null ? enabled : (import.meta.env ? import.meta.env.MODE : void 0) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
} catch (_e) {
}
if (!extensionConnector) {
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && enabled) {
console.warn(
"[zustand devtools middleware] Please install/enable Redux devtools extension"
);
}
return fn(set, get, api);
}
const { connection, ...connectionInformation } = extractConnectionInformation(store, extensionConnector, options);
let isRecording = true;
api.setState = (state, replace, nameOrAction) => {
const r = set(state, replace);
if (!isRecording) return r;
const action = nameOrAction === void 0 ? { type: anonymousActionType || "anonymous" } : typeof nameOrAction === "string" ? { type: nameOrAction } : nameOrAction;
if (store === void 0) {
connection == null ? void 0 : connection.send(action, get());
return r;
}
connection == null ? void 0 : connection.send(
{
...action,
type: `${store}/${action.type}`
},
{
...getTrackedConnectionState(options.name),
[store]: api.getState()
}
);
return r;
};
const setStateFromDevtools = (...a) => {
const originalIsRecording = isRecording;
isRecording = false;
set(...a);
isRecording = originalIsRecording;
};
const initialState = fn(api.setState, get, api);
if (connectionInformation.type === "untracked") {
connection == null ? void 0 : connection.init(initialState);
} else {
connectionInformation.stores[connectionInformation.store] = api;
connection == null ? void 0 : connection.init(
Object.fromEntries(
Object.entries(connectionInformation.stores).map(([key, store2]) => [
key,
key === connectionInformation.store ? initialState : store2.getState()
])
)
);
}
if (api.dispatchFromDevtools && typeof api.dispatch === "function") {
let didWarnAboutReservedActionType = false;
const originalDispatch = api.dispatch;
api.dispatch = (...a) => {
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && a[0].type === "__setState" && !didWarnAboutReservedActionType) {
console.warn(
'[zustand devtools middleware] "__setState" action type is reserved to set state from the devtools. Avoid using it.'
);
didWarnAboutReservedActionType = true;
}
originalDispatch(...a);
};
}
connection.subscribe((message) => {
var _a;
switch (message.type) {
case "ACTION":
if (typeof message.payload !== "string") {
console.error(
"[zustand devtools middleware] Unsupported action format"
);
return;
}
return parseJsonThen(
message.payload,
(action) => {
if (action.type === "__setState") {
if (store === void 0) {
setStateFromDevtools(action.state);
return;
}
if (Object.keys(action.state).length !== 1) {
console.error(
`
[zustand devtools middleware] Unsupported __setState action format.
When using 'store' option in devtools(), the 'state' should have only one key, which is a value of 'store' that was passed in devtools(),
and value of this only key should be a state object. Example: { "type": "__setState", "state": { "abc123Store": { "foo": "bar" } } }
`
);
}
const stateFromDevtools = action.state[store];
if (stateFromDevtools === void 0 || stateFromDevtools === null) {
return;
}
if (JSON.stringify(api.getState()) !== JSON.stringify(stateFromDevtools)) {
setStateFromDevtools(stateFromDevtools);
}
return;
}
if (!api.dispatchFromDevtools) return;
if (typeof api.dispatch !== "function") return;
api.dispatch(action);
}
);
case "DISPATCH":
switch (message.payload.type) {
case "RESET":
setStateFromDevtools(initialState);
if (store === void 0) {
return connection == null ? void 0 : connection.init(api.getState());
}
return connection == null ? void 0 : connection.init(getTrackedConnectionState(options.name));
case "COMMIT":
if (store === void 0) {
connection == null ? void 0 : connection.init(api.getState());
return;
}
return connection == null ? void 0 : connection.init(getTrackedConnectionState(options.name));
case "ROLLBACK":
return parseJsonThen(message.state, (state) => {
if (store === void 0) {
setStateFromDevtools(state);
connection == null ? void 0 : connection.init(api.getState());
return;
}
setStateFromDevtools(state[store]);
connection == null ? void 0 : connection.init(getTrackedConnectionState(options.name));
});
case "JUMP_TO_STATE":
case "JUMP_TO_ACTION":
return parseJsonThen(message.state, (state) => {
if (store === void 0) {
setStateFromDevtools(state);
return;
}
if (JSON.stringify(api.getState()) !== JSON.stringify(state[store])) {
setStateFromDevtools(state[store]);
}
});
case "IMPORT_STATE": {
const { nextLiftedState } = message.payload;
const lastComputedState = (_a = nextLiftedState.computedStates.slice(-1)[0]) == null ? void 0 : _a.state;
if (!lastComputedState) return;
if (store === void 0) {
setStateFromDevtools(lastComputedState);
} else {
setStateFromDevtools(lastComputedState[store]);
}
connection == null ? void 0 : connection.send(
null,
// FIXME no-any
nextLiftedState
);
return;
}
case "PAUSE_RECORDING":
return isRecording = !isRecording;
}
return;
}
});
return initialState;
};
var devtools = devtoolsImpl;
var parseJsonThen = (stringified, f) => {
let parsed;
try {
parsed = JSON.parse(stringified);
} catch (e) {
console.error(
"[zustand devtools middleware] Could not parse the received json",
e
);
}
if (parsed !== void 0) f(parsed);
};
var subscribeWithSelectorImpl = (fn) => (set, get, api) => {
const origSubscribe = api.subscribe;
api.subscribe = (selector, optListener, options) => {
let listener = selector;
if (optListener) {
const equalityFn = (options == null ? void 0 : options.equalityFn) || Object.is;
let currentSlice = selector(api.getState());
listener = (state) => {
const nextSlice = selector(state);
if (!equalityFn(currentSlice, nextSlice)) {
const previousSlice = currentSlice;
optListener(currentSlice = nextSlice, previousSlice);
}
};
if (options == null ? void 0 : options.fireImmediately) {
optListener(currentSlice, currentSlice);
}
}
return origSubscribe(listener);
};
const initialState = fn(set, get, api);
return initialState;
};
var subscribeWithSelector = subscribeWithSelectorImpl;
var combine = (initialState, create) => (...a) => Object.assign({}, initialState, create(...a));
function createJSONStorage(getStorage, options) {
let storage;
try {
storage = getStorage();
} catch (_e) {
return;
}
const persistStorage = {
getItem: (name) => {
var _a;
const parse = (str2) => {
if (str2 === null) {
return null;
}
return JSON.parse(str2, options == null ? void 0 : options.reviver);
};
const str = (_a = storage.getItem(name)) != null ? _a : null;
if (str instanceof Promise) {
return str.then(parse);
}
return parse(str);
},
setItem: (name, newValue) => storage.setItem(
name,
JSON.stringify(newValue, options == null ? void 0 : options.replacer)
),
removeItem: (name) => storage.removeItem(name)
};
return persistStorage;
}
var toThenable = (fn) => (input) => {
try {
const result = fn(input);
if (result instanceof Promise) {
return result;
}
return {
then(onFulfilled) {
return toThenable(onFulfilled)(result);
},
catch(_onRejected) {
return this;
}
};
} catch (e) {
return {
then(_onFulfilled) {
return this;
},
catch(onRejected) {
return toThenable(onRejected)(e);
}
};
}
};
var oldImpl = (config, baseOptions) => (set, get, api) => {
let options = {
getStorage: () => localStorage,
serialize: JSON.stringify,
deserialize: JSON.parse,
partialize: (state) => state,
version: 0,
merge: (persistedState, currentState) => ({
...currentState,
...persistedState
}),
...baseOptions
};
let hasHydrated = false;
const hydrationListeners = /* @__PURE__ */ new Set();
const finishHydrationListeners = /* @__PURE__ */ new Set();
let storage;
try {
storage = options.getStorage();
} catch (_e) {
}
if (!storage) {
return config(
(...args) => {
console.warn(
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
);
set(...args);
},
get,
api
);
}
const thenableSerialize = toThenable(options.serialize);
const setItem = () => {
const state = options.partialize({ ...get() });
let errorInSync;
const thenable = thenableSerialize({ state, version: options.version }).then(
(serializedValue) => storage.setItem(options.name, serializedValue)
).catch((e) => {
errorInSync = e;
});
if (errorInSync) {
throw errorInSync;
}
return thenable;
};
const savedSetState = api.setState;
api.setState = (state, replace) => {
savedSetState(state, replace);
void setItem();
};
const configResult = config(
(...args) => {
set(...args);
void setItem();
},
get,
api
);
let stateFromStorage;
const hydrate = () => {
var _a;
if (!storage) return;
hasHydrated = false;
hydrationListeners.forEach((cb) => cb(get()));
const postRehydrationCallback = ((_a = options.onRehydrateStorage) == null ? void 0 : _a.call(options, get())) || void 0;
return toThenable(storage.getItem.bind(storage))(options.name).then((storageValue) => {
if (storageValue) {
return options.deserialize(storageValue);
}
}).then((deserializedStorageValue) => {
if (deserializedStorageValue) {
if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
if (options.migrate) {
return options.migrate(
deserializedStorageValue.state,
deserializedStorageValue.version
);
}
console.error(
`State loaded from storage couldn't be migrated since no migrate function was provided`
);
} else {
return deserializedStorageValue.state;
}
}
}).then((migratedState) => {
var _a2;
stateFromStorage = options.merge(
migratedState,
(_a2 = get()) != null ? _a2 : configResult
);
set(stateFromStorage, true);
return setItem();
}).then(() => {
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
hasHydrated = true;
finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
}).catch((e) => {
postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
});
};
api.persist = {
setOptions: (newOptions) => {
options = {
...options,
...newOptions
};
if (newOptions.getStorage) {
storage = newOptions.getStorage();
}
},
clearStorage: () => {
storage == null ? void 0 : storage.removeItem(options.name);
},
getOptions: () => options,
rehydrate: () => hydrate(),
hasHydrated: () => hasHydrated,
onHydrate: (cb) => {
hydrationListeners.add(cb);
return () => {
hydrationListeners.delete(cb);
};
},
onFinishHydration: (cb) => {
finishHydrationListeners.add(cb);
return () => {
finishHydrationListeners.delete(cb);
};
}
};
hydrate();
return stateFromStorage || configResult;
};
var newImpl = (config, baseOptions) => (set, get, api) => {
let options = {
storage: createJSONStorage(() => localStorage),
partialize: (state) => state,
version: 0,
merge: (persistedState, currentState) => ({
...currentState,
...persistedState
}),
...baseOptions
};
let hasHydrated = false;
const hydrationListeners = /* @__PURE__ */ new Set();
const finishHydrationListeners = /* @__PURE__ */ new Set();
let storage = options.storage;
if (!storage) {
return config(
(...args) => {
console.warn(
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
);
set(...args);
},
get,
api
);
}
const setItem = () => {
const state = options.partialize({ ...get() });
return storage.setItem(options.name, {
state,
version: options.version
});
};
const savedSetState = api.setState;
api.setState = (state, replace) => {
savedSetState(state, replace);
void setItem();
};
const configResult = config(
(...args) => {
set(...args);
void setItem();
},
get,
api
);
api.getInitialState = () => configResult;
let stateFromStorage;
const hydrate = () => {
var _a, _b;
if (!storage) return;
hasHydrated = false;
hydrationListeners.forEach((cb) => {
var _a2;
return cb((_a2 = get()) != null ? _a2 : configResult);
});
const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0;
return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
if (deserializedStorageValue) {
if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
if (options.migrate) {
return [
true,
options.migrate(
deserializedStorageValue.state,
deserializedStorageValue.version
)
];
}
console.error(
`State loaded from storage couldn't be migrated since no migrate function was provided`
);
} else {
return [false, deserializedStorageValue.state];
}
}
return [false, void 0];
}).then((migrationResult) => {
var _a2;
const [migrated, migratedState] = migrationResult;
stateFromStorage = options.merge(
migratedState,
(_a2 = get()) != null ? _a2 : configResult
);
set(stateFromStorage, true);
if (migrated) {
return setItem();
}
}).then(() => {
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
stateFromStorage = get();
hasHydrated = true;
finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
}).catch((e) => {
postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
});
};
api.persist = {
setOptions: (newOptions) => {
options = {
...options,
...newOptions
};
if (newOptions.storage) {
storage = newOptions.storage;
}
},
clearStorage: () => {
storage == null ? void 0 : storage.removeItem(options.name);
},
getOptions: () => options,
rehydrate: () => hydrate(),
hasHydrated: () => hasHydrated,
onHydrate: (cb) => {
hydrationListeners.add(cb);
return () => {
hydrationListeners.delete(cb);
};
},
onFinishHydration: (cb) => {
finishHydrationListeners.add(cb);
return () => {
finishHydrationListeners.delete(cb);
};
}
};
if (!options.skipHydration) {
hydrate();
}
return stateFromStorage || configResult;
};
var persistImpl = (config, baseOptions) => {
if ("getStorage" in baseOptions || "serialize" in baseOptions || "deserialize" in baseOptions) {
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
console.warn(
"[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead."
);
}
return oldImpl(config, baseOptions);
}
return newImpl(config, baseOptions);
};
var persist = persistImpl;
export {
combine,
createJSONStorage,
devtools,
persist,
redux,
subscribeWithSelector
};
//# sourceMappingURL=zustand_middleware.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,44 @@
import React from 'react'
import React, { useEffect } from 'react'
import { WorldLayer } from './components/canvas'
import { GridOverlay } from './components/canvas'
import { Minimap } from './components/canvas'
import { TopBar } from './components/shared'
import { Spotlight } from './components/shared'
import { useCanvasStore } from './state'
import { useKeyboardShortcuts } from './hooks'
import { useSpotlightStore } from './state/spotlightStore'
export function App() {
const { zoom } = useCanvasStore()
const { setQuery, setFilter, setSel, close } = useSpotlightStore()
useEffect(() => {
const handleSpotlightTrigger = (e: any) => {
const { query } = e.detail
setQuery(query)
setFilter(null)
setSel(0)
useSpotlightStore.getState().setOpen(true)
}
window.addEventListener('spotlight-trigger', handleSpotlightTrigger as any)
return () => window.removeEventListener('spotlight-trigger', handleSpotlightTrigger as any)
}, [setQuery, setFilter, setSel])
useKeyboardShortcuts({
onOpenSpotlight: () => {
setQuery('')
setFilter(null)
setSel(0)
close()
},
onCloseSpotlight: () => close(),
onNavigateUp: () => {},
onNavigateDown: () => {},
onNavigateLeft: () => {},
onNavigateRight: () => {},
onSelect: () => {},
})
const canvasStyle: React.CSSProperties = {
position: 'fixed',
@@ -56,7 +87,7 @@ export function App() {
>
<span>Space + drag to pan</span>
<span style={{ marginLeft: '12px' }}>Scroll to pan</span>
<span style={{ marginLeft: '12px' }}>Click to spotlight</span>
<span style={{ marginLeft: '12px' }}>Type to spotlight</span>
</div>
</div>
)

View File

@@ -1,10 +1,20 @@
import React, { HTMLAttributes } from 'react'
import React, { HTMLAttributes, useCallback } from 'react'
import { useCanvasStore } from '../../state/canvasStore'
import { useSpotlightStore } from '../../state/spotlightStore'
interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {}
export const GridOverlay: React.FC<GridOverlayProps> = ({ ...props }) => {
useCanvasStore()
const { setOpen, open } = useSpotlightStore()
const handleCanvasClick = useCallback((e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (!open) {
setOpen(true)
}
}, [setOpen, open])
const style: React.CSSProperties = {
position: 'absolute',
@@ -12,7 +22,6 @@ export const GridOverlay: React.FC<GridOverlayProps> = ({ ...props }) => {
left: 0,
right: 0,
bottom: 0,
pointerEvents: 'none',
backgroundSize: '34px 34px, 170px 170px',
backgroundPosition: '0 0, 0 0',
backgroundImage: `
@@ -23,5 +32,5 @@ export const GridOverlay: React.FC<GridOverlayProps> = ({ ...props }) => {
`,
}
return <div style={style} {...props} />
return <div style={style} onMouseDown={handleCanvasClick} {...props} />
}

View File

@@ -1,6 +1,7 @@
import React, { HTMLAttributes } from 'react'
import React, { HTMLAttributes, useCallback } from 'react'
import { useCanvasStore } from '../../state/canvasStore'
import { useKrateStore } from '../../state/krateStore'
import { useSpotlightStore } from '../../state/spotlightStore'
import { Krate } from '../krate'
interface WorldLayerProps extends HTMLAttributes<HTMLDivElement> {}
@@ -8,6 +9,14 @@ interface WorldLayerProps extends HTMLAttributes<HTMLDivElement> {}
export const WorldLayer: React.FC<WorldLayerProps> = ({ ...props }) => {
const { camX, camY, zoom, collapsed } = useCanvasStore()
const { krates } = useKrateStore()
const { setOpen, open } = useSpotlightStore()
const handleCanvasClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
if (!open) {
setOpen(true)
}
}, [setOpen, open])
const style: React.CSSProperties = {
position: 'absolute',
@@ -18,7 +27,7 @@ export const WorldLayer: React.FC<WorldLayerProps> = ({ ...props }) => {
}
return (
<div style={style} {...props}>
<div style={style} onMouseDown={handleCanvasClick} {...props}>
{Array.from(krates.values())
.filter((k: any) => !k.minimized)
.map((krate: any) => (

View File

@@ -1,10 +1,33 @@
import React, { HTMLAttributes } from 'react'
import React, { HTMLAttributes, useEffect, useRef } from 'react'
import { useSpotlightStore } from '../../state/spotlightStore'
interface SpotlightProps extends HTMLAttributes<HTMLDivElement> {}
export const Spotlight: React.FC<SpotlightProps> = ({ ...props }) => {
const { open, query, filterType } = useSpotlightStore()
const { open, query, filterType, sel, setSel, results } = useSpotlightStore()
const { setQuery, close, setOpen } = useSpotlightStore()
const inputRef = useRef<HTMLInputElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (open && inputRef.current) {
inputRef.current.focus()
}
const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
close()
}
}
if (open) {
document.addEventListener('mousedown', handleClickOutside)
}
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [open])
if (!open) return null
@@ -19,6 +42,7 @@ export const Spotlight: React.FC<SpotlightProps> = ({ ...props }) => {
return (
<div
ref={containerRef}
style={{
position: 'absolute',
top: '100px',
@@ -48,10 +72,31 @@ export const Spotlight: React.FC<SpotlightProps> = ({ ...props }) => {
>
<span style={{ color: '#888' }}>🔍</span>
<input
ref={inputRef}
type="text"
value={query}
placeholder="Search..."
autoFocus
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
e.preventDefault()
setSel(Math.min(sel + 1, results.length - 1))
} else if (e.key === 'ArrowUp') {
e.preventDefault()
setSel(Math.max(0, sel - 1))
} else if (e.key === 'Enter') {
e.preventDefault()
const result = results[sel]
if (result) {
setOpen(false)
close()
}
} else if (e.key === 'Escape') {
e.preventDefault()
close()
}
}}
style={{
flex: 1,
background: 'transparent',
@@ -93,8 +138,10 @@ export const Spotlight: React.FC<SpotlightProps> = ({ ...props }) => {
))}
</div>
<div style={{ padding: '8px 16px', color: '#888', fontSize: '12px' }}>
Select: Enter, Close: Esc
<div style={{ padding: '8px 16px', borderTop: '1px solid rgba(140,165,200,0.1)', fontSize: '10px', color: '#6b7280', display: 'flex', gap: '16px' }}>
<span><span style={{ color: '#9fb0c8' }}></span> pick</span>
<span><span style={{ color: '#9fb0c8' }}>Enter</span> create krate</span>
<span><span style={{ color: '#9fb0c8' }}>Esc</span> close</span>
</div>
</div>
</div>

View File

@@ -0,0 +1,75 @@
export interface K8sResource {
type: string
name: string
namespace?: string
status?: string
clusterIP?: string
ports?: string
}
export const NAMESPACE_COLORS: Record<string, string> = {
default: '#6fb1ff',
'kube-system': '#9c88ff',
'kube-public': '#4dd6e8',
production: '#6fb1ff',
staging: '#4dd6e8',
development: '#4dd6e8',
}
export const MOCK_NAMESPACES = [
{ name: 'default', status: 'Active' },
{ name: 'kube-system', status: 'Active' },
{ name: 'kube-public', status: 'Active' },
{ name: 'production', status: 'Active' },
{ name: 'staging', status: 'Active' },
{ name: 'development', status: 'Active' },
]
export const MOCK_PODS: K8sResource[] = [
{ type: 'pod', name: 'nginx-7d9f8', namespace: 'default', status: 'Running' },
{ type: 'pod', name: 'frontend-5a8b2', namespace: 'default', status: 'Running' },
{ type: 'pod', name: 'backend-9c3d4', namespace: 'production', status: 'Running' },
{ type: 'pod', name: 'api-server-2e7f1', namespace: 'production', status: 'Running' },
{ type: 'pod', name: 'worker-4b6c8', namespace: 'production', status: 'Pending' },
{ type: 'pod', name: 'cache-redis-1', namespace: 'staging', status: 'Running' },
{ type: 'pod', name: 'db-postgres-0', namespace: 'staging', status: 'Running' },
{ type: 'pod', name: 'db-postgres-1', namespace: 'staging', status: 'Pending' },
{ type: 'pod', name: 'monitoring-8a2b3', namespace: 'kube-system', status: 'Running' },
{ type: 'pod', name: 'dns-core-5d7e9', namespace: 'kube-system', status: 'Running' },
{ type: 'pod', name: 'log-aggregator-3c4d5', namespace: 'kube-system', status: 'Running' },
{ type: 'pod', name: 'gateway-7f9a2', namespace: 'production', status: 'Running' },
{ type: 'pod', name: 'frontend-dev-1', namespace: 'development', status: 'Running' },
{ type: 'pod', name: 'backend-dev-1', namespace: 'development', status: 'Running' },
{ type: 'pod', name: 'test-runner-6g8h9', namespace: 'default', status: 'Completed' },
]
export const MOCK_DEPLOYMENTS: K8sResource[] = [
{ type: 'deployment', name: 'nginx', namespace: 'default', status: 'Ready' },
{ type: 'deployment', name: 'frontend', namespace: 'default', status: 'Ready' },
{ type: 'deployment', name: 'backend', namespace: 'production', status: 'Ready' },
{ type: 'deployment', name: 'api-server', namespace: 'production', status: 'Ready' },
{ type: 'deployment', name: 'worker', namespace: 'production', status: 'Pending' },
{ type: 'deployment', name: 'redis', namespace: 'staging', status: 'Ready' },
{ type: 'deployment', name: 'postgres', namespace: 'staging', status: 'Ready' },
{ type: 'deployment', name: 'gateway', namespace: 'production', status: 'Ready' },
]
export const MOCK_SERVICES: K8sResource[] = [
{ type: 'service', name: 'nginx-svc', namespace: 'default', clusterIP: '10.96.0.10', ports: '80/TCP' },
{ type: 'service', name: 'frontend-svc', namespace: 'default', clusterIP: '10.96.0.20', ports: '80/TCP' },
{ type: 'service', name: 'backend-svc', namespace: 'production', clusterIP: '10.96.0.30', ports: '8080/TCP' },
{ type: 'service', name: 'api-server-svc', namespace: 'production', clusterIP: '10.96.0.40', ports: '8080/TCP' },
{ type: 'service', name: 'redis-svc', namespace: 'staging', clusterIP: '10.96.0.50', ports: '6379/TCP' },
{ type: 'service', name: 'postgres-svc', namespace: 'staging', clusterIP: '10.96.0.60', ports: '5432/TCP' },
]
export const MOCK_RESOURCES = [
...MOCK_NAMESPACES.map(ns => ({
type: 'namespace' as const,
name: ns.name,
status: ns.status,
})),
...MOCK_PODS,
...MOCK_DEPLOYMENTS,
...MOCK_SERVICES,
]

View File

@@ -0,0 +1,147 @@
import { useMemo } from 'react'
import { useKrateStore } from '../state/krateStore'
import { useCanvasStore } from '../state/canvasStore'
import { NAMESPACE_COLORS } from '../data/mockResources'
interface WindowInfo {
type: string
title: string
}
interface CreateKrateOptions {
title: string
type: string
namespace?: string
color?: string
windows?: WindowInfo[]
}
export const useCreateKrate = () => {
const addKrate = useKrateStore((state) => state.addKrate)
const setCam = useCanvasStore((state) => state.setCam)
const setZoom = useCanvasStore((state) => state.setZoom)
const COLLISION_TOLERANCE = 20
const findNonOverlappingPosition = (width: number, height: number, Krates: Map<string, any>): { x: number; y: number } => {
const getAABB = (krate: any) => ({
x1: krate.x - COLLISION_TOLERANCE,
y1: krate.y - COLLISION_TOLERANCE,
x2: krate.x + krate.width + COLLISION_TOLERANCE,
y2: krate.y + krate.height + COLLISION_TOLERANCE,
})
const rectsOverlap = (r1: any, r2: any) => {
return r1.x1 < r2.x2 && r1.x2 > r2.x1 && r1.y1 < r2.y2 && r1.y2 > r2.y1
}
const checkOverlap = (x: number, y: number, Krates: Map<string, any>): boolean => {
const newRect = {
x1: x - COLLISION_TOLERANCE,
y1: y - COLLISION_TOLERANCE,
x2: x + width + COLLISION_TOLERANCE,
y2: y + height + COLLISION_TOLERANCE,
}
for (const krate of Krates.values()) {
const existingRect = getAABB(krate)
if (rectsOverlap(newRect, existingRect)) {
return true
}
}
return false
}
let x = 100
let y = 100
let attempts = 0
const maxAttempts = 100
while (attempts < maxAttempts) {
if (!checkOverlap(x, y, Krates)) {
return { x, y }
}
x += width / 2
if (x > 8000) {
x = 100
y += height / 2
}
attempts++
}
return { x, y }
}
const createKrate = (options: CreateKrateOptions) => {
const { title, type, namespace, color, windows } = options
const Krates = useKrateStore.getState().krates
const { x, y } = findNonOverlappingPosition(800, 600, Krates)
const krateColor = color || (namespace ? NAMESPACE_COLORS[namespace] || '#6fb1ff' : '#6fb1ff')
const defaultWindows: Map<string, any> = new Map()
if (windows && windows.length > 0) {
windows.forEach((win, index) => {
defaultWindows.set(`${title}-${index}`, {
id: `${title}-${index}`,
type: win.type,
title: win.title,
x: index * 412,
y: 0,
width: 380,
height: 360,
color: krateColor,
})
})
} else {
const windowTypes = type === 'namespace' ? ['collection'] : ['logs', 'describe']
windowTypes.forEach((winType, index) => {
defaultWindows.set(`${title}-${winType}`, {
id: `${title}-${winType}`,
type: winType,
title: winType === 'logs' ? 'Logs' : 'Describe',
x: index * 412,
y: 0,
width: 380,
height: 360,
color: krateColor,
})
})
}
const newKrate = {
id: `krate-${Date.now()}`,
type,
title,
x,
y,
width: 800,
height: 600,
color: krateColor,
minimized: false,
windowLayout: {
cols: 2,
rows: 2,
cells: new Map(),
},
windows: defaultWindows,
}
addKrate(newKrate)
setCam(x + 400, y + 300)
setZoom(0.92)
}
return useMemo(
() => ({
createKrate,
}),
[]
)
}

View File

@@ -1,4 +1,5 @@
import { useEffect } from 'react'
import { useSpotlightStore } from '../state/spotlightStore'
export const useKeyboardShortcuts = (handlers: {
onOpenSpotlight: () => void
@@ -9,33 +10,79 @@ export const useKeyboardShortcuts = (handlers: {
onNavigateRight: () => void
onSelect: () => void
}) => {
const { open, sel, setSel } = useSpotlightStore()
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey || e.metaKey) return
if (e.ctrlKey || e.metaKey || e.altKey) return
switch (e.key) {
case 'Escape':
if (open) {
if (e.key === 'Escape') {
handlers.onCloseSpotlight()
break
case 'k':
handlers.onNavigateUp()
break
case 'j':
handlers.onNavigateDown()
break
case 'h':
handlers.onNavigateLeft()
break
case 'l':
handlers.onNavigateRight()
break
case 'Enter':
return
}
if (e.key === 'ArrowDown') {
e.preventDefault()
setSel(Math.min(sel + 1, useSpotlightStore.getState().results.length - 1))
return
}
if (e.key === 'ArrowUp') {
e.preventDefault()
setSel(Math.max(0, sel - 1))
return
}
if (e.key === 'PageDown') {
e.preventDefault()
setSel(Math.min(sel + 10, useSpotlightStore.getState().results.length - 1))
return
}
if (e.key === 'PageUp') {
e.preventDefault()
setSel(Math.max(0, sel - 10))
return
}
if (e.key === 'Home') {
e.preventDefault()
setSel(0)
return
}
if (e.key === 'End') {
e.preventDefault()
setSel(useSpotlightStore.getState().results.length - 1)
return
}
if (e.key === 'Enter') {
handlers.onSelect()
break
return
}
}
if (!open) {
switch (e.key) {
case 'k':
handlers.onNavigateUp()
break
case 'j':
handlers.onNavigateDown()
break
case 'h':
handlers.onNavigateLeft()
break
case 'l':
handlers.onNavigateRight()
break
}
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [handlers])
}, [open, sel, handlers, setSel])
}

View File

@@ -1,3 +1,23 @@
export const useKubernetes = () => {
return { nodes: [], pods: [], deployments: [], services: [] }
import { useMemo } from 'react'
import { MOCK_NAMESPACES, MOCK_PODS, MOCK_DEPLOYMENTS, MOCK_SERVICES } from '../data/mockResources'
export interface K8sResource {
type: string
name: string
namespace?: string
status?: string
color?: string
}
export const useKubernetes = () => {
return useMemo(
() => ({
namespaces: MOCK_NAMESPACES,
pods: MOCK_PODS,
deployments: MOCK_DEPLOYMENTS,
services: MOCK_SERVICES,
isLoading: false,
}),
[]
)
}

View File

@@ -1,52 +1,134 @@
import { useMemo } from 'react'
import { fuzzySearch } from '../utils/fuzzy'
import { MOCK_NAMESPACES, MOCK_PODS, MOCK_DEPLOYMENTS, MOCK_SERVICES } from '../data/mockResources'
export interface SpotlightResource {
id: string
type: string
name: string
namespace?: string
status?: string
color: string
}
export const useSpotlight = () => {
const fuzzy = (query: string, text: string): number => {
if (!query) return 0
const queryLower = query.toLowerCase()
const textLower = text.toLowerCase()
let score = 0
let queryIndex = 0
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
if (textLower[i] === queryLower[queryIndex]) {
score++
queryIndex++
}
const executeSearch = (query: string, filterType: string | null): SpotlightResource[] => {
if (!query || query.trim() === '') {
return []
}
return score / Math.max(query.length, text.length)
}
const filterItems = <T extends { name: string; type: string }>(
items: T[],
query: string,
filterType: string | null
): T[] => {
return items.filter((item) => {
const matchesQuery = fuzzy(query, item.name) > 0.3
const matchesType = !filterType || filterType === 'all' || item.type === filterType
return matchesQuery && matchesType
})
}
const resources: SpotlightResource[] = []
const debounce = <T extends (...args: any[]) => any>(fn: T, delay: number) => {
let timeoutId: NodeJS.Timeout | null = null
return (...args: Parameters<T>): Promise<ReturnType<T>> => {
return new Promise((resolve) => {
if (timeoutId) clearTimeout(timeoutId)
timeoutId = setTimeout(() => resolve(fn(...args)), delay)
const addIfExists = (items: any[], type: string) => {
items.forEach((item, index) => {
const name = item.name || item.metadata?.name
const ns = item.namespace || item.metadata?.namespace || 'default'
const color = getNamespaceColor(ns)
const score = fuzzySearch(query, name)
if (score > 0.3) {
if (!filterType || filterType === 'all' || filterType === type) {
resources.push({
id: `${type}-${index}-${name}`,
type,
name,
namespace: ns,
color: color,
})
}
}
})
}
if (!filterType || filterType === 'all' || filterType === 'namespace') {
MOCK_NAMESPACES.forEach((ns, index) => {
if (fuzzySearch(query, ns.name) > 0.3) {
resources.push({
id: `namespace-${index}-${ns.name}`,
type: 'namespace',
name: ns.name,
color: getNamespaceColor(ns.name),
})
}
})
}
if (!filterType || filterType === 'all' || filterType === 'pod') {
addIfExists(MOCK_PODS, 'pod')
}
if (!filterType || filterType === 'all' || filterType === 'deployment') {
addIfExists(MOCK_DEPLOYMENTS, 'deployment')
}
if (!filterType || filterType === 'all' || filterType === 'service') {
addIfExists(MOCK_SERVICES, 'service')
}
return resources.sort((a, b) => fuzzySearch(query, b.name) - fuzzySearch(query, a.name))
}
const getQuickActions = (query: string): SpotlightResource[] => {
const actions: SpotlightResource[] = []
const namespaces = Array.from(new Set([...MOCK_NAMESPACES.map((ns) => ns.name)]))
namespaces.forEach((ns, index) => {
actions.push({
id: `quick-${index}-${ns}`,
type: 'quick_create',
name: `Create krate for ns/${ns}`,
namespace: ns,
color: getNamespaceColor(ns),
})
})
actions.push({
id: 'quick-pods',
type: 'quick_create',
name: 'All pods',
color: '#6fb1ff',
})
actions.push({
id: 'quick-svcs',
type: 'quick_create',
name: 'All services',
color: '#6fb1ff',
})
actions.push({
id: 'quick-deployments',
type: 'quick_create',
name: 'All deployments',
color: '#6fb1ff',
})
if (query.trim()) {
actions.push({
id: 'quick-search',
type: 'quick_create',
name: `Search again for "${query}"`,
color: '#4dd6e8',
})
}
return actions
}
const getNamespaceColor = (namespace: string): string => {
if (namespace === 'default') return '#6fb1ff'
if (namespace === 'kube-system') return '#9c88ff'
if (namespace === 'kube-public') return '#4dd6e8'
if (namespace === 'production') return '#6fb1ff'
if (namespace === 'staging') return '#4dd6e8'
return '#6fb1ff'
}
return useMemo(
() => ({
fuzzy,
filterItems,
debounce,
executeSearch,
getQuickActions,
getNamespaceColor,
}),
[]
)

View File

@@ -1,6 +1,23 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { App } from './App'
import { useSpotlightStore } from './state/spotlightStore'
let pendingQuery = ''
const handleKeyDown = (e: KeyboardEvent) => {
const { open } = useSpotlightStore.getState()
if (e.ctrlKey || e.metaKey || e.altKey || open) return
const isCharacter = e.key.length === 1 && e.key.match(/[a-zA-Z0-9]/)
if (isCharacter) {
pendingQuery = e.key
const event = new CustomEvent('spotlight-trigger', { detail: { query: pendingQuery } })
window.dispatchEvent(event)
}
}
window.addEventListener('keydown', handleKeyDown)
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>

View File

@@ -2,3 +2,5 @@ export { useCanvasStore } from './canvasStore'
export { useKrateStore } from './krateStore'
export { useSpotlightStore } from './spotlightStore'
export { useUserStore } from './userStore'
export * from './spotlightStore'

View File

@@ -1,5 +1,6 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { NAMESPACE_COLORS } from '../data/mockResources'
export interface Krate {
id: string
@@ -28,6 +29,11 @@ export interface KrateWindow {
state: unknown
}
export interface WindowInfo {
type: string
title: string
}
export interface KrateStore {
krates: Map<string, Krate>
selectedKrateId: string | null
@@ -35,6 +41,7 @@ export interface KrateStore {
removeKrate: (id: string) => void
selectKrate: (id: string | null) => void
updateKrate: (id: string, updates: Partial<Krate>) => void
createKrate: (title: string, type: string, namespace?: string, color?: string, windows?: WindowInfo[]) => void
}
const INITIAL_STATE: KrateStore = {
@@ -44,10 +51,11 @@ const INITIAL_STATE: KrateStore = {
removeKrate: () => {},
selectKrate: () => {},
updateKrate: () => {},
createKrate: () => {},
}
export const useKrateStore = create<KrateStore>()(
devtools((set) => ({
devtools((set, get) => ({
...INITIAL_STATE,
addKrate: (krate: Krate) =>
set((state) => ({ krates: new Map(state.krates).set(krate.id, krate) })),
@@ -66,5 +74,99 @@ export const useKrateStore = create<KrateStore>()(
krates: new Map(state.krates).set(id, { ...existing, ...updates }),
}
}),
createKrate: (title: string, type: string, namespace?: string, color?: string, windows?: WindowInfo[]) => {
const { krates } = get()
const krateColor = color || (namespace ? NAMESPACE_COLORS[namespace] || '#6fb1ff' : '#6fb1ff')
const findNonOverlappingPosition = (width: number, height: number, Krates: Map<string, Krate>): { x: number; y: number } => {
const COLLISION_TOLERANCE = 20
const getAABB = (krate: Krate) => ({
x1: krate.x - COLLISION_TOLERANCE,
y1: krate.y - COLLISION_TOLERANCE,
x2: krate.x + krate.width + COLLISION_TOLERANCE,
y2: krate.y + krate.height + COLLISION_TOLERANCE,
})
const rectsOverlap = (r1: any, r2: any) => {
return r1.x1 < r2.x2 && r1.x2 > r2.x1 && r1.y1 < r2.y2 && r1.y2 > r2.y1
}
const checkOverlap = (x: number, y: number, Krates: Map<string, Krate>): boolean => {
const newRect = {
x1: x - COLLISION_TOLERANCE,
y1: y - COLLISION_TOLERANCE,
x2: x + width + COLLISION_TOLERANCE,
y2: y + height + COLLISION_TOLERANCE,
}
for (const krate of Krates.values()) {
const existingRect = getAABB(krate)
if (rectsOverlap(newRect, existingRect)) {
return true
}
}
return false
}
let x = 100
let y = 100
let attempts = 0
const maxAttempts = 100
while (attempts < maxAttempts) {
if (!checkOverlap(x, y, Krates)) {
return { x, y }
}
x += width / 2
if (x > 8000) {
x = 100
y += height / 2
}
attempts++
}
return { x, y }
}
const { x, y } = findNonOverlappingPosition(800, 600, krates)
const defaultWindows: Map<string, KrateWindow> = new Map()
const windowsToCreate = windows || (type === 'namespace' ? [{ type: 'collection', title: 'Status' }] : [{ type: 'logs', title: 'Logs' }, { type: 'describe', title: 'Describe' }])
windowsToCreate.forEach((win, index) => {
defaultWindows.set(`${title}-${index}`, {
id: `${title}-${index}`,
type: win.type,
title: win.title,
state: {},
})
})
const newKrate: Krate = {
id: `krate-${Date.now()}`,
type,
title,
x,
y,
width: 800,
height: 600,
color: krateColor,
minimized: false,
windowLayout: {
cols: 2,
rows: 2,
cells: new Map(),
},
windows: defaultWindows,
}
set((state) => ({
krates: new Map(state.krates).set(newKrate.id, newKrate),
}))
},
}))
)

View File

@@ -1,32 +1,61 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
export interface SpotlightResult {
id: string
type: string
name: string
namespace?: string
color: string
}
export interface SpotlightState {
open: boolean
query: string
filterType: string | null
sel: number
navigated: boolean
results: SpotlightResult[]
loading: boolean
error: string | null
}
export type SpotlightFilter = 'all' | 'krate' | 'namespace' | 'pod' | 'deployment' | 'service'
interface SpotlightStore extends SpotlightState {
setOpen: (open: boolean) => void
setQuery: (query: string) => void
setFilter: (filterType: SpotlightFilter | null) => void
setSel: (sel: number) => void
setResults: (results: SpotlightResult[]) => void
setLoading: (loading: boolean) => void
setError: (error: string | null) => void
toggle: () => void
close: () => void
}
const INITIAL_STATE: SpotlightState = {
open: false,
query: '',
filterType: null,
sel: 0,
navigated: false,
results: [],
loading: false,
error: null,
}
export const useSpotlightStore = create<SpotlightState>()(
export const useSpotlightStore = create<SpotlightStore>()(
devtools((set) => ({
...INITIAL_STATE,
setOpen: (open: boolean) => set({ open }),
setQuery: (query: string) => set({ query, navigated: false, sel: 0 }),
setFilter: (filterType: SpotlightFilter | null) => set({ filterType }),
setSel: (sel: number) => set({ sel, navigated: true }),
setResults: (results: SpotlightResult[]) => set({ results }),
setLoading: (loading: boolean) => set({ loading }),
setError: (error: string | null) => set({ error }),
toggle: () => set((state) => ({ open: !state.open })),
close: () => set({ open: false, query: '', filterType: null, sel: 0, navigated: false }),
close: () => set({ open: false, query: '', filterType: null, sel: 0, navigated: false, results: [], error: null }),
}))
)

881
design/Krates.dc.html Normal file
View File

@@ -0,0 +1,881 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:#0b0e13}
::selection{background:rgba(120,200,230,.28)}
@keyframes spotIn{from{opacity:0;transform:translate(-50%,-8px)}to{opacity:1;transform:translate(-50%,0)}}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes popIn{from{opacity:0;transform:scale(.97)}to{opacity:1;transform:scale(1)}}
@keyframes blink{50%{opacity:0}}
@keyframes floatY{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
input::placeholder{color:#5a6680}
pre::-webkit-scrollbar,div::-webkit-scrollbar{width:8px;height:8px}
pre::-webkit-scrollbar-thumb,div::-webkit-scrollbar-thumb{background:rgba(140,165,200,.18);border-radius:4px}
</style>
</helmet>
<div ref="{{ setRoot }}" style="position:fixed;inset:0;overflow:hidden;background:#0b0e13;font-family:'IBM Plex Sans',system-ui,sans-serif;color:#c7d2e0;cursor:{{ cursor }};user-select:none" onMouseDownCapture="{{ onRootDownCapture }}" onMouseDown="{{ onBgDown }}" onWheel="{{ onWheel }}">
<!-- ====== WORLD LAYER ====== -->
<div style="position:absolute;left:0;top:0;width:12000px;height:8000px;transform:{{ worldTransform }};transform-origin:0 0;transition:{{ worldTransition }};background-color:#0b0e13;background-image:linear-gradient(rgba(125,145,175,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(125,145,175,.04) 1px,transparent 1px),linear-gradient(rgba(125,145,175,.075) 1px,transparent 1px),linear-gradient(90deg,rgba(125,145,175,.075) 1px,transparent 1px);background-size:34px 34px,34px 34px,170px 170px,170px 170px">
<!-- krate frames + headers -->
<sc-for list="{{ frames }}" as="f" hint-placeholder-count="0">
<div style="position:absolute;left:{{ f.left }}px;top:{{ f.top }}px;width:{{ f.w }}px;height:{{ f.h }}px;border:1px dashed {{ f.border }};border-radius:18px;background:{{ f.fill }};pointer-events:none"></div>
<div style="position:absolute;left:{{ f.labelLeft }}px;top:{{ f.labelTop }}px;display:flex;align-items:center;gap:9px;padding:6px 12px;border:1px solid {{ f.border }};border-radius:9px;background:rgba(15,19,27,.94);font-family:'IBM Plex Mono',monospace;font-size:13px;letter-spacing:.02em;color:#dbe3ef;cursor:grab" onMouseDown="{{ f.onDrag }}" onDoubleClick="{{ f.onToggleCollapse }}">
<span style="width:8px;height:8px;border-radius:2px;background:{{ f.color }}"></span>{{ f.label }}
<span style="font-size:10px;color:{{ f.statusColor }};border:1px solid {{ f.statusColor }};border-radius:5px;padding:1px 6px">{{ f.status }}</span>
<span style="font-size:11px;color:#6b7890">{{ f.count }} views</span>
<button onClick="{{ f.onCollapse }}" onMouseDown="{{ stopDown }}" title="collapse krate" style="border:none;background:transparent;color:#6b7890;font-size:15px;cursor:pointer;padding:0 0 0 2px;line-height:1"></button>
<button onClick="{{ f.onClose }}" onMouseDown="{{ stopDown }}" style="border:none;background:transparent;color:#6b7890;font-size:14px;cursor:pointer;padding:0 0 0 2px;line-height:1">×</button>
</div>
</sc-for>
<!-- collapsed krate bars -->
<sc-for list="{{ minis }}" as="m" hint-placeholder-count="0">
<div onDoubleClick="{{ m.onExpand }}" style="position:absolute;left:{{ m.x }}px;top:{{ m.y }}px;width:300px;display:flex;align-items:center;gap:10px;padding:11px 13px;border:1px solid {{ m.outline }};border-left:3px solid {{ m.color }};border-radius:11px;background:rgba(15,19,27,.97);box-shadow:0 14px 40px rgba(0,0,0,.5);cursor:grab" onMouseDown="{{ m.onDrag }}">
<span style="width:9px;height:9px;border-radius:2px;background:{{ m.color }};flex:none"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:13px;color:#e6edf6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">{{ m.label }}</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:{{ m.statusColor }};flex:none">{{ m.count }} views</span>
<button onClick="{{ m.onExpand }}" onMouseDown="{{ stopDown }}" title="expand" style="border:none;background:transparent;color:#9fb0c8;font-size:13px;cursor:pointer;padding:0 2px;line-height:1"></button>
<button onClick="{{ m.onClose }}" onMouseDown="{{ stopDown }}" style="border:none;background:transparent;color:#6b7890;font-size:14px;cursor:pointer;padding:0;line-height:1">×</button>
</div>
</sc-for>
<sc-for list="{{ wins }}" as="w" hint-placeholder-count="0">
<div style="position:absolute;left:{{ w.x }}px;top:{{ w.y }}px;width:{{ w.w }}px;z-index:{{ w.z }};background:rgba(14,18,25,.98);border:1px solid {{ w.outline }};border-radius:11px;box-shadow:0 18px 50px rgba(0,0,0,.5);overflow:hidden;animation:popIn .16s ease" onMouseEnter="{{ w.onWinEnter }}" onMouseLeave="{{ w.onWinLeave }}" onMouseDown="{{ stopDown }}" onWheel="{{ winWheel }}" onDoubleClick="{{ stopDown }}">
<div style="display:flex;align-items:center;gap:9px;padding:9px 11px;border-bottom:1px solid rgba(140,165,200,.13);background:rgba(20,26,36,.7);cursor:grab" onMouseDown="{{ w.onDrag }}" onDoubleClick="{{ w.onFocus }}">
<span style="width:9px;height:9px;border-radius:2px;background:{{ w.color }};clip-path:{{ w.glyphClip }};flex:none"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:12.5px;color:#dbe3ef;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">{{ w.title }}</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:{{ w.statusColor }};border:1px solid {{ w.statusColor }};border-radius:5px;padding:1px 6px;opacity:.9">{{ w.status }}</span>
<button onClick="{{ w.onFocus }}" onMouseDown="{{ stopDown }}" title="zoom to window (z)" style="border:none;background:transparent;color:#7e8aa2;font-size:12px;cursor:pointer;padding:0 2px;line-height:1"></button>
<button onClick="{{ w.onRelated }}" onMouseDown="{{ stopDown }}" title="open related" style="border:none;background:transparent;color:#7e8aa2;font-size:13px;cursor:pointer;padding:0 2px;line-height:1;display:{{ w.relatedDisplay }}"></button>
<button onClick="{{ w.onClose }}" onMouseDown="{{ stopDown }}" style="border:none;background:transparent;color:#7e8aa2;font-size:15px;cursor:pointer;padding:0 2px;line-height:1">×</button>
</div>
<sc-if value="{{ w.normal }}" hint-placeholder-val="{{ true }}">
<div style="display:flex;gap:2px;padding:6px 8px 0;border-bottom:1px solid rgba(140,165,200,.1)">
<sc-for list="{{ w.tabs }}" as="t" hint-placeholder-count="4">
<button onClick="{{ t.onClick }}" onMouseDown="{{ stopDown }}" style="border:none;background:{{ t.bg }};color:{{ t.fg }};font-family:'IBM Plex Mono',monospace;font-size:11.5px;padding:6px 10px;border-radius:7px 7px 0 0;cursor:{{ t.cursor }};opacity:{{ t.opacity }}">{{ t.label }}</button>
</sc-for>
</div>
</sc-if>
<sc-if value="{{ w.isText }}" hint-placeholder-val="{{ true }}">
<pre style="margin:0;padding:13px 15px;height:{{ w.bodyH }}px;overflow:auto;font-family:'IBM Plex Mono',monospace;font-size:12px;line-height:1.65;color:#b9c6d8;white-space:pre-wrap;word-break:break-word;user-select:text;cursor:text">{{ w.body }}</pre>
</sc-if>
<sc-if value="{{ w.isLogs }}" hint-placeholder-val="{{ false }}">
<div ref="{{ w.logRef }}" onScroll="{{ w.onLogScroll }}" style="height:{{ w.bodyH }}px;overflow:auto;padding:12px 14px;background:#090c11;font-family:'IBM Plex Mono',monospace;font-size:11.5px;line-height:1.6;user-select:text;cursor:text">
<sc-for list="{{ w.logLines }}" as="ln" hint-placeholder-count="6"><div style="color:{{ ln.color }};white-space:pre-wrap">{{ ln.text }}</div></sc-for>
<div style="display:flex;align-items:center;gap:7px;color:#5a6680;font-size:10px;margin-top:7px"><span style="width:6px;height:6px;border-radius:50%;background:#4ad07a;animation:blink 1.2s ease-in-out infinite"></span>streaming…</div>
</div>
</sc-if>
<sc-if value="{{ w.isShell }}" hint-placeholder-val="{{ false }}">
<div onMouseEnter="{{ w.onShEnter }}" onMouseLeave="{{ w.onShLeave }}" style="height:{{ w.bodyH }}px;display:flex;flex-direction:column;background:#090c11">
<div ref="{{ w.shRef }}" onScroll="{{ w.onShScroll }}" style="flex:1;overflow:auto;padding:12px 14px;font-family:'IBM Plex Mono',monospace;font-size:12px;line-height:1.6;user-select:text;cursor:text">
<sc-for list="{{ w.shellLines }}" as="ln" hint-placeholder-count="2"><div style="color:{{ ln.color }};white-space:pre-wrap;word-break:break-word">{{ ln.text }}</div></sc-for>
</div>
<div style="display:flex;align-items:center;gap:8px;padding:9px 13px;border-top:1px solid rgba(140,165,200,.12)">
<span style="color:{{ accent }};font-family:'IBM Plex Mono',monospace;font-size:11.5px;white-space:nowrap">{{ w.promptStr }}</span>
<input ref="{{ w.shInputRef }}" onKeyDown="{{ w.onShellKey }}" onMouseDown="{{ stopDown }}" placeholder="type a command…" spellcheck="false" autocomplete="off" style="flex:1;min-width:0;border:none;outline:none;background:transparent;color:#e6edf6;font-family:'IBM Plex Mono',monospace;font-size:12px"/>
</div>
</div>
</sc-if>
<sc-if value="{{ w.isCollection }}" hint-placeholder-val="{{ false }}">
<div onMouseEnter="{{ w.onCollEnter }}" onMouseLeave="{{ w.onCollLeave }}">
<div style="display:flex;align-items:center;gap:8px;padding:8px 11px;border-bottom:1px solid rgba(140,165,200,.1)">
<span style="font-family:'IBM Plex Mono',monospace;font-size:11px;color:{{ accent }};flex:none"></span>
<input ref="{{ w.collInputRef }}" onChange="{{ w.onCollSearch }}" onKeyDown="{{ w.onCollKey }}" onMouseDown="{{ stopDown }}" value="{{ w.searchVal }}" placeholder="filter {{ w.total }} objects…" spellcheck="false" autocomplete="off" style="flex:1;min-width:0;border:none;outline:none;background:transparent;color:#e6edf6;font-family:'IBM Plex Mono',monospace;font-size:12px"/>
<span style="display:flex;align-items:center;gap:5px;padding:2px 8px;border-radius:6px;background:rgba(255,255,255,.03);border:1px solid rgba(140,165,200,.16);font-family:'IBM Plex Mono',monospace;font-size:9.5px;color:#7e8aa2;flex:none">↑↓ ⌥L/S/D/Y</span>
<div style="display:flex;gap:2px;background:rgba(8,11,16,.6);border:1px solid rgba(140,165,200,.16);border-radius:7px;padding:2px;flex:none">
<button onClick="{{ w.onCollList }}" onMouseDown="{{ stopDown }}" style="border:none;border-radius:5px;padding:3px 9px;cursor:pointer;font-family:'IBM Plex Mono',monospace;font-size:10.5px;background:{{ w.listBg }};color:{{ w.listFg }}">list</button>
<button onClick="{{ w.onCollTree }}" onMouseDown="{{ stopDown }}" style="border:none;border-radius:5px;padding:3px 9px;cursor:pointer;font-family:'IBM Plex Mono',monospace;font-size:10.5px;background:{{ w.treeBg }};color:{{ w.treeFg }}">tree</button>
</div>
</div>
<div style="display:flex;align-items:center;gap:7px;padding:7px 12px;border-bottom:1px solid rgba(140,165,200,.08);font-family:'IBM Plex Mono',monospace;font-size:10px">
<span style="display:flex;align-items:center;gap:5px;color:#4ad07a"><span style="width:6px;height:6px;border-radius:50%;background:#4ad07a"></span>{{ w.summOk }} ok</span>
<span style="display:flex;align-items:center;gap:5px;color:#e8b54a"><span style="width:6px;height:6px;border-radius:50%;background:#e8b54a"></span>{{ w.summWarn }} pending</span>
<span style="display:flex;align-items:center;gap:5px;color:#ef6f6f"><span style="width:6px;height:6px;border-radius:50%;background:#ef6f6f"></span>{{ w.summBad }} degraded</span>
<span style="margin-left:auto;color:#6b7890">{{ w.count }} shown</span>
</div>
<div style="height:{{ w.bodyH }}px;overflow:auto;padding:6px">
<sc-for list="{{ w.rows }}" as="r" hint-placeholder-count="6">
<sc-if value="{{ r.isGroup }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;align-items:center;gap:8px;padding:9px 9px 4px;margin-top:2px">
<span style="width:9px;height:9px;background:{{ r.color }};clip-path:{{ r.clip }};border-radius:{{ r.radius }};flex:none"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:#9fb0c8;flex:1">{{ r.groupLabel }}</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:#6b7890;flex:none">{{ r.groupCount }}</span>
</div>
</sc-if>
<sc-if value="{{ r.row }}" hint-placeholder-val="{{ true }}">
<div onClick="{{ r.onOpen }}" onMouseEnter="{{ r.onHover }}" onMouseDown="{{ stopDown }}" style="display:flex;align-items:center;gap:9px;padding:7px 9px 7px 7px;border-radius:8px;cursor:pointer;margin-left:{{ r.indentPx }}px;background:{{ r.selBg }};border-left:2px solid {{ r.selBar }}" style-hover="background:rgba(255,255,255,.04)">
<sc-if value="{{ r.child }}" hint-placeholder-val="{{ false }}"><span style="width:9px;height:9px;border-left:1px solid rgba(140,165,200,.35);border-bottom:1px solid rgba(140,165,200,.35);border-bottom-left-radius:3px;margin:0 -3px -3px 0;flex:none"></span></sc-if>
<span style="width:11px;height:11px;background:{{ r.color }};clip-path:{{ r.clip }};border-radius:{{ r.radius }};flex:none;filter:drop-shadow(0 0 3px {{ r.color }})"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:12px;color:#e6edf6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">{{ r.name }}</span>
<sc-if value="{{ r.hasRel }}" hint-placeholder-val="{{ false }}"><span style="font-family:'IBM Plex Mono',monospace;font-size:9px;color:#6b7890;flex:none">{{ r.rel }}</span></sc-if>
<span style="font-family:'IBM Plex Mono',monospace;font-size:9.5px;color:#7e8aa2;flex:none">{{ r.metric }}</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:9.5px;color:#8493ab;border:1px solid rgba(140,165,200,.2);border-radius:4px;padding:1px 5px;flex:none">{{ r.typeShort }}</span>
<div style="display:flex;gap:3px;flex:none">
<sc-for list="{{ r.views }}" as="v" hint-placeholder-count="0">
<button onClick="{{ v.onClick }}" onMouseDown="{{ stopDown }}" title="open" style="display:flex;align-items:center;justify-content:center;width:18px;height:18px;border:1px solid rgba(140,165,200,.2);background:rgba(255,255,255,.03);border-radius:5px;cursor:pointer;font-family:'IBM Plex Mono',monospace;font-size:9px;color:#9fb0c8">{{ v.letter }}</button>
</sc-for>
</div>
<span style="width:7px;height:7px;border-radius:50%;background:{{ r.statusColor }};flex:none;box-shadow:0 0 6px {{ r.statusColor }}"></span>
</div>
</sc-if>
</sc-for>
<sc-if value="{{ w.empty }}" hint-placeholder-val="{{ false }}">
<div style="padding:22px;text-align:center;font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#6b7890">no objects match filter</div>
</sc-if>
</div>
</div>
</sc-if>
<div onMouseDown="{{ w.onResize }}" style="position:absolute;right:0;bottom:0;width:18px;height:18px;cursor:nwse-resize;border-right:2px solid rgba(140,165,200,.4);border-bottom:2px solid rgba(140,165,200,.4);border-bottom-right-radius:9px"></div>
</div>
</sc-for>
</div>
<!-- ====== COLLAPSED CARDS (screen space) ====== -->
<sc-for list="{{ cards }}" as="c" hint-placeholder-count="0">
<div style="position:absolute;left:{{ c.sx }}px;top:{{ c.sy }}px;width:230px;background:rgba(15,19,27,.97);border:1px solid {{ c.outline }};border-radius:12px;box-shadow:0 16px 44px rgba(0,0,0,.5);overflow:hidden;cursor:grab;z-index:{{ c.z }};transition:{{ c.trans }};animation:popIn .16s ease" onMouseDown="{{ c.onDown }}" onDoubleClick="{{ c.onExpand }}">
<div style="display:flex;align-items:center;gap:8px;padding:11px 13px 9px">
<span style="width:9px;height:9px;border-radius:2px;background:{{ c.color }};flex:none"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:13px;color:#e6edf6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">{{ c.label }}</span>
<span style="width:7px;height:7px;border-radius:50%;background:{{ c.statusColor }};flex:none;box-shadow:0 0 7px {{ c.statusColor }}"></span>
<button onClick="{{ c.onClose }}" onMouseDown="{{ stopDown }}" style="border:none;background:transparent;color:#6b7890;font-size:14px;cursor:pointer;padding:0;line-height:1;flex:none">×</button>
</div>
<div style="display:flex;gap:5px;padding:0 13px 11px;flex-wrap:wrap">
<sc-for list="{{ c.badges }}" as="b" hint-placeholder-count="2">
<span style="display:flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:6px;background:{{ c.tint }};border:1px solid {{ c.outline }};font-family:'IBM Plex Mono',monospace;font-size:11px;color:{{ c.color }}">{{ b }}</span>
</sc-for>
</div>
<div style="padding:8px 13px;border-top:1px solid rgba(140,165,200,.1);background:rgba(8,11,16,.5);font-family:'IBM Plex Mono',monospace;font-size:10px;color:#7e8aa2;display:flex;justify-content:space-between">
<span>{{ c.count }} windows · ns/{{ c.ns }}</span><span style="color:#5a6680">⤢ drag · dbl-click</span>
</div>
</div>
</sc-for>
<!-- ====== RELATED MENU ====== -->
<sc-if value="{{ relMenu }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;left:{{ relMenu.sx }}px;top:{{ relMenu.sy }}px;width:236px;background:rgba(16,20,28,.98);border:1px solid rgba(140,165,200,.24);border-radius:11px;box-shadow:0 22px 60px rgba(0,0,0,.6);overflow:hidden;z-index:55;animation:popIn .14s ease" onMouseDown="{{ stopDown }}" onWheel="{{ stopDown }}">
<div style="padding:9px 13px;font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:#6b7890;border-bottom:1px solid rgba(140,165,200,.1)">related to {{ relMenu.label }}</div>
<div style="max-height:300px;overflow:auto">
<sc-for list="{{ relMenu.items }}" as="it" hint-placeholder-count="3">
<div onClick="{{ it.onClick }}" onMouseDown="{{ stopDown }}" style="display:flex;align-items:center;gap:11px;padding:9px 13px;cursor:pointer" style-hover="background:rgba(255,255,255,.045)">
<span style="width:13px;height:13px;background:{{ it.color }};clip-path:{{ it.clip }};border-radius:{{ it.radius }};flex:none;filter:drop-shadow(0 0 4px {{ it.color }})"></span>
<span style="flex:1;min-width:0;font-family:'IBM Plex Mono',monospace;font-size:12.5px;color:#e6edf6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ it.name }}</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:#8493ab;border:1px solid rgba(140,165,200,.2);border-radius:5px;padding:1px 6px;flex:none">{{ it.typeShort }}</span>
</div>
</sc-for>
</div>
<sc-if value="{{ relMenu.empty }}" hint-placeholder-val="{{ false }}">
<div style="padding:16px;text-align:center;font-family:'IBM Plex Mono',monospace;font-size:11px;color:#6b7890">no related objects</div>
</sc-if>
</div>
</sc-if>
<!-- ====== EMPTY STATE ====== -->
<sc-if value="{{ emptyCanvas }}" hint-placeholder-val="{{ true }}">
<div style="position:absolute;left:50%;top:47%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:20px;pointer-events:none;text-align:center;width:480px">
<div style="width:50px;height:50px;background:{{ accent }};clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%);filter:drop-shadow(0 0 22px {{ accentGlow }});animation:floatY 4s ease-in-out infinite"></div>
<div style="font-family:'IBM Plex Sans',sans-serif;font-size:25px;color:#e8eef6;font-weight:500;letter-spacing:-.01em">Search your cluster</div>
<div style="font-family:'IBM Plex Mono',monospace;font-size:13px;color:#7e8aa2;line-height:1.75">Start typing or click anywhere. Pick a result, then <span style="color:{{ accent }}">⌥L</span> logs · <span style="color:{{ accent }}">⌥S</span> shell · <span style="color:{{ accent }}">⌥D</span> describe · <span style="color:{{ accent }}">⌥Y</span> yaml to open the views you want, grouped on the canvas.</div>
</div>
</sc-if>
<!-- ====== TOP BAR ====== -->
<div style="position:absolute;top:0;left:0;right:0;display:flex;align-items:center;gap:14px;padding:13px 18px;pointer-events:none">
<div style="display:flex;align-items:center;gap:10px;pointer-events:auto">
<div style="display:flex;align-items:center;gap:8px;padding:7px 12px;background:rgba(14,18,25,.82);border:1px solid rgba(140,165,200,.18);border-radius:9px;backdrop-filter:blur(6px)">
<span style="width:8px;height:8px;background:{{ accent }};clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%)"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:13px;letter-spacing:.06em;color:#e3eaf4;font-weight:500">krates</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:11px;color:#6b7890">/ yard</span>
</div>
<div style="display:flex;align-items:center;gap:7px;padding:7px 12px;background:rgba(14,18,25,.82);border:1px solid rgba(140,165,200,.18);border-radius:9px;backdrop-filter:blur(6px);font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#9fb0c8">
<span style="width:6px;height:6px;border-radius:50%;background:#4ad07a;box-shadow:0 0 8px #4ad07a"></span>prod-eu-1
</div>
<sc-if value="{{ krateCount }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;align-items:center;gap:7px;padding:7px 12px;background:rgba(14,18,25,.82);border:1px solid rgba(140,165,200,.18);border-radius:9px;backdrop-filter:blur(6px);font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#7e8aa2">{{ krateCount }} krates</div>
</sc-if>
</div>
<div style="flex:1"></div>
<div style="display:flex;align-items:center;gap:9px;pointer-events:auto">
<div style="display:flex;align-items:center;gap:6px;padding:6px 11px;background:rgba(14,18,25,.82);border:1px solid rgba(140,165,200,.18);border-radius:9px;backdrop-filter:blur(6px);font-family:'IBM Plex Mono',monospace;font-size:11px;color:#7e8aa2">
<span style="width:6px;height:6px;border-radius:50%;background:{{ accent }};animation:blink 1.6s ease-in-out infinite"></span>synced
</div>
<button onClick="{{ onToggleAdmin }}" onMouseDown="{{ stopDown }}" style="display:flex;align-items:center;gap:6px;padding:6px 11px;background:{{ adminBtnBg }};border:1px solid {{ adminBtnBorder }};border-radius:9px;backdrop-filter:blur(6px);font-family:'IBM Plex Mono',monospace;font-size:11px;color:{{ adminBtnFg }};cursor:pointer">◉ admin</button>
<div style="display:flex;align-items:center">
<sc-for list="{{ roster }}" as="u" hint-placeholder-count="4">
<div title="{{ u.name }}" style="width:30px;height:30px;margin-left:-7px;border-radius:50%;background:{{ u.color }};border:2px solid #0b0e13;display:flex;align-items:center;justify-content:center;font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;color:#0b0e13">{{ u.initial }}</div>
</sc-for>
</div>
</div>
</div>
<!-- ====== BOTTOM HINT ====== -->
<div style="position:absolute;right:18px;bottom:18px;display:flex;align-items:center;gap:13px;padding:9px 14px;background:rgba(14,18,25,.8);border:1px solid rgba(140,165,200,.16);border-radius:10px;backdrop-filter:blur(6px);font-family:'IBM Plex Mono',monospace;font-size:11px;color:#7e8aa2;pointer-events:none">
<span><span style="color:#aeb9cc">click / type</span> search</span>
<span style="opacity:.4">·</span>
<span><span style="color:#aeb9cc">scroll</span> pan</span>
<span style="opacity:.4">·</span>
<span><span style="color:#aeb9cc">⌘/ctrl + scroll</span> zoom</span>
<span style="opacity:.4">·</span>
<span><span style="color:#aeb9cc">space-drag</span> pan</span>
<span style="opacity:.4">·</span>
<span><span style="color:#aeb9cc">z</span> zoom window</span>
</div>
<!-- ====== ZOOM PILL ====== -->
<div style="position:absolute;left:18px;bottom:18px;display:flex;align-items:center;gap:9px;padding:8px 13px;background:rgba(14,18,25,.8);border:1px solid rgba(140,165,200,.16);border-radius:10px;backdrop-filter:blur(6px);font-family:'IBM Plex Mono',monospace;font-size:11px;color:#9fb0c8;pointer-events:none">
<span style="width:6px;height:6px;border-radius:50%;background:{{ zoomDotColor }}"></span>{{ zoomLabel }}
</div>
<!-- ====== MINIMAP ====== -->
<sc-if value="{{ minimap }}" hint-placeholder-val="{{ false }}">
<div ref="{{ minimap.setEl }}" onMouseDown="{{ minimap.onDown }}" style="position:absolute;right:18px;bottom:64px;width:{{ minimap.w }}px;height:{{ minimap.h }}px;background:rgba(11,14,19,.92);border:1px solid rgba(140,165,200,.2);border-radius:10px;backdrop-filter:blur(6px);overflow:hidden;cursor:pointer;box-shadow:0 12px 36px rgba(0,0,0,.5)">
<div style="position:absolute;left:6px;top:5px;font-family:'IBM Plex Mono',monospace;font-size:8.5px;letter-spacing:.12em;text-transform:uppercase;color:#5a6680;pointer-events:none">map</div>
<sc-for list="{{ minimap.rects }}" as="r" hint-placeholder-count="0">
<div style="position:absolute;left:{{ r.x }}px;top:{{ r.y }}px;width:{{ r.w }}px;height:{{ r.h }}px;background:{{ r.color }};opacity:.55;border-radius:2px;pointer-events:none"></div>
</sc-for>
<div style="position:absolute;left:{{ minimap.vx }}px;top:{{ minimap.vy }}px;width:{{ minimap.vw }}px;height:{{ minimap.vh }}px;border:1px solid rgba(230,237,246,.85);border-radius:2px;background:rgba(230,237,246,.06);pointer-events:none"></div>
</div>
</sc-if>
<!-- ====== ADMIN DRAWER ====== -->
<sc-if value="{{ admin }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;inset:0;background:rgba(7,9,13,.4);z-index:55;animation:fadeIn .12s ease" onMouseDown="{{ onToggleAdmin }}"></div>
<div style="position:absolute;top:0;right:0;bottom:0;width:380px;background:rgba(13,17,24,.98);border-left:1px solid rgba(140,165,200,.2);box-shadow:-20px 0 60px rgba(0,0,0,.5);z-index:56;display:flex;flex-direction:column;animation:fadeIn .16s ease" onMouseDown="{{ stopDown }}">
<div style="display:flex;align-items:center;gap:10px;padding:16px 18px;border-bottom:1px solid rgba(140,165,200,.14)">
<span style="width:9px;height:9px;border-radius:50%;background:{{ accent }};box-shadow:0 0 8px {{ accent }}"></span>
<div style="flex:1"><div style="font-family:'IBM Plex Mono',monospace;font-size:14px;color:#e8eef6">Yard activity</div><div style="font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#7e8aa2;margin-top:2px">{{ adminCount }} connected · prod-eu-1</div></div>
<button onClick="{{ onToggleAdmin }}" onMouseDown="{{ stopDown }}" style="border:none;background:transparent;color:#7e8aa2;font-size:17px;cursor:pointer;line-height:1">×</button>
</div>
<div style="flex:1;overflow:auto;padding:12px">
<sc-for list="{{ adminUsers }}" as="u" hint-placeholder-count="4">
<div style="margin-bottom:12px;border:1px solid {{ u.border }};border-radius:12px;background:rgba(255,255,255,.02);overflow:hidden">
<div style="display:flex;align-items:center;gap:10px;padding:11px 13px">
<div style="width:30px;height:30px;border-radius:50%;background:{{ u.color }};display:flex;align-items:center;justify-content:center;font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;color:#0b0e13;flex:none">{{ u.initial }}</div>
<div style="flex:1;min-width:0"><div style="font-family:'IBM Plex Mono',monospace;font-size:12.5px;color:#e6edf6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ u.name }}</div><div style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:{{ u.statusColor }};margin-top:1px">{{ u.status }}</div></div>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:#7e8aa2;flex:none">{{ u.krateCount }} krates</span>
</div>
<div style="display:flex;align-items:center;gap:8px;padding:8px 13px;background:rgba(8,11,16,.5);border-top:1px solid rgba(140,165,200,.1)">
<span style="font-family:'IBM Plex Mono',monospace;font-size:11px;color:{{ accent }};flex:none"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#aeb9cc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">{{ u.query }}</span>
<sc-if value="{{ u.typing }}" hint-placeholder-val="{{ false }}"><span style="font-family:'IBM Plex Mono',monospace;font-size:9px;color:{{ accent }};flex:none">typing…</span></sc-if>
</div>
<div style="display:flex;flex-direction:column;gap:1px;padding:4px 6px 7px">
<sc-for list="{{ u.krates }}" as="kr" hint-placeholder-count="2">
<div onClick="{{ kr.onSpectate }}" onMouseDown="{{ stopDown }}" style="display:flex;align-items:center;gap:9px;padding:7px 8px;border-radius:8px;cursor:pointer" style-hover="background:rgba(255,255,255,.04)">
<span style="width:8px;height:8px;border-radius:2px;background:{{ kr.color }};flex:none"></span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#dbe3ef;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">{{ kr.label }}</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:9px;color:#7e8aa2;border:1px solid rgba(140,165,200,.2);border-radius:4px;padding:1px 5px;flex:none">{{ kr.badges }}</span>
<span style="width:6px;height:6px;border-radius:50%;background:{{ kr.statusColor }};flex:none"></span>
</div>
</sc-for>
</div>
</div>
</sc-for>
</div>
</div>
</sc-if>
<!-- ====== SPOTLIGHT ====== -->
<sc-if value="{{ spotOpen }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;inset:0;background:rgba(7,9,13,.34);z-index:60;animation:fadeIn .12s ease" onMouseDown="{{ onSpotBackdrop }}">
<div style="position:absolute;left:50%;top:14%;transform:translateX(-50%);width:min(660px,93vw);animation:spotIn .16s cubic-bezier(.2,.8,.3,1)" onMouseDown="{{ stopDown }}">
<div style="background:rgba(16,20,28,.97);border:1px solid rgba(140,165,200,.26);border-radius:14px;box-shadow:0 32px 90px rgba(0,0,0,.65),0 0 0 1px rgba(0,0,0,.5);overflow:hidden">
<div style="display:flex;align-items:center;gap:11px;padding:15px 17px">
<span style="font-family:'IBM Plex Mono',monospace;font-size:17px;color:{{ accent }};flex:none"></span>
<sc-if value="{{ hasFilter }}" hint-placeholder-val="{{ false }}">
<span style="display:flex;align-items:center;gap:6px;padding:4px 9px;background:{{ accentDim }};border:1px solid {{ accent }};border-radius:7px;font-family:'IBM Plex Mono',monospace;font-size:12.5px;color:{{ accent }};flex:none"><span style="width:8px;height:8px;background:{{ filterColor }};clip-path:{{ filterClip }};border-radius:{{ filterRadius }}"></span>{{ filterLabel }}<span style="color:#6b7890;font-size:11px"></span></span>
</sc-if>
<input ref="{{ setInput }}" value="{{ query }}" onChange="{{ onSearchChange }}" onKeyDown="{{ onSearchKeydown }}" placeholder="{{ placeholder }}" style="flex:1;min-width:0;border:none;outline:none;background:transparent;color:#eef3fa;font-family:'IBM Plex Sans',sans-serif;font-size:18px"/>
<sc-if value="{{ ghost }}" hint-placeholder-val="{{ false }}">
<span style="display:flex;align-items:center;gap:7px;padding:4px 9px;border:1px dashed {{ accent }};border-radius:7px;font-family:'IBM Plex Mono',monospace;font-size:12px;color:{{ accent }};flex:none;white-space:nowrap">⇥ {{ ghost }}</span>
</sc-if>
<sc-if value="{{ sessionActive }}" hint-placeholder-val="{{ false }}">
<span style="display:flex;align-items:center;gap:6px;flex:none">
<sc-for list="{{ buildingViews }}" as="bv" hint-placeholder-count="1"><span style="display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:5px;background:{{ accent }};color:#0b0e13;font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:600">{{ bv }}</span></sc-for>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:#6b7890;white-space:nowrap">esc/idle places</span>
</span>
</sc-if>
</div>
<div style="display:flex;gap:6px;padding:0 16px 12px;flex-wrap:wrap;border-bottom:1px solid rgba(140,165,200,.1)">
<sc-for list="{{ chips }}" as="c" hint-placeholder-count="7">
<button onClick="{{ c.onClick }}" onMouseDown="{{ stopDown }}" style="display:flex;align-items:center;gap:6px;border:1px solid {{ c.border }};background:{{ c.bg }};color:{{ c.fg }};font-family:'IBM Plex Mono',monospace;font-size:11px;padding:4px 9px;border-radius:7px;cursor:pointer">
<span style="width:8px;height:8px;background:{{ c.color }};clip-path:{{ c.clip }};border-radius:{{ c.radius }};flex:none"></span>{{ c.label }}
</button>
</sc-for>
</div>
<div style="max-height:48vh;overflow:auto;padding:8px">
<div style="font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:.14em;color:#6b7890;text-transform:uppercase;padding:6px 9px 8px">{{ resultsHeader }}</div>
<sc-for list="{{ results }}" as="r" hint-placeholder-count="6">
<div onMouseEnter="{{ r.onHover }}" onMouseDown="{{ stopDown }}" onClick="{{ r.onClick }}" style="border-radius:9px;background:{{ r.bg }};margin-bottom:2px;cursor:pointer">
<div style="display:flex;align-items:center;gap:12px;padding:10px 11px">
<span style="width:16px;height:16px;background:{{ r.color }};clip-path:{{ r.clip }};border-radius:{{ r.radius }};flex:none;filter:drop-shadow(0 0 4px {{ r.glow }})"></span>
<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:2px">
<span style="font-family:'IBM Plex Mono',monospace;font-size:13.5px;color:#e6edf6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ r.name }}</span>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#7e8aa2">{{ r.sub }}</span>
</div>
<sc-if value="{{ r.isCrd }}" hint-placeholder-val="{{ false }}">
<span style="font-family:'IBM Plex Mono',monospace;font-size:9px;letter-spacing:.1em;color:#ffd479;border:1px solid rgba(255,212,121,.4);border-radius:5px;padding:2px 6px">CRD</span>
</sc-if>
<sc-if value="{{ r.showBadge }}" hint-placeholder-val="{{ false }}">
<span style="font-family:'IBM Plex Mono',monospace;font-size:9px;letter-spacing:.1em;color:{{ accent }};border:1px solid {{ accentDim }};background:{{ accentDim }};border-radius:5px;padding:2px 6px;flex:none">{{ r.badgeLabel }}</span>
</sc-if>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#8493ab;border:1px solid rgba(140,165,200,.2);border-radius:5px;padding:2px 7px;flex:none">{{ r.typeShort }}</span>
</div>
<sc-if value="{{ r.active }}" hint-placeholder-val="{{ false }}">
<div style="display:flex;align-items:center;gap:7px;padding:0 11px 11px 39px;flex-wrap:wrap">
<sc-if value="{{ r.enter }}" hint-placeholder-val="{{ false }}">
<span style="display:flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:5px;background:{{ accent }};color:#0b0e13;font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600"></span>
</sc-if>
<sc-for list="{{ r.views }}" as="v" hint-placeholder-count="4">
<button onClick="{{ v.onClick }}" onMouseDown="{{ stopDown }}" style="display:flex;align-items:center;gap:7px;border:1px solid {{ v.border }};background:{{ v.bg }};color:{{ v.fg }};font-family:'IBM Plex Mono',monospace;font-size:11.5px;padding:5px 10px 5px 5px;border-radius:8px;cursor:{{ v.cursor }};opacity:{{ v.opacity }}">
<span style="display:flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:5px;background:{{ v.kbdBg }};color:{{ v.kbdFg }};font-size:10px;font-weight:600">{{ v.letter }}</span>{{ v.label }}{{ v.mark }}
</button>
</sc-for>
<span style="font-family:'IBM Plex Mono',monospace;font-size:10px;color:#5a6680;margin-left:2px">{{ r.viewHint }}</span>
</div>
</sc-if>
</div>
</sc-for>
<sc-if value="{{ noResults }}" hint-placeholder-val="{{ false }}">
<div style="padding:24px 11px;text-align:center;font-family:'IBM Plex Mono',monospace;font-size:12px;color:#6b7890">no objects match "{{ query }}"</div>
</sc-if>
</div>
<div style="display:flex;align-items:center;gap:15px;padding:9px 16px;border-top:1px solid rgba(140,165,200,.1);background:rgba(10,13,19,.6);font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#6b7890">
<span><span style="color:#9fb0c8">↑↓</span> pick</span>
<span><span style="color:#9fb0c8">{{ openKeysLabel }}</span> open views</span>
<span><span style="color:#9fb0c8"></span> {{ enterLabel }}</span>
<span><span style="color:#9fb0c8"></span> cycle type</span>
<span style="margin-left:auto"><span style="color:#9fb0c8">esc</span> done</span>
</div>
</div>
</div>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;accent&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;cyan&quot;,&quot;violet&quot;,&quot;amber&quot;],&quot;default&quot;:&quot;cyan&quot;,&quot;tsType&quot;:&quot;string&quot;}}">
class Component extends DCLogic {
constructor(props){
super(props);
this.world = this.genWorld();
this.adj = {};
this.world.links.forEach(l=>{ (this.adj[l.from]=this.adj[l.from]||[]).push(l.to); (this.adj[l.to]=this.adj[l.to]||[]).push(l.from); });
this.byId = {}; this.world.nodes.forEach(n=>this.byId[n.id]=n);
this.state = { camX:140, camY:110, zoom:.92, flying:false, dragging:false, collapsed:false,
spotOpen:false, query:'', filterType:null, sel:0, navigated:false,
krates:[], krateSeq:0, session:null, logs:{}, sh:{}, relMenu:null, cardDrag:null, collSearch:{}, collView:{}, collSel:{}, collNav:{}, zoomedWin:null, admin:false };
this._logEls={}; this._stickLog={}; this._shEls={}; this._stickSh={}; this._shInputEls={}; this._hoverShWid=null; this._space=false;
this._collInputEls={}; this._hoverCollWid=null; this._typeUse={}; this._recentTypes=[]; this._hoverWinId=null; this._collCtx={}; this._collNavTimer={};
}
componentDidMount(){ this._onKey=(e)=>this.handleGlobalKey(e); this._onKeyUp=(e)=>{ if(e.key===' '){ this._space=false; if(this.state.spacePan) this.setState({spacePan:false}); } };
document.addEventListener('keydown', this._onKey); document.addEventListener('keyup', this._onKeyUp);
this._stream=setInterval(()=>this.appendLogs(), 1500); }
componentWillUnmount(){ document.removeEventListener('keydown', this._onKey); document.removeEventListener('keyup', this._onKeyUp); this.clearTimers(); clearInterval(this._stream); }
clearTimers(){ clearTimeout(this._arm); clearTimeout(this._idle); }
armSoon(){ clearTimeout(this._arm); this._arm=setTimeout(()=>{ if(this.state.spotOpen && !this.state.session && !this.state.navigated && this.buildResults(this.state.query,this.state.filterType).length){ this.setState({navigated:true}); } }, 460); }
armDismiss(){ clearTimeout(this._idle); this._idle=setTimeout(()=>{ if(this.state.spotOpen && this.state.session){ this.finishSpotlight(); } }, 520); }
componentDidUpdate(){ if(this._focus && this.inputEl){ this.inputEl.focus(); const v=this.inputEl.value; try{this.inputEl.setSelectionRange(v.length,v.length);}catch(e){} this._focus=false; }
Object.keys(this._logEls).forEach(wid=>{ const el=this._logEls[wid]; if(el && el.isConnected && this._stickLog[wid]!==false) el.scrollTop=el.scrollHeight; });
Object.keys(this._shEls).forEach(wid=>{ const el=this._shEls[wid]; if(el && el.isConnected && this._stickSh[wid]!==false) el.scrollTop=el.scrollHeight; });
if(this._focusCollWid){ const el=this._collInputEls[this._focusCollWid]; if(el && el.isConnected){ try{ el.focus(); }catch(e){} this._focusCollWid=null; } } }
// ---------- data ----------
genWorld(){
const nodes=[], links=[], namespaces=[]; let uid=0; const nid=(p)=>p+'-'+(uid++);
const defs=[
{ name:'payments', color:'#6fb1ff', primaries:[
{type:'deployment',name:'api-gateway',img:'krates/gateway:1.8.2',replicas:'3/3',children:[
{type:'pod',name:'api-gateway-7c4f-x2q'},{type:'pod',name:'api-gateway-7c4f-9kp'},{type:'pod',name:'api-gateway-7c4f-lph'},
{type:'configmap',name:'gateway-config'},{type:'secret',name:'gateway-tls',secret:{'tls.crt':'-----BEGIN CERTIFICATE-----','tls.key':'-----BEGIN PRIVATE KEY-----'}}]},
{type:'service',name:'api-gateway',relTo:'api-gateway',port:'443:8443'},
{type:'deployment',name:'ledger',img:'krates/ledger:2.3.0',replicas:'1/2',status:'Degraded',children:[
{type:'pod',name:'ledger-58d9-aa1'},{type:'pod',name:'ledger-58d9-bb2',status:'Pending'},
{type:'configmap',name:'ledger-config'},{type:'secret',name:'db-credentials',secret:{'username':'payments','password':'s3cr3t-pg!'}}]},
{type:'service',name:'ledger',relTo:'ledger',port:'80:8080'},
{type:'statefulset',name:'postgres',img:'postgres:16',replicas:'2/2',children:[
{type:'pod',name:'postgres-0'},{type:'pod',name:'postgres-1'},
{type:'pvc',name:'pgdata-postgres-0'},{type:'pvc',name:'pgdata-postgres-1'}]},
{type:'crd',name:'paymentroute/checkout',kindName:'PaymentRoute',relTo:'api-gateway'} ]},
{ name:'platform', color:'#5fd0c0', primaries:[
{type:'deployment',name:'auth',img:'krates/auth:4.1.1',replicas:'2/2',children:[
{type:'pod',name:'auth-9f7c-m1'},{type:'pod',name:'auth-9f7c-m2'},
{type:'configmap',name:'auth-config'},{type:'secret',name:'oauth-secret',secret:{'client_id':'krates-web','client_secret':'oa_7xK29fLp'}}]},
{type:'service',name:'auth',relTo:'auth',port:'80:9000'},
{type:'ingress',name:'platform-ingress',relTo:'auth'},
{type:'daemonset',name:'node-exporter',img:'prom/node-exporter:1.7',replicas:'5/5',children:[
{type:'pod',name:'node-exporter-tz4'},{type:'pod',name:'node-exporter-q8r'}]},
{type:'crd',name:'certificate/platform-tls',kindName:'Certificate',relTo:'platform-ingress'} ]},
{ name:'storefront', color:'#e58fb0', primaries:[
{type:'deployment',name:'web',img:'krates/web:7.0.4',replicas:'3/3',children:[
{type:'pod',name:'web-6b5c-aa'},{type:'pod',name:'web-6b5c-bb'},{type:'pod',name:'web-6b5c-cc'},
{type:'configmap',name:'web-config'}]},
{type:'service',name:'web',relTo:'web',port:'80:3000'},
{type:'deployment',name:'cart',img:'krates/cart:3.2.0',replicas:'2/2',children:[
{type:'pod',name:'cart-77d-aa'},{type:'pod',name:'cart-77d-bb'},
{type:'secret',name:'stripe-key',secret:{'STRIPE_SECRET':'sk_live_4eC39HqLy'}}]},
{type:'statefulset',name:'redis',img:'redis:7.2',replicas:'1/1',children:[
{type:'pod',name:'redis-0'},{type:'pvc',name:'redis-data-redis-0'}]} ]},
];
defs.forEach(d=>{ const nameMap={};
d.primaries.forEach(p=>{ const node={id:nid(p.type),type:p.type,name:p.name,ns:d.name,status:p.status||'Ready',data:p}; nodes.push(node); (nameMap[p.name]=nameMap[p.name]||[]).push(node); p._n=node;
(p.children||[]).forEach(c=>{ const cn={id:nid(c.type),type:c.type,name:c.name,ns:d.name,status:c.status||'Running',data:c}; nodes.push(cn); links.push({from:node.id,to:cn.id}); }); });
d.primaries.forEach(p=>{ if(!p.relTo) return; const tg=(nameMap[p.relTo]||[]).find(x=>x.id!==p._n.id); if(tg) links.push({from:p._n.id,to:tg.id}); });
namespaces.push({name:d.name,color:d.color});
});
return {nodes,links,namespaces};
}
meta(t){ const m={
deployment:{label:'Deployment',short:'deploy',color:'#6fb1ff',shape:'circle'},
statefulset:{label:'StatefulSet',short:'sts',color:'#b89cff',shape:'triangle'},
service:{label:'Service',short:'svc',color:'#5fd0c0',shape:'diamond'},
daemonset:{label:'DaemonSet',short:'ds',color:'#ffb27a',shape:'ring'},
ingress:{label:'Ingress',short:'ing',color:'#e58fb0',shape:'triangle'},
crd:{label:'Custom Resource',short:'crd',color:'#ffd479',shape:'hexagon',crd:true},
pod:{label:'Pod',short:'pod',color:'#8aa0bd',shape:'circle'},
configmap:{label:'ConfigMap',short:'cm',color:'#7fb39c',shape:'square'},
secret:{label:'Secret',short:'secret',color:'#c79bd6',shape:'hexagon'},
pvc:{label:'PVC',short:'pvc',color:'#9aa7c7',shape:'square'} };
return m[t]||{label:t,short:t,color:'#8aa0bd',shape:'circle'}; }
shapeCss(shape){ if(shape==='triangle') return {clip:'polygon(50% 0,100% 100%,0 100%)',radius:'0'};
if(shape==='diamond') return {clip:'polygon(50% 0,100% 50%,50% 100%,0 50%)',radius:'0'};
if(shape==='hexagon') return {clip:'polygon(25% 0,75% 0,100% 50%,75% 100%,25% 100%,0 50%)',radius:'0'};
if(shape==='square') return {clip:'none',radius:'3px'};
return {clip:'none',radius:'50%'}; }
statusColor(s){ return s==='Degraded'?'#ef6f6f':(s==='Pending'?'#e8b54a':(s==='Failed'?'#ef6f6f':'#4ad07a')); }
rgba(hex,a){ const h=hex.replace('#',''); const n=parseInt(h.length===3?h.split('').map(c=>c+c).join(''):h,16); return 'rgba('+((n>>16)&255)+','+((n>>8)&255)+','+(n&255)+','+a+')'; }
accentSet(){ const a=this.props.accent||'cyan'; const map={cyan:'#4dd6e8',violet:'#a98cff',amber:'#f5b454'}; const c=map[a]||map.cyan; return {accent:c,glow:this.rgba(c,.5),dim:this.rgba(c,.14)}; }
// ---------- views ----------
viewMeta(){ return {logs:{letter:'L',label:'logs'},shell:{letter:'S',label:'shell'},describe:{letter:'D',label:'describe'},yaml:{letter:'Y',label:'yaml'}}; }
viewOrder(){ return ['logs','shell','describe','yaml']; }
execable(type){ return ['pod','deployment','statefulset','daemonset'].includes(type); }
viewAvail(type,view){ if(view==='yaml'||view==='describe') return true; if(view==='logs'||view==='shell') return this.execable(type); return false; }
defaultView(type){ if(type==='pod') return 'logs'; if(['deployment','statefulset','daemonset'].includes(type)) return 'describe'; return 'yaml'; }
keyToView(k){ return ({l:'logs',s:'shell',d:'describe',y:'yaml'})[k]; }
codeLetter(e){ if(e.code && e.code.indexOf('Key')===0) return e.code.slice(3).toLowerCase(); const k=(e.key||''); return k.length===1?k.toLowerCase():''; }
// ---------- search ----------
aliases(){ return [
{k:['deploy','deployment','deployments','dep'],t:'deployment'},{k:['sts','statefulset','statefulsets'],t:'statefulset'},
{k:['svc','service','services'],t:'service'},{k:['ds','daemonset','daemonsets'],t:'daemonset'},
{k:['ing','ingress','ingresses'],t:'ingress'},{k:['crd','customresource','cr'],t:'crd'},
{k:['po','pod','pods'],t:'pod'},{k:['cm','configmap','configmaps','config'],t:'configmap'},
{k:['secret','secrets','sealed'],t:'secret'},{k:['pvc','pv','volume','volumes'],t:'pvc'} ]; }
suggestType(q,filter){ if(filter) return null; const s=q.trim().toLowerCase(); if(s.length<2) return null;
for(const a of this.aliases()){ for(const k of a.k){ if(k.startsWith(s)||s===k) return a.t; } } return null; }
fuzzy(q,text){ if(!q) return 0; q=q.toLowerCase(); const t=text.toLowerCase(); let qi=0,score=0,prev=-2;
for(let i=0;i<t.length && qi<q.length;i++){ if(t[i]===q[qi]){ let pts=1; if(i===prev+1) pts+=3; if(i===0||/[^a-z0-9]/.test(t[i-1])) pts+=5; score+=pts; prev=i; qi++; } }
return qi===q.length?score:-1; }
boost(t){ const m={crd:60,deployment:50,statefulset:48,service:46,daemonset:44,ingress:42,secret:24,configmap:22,pvc:18,pod:16}; return m[t]||10; }
// ---------- collections ----------
catMembers(type){ return this.world.nodes.filter(n=>n.type===type); }
nsMembers(ns){ return this.world.nodes.filter(n=>n.ns===ns); }
collMembers(scope){ return scope.kind==='namespace'? this.nsMembers(scope.value) : this.catMembers(scope.value); }
collTitle(scope){ return scope.kind==='namespace'? ('ns/'+scope.value) : ('All '+this.meta(scope.value).label+'s'); }
statusSummary(nodes){ const c={}; nodes.forEach(n=>{ c[n.status]=(c[n.status]||0)+1; }); const ok=(c.Ready||0)+(c.Running||0); const warn=(c.Pending||0); const bad=(c.Degraded||0)+(c.Failed||0); return {ok,warn,bad,total:nodes.length}; }
healthMetric(n){ const d=n.data||{}; if(d.replicas) return d.replicas+' ready'; if(n.type==='pod') return 'restarts '+(n.name.length%4); if(n.type==='pvc') return '20Gi · gp3'; if(n.type==='service') return d.port||'ClusterIP'; if(n.type==='secret') return 'Opaque · '+Object.keys(d.secret||{}).length+' keys'; if(n.type==='configmap') return '3 keys'; if(n.type==='ingress') return n.ns+'.krates.io'; return this.meta(n.type).label; }
collTreeByType(members){ const order=['deployment','statefulset','daemonset','service','ingress','crd','pod','configmap','secret','pvc']; const groups=[];
order.forEach(t=>{ const list=members.filter(n=>n.type===t); if(list.length) groups.push({type:t, label:this.meta(t).label+'s', nodes:list}); });
const seen=new Set(order); members.forEach(n=>{ if(!seen.has(n.type)){ seen.add(n.type); groups.push({type:n.type, label:this.meta(n.type).label+'s', nodes:members.filter(x=>x.type===n.type)}); } });
return groups; }
// k9s-xray style relationship tree: each object expands to the resources it references
xrayRel(a,b){ if(b.type==='pod') return 'pod'; if(b.type==='configmap') return 'configMap'; if(b.type==='secret') return 'secret'; if(b.type==='pvc') return 'volume'; if(a.type==='service') return 'selects'; if(a.type==='ingress') return 'routes'; if(a.type==='crd') return 'targets'; return 'ref'; }
buildXray(scope, members){ const isPrim=(n)=>['deployment','statefulset','daemonset','service','ingress','crd'].includes(n.type); const isCfg=(n)=>['configmap','secret','pvc'].includes(n.type);
const neigh=(id)=>(this.adj[id]||[]).map(x=>this.byId[x]); const workloadOf=(pod)=> neigh(pod.id).find(x=>isPrim(x)); const podsOf=(p)=> neigh(p.id).filter(x=>x.type==='pod'); const cfgOf=(p)=> neigh(p.id).filter(isCfg); const rows=[];
const push=(n,depth,rel)=>rows.push({node:n, depth:depth, rel:rel||''});
const expandWorkload=(p,d)=>{ push(p,d,''); podsOf(p).forEach(pod=>{ push(pod,d+1,'pod'); cfgOf(p).forEach(c=>push(c,d+2,this.xrayRel(p,c))); }); if(podsOf(p).length===0) cfgOf(p).forEach(c=>push(c,d+1,this.xrayRel(p,c))); };
if(scope.kind==='namespace'){ members.filter(isPrim).forEach(p=>{ if(p.type==='service'||p.type==='ingress'){ push(p,0,''); neigh(p.id).filter(isPrim).forEach(t=>push(t,1,this.xrayRel(p,t))); } else expandWorkload(p,0); }); }
else { const t=scope.value;
if(t==='pod'){ members.forEach(pod=>{ push(pod,0,''); const w=workloadOf(pod); if(w) cfgOf(w).forEach(c=>push(c,1,this.xrayRel(w,c))); }); }
else if(isPrim({type:t})){ members.forEach(p=>{ if(t==='service'||t==='ingress'){ push(p,0,''); neigh(p.id).filter(isPrim).forEach(tg=>push(tg,1,this.xrayRel(p,tg))); } else expandWorkload(p,0); }); }
else if(isCfg({type:t})){ members.forEach(c=>{ push(c,0,''); neigh(c.id).filter(isPrim).forEach(w=>{ push(w,1,'used by'); podsOf(w).forEach(pod=>push(pod,2,'pod')); }); }); }
else { members.forEach(n=>push(n,0,'')); } }
return rows; }
buildResults(query,filter){ const q=query.trim(); const items=[];
if(!filter){ this.state.krates.forEach(k=>{ let s; if(!q){ s=72; } else { const f=Math.max(this.fuzzy(q,k.label), this.fuzzy(q,'krate '+k.label)); if(f<0) return; s=f*10+66; } items.push({kind:'krate', id:k.id, krate:k, score:s}); }); }
if(filter){ const mem=this.catMembers(filter); items.push({kind:'category', id:'cat:'+filter, scope:{kind:'category',value:filter}, count:mem.length, score:1000}); }
if(!filter){ this.world.namespaces.forEach(ns=>{ const f=q? this.fuzzy(q, 'namespace '+ns.name): 30; const fn=q?this.fuzzy(q,ns.name):30; const sc=Math.max(f,fn); if(q && sc<0) return; items.push({kind:'namespace', id:'ns:'+ns.name, scope:{kind:'namespace',value:ns.name}, count:this.nsMembers(ns.name).length, color:ns.color, score:(q?sc*10+58:58)}); });
[['pod','pods'],['service','services'],['deployment','deployments'],['secret','secrets'],['statefulset','statefulsets'],['configmap','configmaps'],['ingress','ingresses'],['daemonset','daemonsets'],['pvc','pvcs'],['crd','crds']].forEach(([t,word])=>{ if(!q) return; const f=Math.max(this.fuzzy(q,'all '+word), this.fuzzy(q,word)); if(f<0) return; items.push({kind:'category', id:'cat:'+t, scope:{kind:'category',value:t}, count:this.catMembers(t).length, score:f*10+60}); }); }
let pool=this.world.nodes.slice(); if(filter) pool=pool.filter(n=>n.type===filter);
if(!q){ pool.forEach(n=>items.push({kind:'node', id:n.id, node:n, type:n.type, score:this.boost(n.type)})); }
else { pool.forEach(n=>{ const f=this.fuzzy(q,n.name); if(f>=0) items.push({kind:'node', id:n.id, node:n, type:n.type, score:f*10+this.boost(n.type)}); }); }
items.sort((a,b)=> b.score-a.score); return items.slice(0,8); }
// ---------- camera ----------
centerOn(wx,wy,z,fly){ const el=this.rootEl; const W=el?el.clientWidth:1280,H=el?el.clientHeight:760; const zoom=z||this.state.zoom;
this.setState({zoom, camX:W/2-wx*zoom, camY:H/2-wy*zoom, flying:!!fly, collapsed: zoom<0.48? this.state.collapsed : false});
if(fly){ clearTimeout(this._ft); this._ft=setTimeout(()=>this.setState({flying:false}),520); } }
onWheel=(e)=>{ e.preventDefault(); const el=this.rootEl; const rect=el.getBoundingClientRect(); const mx=e.clientX-rect.left,my=e.clientY-rect.top;
if(e.ctrlKey||e.metaKey){ const z0=this.state.zoom; let z1=z0*(1-e.deltaY*0.0042); z1=Math.max(.16,Math.min(2.2,z1));
const wx=(mx-this.state.camX)/z0, wy=(my-this.state.camY)/z0; let collapsed=this.state.collapsed;
if(z1<0.4) collapsed=true; else if(z1>0.5) collapsed=false;
this.setState({zoom:z1, camX:mx-wx*z1, camY:my-wy*z1, flying:false, collapsed}); return; }
this.setState(s=>({camX:s.camX - e.deltaX, camY:s.camY - e.deltaY, flying:false})); };
beginPan(e, canToggle){ const sx=e.clientX,sy=e.clientY,c0x=this.state.camX,c0y=this.state.camY; let moved=false;
const move=(ev)=>{ const dx=ev.clientX-sx,dy=ev.clientY-sy; if(Math.abs(dx)+Math.abs(dy)>4){ if(!moved){moved=true;this.setState({dragging:true});} this.setState({camX:c0x+dx,camY:c0y+dy,flying:false}); } };
const up=()=>{ document.removeEventListener('mousemove',move); document.removeEventListener('mouseup',up); if(this.state.dragging) this.setState({dragging:false});
if(!moved && canToggle && !this._space){ if(!this.state.spotOpen) this.openSpot(''); else this.setState({spotOpen:false,session:null}); } };
document.addEventListener('mousemove',move); document.addEventListener('mouseup',up); }
onRootDownCapture=(e)=>{ if(this._space && e.button===0){ e.preventDefault(); e.stopPropagation(); if(this.state.relMenu) this.setState({relMenu:null}); this.beginPan(e, false); } };
onBgDown=(e)=>{ if(e.button!==0) return; if(this.state.relMenu){ this.setState({relMenu:null}); return; } this.beginPan(e, true); };
// ---------- spotlight ----------
handleGlobalKey(e){ const tag=(e.target&&e.target.tagName)||''; if(tag==='INPUT'||tag==='TEXTAREA') return;
const shEl=this._hoverShWid && this._shInputEls[this._hoverShWid];
if(shEl && !this.state.spotOpen && !e.metaKey && !e.ctrlKey && !e.altKey){ if(e.key!==' '){ if(e.key.length===1){ e.preventDefault(); shEl.focus(); shEl.value+=e.key; return; } if(e.key==='Backspace'){ shEl.focus(); return; } if(e.key==='Enter'){ return; } } }
const ciEl=this._hoverCollWid && this._collInputEls[this._hoverCollWid];
if(ciEl && !this.state.spotOpen && !e.metaKey && !e.ctrlKey){ if(e.key!==' '){ if(this.collKey(this._hoverCollWid, e)){ if(!e.altKey) ciEl.focus(); return; } } }
if(this._hoverWinId && !this._hoverShWid && !this._hoverCollWid && !this.state.spotOpen && !e.metaKey && !e.ctrlKey && !e.altKey && e.key.toLowerCase()==='z'){ e.preventDefault(); this.zoomToggleWindow(this._hoverWinId.kid, this._hoverWinId.wid); return; }
if(e.key===' '){ this._space=true; if(!this.state.spacePan) this.setState({spacePan:true}); if(!this.state.spotOpen) e.preventDefault(); return; }
if(this.state.spotOpen) return;
if(e.metaKey||e.ctrlKey||e.altKey) return; if(e.key==='Escape') return;
if(e.key==='/'){ e.preventDefault(); this.openSpot(''); } else if(e.key.length===1){ this.openSpot(e.key); } }
openSpot(initial){ this._focus=true; this._sessionKid=null; this.clearTimers(); this.setState({spotOpen:true, query:initial, sel:0, filterType:null, navigated:false, session:null}); this.armSoon(); }
filterTypeOrder(){ const all=['deployment','service','pod','secret','configmap','statefulset','daemonset','ingress','crd','pvc'];
const rec=this._recentTypes||[], use=this._typeUse||{};
return all.slice().sort((a,b)=>{ const ra=rec.indexOf(a), rb=rec.indexOf(b); const va=ra<0?999:ra, vb=rb<0?999:rb; if(va!==vb) return va-vb; return (use[b]||0)-(use[a]||0); }); }
recordType(t){ if(!t) return; this._typeUse=this._typeUse||{}; this._typeUse[t]=(this._typeUse[t]||0)+1; this._recentTypes=[t].concat((this._recentTypes||[]).filter(x=>x!==t)).slice(0,10); }
cycleFilter(dir){ const seq=[null].concat(this.filterTypeOrder()); const cur=this.state.filterType; let i=seq.indexOf(cur); if(i<0) i=0; const ni=(i+dir+seq.length)%seq.length; const nt=seq[ni]; this._focus=true; this.setState({filterType:nt, sel:0, navigated:false}); }
onSearchChange=(e)=>{ this.setState({query:e.target.value, sel:0, navigated:false}); this.armSoon(); };
onSearchKeydown=(e)=>{ const res=this.buildResults(this.state.query,this.state.filterType);
if(e.key==='Escape'){ e.preventDefault(); this.finishOrClose(); return; }
if(this.state.session) this.armDismiss();
if(e.key==='ArrowDown'){ e.preventDefault(); this.setState(s=>({sel:Math.min(res.length-1,s.sel+1),navigated:true})); return; }
if(e.key==='ArrowUp'){ e.preventDefault(); this.setState(s=>({sel:Math.max(0,s.sel-1),navigated:true})); return; }
if(e.key==='Tab'){ e.preventDefault(); const sug=this.suggestType(this.state.query,this.state.filterType);
if(sug && !this.state.filterType && !e.shiftKey){ this.recordType(sug); this._focus=true; this.setState({filterType:sug, query:'', sel:0, navigated:false}); }
else { this.cycleFilter(e.shiftKey?-1:1); } return; }
if(e.key==='Backspace' && this.state.query==='' && this.state.filterType){ e.preventDefault(); this.setState({filterType:null,sel:0}); return; }
if(e.key==='Enter'){ e.preventDefault(); if(this.state.session){ this.finishSpotlight(); } else { const r=res[this.state.sel]; if(r){ if(r.kind==='krate'){ this.jumpToKrate(r.id); } else if(r.kind==='category'||r.kind==='namespace'){ this.summonCollection(r.scope); } else { this.openView(r.id,this.defaultView(r.type),true); } } } return; }
if(e.altKey && !e.ctrlKey && !e.metaKey){ const v=this.keyToView(this.codeLetter(e)); if(v){ const r=res[this.state.sel]; if(r && r.kind==='node' && this.viewAvail(r.type,v)){ e.preventDefault(); this.openView(r.id,v,false); return; } } } };
onSpotBackdrop=()=>this.finishOrClose();
finishOrClose(){ this.clearTimers(); if(this.state.session) this.finishSpotlight(); else this.setState({spotOpen:false}); }
// ---------- krates / windows ----------
placeKrate(){ const ks=this.state.krates; if(!ks.length) return {cx:700, cy:480};
const last=ks[ks.length-1]; const lb=this.krateBox(last); return {cx:last.wx+lb.x+lb.w+360, cy:last.wy}; }
gridCols(n){ return n<=1?1:(n<=4?2:3); }
tileWindows(wins){ const gap=14; const N=wins.length; if(!N) return wins; const C=this.gridCols(N); const rows=Math.ceil(N/C);
const colW=[], rowH=[]; wins.forEach((w,i)=>{ const c=i%C, r=Math.floor(i/C); colW[c]=Math.max(colW[c]||300, w.w); rowH[r]=Math.max(rowH[r]||220, w.h); });
const colX=[]; let ax=0; for(let c=0;c<C;c++){ colX[c]=ax; ax+=colW[c]+gap; }
const rowY=[]; let ay=0; for(let r=0;r<rows;r++){ rowY[r]=ay; ay+=rowH[r]+gap; }
return wins.map((w,i)=>{ const c=i%C, r=Math.floor(i/C); return Object.assign({},w,{dx:colX[c], dy:rowY[r], w:colW[c], h:rowH[r], col:c, row:r}); }); }
fitBox(x,y,w,h,maxZ){ const el=this.rootEl; const W=el?el.clientWidth:1280,H=el?el.clientHeight:760; const pad=190; let z=Math.min((W-pad)/w,(H-pad)/h, maxZ||1.0); z=Math.max(0.22,Math.min(2.0,z)); this.centerOn(x+w/2, y+h/2, z, true); }
fitKrate(kid){ const k=this.state.krates.find(x=>x.id===kid); if(!k) return; const b=this.krateBox(k); this.fitBox(k.wx+b.x, k.wy+b.y-44, b.w, b.h+44, 1.0); }
krateBox(k){ if(!k.windows.length) return {x:0,y:0,w:380,h:360};
let a=1e9,b=1e9,c=-1e9,d=-1e9; k.windows.forEach(w=>{ a=Math.min(a,w.dx); b=Math.min(b,w.dy); c=Math.max(c,w.dx+w.w); d=Math.max(d,w.dy+w.h); });
return {x:a,y:b,w:c-a,h:d-b}; }
openView(objId, view, finish){
const s=this.state; let session = (s.session && s.session.objId===objId) ? s.session : null;
let krates=s.krates.map(k=>Object.assign({},k,{windows:k.windows.slice()})); let krateSeq=s.krateSeq;
let krate = session ? krates.find(k=>k.id===session.kid) : null;
if(!krate){ const p=this.placeKrate(); const n=this.byId[objId];
krate={id:'k'+krateSeq, objId, label:n.name, status:n.status, color:(this.world.namespaces.find(x=>x.name===n.ns)||{}).color||'#6fb1ff', wx:p.cx, wy:p.cy, windows:[], seq:0};
krateSeq++; krates.push(krate); session={kid:krate.id, objId, views:[]}; }
if(!krate.windows.find(w=>(w.objId||krate.objId)===objId && w.tab===view)){ let wl=krate.windows.slice();
wl.push({wid:krate.id+'-'+(krate.seq++), objId:objId, tab:view, dx:0, dy:0, w:380, h:360}); krate.windows=this.tileWindows(wl); }
session=Object.assign({},session,{views:Array.from(new Set(session.views.concat([view])))});
this._sessionKid=krate.id;
this.setState({krates, krateSeq, session}, ()=>{ if(finish){ this.finishSpotlight(); } else { this.armDismiss(); } });
}
finishSpotlight(){ this.clearTimers(); const kid=this._sessionKid; this.setState({spotOpen:false, session:null, navigated:false}, ()=>this.fitKrate(kid)); this._sessionKid=null; }
flyToKrate(kid){ this.fitKrate(kid); }
jumpToKrate(kid){ this.clearTimers(); this.setState({spotOpen:false, session:null, navigated:false}, ()=>this.fitKrate(kid)); }
cardLayout(){ const el=this.rootEl; const W=el?el.clientWidth:1280; const cardW=230, cardH=132, gap=20; const cols=Math.max(1, Math.floor((W-72+gap)/(cardW+gap)));
const n=this.state.krates.length; const span=Math.min(n||1,cols); const contentW=span*cardW+(span-1)*gap; const startX=Math.max(36,(W-contentW)/2); const startY=96; return {cardW,cardH,gap,cols,startX,startY}; }
cardSlotAt(x,y){ const L=this.cardLayout(); const col=Math.round((x-L.startX)/(L.cardW+L.gap)); const row=Math.round((y-L.startY)/(L.cardH+L.gap)); const cc=Math.max(0,Math.min(L.cols-1,col)); const idx=row*L.cols+cc; return Math.max(0,Math.min(this.state.krates.length-1,idx)); }
reorderKrate(kid,ti){ this.setState(s=>{ const arr=s.krates.slice(); const from=arr.findIndex(k=>k.id===kid); if(from<0) return null; ti=Math.max(0,Math.min(arr.length-1,ti)); if(from===ti) return null; const it=arr.splice(from,1)[0]; arr.splice(ti,0,it); return {krates:arr}; }); }
startCardDrag(kid,e){ e.stopPropagation(); if(e.button!==0) return; const sx=e.clientX,sy=e.clientY; let moved=false;
const move=(ev)=>{ const dx=ev.clientX-sx,dy=ev.clientY-sy; if(!moved && Math.abs(dx)+Math.abs(dy)>5) moved=true; if(moved){ const ti=this.cardSlotAt(ev.clientX,ev.clientY); this.reorderKrate(kid,ti); this.setState({cardDrag:{id:kid, mx:ev.clientX, my:ev.clientY}}); } };
const up=()=>{ document.removeEventListener('mousemove',move); document.removeEventListener('mouseup',up); this.setState({cardDrag:null}); };
document.addEventListener('mousemove',move); document.addEventListener('mouseup',up); }
focusWindow(kid,wid){ const k=this.state.krates.find(x=>x.id===kid); const w=k&&k.windows.find(x=>x.wid===wid); if(!w) return; this.fitBox(k.wx+w.dx, k.wy+w.dy-44, w.w, w.h+44, 1.35); }
closeWindow(kid,wid){ this.setState(s=>{ let krates=s.krates.map(k=>{ if(k.id!==kid) return k; return Object.assign({},k,{windows:this.tileWindows(k.windows.filter(w=>w.wid!==wid))}); }); krates=krates.filter(k=>k.windows.length>0); return {krates}; }); }
startWinResize(kid,wid,e){ e.stopPropagation(); const sx=e.clientX,sy=e.clientY; const k=this.state.krates.find(x=>x.id===kid); const w=k&&k.windows.find(x=>x.wid===wid); if(!w) return; const w0=w.w,h0=w.h,z=this.state.zoom;
const move=(ev)=>{ const nw=Math.max(300, w0+(ev.clientX-sx)/z), nh=Math.max(220, h0+(ev.clientY-sy)/z); this.setState(s=>({krates:s.krates.map(kk=>kk.id!==kid?kk:Object.assign({},kk,{windows:this.tileWindows(kk.windows.map(ww=>ww.wid===wid?Object.assign({},ww,{w:nw,h:nh}):ww))}))})); };
const up=()=>{ document.removeEventListener('mousemove',move); document.removeEventListener('mouseup',up); };
document.addEventListener('mousemove',move); document.addEventListener('mouseup',up); }
closeKrate(kid){ this.setState(s=>({krates:s.krates.filter(k=>k.id!==kid)})); }
setTab(kid,wid,tab){ this.setState(s=>({krates:s.krates.map(k=>k.id!==kid?k:Object.assign({},k,{windows:k.windows.map(w=>w.wid===wid?Object.assign({},w,{tab}):w)}))})); }
startWinDrag(kid,wid,e){ e.stopPropagation(); const sx=e.clientX,sy=e.clientY; const k=this.state.krates.find(x=>x.id===kid); const w=k&&k.windows.find(x=>x.wid===wid); if(!w) return; const x0=w.dx,y0=w.dy,z=this.state.zoom;
const move=(ev)=>{ const ndx=x0+(ev.clientX-sx)/z, ndy=y0+(ev.clientY-sy)/z; this.setState(s=>({krates:s.krates.map(kk=>kk.id!==kid?kk:Object.assign({},kk,{windows:kk.windows.map(ww=>ww.wid===wid?Object.assign({},ww,{dx:ndx,dy:ndy}):ww)}))})); };
const up=()=>{ document.removeEventListener('mousemove',move); document.removeEventListener('mouseup',up); };
document.addEventListener('mousemove',move); document.addEventListener('mouseup',up); }
startKrateDrag(kid,e){ e.stopPropagation(); const sx=e.clientX,sy=e.clientY; const k=this.state.krates.find(x=>x.id===kid); if(!k) return; const x0=k.wx,y0=k.wy,z=this.state.zoom;
const move=(ev)=>{ const nx=x0+(ev.clientX-sx)/z, ny=y0+(ev.clientY-sy)/z; this.setState(s=>({krates:s.krates.map(kk=>kk.id!==kid?kk:Object.assign({},kk,{wx:nx,wy:ny}))})); };
const up=()=>{ document.removeEventListener('mousemove',move); document.removeEventListener('mouseup',up); this.resolveKrateOverlaps(kid); };
document.addEventListener('mousemove',move); document.addEventListener('mouseup',up); }
krateFootprint(k){ if(k.collapsed){ return {x:k.wx, y:k.wy-44, w:300, h:104}; } const b=this.krateBox(k); return {x:k.wx+b.x-30, y:k.wy+b.y-72, w:b.w+60, h:b.h+102}; }
resolveKrateOverlaps(activeKid){ this.setState(s=>{ const ks=s.krates.map(k=>Object.assign({},k)); const exp=(k)=>{ const f=this.krateFootprint(k); return {x:f.x-14,y:f.y-14,w:f.w+28,h:f.h+28}; };
for(let it=0; it<80; it++){ let moved=false;
for(let i=0;i<ks.length;i++) for(let j=i+1;j<ks.length;j++){ const A=exp(ks[i]), B=exp(ks[j]);
const ox=Math.min(A.x+A.w,B.x+B.w)-Math.max(A.x,B.x); const oy=Math.min(A.y+A.h,B.y+B.h)-Math.max(A.y,B.y);
if(ox>0 && oy>0){ moved=true; if(ox<oy){ const dir=((A.x+A.w/2)<=(B.x+B.w/2))?1:-1; if(ks[i].id===activeKid){ ks[j].wx+=dir*ox; } else if(ks[j].id===activeKid){ ks[i].wx-=dir*ox; } else { ks[i].wx-=dir*ox/2; ks[j].wx+=dir*ox/2; } }
else { const dir=((A.y+A.h/2)<=(B.y+B.h/2))?1:-1; if(ks[i].id===activeKid){ ks[j].wy+=dir*oy; } else if(ks[j].id===activeKid){ ks[i].wy-=dir*oy; } else { ks[i].wy-=dir*oy/2; ks[j].wy+=dir*oy/2; } } } }
if(!moved) break; }
ks.forEach(k=>{ k.wx=Math.round(k.wx/20)*20; k.wy=Math.round(k.wy/20)*20; });
return {krates:ks}; }); }
toggleCollapseKrate(kid){ this.setState(s=>({krates:s.krates.map(k=>k.id===kid?Object.assign({},k,{collapsed:!k.collapsed}):k), zoomedWin:null}), ()=>this.resolveKrateOverlaps(kid)); }
zoomToggleWindow(kid,wid){ const k=this.state.krates.find(x=>x.id===kid); const w=k&&k.windows.find(x=>x.wid===wid); if(!w) return;
if(this.state.zoomedWin===wid){ const ps=this._prevSize&&this._prevSize[wid];
this.setState(s=>({zoomedWin:null, krates:s.krates.map(kk=>kk.id!==kid?kk:Object.assign({},kk,{windows:this.tileWindows(kk.windows.map(ww=>(ww.wid===wid&&ps)?Object.assign({},ww,{w:ps.w,h:ps.h}):ww))}))}), ()=>this.fitKrate(kid)); }
else { const el=this.rootEl; const W=el?el.clientWidth:1280,H=el?el.clientHeight:760; const sideM=22, topInset=68, botInset=66; const nw=Math.max(360,W-2*sideM), nh=Math.max(260,H-topInset-botInset);
this._prevSize=this._prevSize||{}; this._prevSize[wid]={w:w.w,h:w.h};
this.setState(s=>({zoomedWin:wid, krates:s.krates.map(kk=>kk.id!==kid?kk:Object.assign({},kk,{windows:kk.windows.map(ww=>ww.wid===wid?Object.assign({},ww,{w:nw,h:nh}):ww)}))}), ()=>{ const k2=this.state.krates.find(x=>x.id===kid); const w2=k2&&k2.windows.find(x=>x.wid===wid); if(w2){ const camX=sideM-(k2.wx+w2.dx), camY=topInset-(k2.wy+w2.dy); this.setState({zoom:1, camX, camY, flying:true}); clearTimeout(this._ft); this._ft=setTimeout(()=>this.setState({flying:false}),520); } }); } }
mmJump(e){ const r=this._mmEl?this._mmEl.getBoundingClientRect():null; if(!r||!this._mm) return; const mx=e.clientX-r.left, my=e.clientY-r.top; const wx=this._mm.bx+mx/this._mm.scale, wy=this._mm.by+my/this._mm.scale; this.centerOn(wx,wy,this.state.zoom,true); }
nodeYaml(n){ const d=n.data||{};
if(n.type==='secret'){ let s='apiVersion: v1\nkind: Secret\nmetadata:\n name: '+n.name+'\n namespace: '+n.ns+'\ntype: Opaque\ndata: # decoded\n'; const sec=d.secret||{}; Object.keys(sec).forEach(k=>{ s+=' '+k+': '+sec[k]+'\n'; }); return s; }
if(n.type==='configmap'){ return 'apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: '+n.name+'\n namespace: '+n.ns+'\ndata:\n LOG_LEVEL: info\n TIMEOUT: "30s"\n REGION: eu-west-1'; }
if(n.type==='pvc'){ return 'apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: '+n.name+'\n namespace: '+n.ns+'\nspec:\n accessModes: [ReadWriteOnce]\n resources:\n requests:\n storage: 20Gi\n storageClassName: gp3'; }
if(n.type==='pod'){ return 'apiVersion: v1\nkind: Pod\nmetadata:\n name: '+n.name+'\n namespace: '+n.ns+'\nspec:\n containers:\n - name: app\n image: '+(d.img||'krates/app:latest')+'\nstatus:\n phase: '+n.status; }
if(n.type==='service'){ return 'apiVersion: v1\nkind: Service\nmetadata:\n name: '+n.name+'\n namespace: '+n.ns+'\nspec:\n type: ClusterIP\n selector:\n app: '+(d.relTo||n.name)+'\n ports:\n - port: '+((d.port||'80:80').split(':')[0])+'\n targetPort: '+((d.port||'80:80').split(':')[1]); }
if(n.type==='ingress'){ return 'apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: '+n.name+'\n namespace: '+n.ns+'\nspec:\n rules:\n - host: '+n.ns+'.krates.io\n http:\n paths:\n - path: /\n backend:\n service:\n name: '+(d.relTo||'app'); }
if(n.type==='crd'){ return 'apiVersion: krates.io/v1\nkind: '+(d.kindName||'CustomResource')+'\nmetadata:\n name: '+n.name.split('/').pop()+'\n namespace: '+n.ns+'\nspec:\n target: '+(d.relTo||'')+'\nstatus:\n phase: Active'; }
const kind=n.type==='statefulset'?'StatefulSet':(n.type==='daemonset'?'DaemonSet':'Deployment');
return 'apiVersion: apps/v1\nkind: '+kind+'\nmetadata:\n name: '+n.name+'\n namespace: '+n.ns+'\nspec:\n replicas: '+((d.replicas||'1/1').split('/')[1])+'\n template:\n spec:\n containers:\n - name: '+n.name+'\n image: '+(d.img||'krates/app:latest')+'\nstatus:\n readyReplicas: '+((d.replicas||'1/1').split('/')[0]); }
nodeDescribe(n){ const m=this.meta(n.type); const d=n.data||{};
let s='Name: '+n.name+'\nNamespace: '+n.ns+'\nKind: '+m.label+'\nStatus: '+n.status+'\n';
if(d.img) s+='Image: '+d.img+'\n'; if(d.replicas) s+='Replicas: '+d.replicas+' ready\n'; if(d.port) s+='Port: '+d.port+'\n';
s+='Node: ip-10-0-'+(3+(n.name.length%6))+'-'+(12+(n.name.length%40))+'\nAge: '+(2+(n.name.length%18))+'d\nLabels: app='+n.name.split(/[-/]/)[0]+'\n';
const nb=(this.adj[n.id]||[]).map(id=>this.byId[id]).filter(Boolean); s+='\nRelated ('+nb.length+'):\n'+nb.map(x=>' → '+this.meta(x.type).short+'/'+x.name).join('\n'); return s; }
nodeLogs(n){ if(!this.execable(n.type)) return 'No logs for '+this.meta(n.type).label+' objects.';
const base=n.name.split(/[-/]/)[0]; const t=(i)=>'09:'+(12+i)+':'+((10+i*7)%60<10?'0':'')+((10+i*7)%60);
return [t(0)+' INFO '+base+' starting, build '+(n.data&&n.data.img||'dev'),t(1)+' INFO listening on :8080',t(2)+' INFO connected to upstream postgres.payments',t(3)+' WARN retry budget at 82%',t(4)+' INFO GET /healthz 200 1ms',t(5)+' INFO reconciled 14 routes',t(6)+' '+(n.status==='Degraded'?'ERROR liveness probe failed (3/3)':'INFO request 7f3a 200 23ms'),t(7)+' INFO GET /v1/orders 200 14ms'].join('\n'); }
nodeShell(n){ if(!this.execable(n.type)) return 'Exec not available for '+this.meta(n.type).label+'.';
return '$ kubectl exec -it '+n.name+' -n '+n.ns+' -- sh\n/ # whoami\nroot\n/ # ls /app\nbin config data server\n/ # cat /app/config/region\neu-west-1\n/ # \u2588'; }
winBody(n,tab){ if(tab==='describe') return this.nodeDescribe(n); if(tab==='yaml') return this.nodeYaml(n); return ''; }
// ---------- live logs ----------
seedLogs(n){ const base=n.name.split(/[-/]/)[0]; const T=(i)=>'09:'+(40+Math.floor(i/60))+':'+((i%60)<10?'0':'')+(i%60); let i=0;
return [T(i++)+' INFO '+base+' starting, build '+((n.data&&n.data.img)||'dev'), T(i+=7)+' INFO config loaded from /app/config', T(i+=4)+' INFO listening on :8080', T(i+=6)+' INFO connected to postgres.payments', T(i+=5)+' INFO GET /healthz 200 1ms', T(i+=9)+' INFO reconciled 14 routes', T(i+=3)+(n.status==='Degraded'?' ERROR liveness probe failed (3/3)':' INFO GET /v1/orders 200 14ms')]; }
logLine(n){ const base=n.name.split(/[-/]/)[0]; const now=new Date(); const ts=('0'+now.getHours()).slice(-2)+':'+('0'+now.getMinutes()).slice(-2)+':'+('0'+now.getSeconds()).slice(-2);
const paths=['/healthz','/v1/orders','/v1/cart','/metrics','/readyz','/v1/checkout']; const codes=['200','200','200','204','200','503','200','404'];
const c=codes[Math.floor(Math.random()*codes.length)]; const p=paths[Math.floor(Math.random()*paths.length)]; const ms=(1+Math.floor(Math.random()*60));
if(c==='503') return ts+' ERROR '+base+' upstream timeout on '+p+' (retry 2/3)';
if(c==='404') return ts+' WARN '+base+' '+p+' 404 not found';
return ts+' INFO '+base+' GET '+p+' '+c+' '+ms+'ms'; }
logColor(t){ return /ERROR/.test(t)?'#ef6f6f':(/WARN/.test(t)?'#e8b54a':'#aab8cc'); }
appendLogs(){ const S=this.state; if(!S.krates.length) return; let changed=false; const logs=Object.assign({},S.logs);
S.krates.forEach(k=>{ k.windows.forEach(w=>{ const n=this.byId[w.objId||k.objId]; if(w.tab!=='logs'||!this.execable(n.type)) return; const tk=w.wid+'#logs';
const buf=(logs[tk]?logs[tk].slice():this.seedLogs(n)); buf.push(this.logLine(n)); if(buf.length>160) buf.splice(0,buf.length-160); logs[tk]=buf; changed=true; }); });
if(changed) this.setState({logs}); }
// ---------- interactive shell ----------
seedShell(n){ return {cwd:'/', lines:[{t:'out',text:'krates shell · '+n.name+' · ns/'+n.ns},{t:'out',text:"connected. type 'help' for commands."}]}; }
prompt(n,cwd){ return 'root@'+n.name+':'+cwd+'#'; }
shellEval(n,sess,cmd){ const c=cmd.trim(); if(!c) return {out:[]}; const parts=c.split(/\s+/); const a=parts[0];
const fs={'/':['app','bin','etc','proc','var'],'/app':['bin','config','data','server','VERSION'],'/app/config':['region','log_level','feature_flags']};
if(a==='help') return {out:['available: ls cd pwd cat env ps whoami hostname date kubectl echo clear exit']};
if(a==='whoami') return {out:['root']};
if(a==='hostname') return {out:[n.name]};
if(a==='date') return {out:[new Date().toUTCString()]};
if(a==='pwd') return {out:[sess.cwd]};
if(a==='ls') return {out:[(fs[parts[1]||sess.cwd]||fs[sess.cwd]||[]).join(' ')]};
if(a==='env') return {out:['POD_NAME='+n.name,'NAMESPACE='+n.ns,'REGION=eu-west-1','LOG_LEVEL=info','PORT=8080','IMAGE='+((n.data&&n.data.img)||'krates/app')]};
if(a==='ps') return {out:[' PID USER COMMAND',' 1 root /app/server',' 18 root sh',' 42 root ps']};
if(a==='cat'){ const f=(parts[1]||''); if(/region/.test(f)) return {out:['eu-west-1']}; if(/VERSION/.test(f)) return {out:[((n.data&&n.data.img)||'1.0.0')]}; if(/log_level/.test(f)) return {out:['info']}; if(/feature_flags/.test(f)) return {out:['checkout_v2=true','dark_mode=false']}; return {out:['cat: '+f+': No such file or directory']}; }
if(a==='cd'){ let t=parts[1]||'/'; if(t==='..'){ const s=sess.cwd.split('/').filter(Boolean); s.pop(); t='/'+s.join('/'); } else if(t!=='/' && !t.startsWith('/')){ t=(sess.cwd==='/'?'':sess.cwd)+'/'+t; } t=t.replace(/\/+/g,'/'); if(t!=='/'&&t.length>1)t=t.replace(/\/$/,''); if(fs[t]||t==='/') return {out:[],cwd:t}; return {out:['cd: '+parts[1]+': Not a directory']}; }
if(a==='kubectl') return {out:['tip: explore visually in krates — or pipe real kubectl here']};
if(a==='echo') return {out:[parts.slice(1).join(' ')]};
if(a==='exit') return {out:['use × to close the window']};
return {out:[a+": command not found (try 'help')"]}; }
runShell(wid,n,cmd){ const tk=wid+'#shell'; const sh=Object.assign({},this.state.sh); const prev=sh[tk]||this.seedShell(n); const sess={cwd:prev.cwd,lines:prev.lines.slice()};
if(cmd.trim()==='clear'){ sess.lines=[]; sh[tk]=sess; this._stickSh[wid]=true; this.setState({sh}); return; }
sess.lines.push({t:'in',text:this.prompt(n,sess.cwd)+' '+cmd}); const r=this.shellEval(n,sess,cmd); (r.out||[]).forEach(o=>sess.lines.push({t:'out',text:o})); if(r.cwd!==undefined) sess.cwd=r.cwd;
if(sess.lines.length>200) sess.lines.splice(0,sess.lines.length-200); sh[tk]=sess; this._stickSh[wid]=true; this.setState({sh}); }
shellKey(kid,wid,n,e){ if(e.key!=='Enter') return; e.preventDefault(); e.stopPropagation(); const el=e.target; const cmd=el.value; el.value=''; this.runShell(wid,n,cmd); }
// ---------- open related ----------
openRelated(kid,relId){ const view=this.defaultView(this.byId[relId].type);
this.setState(s=>{ const krates=s.krates.map(k=>{ if(k.id!==kid) return k; let wl=k.windows.slice(); if(!wl.find(w=>(w.objId||k.objId)===relId && w.tab===view)){ wl=wl.concat([{wid:k.id+'-'+k.seq, objId:relId, tab:view, dx:0,dy:0,w:380,h:360}]); } return Object.assign({},k,{windows:this.tileWindows(wl), seq:k.seq+1}); }); return {krates, relMenu:null}; }, ()=>this.fitKrate(kid)); }
openInKrate(kid,objId,view){ this.setState(s=>{ const krates=s.krates.map(k=>{ if(k.id!==kid) return k; let wl=k.windows.slice(); if(!wl.find(w=>w.kind!=='collection' && (w.objId||k.objId)===objId && w.tab===view)){ wl=wl.concat([{wid:k.id+'-'+k.seq, objId, tab:view, dx:0,dy:0,w:380,h:360}]); } return Object.assign({},k,{windows:this.tileWindows(wl), seq:k.seq+1}); }); return {krates}; }, ()=>this.fitKrate(kid)); }
// ---------- collections ----------
summonCollection(scope){ this.clearTimers(); if(scope.kind==='category') this.recordType(scope.value); const existing=this.state.krates.find(k=>k.collScope && k.collScope.kind===scope.kind && k.collScope.value===scope.value);
if(existing){ this._focusCollWid=existing.windows[0].wid; this.setState({spotOpen:false, session:null, navigated:false}, ()=>this.fitKrate(existing.id)); return; }
const p=this.placeKrate(); const color = scope.kind==='namespace'? ((this.world.namespaces.find(x=>x.name===scope.value)||{}).color||'#6fb1ff') : this.meta(scope.value).color;
const wid0='k'+this.state.krateSeq+'-0'; const k={ id:'k'+this.state.krateSeq, objId:null, collScope:scope, label:this.collTitle(scope), status:'Ready', color, wx:p.cx, wy:p.cy, seq:1,
windows:[{wid:wid0, kind:'collection', scope, dx:0, dy:0, w:540, h:480}] };
this._focusCollWid=wid0;
this.setState(s=>({krates:s.krates.concat([k]), krateSeq:s.krateSeq+1, spotOpen:false, session:null, navigated:false}), ()=>this.fitKrate(k.id)); }
setCollSearch(wid,val){ this._collNavTimer=this._collNavTimer||{}; clearTimeout(this._collNavTimer[wid]); this.setState(s=>({collSearch:Object.assign({},s.collSearch,{[wid]:val}), collSel:Object.assign({},s.collSel,{[wid]:0}), collNav:Object.assign({},s.collNav,{[wid]:false})})); }
armCollNav(wid){ this._collNavTimer=this._collNavTimer||{}; clearTimeout(this._collNavTimer[wid]); this.setState(s=> s.collNav[wid]?null:({collNav:Object.assign({},s.collNav,{[wid]:true})}) );
this._collNavTimer[wid]=setTimeout(()=>{ this.setState(s=>({collNav:Object.assign({},s.collNav,{[wid]:false})})); }, 500); }
cancelCollNav(wid){ this._collNavTimer=this._collNavTimer||{}; clearTimeout(this._collNavTimer[wid]); this.setState(s=>({collNav:Object.assign({},s.collNav,{[wid]:false})})); }
// unified collection key handling — works whether the filter is focused OR the mouse is just hovering the box
collKey(wid, e){ const ctx=this._collCtx[wid]; if(!ctx) return false; const kid=ctx.kid, nodes=ctx.nodes;
if(e.key==='ArrowDown'){ e.preventDefault(); this.setState(s=>({collSel:Object.assign({},s.collSel,{[wid]:Math.min(nodes.length-1,(s.collSel[wid]||0)+1)})})); return true; }
if(e.key==='ArrowUp'){ e.preventDefault(); this.setState(s=>({collSel:Object.assign({},s.collSel,{[wid]:Math.max(0,(s.collSel[wid]||0)-1)})})); return true; }
const sel=Math.min(this.state.collSel[wid]||0, nodes.length-1); const node=nodes[sel];
if(e.altKey && !e.ctrlKey && !e.metaKey){ const v=this.keyToView(this.codeLetter(e)); if(v && node && this.viewAvail(node.type,v)){ e.preventDefault(); this.openInKrate(kid, node.id, v); return true; } return false; }
if(e.key==='Enter'){ e.preventDefault(); if(node) this.openInKrate(kid, node.id, this.defaultView(node.type)); return true; }
if(e.key==='Escape'){ if(e.target&&e.target.blur)e.target.blur(); return true; }
if(e.key===' ') return false; // reserve for pan
if(e.key.length===1 && !e.metaKey && !e.ctrlKey){ e.preventDefault(); this.setCollSearch(wid, (this.state.collSearch[wid]||'')+e.key); return true; }
if(e.key==='Backspace'){ e.preventDefault(); this.setCollSearch(wid, (this.state.collSearch[wid]||'').slice(0,-1)); return true; }
return false; }
handleCollKey(e, kid, wid, nodes){ this.collKey(wid, e); }
toggleCollView(wid){ this.setState(s=>({collView:Object.assign({},s.collView,{[wid]: (s.collView[wid]==='tree'?'list':'tree')})})); }
toggleAdmin=()=>{ this.setState(s=>({admin:!s.admin})); };
adminData(){ return [
{name:'Dana Okafor', color:'#6fb1ff', status:'active · payments', statusColor:'#4ad07a', query:'ledger', typing:true, krates:[['deploy/ledger','L Y','Degraded'],['All Secrets','▤','Ready']]},
{name:'Wen Li', color:'#5fd0c0', status:'active · platform', statusColor:'#4ad07a', query:'auth oauth-secret', typing:false, krates:[['deploy/auth','D S','Ready'],['ns/platform','▤','Ready']]},
{name:'Ravi Patel', color:'#e58fb0', status:'idle 4m · storefront', statusColor:'#7e8aa2', query:'redis', typing:false, krates:[['sts/redis','L Y','Ready']]},
{name:'You', color:null, status:'active', statusColor:'#4ad07a', query:'—', typing:false, krates:[]} ]; }
// ---------- refs ----------
setRoot=(el)=>{ this.rootEl=el; };
setInput=(el)=>{ this.inputEl=el; };
stopDown=(e)=>{ e.stopPropagation(); };
winWheel=(e)=>{ if(e.ctrlKey||e.metaKey) return; e.stopPropagation(); };
renderVals(){
const S=this.state; const A=this.accentSet(); const VM=this.viewMeta();
const W=this.rootEl?this.rootEl.clientWidth:1280, H=this.rootEl?this.rootEl.clientHeight:760;
const toScreen=(wx,wy)=>({x:S.camX+wx*S.zoom, y:S.camY+wy*S.zoom});
const frames=[]; const wins=[]; const cards=[]; const minis=[];
if(!S.collapsed){
S.krates.forEach(k=>{
if(k.collapsed){ minis.push({ x:k.wx, y:k.wy-44, color:k.color, outline:this.rgba(k.color,.3), label:k.label, status:k.status, statusColor:this.statusColor(k.status), count:k.windows.length,
onExpand:()=>this.toggleCollapseKrate(k.id), onDrag:(e)=>this.startKrateDrag(k.id,e), onClose:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.closeKrate(k.id); } }); return; }
const box=this.krateBox(k); const n=this.byId[k.objId];
frames.push({left:k.wx+box.x-30, top:k.wy+box.y-30, w:box.w+60, h:box.h+60, border:this.rgba(k.color,.3), fill:this.rgba(k.color,.04),
labelLeft:k.wx+box.x-30, labelTop:k.wy+box.y-72, color:k.color, label:k.label, status:k.status, statusColor:this.statusColor(k.status), count:k.windows.length,
onCollapse:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.toggleCollapseKrate(k.id); }, onToggleCollapse:()=>this.toggleCollapseKrate(k.id),
onDrag:(e)=>this.startKrateDrag(k.id,e), onClose:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.closeKrate(k.id); } });
k.windows.forEach(w=>{ if(w.kind==='collection'){ const scope=w.scope; const allMem=this.collMembers(scope); const qv=(S.collSearch[w.wid]||''); const q=qv.trim();
const matches=(nn)=> !q || this.fuzzy(q,nn.name)>=0 || this.fuzzy(q,this.meta(nn.type).short)>=0 || this.fuzzy(q,nn.ns)>=0;
const summ=this.statusSummary(allMem); const mode=S.collView[w.wid]||'list'; const csel=S.collSel[w.wid]||0;
const rowOf=(nn,depth,rel,oi)=>{ const mm=this.meta(nn.type); const ssc=this.shapeCss(mm.shape); const av=this.viewOrder().filter(v=>this.viewAvail(nn.type,v));
return { id:nn.id+':'+oi, name:nn.name, row:true, isGroup:false, indent:depth||0, indentPx:(depth||0)*20, child:(depth||0)>0, rel:rel||'', hasRel:!!rel, typeShort:mm.short, color:mm.color, clip:ssc.clip, radius:ssc.radius, statusColor:this.statusColor(nn.status), status:nn.status, metric:this.healthMetric(nn),
selected:(oi===csel), selBg:(oi===csel?A.dim:'transparent'), selBar:(oi===csel?A.accent:'transparent'),
views:av.map(v=>({letter:VM[v].letter, onClick:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.openInKrate(k.id, nn.id, v); }})),
onOpen:()=>this.openInKrate(k.id, nn.id, this.defaultView(nn.type)),
onHover:()=>this.setState(s=>({collSel:Object.assign({},s.collSel,{[w.wid]:oi})})),
onRelated:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.setState({relMenu:{kid:k.id, objId:nn.id, sx:Math.min(e.clientX,W-244), sy:Math.min(e.clientY,H-260)}}); } }; };
let rows=[]; let visNodes=[];
if(mode==='tree'){ let xr=this.buildXray(scope, allMem);
if(q){ const keep=new Array(xr.length).fill(false); for(let i=0;i<xr.length;i++){ if(matches(xr[i].node)){ keep[i]=true; let d=xr[i].depth; for(let j=i-1;j>=0&&d>0;j--){ if(xr[j].depth<d){ keep[j]=true; d=xr[j].depth; } } } } xr=xr.filter((r,i)=>keep[i]); }
visNodes=xr.map(r=>r.node); rows=xr.map((r,oi)=>rowOf(r.node, r.depth, r.rel, oi)); }
else { const rank=(nn)=>(nn.status==='Degraded'||nn.status==='Failed')?0:(nn.status==='Pending'?1:2); const list=allMem.filter(matches).slice().sort((a,b)=>rank(a)-rank(b)||a.name.localeCompare(b.name)); visNodes=list; rows=list.map((nn,oi)=>rowOf(nn,0,'',oi)); }
const cwd={ isCollection:true, normal:false, showRelated:false, relatedDisplay:'none', z:(S.zoomedWin===w.wid?60:1), isText:false, isLogs:false, isShell:false, x:k.wx+w.dx, y:k.wy+w.dy, w:w.w, bodyH:Math.max(150,w.h-150), outline:this.rgba(k.color,.22), color:k.color, glyphClip:'none', title:this.collTitle(scope), status:(summ.bad?'Degraded':(summ.warn?'Pending':'Ready')), statusColor:this.statusColor(summ.bad?'Degraded':(summ.warn?'Pending':'Ready')),
total:summ.total, summOk:summ.ok, summWarn:summ.warn, summBad:summ.bad, count:visNodes.length, navMode:!!S.collNav[w.wid], searchVal:qv, listBg:(mode!=='tree'?A.accent:'transparent'), listFg:(mode!=='tree'?'#0b0e13':'#9fb0c8'), treeBg:(mode==='tree'?A.accent:'transparent'), treeFg:(mode==='tree'?'#0b0e13':'#9fb0c8'), rows, empty:rows.length===0,
onCollSearch:(e)=>this.setCollSearch(w.wid, e.target.value), onCollKey:(e)=>this.handleCollKey(e, k.id, w.wid, visNodes), onCollList:()=>this.setState(s=>({collView:Object.assign({},s.collView,{[w.wid]:'list'}), collSel:Object.assign({},s.collSel,{[w.wid]:0})})), onCollTree:()=>this.setState(s=>({collView:Object.assign({},s.collView,{[w.wid]:'tree'}), collSel:Object.assign({},s.collSel,{[w.wid]:0})})),
collInputRef:(el)=>{ if(el) this._collInputEls[w.wid]=el; }, onCollEnter:()=>{ this._hoverCollWid=w.wid; }, onCollLeave:()=>{ if(this._hoverCollWid===w.wid) this._hoverCollWid=null; },
onClose:()=>this.closeWindow(k.id,w.wid), onDrag:(e)=>this.startKrateDrag(k.id,e), onFocus:()=>this.zoomToggleWindow(k.id,w.wid), onWinEnter:()=>{ this._hoverWinId={kid:k.id,wid:w.wid}; }, onWinLeave:()=>{ if(this._hoverWinId&&this._hoverWinId.wid===w.wid) this._hoverWinId=null; }, onResize:(e)=>this.startWinResize(k.id,w.wid,e) };
this._collCtx[w.wid]={kid:k.id, nodes:visNodes};
wins.push(cwd); return; }
const wn=this.byId[w.objId||k.objId]; const m=this.meta(wn.type); const sc=this.shapeCss(m.shape); const tk=w.wid+'#'+w.tab;
const tabs=this.viewOrder().map(v=>{ const dis=!this.viewAvail(wn.type,v); const act=w.tab===v; return {label:VM[v].label,bg:act?'rgba(30,38,52,.9)':'transparent',fg:act?'#e6edf6':(dis?'#54607a':'#7e8aa2'),cursor:dis?'default':'pointer',opacity:dis?.45:1,onClick:()=>dis?null:this.setTab(k.id,w.wid,v)}; });
const isShell=w.tab==='shell'&&this.execable(wn.type); const isLogs=w.tab==='logs'&&this.execable(wn.type); const isText=!isShell&&!isLogs;
const wd={ normal:true, isCollection:false, showRelated:true, relatedDisplay:'inline-block', z:(S.zoomedWin===w.wid?60:1), x:k.wx+w.dx, y:k.wy+w.dy, w:w.w, bodyH:Math.max(120,w.h-76), outline:this.rgba(k.color,.22), color:m.color, glyphClip:sc.clip, title:VM[w.tab].label+' · '+wn.name, status:wn.status, statusColor:this.statusColor(wn.status), tabs, isShell, isLogs, isText, body:isText?this.winBody(wn,w.tab):'',
onClose:()=>this.closeWindow(k.id,w.wid), onDrag:(e)=>this.startKrateDrag(k.id,e), onFocus:()=>this.zoomToggleWindow(k.id,w.wid), onWinEnter:()=>{ this._hoverWinId={kid:k.id,wid:w.wid}; }, onWinLeave:()=>{ if(this._hoverWinId&&this._hoverWinId.wid===w.wid) this._hoverWinId=null; }, onResize:(e)=>this.startWinResize(k.id,w.wid,e),
onRelated:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.setState({relMenu:{kid:k.id, objId:w.objId||k.objId, sx:Math.min(e.clientX, W-244), sy:Math.min(e.clientY, H-260)}}); } };
if(isLogs){ const wid=w.wid; const buf=(S.logs[tk]||this.seedLogs(wn)); wd.logLines=buf.map(t=>({text:t,color:this.logColor(t)})); wd.logRef=(el)=>{ if(el) this._logEls[wid]=el; }; wd.onLogScroll=(e)=>{ const el=e.target; this._stickLog[wid]= el.scrollTop+el.clientHeight >= el.scrollHeight-14; }; }
if(isShell){ const wid=w.wid; const sess=(S.sh[tk]||this.seedShell(wn)); wd.shellLines=sess.lines.map(L=>({text:L.text,color:L.t==='in'?A.accent:(/not found|No such|Not a directory|failed|timeout/i.test(L.text)?'#ef6f6f':'#aab8cc')})); wd.promptStr=this.prompt(wn,sess.cwd); wd.shRef=(el)=>{ if(el) this._shEls[wid]=el; }; wd.onShScroll=(e)=>{ const el=e.target; this._stickSh[wid]= el.scrollTop+el.clientHeight >= el.scrollHeight-14; }; wd.onShellKey=(e)=>this.shellKey(k.id,wid,wn,e); wd.shInputRef=(el)=>{ if(el) this._shInputEls[wid]=el; }; wd.onShEnter=()=>{ this._hoverShWid=wid; }; wd.onShLeave=()=>{ if(this._hoverShWid===wid) this._hoverShWid=null; }; }
wins.push(wd); });
});
} else {
const L=this.cardLayout(); const drag=S.cardDrag;
S.krates.forEach((k,i)=>{ const col=i%L.cols, row=Math.floor(i/L.cols); let sx=L.startX+col*(L.cardW+L.gap), sy=L.startY+row*(L.cardH+L.gap); let dragging=false;
if(drag && drag.id===k.id && drag.mx!=null){ sx=drag.mx-L.cardW/2; sy=drag.my-L.cardH/2; dragging=true; }
cards.push({sx, sy, dragging, z:dragging?60:5, trans:dragging?'none':'left .18s cubic-bezier(.2,.8,.3,1), top .18s cubic-bezier(.2,.8,.3,1)', label:k.label, color:k.color, statusColor:this.statusColor(k.status), outline:this.rgba(k.color,.3), tint:this.rgba(k.color,.12),
badges:k.windows.map(w=>w.kind==='collection'?'▤':VM[w.tab].letter), count:k.windows.length, ns:(k.objId?this.byId[k.objId].ns:(k.collScope&&k.collScope.kind==='namespace'?k.collScope.value:'cluster')), onExpand:()=>this.flyToKrate(k.id), onDown:(e)=>this.startCardDrag(k.id,e), onClose:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.closeKrate(k.id); } }); });
}
const users=[{name:'Dana Okafor',color:'#6fb1ff'},{name:'Wen Li',color:'#5fd0c0'},{name:'Ravi Patel',color:'#e58fb0'},{name:'You',color:A.accent}];
const roster=users.map(u=>({name:u.name,color:u.color,initial:u.name==='You'?'★':u.name.split(' ').map(p=>p[0]).join('').slice(0,2)}));
const initialOf=(n)=> n==='You'?'★':n.split(' ').map(p=>p[0]).join('').slice(0,2);
const adminUsers=this.adminData().map(u=>{ const isYou=u.name==='You'; const color=isYou?A.accent:u.color;
const krs = isYou ? S.krates.map(k=>({label:k.label, color:k.color, badges:k.windows.map(w=>w.kind==='collection'?'▤':VM[w.tab].letter).join(' '), statusColor:this.statusColor(k.status), onSpectate:()=>{ this.setState({admin:false}); this.fitKrate(k.id); }})) : u.krates.map(kr=>({label:kr[0], color:color, badges:kr[1], statusColor:this.statusColor(kr[2]), onSpectate:()=>this.setState({admin:false})}));
return { name:u.name, color, initial:initialOf(u.name), border:this.rgba(color,.3), status:u.status, statusColor:u.statusColor, query:(isYou?(S.query||'—'):u.query), typing:(isYou?S.spotOpen:u.typing), krateCount:krs.length, krates:krs }; });
// spotlight
const results=this.buildResults(S.query,S.filterType); const sug=this.suggestType(S.query,S.filterType);
const resDisplay=results.map((it,i)=>{ const active=i===S.sel;
if(it.kind==='krate'){ const k=it.krate; const vk=k.windows.map(w=>w.kind==='collection'?'▤':VM[w.tab].letter).join(' ');
return { isKrate:true, id:k.id, name:k.label, sub:k.windows.length+' window'+(k.windows.length===1?'':'s')+' · '+vk, color:k.color, clip:'none', radius:'3px', glow:this.rgba(k.color,.5),
typeShort:'krate', isCrd:false, active, bg:active?A.dim:'transparent', views:[], viewKeys:'', enter:true, showBadge:true, badgeLabel:'KRATE',
viewHint:'open working set', onHover:()=>this.setState({sel:i}), onClick:()=>this.jumpToKrate(k.id) }; }
if(it.kind==='category'||it.kind==='namespace'){ const isNs=it.kind==='namespace'; const col=isNs?(it.color||'#6fb1ff'):this.meta(it.scope.value).color; const sc=isNs?{clip:'none',radius:'4px'}:this.shapeCss(this.meta(it.scope.value).shape);
const summ=this.statusSummary(this.collMembers(it.scope)); const sub=it.count+' objects · '+summ.ok+' ok'+(summ.warn?' · '+summ.warn+' pending':'')+(summ.bad?' · '+summ.bad+' degraded':'');
return { isColl:true, id:it.id, name:this.collTitle(it.scope), sub, color:col, clip:sc.clip, radius:sc.radius, glow:this.rgba(col,.5),
typeShort:isNs?'namespace':'category', collBadge:true, active, bg:active?A.dim:'transparent', views:[], viewKeys:'', enter:true, showBadge:true, badgeLabel:isNs?'NS':'CATEGORY',
viewHint:'open status view', onHover:()=>this.setState({sel:i}), onClick:()=>this.summonCollection(it.scope) }; }
const n=it.node; const m=this.meta(n.type); const sc=this.shapeCss(m.shape);
const sessionViews = (S.session && S.session.objId===n.id) ? S.session.views : [];
const avViews=this.viewOrder().filter(v=>this.viewAvail(n.type,v));
const views=avViews.map(v=>{ const opened=sessionViews.indexOf(v)>=0;
return { letter:'⌥'+VM[v].letter, label:VM[v].label, mark: opened?' ✓':'', cursor:'pointer', opacity:1,
border: opened?A.accent:'rgba(140,165,200,.22)', bg: opened?A.dim:'rgba(255,255,255,.02)', fg: opened?A.accent:'#dbe3ef',
kbdBg: opened?A.accent:'rgba(140,165,200,.16)', kbdFg: opened?'#0b0e13':'#9fb0c8',
onClick:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.openView(n.id,v,false); } }; });
return { isKrate:false, id:n.id, name:n.name, sub:m.label+' · ns/'+n.ns, color:m.color, clip:sc.clip, radius:sc.radius, glow:this.rgba(m.color,.5),
typeShort:m.short, isCrd:!!m.crd, active, bg:active?A.dim:'transparent', views, viewKeys:avViews.map(v=>'⌥'+VM[v].letter).join(' '), enter:false, showBadge:false, badgeLabel:'',
viewHint: 'hover or ↓, then ⌥ + letter',
onHover:()=>this.setState({sel:i}), onClick:()=>this.openView(n.id,this.defaultView(n.type),true) }; });
const chipLabel={deployment:'deploy',service:'svc',pod:'pods',secret:'secrets',configmap:'config',statefulset:'sts',daemonset:'ds',ingress:'ing',crd:'crd',pvc:'pvc'};
const chipTypes=[['','all']].concat(this.filterTypeOrder().map(t=>[t, chipLabel[t]||this.meta(t).short]));
const chips=chipTypes.map(ct=>{ const t=ct[0]; const m=t?this.meta(t):{color:'#8aa0bd'}; const sc=t?this.shapeCss(m.shape):{clip:'none',radius:'50%'}; const active=(S.filterType||'')===t;
return {label:ct[1],color:m.color,clip:sc.clip,radius:sc.radius,bg:active?A.dim:'rgba(255,255,255,.02)',fg:active?A.accent:'#9fb0c8',border:active?A.accent:'rgba(140,165,200,.18)',onClick:()=>{ this.recordType(t); this._focus=true; this.setState({filterType:t||null, sel:0, navigated:false}); }}; });
const fm=S.filterType?this.meta(S.filterType):null; const fsc=fm?this.shapeCss(fm.shape):null;
const selRow=resDisplay[S.sel]; const openKeysLabel=(selRow && selRow.viewKeys) ? selRow.viewKeys : (selRow && selRow.enter ? '⏎ open' : '⌥L ⌥S ⌥D ⌥Y');
const sessionActive=!!S.session; const buildingViews= S.session? S.session.views.map(v=>VM[v].letter):[];
let relMenu=null; if(S.relMenu){ const o=this.byId[S.relMenu.objId]; const items=(this.adj[S.relMenu.objId]||[]).map(id=>{ const rn=this.byId[id]; const rm=this.meta(rn.type); const rsc=this.shapeCss(rm.shape);
return {name:rn.name, typeShort:rm.short, color:rm.color, clip:rsc.clip, radius:rsc.radius, onClick:(e)=>{ if(e&&e.stopPropagation)e.stopPropagation(); this.openRelated(S.relMenu.kid, id); }}; });
relMenu={sx:S.relMenu.sx, sy:S.relMenu.sy, label:o.name, items, empty:items.length===0}; }
const zoomPct=Math.round(S.zoom*100);
// minimap
let minimap=null;
if(S.krates.length){ const fps=S.krates.map(k=>this.krateFootprint(k));
let bx=1e9,by=1e9,bx2=-1e9,by2=-1e9; fps.forEach(f=>{ bx=Math.min(bx,f.x); by=Math.min(by,f.y); bx2=Math.max(bx2,f.x+f.w); by2=Math.max(by2,f.y+f.h); });
const vx1=(-S.camX)/S.zoom, vy1=(-S.camY)/S.zoom, vx2=(W-S.camX)/S.zoom, vy2=(H-S.camY)/S.zoom;
bx=Math.min(bx,vx1); by=Math.min(by,vy1); bx2=Math.max(bx2,vx2); by2=Math.max(by2,vy2);
const pad=140; bx-=pad; by-=pad; bx2+=pad; by2+=pad; const bw=bx2-bx, bh=by2-by;
const mmW=196, mmH=132; const scale=Math.min(mmW/bw, mmH/bh); const offX=(mmW-bw*scale)/2, offY=(mmH-bh*scale)/2;
this._mm={bx:bx-offX/scale, by:by-offY/scale, scale}; this._mmW=mmW; this._mmH=mmH;
const mapX=(x)=>(x-bx)*scale+offX, mapY=(y)=>(y-by)*scale+offY;
const rects=S.krates.map((k,i)=>{ const f=fps[i]; return {x:mapX(f.x), y:mapY(f.y), w:Math.max(6,f.w*scale), h:Math.max(5,f.h*scale), color:k.color, collapsed:!!k.collapsed}; });
minimap={ w:mmW, h:mmH, rects, vx:mapX(vx1), vy:mapY(vy1), vw:(vx2-vx1)*scale, vh:(vy2-vy1)*scale, onDown:(e)=>{ e.stopPropagation(); this.mmJump(e); }, setEl:(el)=>{ this._mmEl=el; } };
} else { this._mm=null; }
return {
cursor:S.dragging?'grabbing':(S.spacePan?'grab':'default'),
worldTransform:'translate('+S.camX+'px,'+S.camY+'px) scale('+S.zoom+')',
worldTransition:S.flying?'transform .52s cubic-bezier(.22,.8,.28,1)':'none',
accent:A.accent, accentGlow:A.glow, accentDim:A.dim,
frames, wins, cards, minis, minimap, roster, relMenu, krateCount:S.krates.length,
admin:S.admin, adminUsers, adminCount:adminUsers.length, onToggleAdmin:this.toggleAdmin,
adminBtnBg:S.admin?A.dim:'rgba(14,18,25,.82)', adminBtnBorder:S.admin?A.accent:'rgba(140,165,200,.18)', adminBtnFg:S.admin?A.accent:'#7e8aa2',
emptyCanvas: S.krates.length===0 && !S.spotOpen,
zoomLabel: (S.collapsed?'overview · ':'')+zoomPct+'%', zoomDotColor: S.collapsed?A.accent:'#4ad07a',
spotOpen:S.spotOpen, query:S.query, sel:S.sel,
hasFilter:!!S.filterType, filterLabel:fm?fm.label:'', filterColor:fm?fm.color:'#8aa0bd', filterClip:fsc?fsc.clip:'none', filterRadius:fsc?fsc.radius:'50%',
placeholder:S.filterType?('search '+fm.label.toLowerCase()+'s…'):'Search pods, services, secrets, CRDs…',
ghost: sug? this.meta(sug).label : '',
chips, results:resDisplay, noResults:S.query.trim()!=='' && resDisplay.length===0,
resultsHeader: S.query.trim()? 'results' : (S.filterType? (fm.label.toLowerCase()+'s') : 'top objects'),
enterLabel: S.session? 'place' : 'open default', openKeysLabel, sessionActive, buildingViews,
onSearchChange:this.onSearchChange, onSearchKeydown:this.onSearchKeydown, setInput:this.setInput, onSpotBackdrop:this.onSpotBackdrop,
onBgDown:this.onBgDown, onRootDownCapture:this.onRootDownCapture, onWheel:this.onWheel, setRoot:this.setRoot, stopDown:this.stopDown, winWheel:this.winWheel };
}
}
</script>
</body>
</html>

167
design/server.js Normal file
View File

@@ -0,0 +1,167 @@
const http = require('http');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const PORT = 5000;
const HTML_FILE = path.join(__dirname, 'Krates.dc.html');
const SUPPORT_FILE = path.join(__dirname, 'support.js');
const MIME_TYPES = {
'.html': 'text/html',
'.js': 'application/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.ico': 'image/x-icon',
'.wasm': 'application/wasm'
};
function getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return MIME_TYPES[ext] || 'application/octet-stream';
}
async function fetchClusterData() {
try {
const namespaces = JSON.parse(execSync('kubectl get namespaces -o json').toString());
const nodes = JSON.parse(execSync('kubectl get nodes -o json').toString());
const pods = JSON.parse(execSync('kubectl get pods --all-namespaces -o json').toString());
return {
namespaces: namespaces.items.map(ns => ({
name: ns.metadata.name,
status: ns.status.phase,
age: ns.metadata.creationTimestamp
})),
nodes: nodes.items.map(node => ({
name: node.metadata.name,
status: node.status.conditions.find(c => c.type === 'Ready').status,
role: Object.keys(node.metadata.labels || {}).filter(k => k.includes('node-role') || k === 'control-plane').join(', ') || 'worker',
version: node.status.nodeInfo.kubeletVersion
})),
podCount: pods.items.length,
podsByNamespace: {}
};
} catch (error) {
console.error('Error fetching cluster data:', error.message);
return null;
}
}
function createServer() {
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const filePath = url.pathname === '/' ? HTML_FILE : path.join(__dirname, url.pathname);
if (url.pathname === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', cluster: 'covert-gpt-cluster' }));
return;
}
if (url.pathname === '/api/cluster') {
const data = await fetchClusterData();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data || { error: 'Failed to fetch cluster data' }));
return;
}
if (url.pathname === '/api/pods') {
try {
const { execSync } = require('child_process');
const pods = JSON.parse(execSync('kubectl get pods --all-namespaces -o json', { maxBuffer: 1024 * 1024 * 10 }).toString());
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(pods));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
return;
}
if (url.pathname === '/api/namespaces') {
try {
const namespaces = JSON.parse(execSync('kubectl get namespaces -o json').toString());
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(namespaces));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
return;
}
if (url.pathname === '/api/nodes') {
try {
const nodes = JSON.parse(execSync('kubectl get nodes -o json').toString());
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(nodes));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
return;
}
if (url.pathname === '/api/pod/logs' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try {
const { namespace, pod, container } = JSON.parse(body);
let cmd = `kubectl logs ${pod} -n ${namespace}`;
if (container) cmd += ` -c ${container}`;
const logs = execSync(cmd).toString();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ logs }));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
});
return;
}
if (!filePath.startsWith(__dirname)) {
res.writeHead(404);
res.end('Not found');
return;
}
if (!fs.existsSync(filePath)) {
res.writeHead(404);
res.end('Not found');
return;
}
const mimeType = getMimeType(filePath);
const content = fs.readFileSync(filePath);
res.writeHead(200, { 'Content-Type': mimeType });
res.end(content);
});
return server;
}
async function start() {
console.log('Starting Krates server...');
const server = createServer();
server.listen(PORT, () => {
console.log(`Krates server running at http://localhost:${PORT}`);
console.log(`Health check: http://localhost:${PORT}/health`);
console.log(`Cluster API: http://localhost:${PORT}/api/cluster`);
});
server.on('error', (error) => {
console.error(`Server error: ${error.message}`);
process.exit(1);
});
}
start();

1464
design/support.js Normal file

File diff suppressed because it is too large Load Diff