@gbdx/devis 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28482,104 +28482,120 @@ const createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
28482
28482
  * This source code is licensed under the MIT license.
28483
28483
  * See the LICENSE file in the root directory of this source tree.
28484
28484
  */
28485
- const __iconNode$c = [["path", { "d": "M13 3l0 7l6 0l-8 11l0 -7l-6 0l8 -11", "key": "svg-0" }]];
28486
- const IconBolt = createReactComponent("outline", "bolt", "Bolt", __iconNode$c);
28485
+ const __iconNode$e = [["path", { "d": "M13 3l0 7l6 0l-8 11l0 -7l-6 0l8 -11", "key": "svg-0" }]];
28486
+ const IconBolt = createReactComponent("outline", "bolt", "Bolt", __iconNode$e);
28487
28487
  /**
28488
28488
  * @license @tabler/icons-react v3.44.0 - MIT
28489
28489
  *
28490
28490
  * This source code is licensed under the MIT license.
28491
28491
  * See the LICENSE file in the root directory of this source tree.
28492
28492
  */
28493
- const __iconNode$b = [["path", { "d": "M16 3v18l4 -2.5v-13l-4 -2.5", "key": "svg-0" }], ["path", { "d": "M9.165 13.903l-4.165 3.597l-2 -1l4.333 -4.5m1.735 -1.802l6.932 -7.198v5l-4.795 4.141", "key": "svg-1" }], ["path", { "d": "M16 16.5l-11 -10l-2 1l13 13.5", "key": "svg-2" }]];
28494
- const IconBrandVscode = createReactComponent("outline", "brand-vscode", "BrandVscode", __iconNode$b);
28493
+ const __iconNode$d = [["path", { "d": "M16 3v18l4 -2.5v-13l-4 -2.5", "key": "svg-0" }], ["path", { "d": "M9.165 13.903l-4.165 3.597l-2 -1l4.333 -4.5m1.735 -1.802l6.932 -7.198v5l-4.795 4.141", "key": "svg-1" }], ["path", { "d": "M16 16.5l-11 -10l-2 1l13 13.5", "key": "svg-2" }]];
28494
+ const IconBrandVscode = createReactComponent("outline", "brand-vscode", "BrandVscode", __iconNode$d);
28495
28495
  /**
28496
28496
  * @license @tabler/icons-react v3.44.0 - MIT
28497
28497
  *
28498
28498
  * This source code is licensed under the MIT license.
28499
28499
  * See the LICENSE file in the root directory of this source tree.
28500
28500
  */
28501
- const __iconNode$a = [["path", { "d": "M6 9l6 6l6 -6", "key": "svg-0" }]];
28502
- const IconChevronDown = createReactComponent("outline", "chevron-down", "ChevronDown", __iconNode$a);
28501
+ const __iconNode$c = [["path", { "d": "M6 9l6 6l6 -6", "key": "svg-0" }]];
28502
+ const IconChevronDown = createReactComponent("outline", "chevron-down", "ChevronDown", __iconNode$c);
28503
28503
  /**
28504
28504
  * @license @tabler/icons-react v3.44.0 - MIT
28505
28505
  *
28506
28506
  * This source code is licensed under the MIT license.
28507
28507
  * See the LICENSE file in the root directory of this source tree.
28508
28508
  */
28509
- const __iconNode$9 = [["path", { "d": "M14 3v4a1 1 0 0 0 1 1h4", "key": "svg-0" }], ["path", { "d": "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2", "key": "svg-1" }], ["path", { "d": "M9 9l1 0", "key": "svg-2" }], ["path", { "d": "M9 13l6 0", "key": "svg-3" }], ["path", { "d": "M9 17l6 0", "key": "svg-4" }]];
28510
- const IconFileText = createReactComponent("outline", "file-text", "FileText", __iconNode$9);
28509
+ const __iconNode$b = [["path", { "d": "M6 15l6 -6l6 6", "key": "svg-0" }]];
28510
+ const IconChevronUp = createReactComponent("outline", "chevron-up", "ChevronUp", __iconNode$b);
28511
28511
  /**
28512
28512
  * @license @tabler/icons-react v3.44.0 - MIT
28513
28513
  *
28514
28514
  * This source code is licensed under the MIT license.
28515
28515
  * See the LICENSE file in the root directory of this source tree.
28516
28516
  */
28517
- const __iconNode$8 = [["path", { "d": "M5 19l2.757 -7.351a1 1 0 0 1 .936 -.649h12.307a1 1 0 0 1 .986 1.164l-.996 5.211a2 2 0 0 1 -1.964 1.625h-14.026a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v2", "key": "svg-0" }]];
28518
- const IconFolderOpen = createReactComponent("outline", "folder-open", "FolderOpen", __iconNode$8);
28517
+ const __iconNode$a = [["path", { "d": "M14 3v4a1 1 0 0 0 1 1h4", "key": "svg-0" }], ["path", { "d": "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2", "key": "svg-1" }], ["path", { "d": "M9 9l1 0", "key": "svg-2" }], ["path", { "d": "M9 13l6 0", "key": "svg-3" }], ["path", { "d": "M9 17l6 0", "key": "svg-4" }]];
28518
+ const IconFileText = createReactComponent("outline", "file-text", "FileText", __iconNode$a);
28519
28519
  /**
28520
28520
  * @license @tabler/icons-react v3.44.0 - MIT
28521
28521
  *
28522
28522
  * This source code is licensed under the MIT license.
28523
28523
  * See the LICENSE file in the root directory of this source tree.
28524
28524
  */
28525
- const __iconNode$7 = [["path", { "d": "M12 19h-7a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v3.5", "key": "svg-0" }], ["path", { "d": "M16 19h6", "key": "svg-1" }], ["path", { "d": "M19 16v6", "key": "svg-2" }]];
28526
- const IconFolderPlus = createReactComponent("outline", "folder-plus", "FolderPlus", __iconNode$7);
28525
+ const __iconNode$9 = [["path", { "d": "M5 19l2.757 -7.351a1 1 0 0 1 .936 -.649h12.307a1 1 0 0 1 .986 1.164l-.996 5.211a2 2 0 0 1 -1.964 1.625h-14.026a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v2", "key": "svg-0" }]];
28526
+ const IconFolderOpen = createReactComponent("outline", "folder-open", "FolderOpen", __iconNode$9);
28527
28527
  /**
28528
28528
  * @license @tabler/icons-react v3.44.0 - MIT
28529
28529
  *
28530
28530
  * This source code is licensed under the MIT license.
28531
28531
  * See the LICENSE file in the root directory of this source tree.
28532
28532
  */
28533
- const __iconNode$6 = [["path", { "d": "M4 6l16 0", "key": "svg-0" }], ["path", { "d": "M4 12l16 0", "key": "svg-1" }], ["path", { "d": "M4 18l16 0", "key": "svg-2" }]];
28534
- const IconMenu2 = createReactComponent("outline", "menu-2", "Menu2", __iconNode$6);
28533
+ const __iconNode$8 = [["path", { "d": "M12 19h-7a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v3.5", "key": "svg-0" }], ["path", { "d": "M16 19h6", "key": "svg-1" }], ["path", { "d": "M19 16v6", "key": "svg-2" }]];
28534
+ const IconFolderPlus = createReactComponent("outline", "folder-plus", "FolderPlus", __iconNode$8);
28535
28535
  /**
28536
28536
  * @license @tabler/icons-react v3.44.0 - MIT
28537
28537
  *
28538
28538
  * This source code is licensed under the MIT license.
28539
28539
  * See the LICENSE file in the root directory of this source tree.
28540
28540
  */
28541
- const __iconNode$5 = [["path", { "d": "M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008", "key": "svg-0" }]];
28542
- const IconMoon = createReactComponent("outline", "moon", "Moon", __iconNode$5);
28541
+ const __iconNode$7 = [["path", { "d": "M4 6l16 0", "key": "svg-0" }], ["path", { "d": "M4 12l16 0", "key": "svg-1" }], ["path", { "d": "M4 18l16 0", "key": "svg-2" }]];
28542
+ const IconMenu2 = createReactComponent("outline", "menu-2", "Menu2", __iconNode$7);
28543
28543
  /**
28544
28544
  * @license @tabler/icons-react v3.44.0 - MIT
28545
28545
  *
28546
28546
  * This source code is licensed under the MIT license.
28547
28547
  * See the LICENSE file in the root directory of this source tree.
28548
28548
  */
28549
- const __iconNode$4 = [["path", { "d": "M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4", "key": "svg-0" }], ["path", { "d": "M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4", "key": "svg-1" }]];
28550
- const IconRefresh = createReactComponent("outline", "refresh", "Refresh", __iconNode$4);
28549
+ const __iconNode$6 = [["path", { "d": "M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008", "key": "svg-0" }]];
28550
+ const IconMoon = createReactComponent("outline", "moon", "Moon", __iconNode$6);
28551
28551
  /**
28552
28552
  * @license @tabler/icons-react v3.44.0 - MIT
28553
28553
  *
28554
28554
  * This source code is licensed under the MIT license.
28555
28555
  * See the LICENSE file in the root directory of this source tree.
28556
28556
  */
28557
- const __iconNode$3 = [["path", { "d": "M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0", "key": "svg-0" }], ["path", { "d": "M21 21l-6 -6", "key": "svg-1" }]];
28558
- const IconSearch = createReactComponent("outline", "search", "Search", __iconNode$3);
28557
+ const __iconNode$5 = [["path", { "d": "M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4", "key": "svg-0" }], ["path", { "d": "M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4", "key": "svg-1" }]];
28558
+ const IconRefresh = createReactComponent("outline", "refresh", "Refresh", __iconNode$5);
28559
28559
  /**
28560
28560
  * @license @tabler/icons-react v3.44.0 - MIT
28561
28561
  *
28562
28562
  * This source code is licensed under the MIT license.
28563
28563
  * See the LICENSE file in the root directory of this source tree.
28564
28564
  */
28565
- const __iconNode$2 = [["path", { "d": "M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0", "key": "svg-0" }], ["path", { "d": "M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7", "key": "svg-1" }]];
28566
- const IconSun = createReactComponent("outline", "sun", "Sun", __iconNode$2);
28565
+ const __iconNode$4 = [["path", { "d": "M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0", "key": "svg-0" }], ["path", { "d": "M21 21l-6 -6", "key": "svg-1" }]];
28566
+ const IconSearch = createReactComponent("outline", "search", "Search", __iconNode$4);
28567
28567
  /**
28568
28568
  * @license @tabler/icons-react v3.44.0 - MIT
28569
28569
  *
28570
28570
  * This source code is licensed under the MIT license.
28571
28571
  * See the LICENSE file in the root directory of this source tree.
28572
28572
  */
28573
- const __iconNode$1 = [["path", { "d": "M8 9l3 3l-3 3", "key": "svg-0" }], ["path", { "d": "M13 15l3 0", "key": "svg-1" }], ["path", { "d": "M3 6a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2l0 -12", "key": "svg-2" }]];
28574
- const IconTerminal2 = createReactComponent("outline", "terminal-2", "Terminal2", __iconNode$1);
28573
+ const __iconNode$3 = [["path", { "d": "M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0", "key": "svg-0" }], ["path", { "d": "M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7", "key": "svg-1" }]];
28574
+ const IconSun = createReactComponent("outline", "sun", "Sun", __iconNode$3);
28575
28575
  /**
28576
28576
  * @license @tabler/icons-react v3.44.0 - MIT
28577
28577
  *
28578
28578
  * This source code is licensed under the MIT license.
28579
28579
  * See the LICENSE file in the root directory of this source tree.
28580
28580
  */
28581
- const __iconNode = [["path", { "d": "M4 7l16 0", "key": "svg-0" }], ["path", { "d": "M10 11l0 6", "key": "svg-1" }], ["path", { "d": "M14 11l0 6", "key": "svg-2" }], ["path", { "d": "M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12", "key": "svg-3" }], ["path", { "d": "M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3", "key": "svg-4" }]];
28582
- const IconTrash = createReactComponent("outline", "trash", "Trash", __iconNode);
28581
+ const __iconNode$2 = [["path", { "d": "M8 9l3 3l-3 3", "key": "svg-0" }], ["path", { "d": "M13 15l3 0", "key": "svg-1" }], ["path", { "d": "M3 6a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2l0 -12", "key": "svg-2" }]];
28582
+ const IconTerminal2 = createReactComponent("outline", "terminal-2", "Terminal2", __iconNode$2);
28583
+ /**
28584
+ * @license @tabler/icons-react v3.44.0 - MIT
28585
+ *
28586
+ * This source code is licensed under the MIT license.
28587
+ * See the LICENSE file in the root directory of this source tree.
28588
+ */
28589
+ const __iconNode$1 = [["path", { "d": "M4 7l16 0", "key": "svg-0" }], ["path", { "d": "M10 11l0 6", "key": "svg-1" }], ["path", { "d": "M14 11l0 6", "key": "svg-2" }], ["path", { "d": "M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12", "key": "svg-3" }], ["path", { "d": "M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3", "key": "svg-4" }]];
28590
+ const IconTrash = createReactComponent("outline", "trash", "Trash", __iconNode$1);
28591
+ /**
28592
+ * @license @tabler/icons-react v3.44.0 - MIT
28593
+ *
28594
+ * This source code is licensed under the MIT license.
28595
+ * See the LICENSE file in the root directory of this source tree.
28596
+ */
28597
+ const __iconNode = [["path", { "d": "M18 6l-12 12", "key": "svg-0" }], ["path", { "d": "M6 6l12 12", "key": "svg-1" }]];
28598
+ const IconX = createReactComponent("outline", "x", "X", __iconNode);
28583
28599
  const header$2 = "_header_1cfdg_1";
28584
28600
  const headerInner = "_headerInner_1cfdg_6";
28585
28601
  const brand = "_brand_1cfdg_12";
@@ -29099,6 +29115,75 @@ async function fetchCaddyRouteCount() {
29099
29115
  return null;
29100
29116
  }
29101
29117
  }
29118
+ function collectCaddyHosts(match) {
29119
+ if (!Array.isArray(match)) return [];
29120
+ const hosts = [];
29121
+ for (const m of match) {
29122
+ const host = m == null ? void 0 : m.host;
29123
+ if (Array.isArray(host)) {
29124
+ for (const h of host) if (typeof h === "string") hosts.push(h);
29125
+ }
29126
+ }
29127
+ return [...new Set(hosts)];
29128
+ }
29129
+ function collectCaddyHandlers(handle) {
29130
+ const upstreams = [];
29131
+ const handlers = [];
29132
+ const walk = (arr) => {
29133
+ if (!Array.isArray(arr)) return;
29134
+ for (const h of arr) {
29135
+ if (!h || typeof h !== "object") continue;
29136
+ const obj = h;
29137
+ if (typeof obj.handler === "string") handlers.push(obj.handler);
29138
+ if (Array.isArray(obj.upstreams)) {
29139
+ for (const u of obj.upstreams) {
29140
+ const dial = u == null ? void 0 : u.dial;
29141
+ if (typeof dial === "string") upstreams.push(dial);
29142
+ }
29143
+ }
29144
+ if (Array.isArray(obj.routes)) {
29145
+ for (const r2 of obj.routes) walk(r2 == null ? void 0 : r2.handle);
29146
+ }
29147
+ }
29148
+ };
29149
+ walk(handle);
29150
+ return { upstreams: [...new Set(upstreams)], handlers: [...new Set(handlers)] };
29151
+ }
29152
+ function parseCaddyRoute(route) {
29153
+ const r2 = route ?? {};
29154
+ const id = typeof r2["@id"] === "string" ? r2["@id"] : null;
29155
+ let slug = null;
29156
+ let appName = null;
29157
+ if (id) {
29158
+ const parts = id.split(":");
29159
+ if (parts.length >= 3 && parts[1] === "route") {
29160
+ slug = parts[0];
29161
+ appName = parts.slice(2).join(":");
29162
+ }
29163
+ }
29164
+ const hosts = collectCaddyHosts(r2.match);
29165
+ const { upstreams, handlers } = collectCaddyHandlers(r2.handle);
29166
+ return { id, slug, appName, hosts, upstreams, handlers };
29167
+ }
29168
+ async function fetchCaddyRoutes() {
29169
+ var _a, _b;
29170
+ const body2 = await caddyGet("/config/");
29171
+ if (!body2) return [];
29172
+ let cfg;
29173
+ try {
29174
+ cfg = JSON.parse(body2);
29175
+ } catch {
29176
+ return [];
29177
+ }
29178
+ const servers = ((_b = (_a = cfg.apps) == null ? void 0 : _a.http) == null ? void 0 : _b.servers) ?? {};
29179
+ const out = [];
29180
+ for (const srv of Object.values(servers)) {
29181
+ if (Array.isArray(srv == null ? void 0 : srv.routes)) {
29182
+ for (const route of srv.routes) out.push(parseCaddyRoute(route));
29183
+ }
29184
+ }
29185
+ return out;
29186
+ }
29102
29187
  function caddyPaths() {
29103
29188
  const home = getGlobalContext().homeDir;
29104
29189
  return {
@@ -29339,6 +29424,75 @@ function statusLabel(info) {
29339
29424
  }
29340
29425
  function CaddyView({ caddy, onLogs, onChanged, openLogKey }) {
29341
29426
  var _a, _b;
29427
+ const [routesOpen, setRoutesOpen] = reactExports.useState(false);
29428
+ const [routes, setRoutes] = reactExports.useState(null);
29429
+ const [routesLoading, setRoutesLoading] = reactExports.useState(false);
29430
+ async function loadRoutes() {
29431
+ setRoutesLoading(true);
29432
+ try {
29433
+ setRoutes(await fetchCaddyRoutes());
29434
+ } catch (err) {
29435
+ notifications.show({ color: "red", message: String(err) });
29436
+ setRoutes([]);
29437
+ } finally {
29438
+ setRoutesLoading(false);
29439
+ }
29440
+ }
29441
+ function openRoutes() {
29442
+ setRoutesOpen(true);
29443
+ void loadRoutes();
29444
+ }
29445
+ function renderRoutesBody() {
29446
+ if (routesLoading && routes === null) {
29447
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { justify: "center", gap: "xs", p: "lg", children: [
29448
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Loader, { size: "sm" }),
29449
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "불러오는 중..." })
29450
+ ] });
29451
+ }
29452
+ const sorted = [...routes ?? []].sort((a, b) => {
29453
+ if (!!a.slug !== !!b.slug) return a.slug ? -1 : 1;
29454
+ const s = (a.slug ?? "").localeCompare(b.slug ?? "");
29455
+ return s !== 0 ? s : (a.appName ?? "").localeCompare(b.appName ?? "");
29456
+ });
29457
+ if (sorted.length === 0) {
29458
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", p: "md", children: "등록된 라우트가 없습니다." });
29459
+ }
29460
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { gap: "sm", children: [
29461
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { justify: "space-between", children: [
29462
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Text, { size: "sm", c: "dimmed", children: [
29463
+ "총 ",
29464
+ sorted.length,
29465
+ "개"
29466
+ ] }),
29467
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { size: "compact-xs", variant: "subtle", loading: routesLoading, onClick: () => void loadRoutes(), children: "새로고침" })
29468
+ ] }),
29469
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Table, { striped: true, highlightOnHover: true, horizontalSpacing: "md", verticalSpacing: "xs", children: [
29470
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Thead, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29471
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Th, { children: "워크스페이스" }),
29472
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Th, { children: "앱" }),
29473
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Th, { children: "호스트" }),
29474
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Th, { children: "업스트림" })
29475
+ ] }) }),
29476
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Tbody, { children: sorted.map((r2, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29477
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: r2.slug ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "light", color: "grape", children: r2.slug }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "xs", c: "dimmed", children: "(기타)" }) }),
29478
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", children: r2.appName ?? (r2.handlers.join(", ") || "-") }) }),
29479
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: r2.hosts.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "-" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Stack, { gap: 2, children: r2.hosts.map((h) => /* @__PURE__ */ jsxRuntimeExports.jsx(
29480
+ Anchor,
29481
+ {
29482
+ size: "sm",
29483
+ onClick: (e) => {
29484
+ e.preventDefault();
29485
+ void openExternal(`https://${h}`);
29486
+ },
29487
+ children: h
29488
+ },
29489
+ h
29490
+ )) }) }),
29491
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: r2.upstreams.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "-" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Stack, { gap: 2, children: r2.upstreams.map((u) => /* @__PURE__ */ jsxRuntimeExports.jsx(Code, { children: u }, u)) }) })
29492
+ ] }, r2.id ?? `route-${i}`)) })
29493
+ ] })
29494
+ ] });
29495
+ }
29342
29496
  async function act(action) {
29343
29497
  try {
29344
29498
  await caddyAction(action);
@@ -29403,73 +29557,100 @@ function CaddyView({ caddy, onLogs, onChanged, openLogKey }) {
29403
29557
  ] }) })
29404
29558
  ] });
29405
29559
  }
29406
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Stack, { gap: "xl", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SectionCard, { title: "Caddy (시스템 공유 HTTPS 리버스 프록시)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Table, { verticalSpacing: "sm", horizontalSpacing: "md", layout: "fixed", className: classes$6.table, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tbody, { children: [
29407
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29408
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { w: 200, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "상태" }) }),
29409
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 8, children: [
29410
- /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status, label }),
29411
- ((_a = caddy.proc) == null ? void 0 : _a.pid) ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Text, { size: "xs", c: "dimmed", children: [
29412
- "PID ",
29413
- caddy.proc.pid
29414
- ] }) : null,
29415
- ((_b = caddy.proc) == null ? void 0 : _b.restarts) ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { size: "xs", variant: "light", color: "gray", children: [
29416
- "재시작 ",
29417
- caddy.proc.restarts,
29418
- "회"
29419
- ] }) : null
29420
- ] }) })
29421
- ] }),
29422
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29423
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "Admin API" }) }),
29424
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 8, children: [
29425
- /* @__PURE__ */ jsxRuntimeExports.jsx(
29426
- Anchor,
29427
- {
29428
- size: "sm",
29429
- onClick: (e) => {
29430
- e.preventDefault();
29431
- void openExternal(`${caddy.adminUrl}/config/`);
29432
- },
29433
- children: caddy.adminUrl
29434
- }
29435
- ),
29436
- caddy.reachable ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { size: "xs", color: "teal", variant: "light", children: "응답" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { size: "xs", color: "gray", variant: "light", children: "미응답" })
29437
- ] }) })
29438
- ] }),
29439
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29440
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "등록 라우트" }) }),
29441
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: caddy.routeCount === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "-" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Code, { children: caddy.routeCount }) })
29442
- ] }),
29443
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29444
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "관리" }) }),
29445
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 6, children: [
29446
- /* @__PURE__ */ jsxRuntimeExports.jsx(
29447
- Button,
29448
- {
29449
- size: "compact-xs",
29450
- variant: "default",
29451
- onClick: () => onLogs({
29452
- streamKey: caddyStreamKey(),
29453
- title: "caddy (시스템)",
29454
- openStream: openCaddyLogStream,
29455
- actions: acts,
29456
- onClear: () => clearCaddyLogs(),
29457
- timestamped: false
29458
- }),
29459
- children: "로그"
29460
- }
29461
- ),
29462
- acts.map((a) => /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { size: "compact-xs", variant: "light", color: a.color, onClick: a.run, children: a.label }, a.label))
29463
- ] }) })
29464
- ] }),
29465
- pathRow("데이터 디렉터리", caddy.dataDir),
29466
- pathRow("부트스트랩 config", caddy.bootstrapFile, true),
29467
- pathRow("로그 파일", caddy.logFile, true),
29468
- caddy.error ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29469
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "에러" }) }),
29470
- /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "red", children: caddy.error }) })
29471
- ] }) : null
29472
- ] }) }) }) });
29560
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { gap: "xl", children: [
29561
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SectionCard, { title: "Caddy (시스템 공유 HTTPS 리버스 프록시)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Table, { verticalSpacing: "sm", horizontalSpacing: "md", layout: "fixed", className: classes$6.table, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tbody, { children: [
29562
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29563
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { w: 200, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "상태" }) }),
29564
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 8, children: [
29565
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status, label }),
29566
+ ((_a = caddy.proc) == null ? void 0 : _a.pid) ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Text, { size: "xs", c: "dimmed", children: [
29567
+ "PID ",
29568
+ caddy.proc.pid
29569
+ ] }) : null,
29570
+ ((_b = caddy.proc) == null ? void 0 : _b.restarts) ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { size: "xs", variant: "light", color: "gray", children: [
29571
+ "재시작 ",
29572
+ caddy.proc.restarts,
29573
+ "회"
29574
+ ] }) : null
29575
+ ] }) })
29576
+ ] }),
29577
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29578
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "Admin API" }) }),
29579
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 8, children: [
29580
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29581
+ Anchor,
29582
+ {
29583
+ size: "sm",
29584
+ onClick: (e) => {
29585
+ e.preventDefault();
29586
+ void openExternal(`${caddy.adminUrl}/config/`);
29587
+ },
29588
+ children: caddy.adminUrl
29589
+ }
29590
+ ),
29591
+ caddy.reachable ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { size: "xs", color: "teal", variant: "light", children: "응답" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { size: "xs", color: "gray", variant: "light", children: "미응답" })
29592
+ ] }) })
29593
+ ] }),
29594
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29595
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "등록 라우트" }) }),
29596
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: caddy.routeCount === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "-" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 6, wrap: "nowrap", children: [
29597
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Code, { children: caddy.routeCount }),
29598
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: "등록된 라우트 보기", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
29599
+ ActionIcon,
29600
+ {
29601
+ size: "sm",
29602
+ variant: "subtle",
29603
+ color: "gray",
29604
+ disabled: !caddy.routeCount,
29605
+ onClick: openRoutes,
29606
+ "aria-label": "등록된 라우트 보기",
29607
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconSearch, { size: 14 })
29608
+ }
29609
+ ) })
29610
+ ] }) })
29611
+ ] }),
29612
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29613
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "관리" }) }),
29614
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 6, children: [
29615
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29616
+ Button,
29617
+ {
29618
+ size: "compact-xs",
29619
+ variant: "default",
29620
+ onClick: () => onLogs({
29621
+ streamKey: caddyStreamKey(),
29622
+ title: "caddy (시스템)",
29623
+ openStream: openCaddyLogStream,
29624
+ actions: acts,
29625
+ onClear: () => clearCaddyLogs(),
29626
+ timestamped: false
29627
+ }),
29628
+ children: "로그"
29629
+ }
29630
+ ),
29631
+ acts.map((a) => /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { size: "compact-xs", variant: "light", color: a.color, onClick: a.run, children: a.label }, a.label))
29632
+ ] }) })
29633
+ ] }),
29634
+ pathRow("데이터 디렉터리", caddy.dataDir),
29635
+ pathRow("부트스트랩 config", caddy.bootstrapFile, true),
29636
+ pathRow("로그 파일", caddy.logFile, true),
29637
+ caddy.error ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Table.Tr, { children: [
29638
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "dimmed", children: "에러" }) }),
29639
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Table.Td, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "sm", c: "red", children: caddy.error }) })
29640
+ ] }) : null
29641
+ ] }) }) }),
29642
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29643
+ Modal,
29644
+ {
29645
+ opened: routesOpen,
29646
+ onClose: () => setRoutesOpen(false),
29647
+ title: "등록된 Caddy 라우트",
29648
+ size: "min(1000px, 90vw)",
29649
+ centered: true,
29650
+ children: renderRoutesBody()
29651
+ }
29652
+ )
29653
+ ] });
29473
29654
  }
29474
29655
  const bar = "_bar_8l5j4_5";
29475
29656
  const search = "_search_8l5j4_15";
@@ -31506,6 +31687,60 @@ function segStyle(seg) {
31506
31687
  textDecoration: d.includes("underline") ? "underline" : void 0
31507
31688
  };
31508
31689
  }
31690
+ const MATCH_STYLE = { background: "var(--mantine-color-yellow-4)", color: "#000" };
31691
+ const ACTIVE_MATCH_STYLE = { background: "var(--mantine-color-orange-5)", color: "#000" };
31692
+ function highlightSegments(entry, query, activeStart) {
31693
+ const lower = entry.text.toLowerCase();
31694
+ const ranges = [];
31695
+ let from = 0;
31696
+ while (query) {
31697
+ const idx = lower.indexOf(query, from);
31698
+ if (idx === -1) break;
31699
+ ranges.push([idx, idx + query.length]);
31700
+ from = idx + query.length;
31701
+ }
31702
+ if (ranges.length === 0) {
31703
+ return entry.segments.map((seg, i) => (
31704
+ // biome-ignore lint/suspicious/noArrayIndexKey: 파싱된 로그 줄은 불변
31705
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: segStyle(seg), children: seg.content }, i)
31706
+ ));
31707
+ }
31708
+ const nodes = [];
31709
+ let offset2 = 0;
31710
+ let key = 0;
31711
+ for (const seg of entry.segments) {
31712
+ const base = segStyle(seg);
31713
+ const text = seg.content;
31714
+ const segStart = offset2;
31715
+ const segEnd = offset2 + text.length;
31716
+ let pos = segStart;
31717
+ while (pos < segEnd) {
31718
+ const range = ranges.find(([, e]) => e > pos);
31719
+ if (!range || range[0] >= segEnd) {
31720
+ nodes.push(
31721
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: base, children: text.slice(pos - segStart) }, `s${key++}`)
31722
+ );
31723
+ break;
31724
+ }
31725
+ const [ms, me] = range;
31726
+ if (pos < ms) {
31727
+ nodes.push(
31728
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: base, children: text.slice(pos - segStart, ms - segStart) }, `s${key++}`)
31729
+ );
31730
+ pos = ms;
31731
+ } else {
31732
+ const end = Math.min(me, segEnd);
31733
+ const mark = ms === activeStart ? ACTIVE_MATCH_STYLE : MATCH_STYLE;
31734
+ nodes.push(
31735
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { ...base, ...mark }, children: text.slice(pos - segStart, end - segStart) }, `s${key++}`)
31736
+ );
31737
+ pos = end;
31738
+ }
31739
+ }
31740
+ offset2 = segEnd;
31741
+ }
31742
+ return nodes;
31743
+ }
31509
31744
  const ANSER_OPTS = { json: true, remove_empty: true, use_classes: false };
31510
31745
  const TS_PREFIX = /^\d{4}-\d{2}-\d{2}T\d{2}:/;
31511
31746
  function parseLine(raw, timestamped) {
@@ -31514,13 +31749,17 @@ function parseLine(raw, timestamped) {
31514
31749
  if (sp > 0) {
31515
31750
  const ts = raw.slice(0, sp);
31516
31751
  if (TS_PREFIX.test(ts)) {
31517
- return { timestamp: ts, segments: Anser.ansiToJson(raw.slice(sp + 1), ANSER_OPTS) };
31752
+ return makeEntry(raw.slice(sp + 1), ts);
31518
31753
  }
31519
31754
  }
31520
31755
  }
31521
- return { segments: Anser.ansiToJson(raw, ANSER_OPTS) };
31756
+ return makeEntry(raw);
31522
31757
  }
31523
- function LogStream({ openStream, clearSignal, timestamped, loadHistory }) {
31758
+ function makeEntry(content, timestamp) {
31759
+ const segments = Anser.ansiToJson(content, ANSER_OPTS);
31760
+ return { timestamp, segments, text: segments.map((s) => s.content).join("") };
31761
+ }
31762
+ const LogStream = reactExports.forwardRef(function LogStream2({ openStream, clearSignal, timestamped, loadHistory, query = "", onMatchState }, ref) {
31524
31763
  const [lines, setLines] = reactExports.useState([]);
31525
31764
  const [reachedStart, setReachedStart] = reactExports.useState(false);
31526
31765
  const [loadingHistory, setLoadingHistory] = reactExports.useState(false);
@@ -31572,12 +31811,59 @@ function LogStream({ openStream, clearSignal, timestamped, loadHistory }) {
31572
31811
  estimateSize: () => LINE_HEIGHT,
31573
31812
  overscan: 24
31574
31813
  });
31814
+ const q = query.trim().toLowerCase();
31815
+ const matches = reactExports.useMemo(() => {
31816
+ if (!q) return [];
31817
+ const out = [];
31818
+ for (let i = 0; i < lines.length; i++) {
31819
+ const lower = lines[i].text.toLowerCase();
31820
+ let pos = lower.indexOf(q);
31821
+ while (pos !== -1) {
31822
+ out.push({ line: i, start: pos });
31823
+ pos = lower.indexOf(q, pos + q.length);
31824
+ }
31825
+ }
31826
+ return out;
31827
+ }, [lines, q]);
31828
+ const matchLineSet = reactExports.useMemo(() => new Set(matches.map((m) => m.line)), [matches]);
31829
+ const matchesRef = reactExports.useRef(matches);
31830
+ matchesRef.current = matches;
31831
+ const [cursor, setCursor] = reactExports.useState(0);
31832
+ const total = matches.length;
31833
+ const safeCursor = total === 0 ? 0 : Math.min(cursor, total - 1);
31834
+ const activeMatch = total === 0 ? null : matches[safeCursor];
31835
+ const cursorRef = reactExports.useRef(safeCursor);
31836
+ cursorRef.current = safeCursor;
31837
+ reactExports.useEffect(() => {
31838
+ onMatchState == null ? void 0 : onMatchState({ current: total === 0 ? 0 : safeCursor + 1, total });
31839
+ }, [total, safeCursor, onMatchState]);
31840
+ reactExports.useEffect(() => {
31841
+ cursorRef.current = 0;
31842
+ setCursor(0);
31843
+ if (q && matchesRef.current.length > 0) {
31844
+ stick.current = false;
31845
+ virtualizer.scrollToIndex(matchesRef.current[0].line, { align: "center" });
31846
+ }
31847
+ }, [q]);
31848
+ const navigate = reactExports.useCallback(
31849
+ (dir) => {
31850
+ const items = matchesRef.current;
31851
+ if (items.length === 0) return;
31852
+ const nextC = (cursorRef.current + dir + items.length) % items.length;
31853
+ cursorRef.current = nextC;
31854
+ stick.current = false;
31855
+ setCursor(nextC);
31856
+ virtualizer.scrollToIndex(items[nextC].line, { align: "center" });
31857
+ },
31858
+ [virtualizer]
31859
+ );
31860
+ reactExports.useImperativeHandle(ref, () => ({ next: () => navigate(1), prev: () => navigate(-1) }), [navigate]);
31575
31861
  reactExports.useEffect(() => {
31576
31862
  if (restoreToIndex.current !== null) return;
31577
- if (stick.current && lines.length > 0) {
31863
+ if (!q && stick.current && lines.length > 0) {
31578
31864
  virtualizer.scrollToIndex(lines.length - 1, { align: "end" });
31579
31865
  }
31580
- }, [lines.length, virtualizer]);
31866
+ }, [lines.length, virtualizer, q]);
31581
31867
  reactExports.useEffect(() => {
31582
31868
  if (restoreToIndex.current === null) return;
31583
31869
  const idx = restoreToIndex.current;
@@ -31657,35 +31943,69 @@ function LogStream({ openStream, clearSignal, timestamped, loadHistory }) {
31657
31943
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "↑ 위로 스크롤하면 과거 로그를 불러옵니다" })
31658
31944
  }
31659
31945
  ) : null,
31660
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative" }, children: virtualizer.getVirtualItems().map((vi) => (
31661
- // data-index + measureElement ref 줄바꿈된 로그의 실제 높이를 측정해
31662
- // 가상 스크롤 위치를 맞춘다 (고정 높이로 두면 긴 줄이 겹쳐 보인다).
31663
- /* @__PURE__ */ jsxRuntimeExports.jsx(
31664
- "div",
31665
- {
31666
- "data-index": vi.index,
31667
- ref: virtualizer.measureElement,
31668
- style: {
31669
- position: "absolute",
31670
- top: 0,
31671
- left: 0,
31672
- width: "100%",
31673
- transform: `translateY(${vi.start}px)`,
31674
- whiteSpace: "pre-wrap",
31675
- wordBreak: "break-all",
31676
- padding: "0 12px"
31946
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative" }, children: virtualizer.getVirtualItems().map((vi) => {
31947
+ const activeStart = activeMatch && activeMatch.line === vi.index ? activeMatch.start : -1;
31948
+ return (
31949
+ // data-index + measureElement ref — 줄바꿈된 긴 로그의 실제 높이를 측정해
31950
+ // 가상 스크롤 위치를 맞춘다 (고정 높이로 두면 긴 줄이 겹쳐 보인다).
31951
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
31952
+ "div",
31953
+ {
31954
+ "data-index": vi.index,
31955
+ ref: virtualizer.measureElement,
31956
+ style: {
31957
+ position: "absolute",
31958
+ top: 0,
31959
+ left: 0,
31960
+ width: "100%",
31961
+ transform: `translateY(${vi.start}px)`,
31962
+ whiteSpace: "pre-wrap",
31963
+ wordBreak: "break-all",
31964
+ padding: "0 12px",
31965
+ background: activeStart !== -1 ? "var(--mantine-color-yellow-light)" : void 0
31966
+ },
31967
+ children: q && matchLineSet.has(vi.index) ? highlightSegments(lines[vi.index], q, activeStart) : lines[vi.index].segments.map((seg, i) => (
31968
+ // biome-ignore lint/suspicious/noArrayIndexKey: 파싱된 로그 줄은 불변이라 세그먼트 순서가 바뀌지 않는다
31969
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: segStyle(seg), children: seg.content }, i)
31970
+ ))
31677
31971
  },
31678
- children: lines[vi.index].segments.map((seg, i) => (
31679
- // biome-ignore lint/suspicious/noArrayIndexKey: 파싱된 로그 줄은 불변이라 세그먼트 순서가 바뀌지 않는다
31680
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: segStyle(seg), children: seg.content }, i)
31681
- ))
31682
- },
31683
- vi.key
31684
- )
31685
- )) })
31972
+ vi.key
31973
+ )
31974
+ );
31975
+ }) })
31686
31976
  ]
31687
31977
  }
31688
31978
  );
31979
+ });
31980
+ function LogSearchControls({ query, onQueryChange, match, onPrev, onNext }) {
31981
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
31982
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
31983
+ TextInput,
31984
+ {
31985
+ size: "xs",
31986
+ w: 180,
31987
+ placeholder: "로그 찾기",
31988
+ leftSection: /* @__PURE__ */ jsxRuntimeExports.jsx(IconSearch, { size: 14 }),
31989
+ value: query,
31990
+ onChange: (e) => onQueryChange(e.currentTarget.value),
31991
+ onKeyDown: (e) => {
31992
+ if (e.key === "Enter") {
31993
+ e.preventDefault();
31994
+ if (e.shiftKey) onPrev();
31995
+ else onNext();
31996
+ } else if (e.key === "Escape") {
31997
+ onQueryChange("");
31998
+ }
31999
+ },
32000
+ rightSection: query ? /* @__PURE__ */ jsxRuntimeExports.jsx(ActionIcon, { size: "sm", variant: "subtle", color: "gray", onClick: () => onQueryChange(""), "aria-label": "검색어 지우기", children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconX, { size: 14 }) }) : null
32001
+ }
32002
+ ),
32003
+ query ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: 4, wrap: "nowrap", children: [
32004
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Text, { size: "xs", c: "dimmed", style: { fontVariantNumeric: "tabular-nums", minWidth: 36, textAlign: "right" }, children: match.total ? `${match.current}/${match.total}` : "0/0" }),
32005
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ActionIcon, { size: "sm", variant: "default", disabled: !match.total, onClick: onPrev, "aria-label": "이전 매치", children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconChevronUp, { size: 14 }) }),
32006
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ActionIcon, { size: "sm", variant: "default", disabled: !match.total, onClick: onNext, "aria-label": "다음 매치", children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconChevronDown, { size: 14 }) })
32007
+ ] }) : null
32008
+ ] });
31689
32009
  }
31690
32010
  const ROOT_FONT_PX = 16;
31691
32011
  const MIN_HEIGHT_PX = 15 * ROOT_FONT_PX;
@@ -31706,6 +32026,9 @@ function writeStoredHeight(px2) {
31706
32026
  function LogDrawer({ target, onClose }) {
31707
32027
  var _a;
31708
32028
  const [clearSignal, setClearSignal] = reactExports.useState(0);
32029
+ const [query, setQuery] = reactExports.useState("");
32030
+ const [match, setMatch] = reactExports.useState({ current: 0, total: 0 });
32031
+ const streamRef = reactExports.useRef(null);
31709
32032
  const [height, setHeight] = reactExports.useState(() => {
31710
32033
  if (typeof window === "undefined") return 480;
31711
32034
  const stored = readStoredHeight();
@@ -31723,6 +32046,9 @@ function LogDrawer({ target, onClose }) {
31723
32046
  if (resizing) return;
31724
32047
  writeStoredHeight(height);
31725
32048
  }, [resizing, height]);
32049
+ reactExports.useEffect(() => {
32050
+ setQuery("");
32051
+ }, [target == null ? void 0 : target.streamKey]);
31726
32052
  async function handleClear() {
31727
32053
  var _a2;
31728
32054
  setClearSignal((n) => n + 1);
@@ -31778,6 +32104,22 @@ function LogDrawer({ target, onClose }) {
31778
32104
  (_a = target == null ? void 0 : target.actions) == null ? void 0 : _a.map((a) => /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { size: "compact-xs", variant: "light", color: a.color, onClick: a.run, children: a.label }, a.label))
31779
32105
  ] }) }),
31780
32106
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { gap: "xs", children: [
32107
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32108
+ LogSearchControls,
32109
+ {
32110
+ query,
32111
+ onQueryChange: setQuery,
32112
+ match,
32113
+ onPrev: () => {
32114
+ var _a2;
32115
+ return (_a2 = streamRef.current) == null ? void 0 : _a2.prev();
32116
+ },
32117
+ onNext: () => {
32118
+ var _a2;
32119
+ return (_a2 = streamRef.current) == null ? void 0 : _a2.next();
32120
+ }
32121
+ }
32122
+ ),
31781
32123
  /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { size: "compact-xs", variant: "default", onClick: () => void handleClear(), children: "Clear" }),
31782
32124
  /* @__PURE__ */ jsxRuntimeExports.jsx(Drawer.CloseButton, {})
31783
32125
  ] })
@@ -31785,10 +32127,13 @@ function LogDrawer({ target, onClose }) {
31785
32127
  /* @__PURE__ */ jsxRuntimeExports.jsx(Drawer.Body, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: classes$2.streamContainer, style: { height: `${height - 80}px` }, children: target !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
31786
32128
  LogStream,
31787
32129
  {
32130
+ ref: streamRef,
31788
32131
  openStream: target.openStream,
31789
32132
  clearSignal,
31790
32133
  timestamped: target.timestamped,
31791
- loadHistory: target.loadHistory
32134
+ loadHistory: target.loadHistory,
32135
+ query,
32136
+ onMatchState: setMatch
31792
32137
  },
31793
32138
  target.streamKey
31794
32139
  ) }) })
@@ -32086,17 +32431,27 @@ function ProjectDetail({ opened, entry, workspaceRoot, initialSelection, procAct
32086
32431
  var _a, _b;
32087
32432
  const [selection, setSelection] = reactExports.useState(initialSelection ?? "log");
32088
32433
  const [, force] = reactExports.useReducer((x) => x + 1, 0);
32434
+ const [query, setQuery] = reactExports.useState("");
32435
+ const [match, setMatch] = reactExports.useState({ current: 0, total: 0 });
32436
+ const streamRef = reactExports.useRef(null);
32089
32437
  reactExports.useEffect(() => {
32090
- if (opened) setSelection(initialSelection ?? "log");
32438
+ if (opened) {
32439
+ setSelection(initialSelection ?? "log");
32440
+ setQuery("");
32441
+ }
32091
32442
  }, [opened, initialSelection]);
32092
32443
  reactExports.useEffect(() => {
32093
32444
  return subscribe(() => force());
32094
32445
  }, []);
32446
+ reactExports.useEffect(() => {
32447
+ setQuery("");
32448
+ }, [selection]);
32095
32449
  if (!entry) return null;
32096
32450
  const procName = (_a = entry.proc) == null ? void 0 : _a.name;
32097
32451
  const procDisplayName = (_b = entry.proc) == null ? void 0 : _b.displayName;
32098
32452
  const projectKey = procDisplayName ?? entry.name.split("/").pop() ?? entry.name;
32099
32453
  const processes = listProcesses(projectKey);
32454
+ const streamShown = selection === "log" ? Boolean(procName) : Boolean(getProcess(selection));
32100
32455
  function handleSelectScript(scriptName) {
32101
32456
  if (!(entry == null ? void 0 : entry.dir)) return;
32102
32457
  const id = startScript(
@@ -32125,9 +32480,12 @@ function ProjectDetail({ opened, entry, workspaceRoot, initialSelection, procAct
32125
32480
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
32126
32481
  LogStream,
32127
32482
  {
32483
+ ref: streamRef,
32128
32484
  openStream: () => openPm2LogStream(procName),
32129
32485
  clearSignal: 0,
32130
- timestamped: false
32486
+ timestamped: false,
32487
+ query,
32488
+ onMatchState: setMatch
32131
32489
  },
32132
32490
  "pm2-log"
32133
32491
  );
@@ -32139,9 +32497,12 @@ function ProjectDetail({ opened, entry, workspaceRoot, initialSelection, procAct
32139
32497
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
32140
32498
  LogStream,
32141
32499
  {
32500
+ ref: streamRef,
32142
32501
  openStream: () => openScriptStream(proc.id),
32143
32502
  clearSignal: 0,
32144
- timestamped: false
32503
+ timestamped: false,
32504
+ query,
32505
+ onMatchState: setMatch
32145
32506
  },
32146
32507
  `script-${proc.id}`
32147
32508
  );
@@ -32221,7 +32582,35 @@ function ProjectDetail({ opened, entry, workspaceRoot, initialSelection, procAct
32221
32582
  ]
32222
32583
  }
32223
32584
  ),
32224
- /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { style: { flex: "1 1 auto", minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" }, children: renderContent() })
32585
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { style: { flex: "1 1 auto", minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
32586
+ streamShown ? /* @__PURE__ */ jsxRuntimeExports.jsx(
32587
+ Group,
32588
+ {
32589
+ justify: "flex-end",
32590
+ gap: "xs",
32591
+ px: "xs",
32592
+ py: 6,
32593
+ style: { flex: "0 0 auto", borderBottom: "1px solid var(--mantine-color-default-border)" },
32594
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32595
+ LogSearchControls,
32596
+ {
32597
+ query,
32598
+ onQueryChange: setQuery,
32599
+ match,
32600
+ onPrev: () => {
32601
+ var _a2;
32602
+ return (_a2 = streamRef.current) == null ? void 0 : _a2.prev();
32603
+ },
32604
+ onNext: () => {
32605
+ var _a2;
32606
+ return (_a2 = streamRef.current) == null ? void 0 : _a2.next();
32607
+ }
32608
+ }
32609
+ )
32610
+ }
32611
+ ) : null,
32612
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { style: { flex: "1 1 auto", minHeight: 0 }, children: renderContent() })
32613
+ ] })
32225
32614
  ] })
32226
32615
  }
32227
32616
  );
@@ -33167,6 +33556,13 @@ function isEditable(el) {
33167
33556
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") return true;
33168
33557
  return el.isContentEditable;
33169
33558
  }
33559
+ function setNativeValue(el, value) {
33560
+ var _a;
33561
+ const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
33562
+ const setter = (_a = Object.getOwnPropertyDescriptor(proto, "value")) == null ? void 0 : _a.set;
33563
+ if (setter) setter.call(el, value);
33564
+ else el.value = value;
33565
+ }
33170
33566
  async function doCopy() {
33171
33567
  var _a;
33172
33568
  const text = (_a = window.getSelection()) == null ? void 0 : _a.toString();
@@ -33188,7 +33584,7 @@ async function doCut(el) {
33188
33584
  } catch {
33189
33585
  return false;
33190
33586
  }
33191
- el.value = el.value.slice(0, start) + el.value.slice(end);
33587
+ setNativeValue(el, el.value.slice(0, start) + el.value.slice(end));
33192
33588
  el.selectionStart = el.selectionEnd = start;
33193
33589
  el.dispatchEvent(new Event("input", { bubbles: true }));
33194
33590
  return true;
@@ -33202,7 +33598,7 @@ async function doPaste(el) {
33202
33598
  }
33203
33599
  const start = el.selectionStart ?? el.value.length;
33204
33600
  const end = el.selectionEnd ?? el.value.length;
33205
- el.value = el.value.slice(0, start) + text + el.value.slice(end);
33601
+ setNativeValue(el, el.value.slice(0, start) + text + el.value.slice(end));
33206
33602
  el.selectionStart = el.selectionEnd = start + text.length;
33207
33603
  el.dispatchEvent(new Event("input", { bubbles: true }));
33208
33604
  return true;
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Devis</title>
7
- <script type="module" crossorigin src="./assets/index-B3EEPtRc.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-0_fn9uMh.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-BDxL9RBN.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gbdx/devis",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Local development dashboard for pnpm monorepos — workspace scanning, process management, HTTPS proxy, Docker, and more.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "gibigspub@gmail.com",