From 380b413b7008398ae47f24e767c376fe7772e558 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Thu, 13 Apr 2023 19:15:35 -0400 Subject: [PATCH] feat: cross-job ("top") dashboard update to ink4 --- package-lock.json | 814 +++++++++--------- .../plugin-codeflare-dashboard/package.json | 3 +- .../components/{Dashboard => Job}/Grid.tsx | 0 .../{Dashboard => Job}/Timeline.tsx | 7 +- .../components/{Dashboard => Job}/index.tsx | 0 .../components/{Dashboard => Job}/stats.ts | 0 .../components/{Dashboard => Job}/types.ts | 0 .../src/components/Top/defaults.ts | 25 + .../src/components/Top/index.tsx | 353 ++++++++ .../src/components/Top/types.ts | 52 ++ .../src/controller/dashboard/db.tsx | 8 +- .../src/controller/dashboard/generic/Demo.ts | 2 +- .../src/controller/dashboard/history.ts | 2 +- .../controller/dashboard/{index.ts => job.ts} | 23 +- .../src/controller/dashboard/options.ts | 22 +- .../src/controller/dashboard/status/Demo.ts | 2 +- .../src/controller/dashboard/status/Live.ts | 2 +- .../src/controller/dashboard/status/index.ts | 2 +- .../src/controller/dashboard/term.ts | 20 + .../src/controller/dashboard/top.ts | 316 +++++++ .../controller/dashboard/utilization/Demo.ts | 2 +- .../controller/dashboard/utilization/Live.ts | 2 +- .../controller/dashboard/utilization/index.ts | 2 +- .../src/controller/dump.ts | 2 +- .../plugin-codeflare-dashboard/src/plugin.ts | 20 +- .../plugin-codeflare-dashboard/tsconfig.json | 4 +- 26 files changed, 1236 insertions(+), 449 deletions(-) rename plugins/plugin-codeflare-dashboard/src/components/{Dashboard => Job}/Grid.tsx (100%) rename plugins/plugin-codeflare-dashboard/src/components/{Dashboard => Job}/Timeline.tsx (97%) rename plugins/plugin-codeflare-dashboard/src/components/{Dashboard => Job}/index.tsx (100%) rename plugins/plugin-codeflare-dashboard/src/components/{Dashboard => Job}/stats.ts (100%) rename plugins/plugin-codeflare-dashboard/src/components/{Dashboard => Job}/types.ts (100%) create mode 100644 plugins/plugin-codeflare-dashboard/src/components/Top/defaults.ts create mode 100644 plugins/plugin-codeflare-dashboard/src/components/Top/index.tsx create mode 100644 plugins/plugin-codeflare-dashboard/src/components/Top/types.ts rename plugins/plugin-codeflare-dashboard/src/controller/dashboard/{index.ts => job.ts} (90%) create mode 100644 plugins/plugin-codeflare-dashboard/src/controller/dashboard/term.ts create mode 100644 plugins/plugin-codeflare-dashboard/src/controller/dashboard/top.ts diff --git a/package-lock.json b/package-lock.json index 3c268e73..1d4eea06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1663,11 +1663,6 @@ "@types/node": "*" } }, - "node_modules/@types/yoga-layout": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", - "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.58.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", @@ -2237,6 +2232,7 @@ }, "node_modules/ansi-escapes": { "version": "4.3.2", + "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.21.3" @@ -2369,6 +2365,7 @@ }, "node_modules/astral-regex": { "version": "2.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2384,6 +2381,17 @@ "node": ">=4" } }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "dev": true, @@ -2940,6 +2948,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2955,6 +2964,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3037,7 +3047,6 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, "funding": [ { "type": "github", @@ -3079,8 +3088,20 @@ "node": ">=6" } }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", + "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -3101,7 +3122,6 @@ }, "node_modules/cli-truncate": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", @@ -3180,6 +3200,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3395,6 +3426,14 @@ "node": ">= 0.6" } }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/cookie": { "version": "0.5.0", "license": "MIT", @@ -4400,7 +4439,6 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "dev": true, "license": "MIT" }, "node_modules/ee-first": { @@ -4457,7 +4495,6 @@ }, "node_modules/emoji-regex": { "version": "9.2.2", - "dev": true, "license": "MIT" }, "node_modules/emojis-list": { @@ -6537,6 +6574,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, "node_modules/is-core-module": { "version": "2.11.0", "license": "MIT", @@ -6577,7 +6625,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -9354,6 +9401,14 @@ "tslib": "^2.0.3" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "dev": true, @@ -10402,6 +10457,7 @@ "version": "4.27.4", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.27.4.tgz", "integrity": "sha512-dvZjrAJjahd6NNl7dDwEk5TyHsWJxDpYL7VnD9jdEr98EEEsVhw9G8JDX54Nrb3XIIOBlJDpjo3AuBuychX9zg==", + "devOptional": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -10485,6 +10541,21 @@ "inline-style-parser": "0.1.1" } }, + "node_modules/react-reconciler": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz", + "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, "node_modules/readable-stream": { "version": "3.6.0", "license": "MIT", @@ -10774,6 +10845,7 @@ }, "node_modules/restore-cursor": { "version": "3.1.0", + "dev": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -10785,6 +10857,7 @@ }, "node_modules/restore-cursor/node_modules/mimic-fn": { "version": "2.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10792,6 +10865,7 @@ }, "node_modules/restore-cursor/node_modules/onetime": { "version": "5.1.2", + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -11419,6 +11493,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", + "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11467,7 +11542,6 @@ }, "node_modules/slice-ansi": { "version": "5.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", @@ -11482,7 +11556,6 @@ }, "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "6.2.1", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11776,7 +11849,6 @@ }, "node_modules/string-width": { "version": "5.1.2", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -11792,7 +11864,6 @@ }, "node_modules/string-width/node_modules/strip-ansi": { "version": "7.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -12391,6 +12462,7 @@ }, "node_modules/type-fest": { "version": "0.21.3", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -13438,6 +13510,20 @@ "node": ">=8" } }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wildcard": { "version": "2.0.0", "dev": true, @@ -13509,6 +13595,7 @@ }, "node_modules/ws": { "version": "7.5.9", + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.3.0" @@ -13645,16 +13732,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoga-layout-prebuilt": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz", - "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==", - "dependencies": { - "@types/yoga-layout": "1.9.2" - }, - "engines": { - "node": ">=8" - } + "node_modules/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==" }, "node_modules/zip-webpack-plugin": { "version": "4.0.1", @@ -13715,8 +13796,9 @@ "dependencies": { "@logdna/tail-file": "^3.0.1", "chokidar": "^3.5.3", - "ink": "^3.2.0", + "ink": "^4.1.0", "madwizard": "^9.0.3", + "pretty-bytes": "^6.1.0", "pretty-ms": "^8.0.0", "strip-ansi": "^7.0.1" }, @@ -13725,222 +13807,173 @@ "react-devtools-core": "^4.27.4" } }, - "plugins/plugin-codeflare-dashboard/node_modules/auto-bind": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", - "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", + "plugins/plugin-codeflare-dashboard/node_modules/ansi-escapes": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.1.0.tgz", + "integrity": "sha512-bQyg9bzRntwR/8b89DOEhGwctcwCrbWW/TuqTQnpqpy5Fz3aovcOTj5i8NJV6AHc8OGNdMaqdxAWww8pz2kiKg==", + "dependencies": { + "type-fest": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "plugins/plugin-codeflare-dashboard/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "plugins/plugin-codeflare-dashboard/node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "plugins/plugin-codeflare-dashboard/node_modules/ansi-escapes/node_modules/type-fest": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.8.0.tgz", + "integrity": "sha512-FVNSzGQz9Th+/9R6Lvv7WIAkstylfHN2/JYxkyhhmKFYh9At2DST8t6L6Lref9eYO8PXFTfG9Sg1Agg0K3vq3Q==", "engines": { - "node": ">=6" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "plugins/plugin-codeflare-dashboard/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "plugins/plugin-codeflare-dashboard/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "plugins/plugin-codeflare-dashboard/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "engines": { - "node": ">=8" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "plugins/plugin-codeflare-dashboard/node_modules/code-excerpt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-3.0.0.tgz", - "integrity": "sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw==", + "plugins/plugin-codeflare-dashboard/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dependencies": { - "convert-to-spaces": "^1.0.1" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "plugins/plugin-codeflare-dashboard/node_modules/convert-to-spaces": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz", - "integrity": "sha512-cj09EBuObp9gZNQCzc7hByQyrs6jVGE+o9kSJmeUoj+GiPiJvi5LYqEH/Hmme4+MTLHM+Ejtq+FChpjjEnsPdQ==", + "plugins/plugin-codeflare-dashboard/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "engines": { - "node": ">= 4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "plugins/plugin-codeflare-dashboard/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, "plugins/plugin-codeflare-dashboard/node_modules/ink": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", - "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-4.1.0.tgz", + "integrity": "sha512-cMGAHZN7aKSO3ZpOrIVE3nO95quV/Jx8MwO3TC5SR3lDAg+i16MCqKtzACnfd4mDYRN6AiZHh6eytrcxGMGsgA==", "dependencies": { - "ansi-escapes": "^4.2.1", - "auto-bind": "4.0.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.0", - "cli-cursor": "^3.1.0", - "cli-truncate": "^2.1.0", - "code-excerpt": "^3.0.0", - "indent-string": "^4.0.0", - "is-ci": "^2.0.0", - "lodash": "^4.17.20", - "patch-console": "^1.0.0", - "react-devtools-core": "^4.19.1", - "react-reconciler": "^0.26.2", - "scheduler": "^0.20.2", - "signal-exit": "^3.0.2", - "slice-ansi": "^3.0.0", - "stack-utils": "^2.0.2", - "string-width": "^4.2.2", + "ansi-escapes": "^6.0.0", + "auto-bind": "^5.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^3.1.0", + "code-excerpt": "^4.0.0", + "indent-string": "^5.0.0", + "is-ci": "^3.0.1", + "lodash": "^4.17.21", + "patch-console": "^2.0.0", + "react-reconciler": "^0.29.0", + "scheduler": "^0.23.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^6.0.0", + "stack-utils": "^2.0.6", + "string-width": "^5.1.2", "type-fest": "^0.12.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^6.2.0", - "ws": "^7.5.5", - "yoga-layout-prebuilt": "^1.9.6" + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0", + "ws": "^8.12.0", + "yoga-wasm-web": "~0.3.3" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "peerDependencies": { - "@types/react": ">=16.8.0", - "react": ">=16.8.0" + "@types/react": ">=18.0.0", + "react": ">=18.0.0", + "react-devtools-core": "^4.19.1" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "react-devtools-core": { + "optional": true } } }, - "plugins/plugin-codeflare-dashboard/node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "plugins/plugin-codeflare-dashboard/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "plugins/plugin-codeflare-dashboard/node_modules/patch-console": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-1.0.0.tgz", - "integrity": "sha512-nxl9nrnLQmh64iTzMfyylSlRozL7kAXIaxw1fVcLYdyhNkJCRUzirRZTikXGJsg+hc4fqpneTK6iU2H1Q8THSA==", + "plugins/plugin-codeflare-dashboard/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "engines": { - "node": ">=10" + "node": ">=6" } }, - "plugins/plugin-codeflare-dashboard/node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "peer": true, + "plugins/plugin-codeflare-dashboard/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "plugins/plugin-codeflare-dashboard/node_modules/react-reconciler": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", - "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "plugins/plugin-codeflare-dashboard/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependencies": { - "react": "^17.0.2" - } - }, - "plugins/plugin-codeflare-dashboard/node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "plugins/plugin-codeflare-dashboard/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "plugins/plugin-codeflare-dashboard/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-6.0.0.tgz", + "integrity": "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA==", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" }, "engines": { - "node": ">=8" - } - }, - "plugins/plugin-codeflare-dashboard/node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "plugins/plugin-codeflare-dashboard/node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=14.16" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "plugins/plugin-codeflare-dashboard/node_modules/strip-ansi": { @@ -13968,47 +14001,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "plugins/plugin-codeflare-dashboard/node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "plugins/plugin-codeflare-dashboard/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "plugins/plugin-codeflare-dashboard/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "plugins/plugin-codeflare-dashboard/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "engines": { - "node": ">=8" - } - }, - "plugins/plugin-codeflare-dashboard/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=10.0.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "plugins/plugin-codeflare/node_modules/define-lazy-prop": { @@ -14658,165 +14684,110 @@ "@logdna/tail-file": "^3.0.1", "@types/split2": "^3.2.1", "chokidar": "^3.5.3", - "ink": "^3.2.0", + "ink": "^4.1.0", "madwizard": "^9.0.3", + "pretty-bytes": "^6.1.0", "pretty-ms": "^8.0.0", "react-devtools-core": "^4.27.4", "strip-ansi": "^7.0.1" }, "dependencies": { - "auto-bind": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", - "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==" - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "ansi-escapes": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.1.0.tgz", + "integrity": "sha512-bQyg9bzRntwR/8b89DOEhGwctcwCrbWW/TuqTQnpqpy5Fz3aovcOTj5i8NJV6AHc8OGNdMaqdxAWww8pz2kiKg==", "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "type-fest": "^3.0.0" + }, + "dependencies": { + "type-fest": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.8.0.tgz", + "integrity": "sha512-FVNSzGQz9Th+/9R6Lvv7WIAkstylfHN2/JYxkyhhmKFYh9At2DST8t6L6Lref9eYO8PXFTfG9Sg1Agg0K3vq3Q==" + } } }, - "code-excerpt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-3.0.0.tgz", - "integrity": "sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw==", + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==" + }, + "cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "requires": { - "convert-to-spaces": "^1.0.1" + "restore-cursor": "^4.0.0" } }, - "convert-to-spaces": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz", - "integrity": "sha512-cj09EBuObp9gZNQCzc7hByQyrs6jVGE+o9kSJmeUoj+GiPiJvi5LYqEH/Hmme4+MTLHM+Ejtq+FChpjjEnsPdQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==" }, "ink": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", - "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-4.1.0.tgz", + "integrity": "sha512-cMGAHZN7aKSO3ZpOrIVE3nO95quV/Jx8MwO3TC5SR3lDAg+i16MCqKtzACnfd4mDYRN6AiZHh6eytrcxGMGsgA==", "requires": { - "ansi-escapes": "^4.2.1", - "auto-bind": "4.0.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.0", - "cli-cursor": "^3.1.0", - "cli-truncate": "^2.1.0", - "code-excerpt": "^3.0.0", - "indent-string": "^4.0.0", - "is-ci": "^2.0.0", - "lodash": "^4.17.20", - "patch-console": "^1.0.0", - "react-devtools-core": "^4.19.1", - "react-reconciler": "^0.26.2", - "scheduler": "^0.20.2", - "signal-exit": "^3.0.2", - "slice-ansi": "^3.0.0", - "stack-utils": "^2.0.2", - "string-width": "^4.2.2", + "ansi-escapes": "^6.0.0", + "auto-bind": "^5.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^3.1.0", + "code-excerpt": "^4.0.0", + "indent-string": "^5.0.0", + "is-ci": "^3.0.1", + "lodash": "^4.17.21", + "patch-console": "^2.0.0", + "react-reconciler": "^0.29.0", + "scheduler": "^0.23.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^6.0.0", + "stack-utils": "^2.0.6", + "string-width": "^5.1.2", "type-fest": "^0.12.0", - "widest-line": "^3.1.0", - "wrap-ansi": "^6.2.0", - "ws": "^7.5.5", - "yoga-layout-prebuilt": "^1.9.6" - } - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0", + "ws": "^8.12.0", + "yoga-wasm-web": "~0.3.3" } }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "patch-console": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-1.0.0.tgz", - "integrity": "sha512-nxl9nrnLQmh64iTzMfyylSlRozL7kAXIaxw1fVcLYdyhNkJCRUzirRZTikXGJsg+hc4fqpneTK6iU2H1Q8THSA==" - }, - "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, - "react-reconciler": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", - "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "mimic-fn": "^2.1.0" } }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" } }, "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-6.0.0.tgz", + "integrity": "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA==", "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" } }, "strip-ansi": { @@ -14832,38 +14803,21 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "requires": { - "string-width": "^4.0.0" - } - }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "requires": {} } } }, @@ -15545,11 +15499,6 @@ "@types/node": "*" } }, - "@types/yoga-layout": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", - "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==" - }, "@typescript-eslint/eslint-plugin": { "version": "5.58.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", @@ -15915,6 +15864,7 @@ }, "ansi-escapes": { "version": "4.3.2", + "dev": true, "requires": { "type-fest": "^0.21.3" } @@ -16006,7 +15956,8 @@ } }, "astral-regex": { - "version": "2.0.0" + "version": "2.0.0", + "dev": true }, "attr-accept": { "version": "1.1.3", @@ -16014,6 +15965,11 @@ "core-js": "^2.5.0" } }, + "auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==" + }, "available-typed-arrays": { "version": "1.0.5", "dev": true @@ -16386,6 +16342,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -16395,6 +16352,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -16435,8 +16393,7 @@ "ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" }, "cipher-base": { "version": "1.0.4", @@ -16459,8 +16416,14 @@ "clean-stack": { "version": "2.2.0" }, + "cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==" + }, "cli-cursor": { "version": "3.1.0", + "dev": true, "requires": { "restore-cursor": "^3.1.0" } @@ -16470,7 +16433,6 @@ }, "cli-truncate": { "version": "3.1.0", - "dev": true, "requires": { "slice-ansi": "^5.0.0", "string-width": "^5.0.0" @@ -16519,6 +16481,14 @@ "mimic-response": "^1.0.0" } }, + "code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "requires": { + "convert-to-spaces": "^2.0.1" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -16672,6 +16642,11 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, + "convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==" + }, "cookie": { "version": "0.5.0" }, @@ -17308,8 +17283,7 @@ } }, "eastasianwidth": { - "version": "0.2.0", - "dev": true + "version": "0.2.0" }, "ee-first": { "version": "1.1.1", @@ -17357,8 +17331,7 @@ } }, "emoji-regex": { - "version": "9.2.2", - "dev": true + "version": "9.2.2" }, "emojis-list": { "version": "3.0.0" @@ -18720,6 +18693,14 @@ "version": "1.2.7", "dev": true }, + "is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "requires": { + "ci-info": "^3.2.0" + } + }, "is-core-module": { "version": "2.11.0", "requires": { @@ -18736,8 +18717,7 @@ "version": "2.1.1" }, "is-fullwidth-code-point": { - "version": "4.0.0", - "dev": true + "version": "4.0.0" }, "is-generator-function": { "version": "1.0.10", @@ -20446,6 +20426,11 @@ "tslib": "^2.0.3" } }, + "patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==" + }, "path-browserify": { "version": "1.0.1", "dev": true @@ -21084,6 +21069,7 @@ "version": "4.27.4", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.27.4.tgz", "integrity": "sha512-dvZjrAJjahd6NNl7dDwEk5TyHsWJxDpYL7VnD9jdEr98EEEsVhw9G8JDX54Nrb3XIIOBlJDpjo3AuBuychX9zg==", + "devOptional": true, "requires": { "shell-quote": "^1.6.1", "ws": "^7" @@ -21148,6 +21134,15 @@ } } }, + "react-reconciler": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz", + "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, "readable-stream": { "version": "3.6.0", "requires": { @@ -21340,16 +21335,19 @@ }, "restore-cursor": { "version": "3.1.0", + "dev": true, "requires": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "dependencies": { "mimic-fn": { - "version": "2.1.0" + "version": "2.1.0", + "dev": true }, "onetime": { "version": "5.1.2", + "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -21769,7 +21767,8 @@ "shell-quote": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", - "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==" + "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", + "devOptional": true }, "shelljs": { "version": "0.8.5", @@ -21801,15 +21800,13 @@ }, "slice-ansi": { "version": "5.0.0", - "dev": true, "requires": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" }, "dependencies": { "ansi-styles": { - "version": "6.2.1", - "dev": true + "version": "6.2.1" } } }, @@ -22022,7 +22019,6 @@ }, "string-width": { "version": "5.1.2", - "dev": true, "requires": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -22031,7 +22027,6 @@ "dependencies": { "strip-ansi": { "version": "7.0.1", - "dev": true, "requires": { "ansi-regex": "^6.0.1" } @@ -22404,7 +22399,8 @@ } }, "type-fest": { - "version": "0.21.3" + "version": "0.21.3", + "dev": true }, "type-is": { "version": "1.6.18", @@ -23060,6 +23056,14 @@ } } }, + "widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "requires": { + "string-width": "^5.0.1" + } + }, "wildcard": { "version": "2.0.0", "dev": true @@ -23106,6 +23110,7 @@ }, "ws": { "version": "7.5.9", + "devOptional": true, "requires": {} }, "xtend": { @@ -23185,13 +23190,10 @@ "version": "0.1.0", "dev": true }, - "yoga-layout-prebuilt": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz", - "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==", - "requires": { - "@types/yoga-layout": "1.9.2" - } + "yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==" }, "zip-webpack-plugin": { "version": "4.0.1", diff --git a/plugins/plugin-codeflare-dashboard/package.json b/plugins/plugin-codeflare-dashboard/package.json index 4675eb5c..b87d1a51 100644 --- a/plugins/plugin-codeflare-dashboard/package.json +++ b/plugins/plugin-codeflare-dashboard/package.json @@ -30,8 +30,9 @@ "dependencies": { "@logdna/tail-file": "^3.0.1", "chokidar": "^3.5.3", - "ink": "^3.2.0", + "ink": "^4.1.0", "madwizard": "^9.0.3", + "pretty-bytes": "^6.1.0", "pretty-ms": "^8.0.0", "strip-ansi": "^7.0.1" } diff --git a/plugins/plugin-codeflare-dashboard/src/components/Dashboard/Grid.tsx b/plugins/plugin-codeflare-dashboard/src/components/Job/Grid.tsx similarity index 100% rename from plugins/plugin-codeflare-dashboard/src/components/Dashboard/Grid.tsx rename to plugins/plugin-codeflare-dashboard/src/components/Job/Grid.tsx diff --git a/plugins/plugin-codeflare-dashboard/src/components/Dashboard/Timeline.tsx b/plugins/plugin-codeflare-dashboard/src/components/Job/Timeline.tsx similarity index 97% rename from plugins/plugin-codeflare-dashboard/src/components/Dashboard/Timeline.tsx rename to plugins/plugin-codeflare-dashboard/src/components/Job/Timeline.tsx index 2ddf6535..38b02219 100644 --- a/plugins/plugin-codeflare-dashboard/src/components/Dashboard/Timeline.tsx +++ b/plugins/plugin-codeflare-dashboard/src/components/Job/Timeline.tsx @@ -28,10 +28,7 @@ type Props = { export default class Timeline extends React.PureComponent { /** Text to use for one cell's worth of time */ - private readonly block = { - historic: "■", - latest: "▏", - } + private readonly block = "■" /** This will help us compute whether we are about to overflow terminal width. */ private get maxLabelLength() { @@ -64,7 +61,7 @@ export default class Timeline extends React.PureComponent { return ( - {this.block.historic} + {this.block} ) } diff --git a/plugins/plugin-codeflare-dashboard/src/components/Dashboard/index.tsx b/plugins/plugin-codeflare-dashboard/src/components/Job/index.tsx similarity index 100% rename from plugins/plugin-codeflare-dashboard/src/components/Dashboard/index.tsx rename to plugins/plugin-codeflare-dashboard/src/components/Job/index.tsx diff --git a/plugins/plugin-codeflare-dashboard/src/components/Dashboard/stats.ts b/plugins/plugin-codeflare-dashboard/src/components/Job/stats.ts similarity index 100% rename from plugins/plugin-codeflare-dashboard/src/components/Dashboard/stats.ts rename to plugins/plugin-codeflare-dashboard/src/components/Job/stats.ts diff --git a/plugins/plugin-codeflare-dashboard/src/components/Dashboard/types.ts b/plugins/plugin-codeflare-dashboard/src/components/Job/types.ts similarity index 100% rename from plugins/plugin-codeflare-dashboard/src/components/Dashboard/types.ts rename to plugins/plugin-codeflare-dashboard/src/components/Job/types.ts diff --git a/plugins/plugin-codeflare-dashboard/src/components/Top/defaults.ts b/plugins/plugin-codeflare-dashboard/src/components/Top/defaults.ts new file mode 100644 index 00000000..08ec7403 --- /dev/null +++ b/plugins/plugin-codeflare-dashboard/src/components/Top/defaults.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2023 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Resource } from "./types.js" + +const defaultValueFor: Record = { + cpu: 50, // 50m + mem: 64 * 1024 * 1024, + gpu: 0, +} + +export default defaultValueFor diff --git a/plugins/plugin-codeflare-dashboard/src/components/Top/index.tsx b/plugins/plugin-codeflare-dashboard/src/components/Top/index.tsx new file mode 100644 index 00000000..d1f48c23 --- /dev/null +++ b/plugins/plugin-codeflare-dashboard/src/components/Top/index.tsx @@ -0,0 +1,353 @@ +/* + * Copyright 2023 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from "react" +import prettyMillis from "pretty-ms" +import prettyBytes from "pretty-bytes" +import { Box, Text, TextProps } from "ink" +import { emitKeypressEvents } from "readline" + +import type { JobRec, HostRec, PodRec, OnData, UpdatePayload, Resource, ResourceSpec } from "./types.js" + +import defaultValueFor from "./defaults.js" +import { Breakdown, ValidResources } from "./types.js" + +import { themes } from "../../controller/dashboard/utilization/theme.js" +import { defaultUtilizationThemes } from "../../controller/dashboard/grids.js" + +type UI = { + /** Force a refresh */ + refreshCycle?: number +} + +type Props = UI & { + initWatcher: (cb: OnData) => void +} + +type Group = { + groupIdx: number + job: JobRec + ctime: number + hosts: HostRec[] + pods: PodRec[] + stats: { min: Breakdown; tot: Breakdown } +} + +type State = UI & { + /** Model from controller */ + rawModel: UpdatePayload + + /** Our grouping of `rawModel` */ + groups: Group[] + + /** Currently selected group */ + selectedGroupIdx: number + + /** Refresh interval so we can update durations */ + refreshCycle: number + + /** Refresher interval */ + refresher: ReturnType +} + +export default class NodesDashboard extends React.PureComponent { + /** Text to use for one cell's worth of time */ + private readonly block = "■" // "▇" + + /** + * % is remainder, we want modulo + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder + */ + private mod(n: number, d: number) { + return ((n % d) + d) % d + } + + public componentDidMount() { + this.props.initWatcher(this.onData) + this.initRefresher() + this.initKeyboardEvents() + } + + public componentWillUnmount() { + this.cleanupRefresher() + this.cleanupKeyboardEvents() + } + + /** So we can update the job age UI even without updates from the controller */ + private initRefresher() { + this.setState({ + refresher: setInterval( + () => this.setState((curState) => ({ refreshCycle: (curState?.refreshCycle || 0) + 1 })), + 10000 + ), + }) + } + + private cleanupRefresher() { + if (this.state?.refresher) { + clearInterval(this.state.refresher) + } + } + + private cleanupKeyboardEvents() { + // TODO do we also need to exit raw mode on ctrl+c? + process.stdin.setRawMode(false) + } + + /** Handle keyboard events from the user */ + private initKeyboardEvents() { + // these are necessary to get keypress events on process.stdin + emitKeypressEvents(process.stdin) + process.stdin.setRawMode(true) + + process.stdin.on("keypress", (str, key) => { + if (key.ctrl && key.name === "c") { + process.exit() + } else if (key.ctrl && key.name === "l") { + this.setState((curState) => ({ refreshCycle: (curState?.refreshCycle || 0) + 1 })) + } else { + switch (key.name) { + case "left": + case "right": + if (this.state.groups) { + const incr = key.name === "left" ? -1 : 1 + this.setState((curState) => ({ + selectedGroupIdx: + curState?.selectedGroupIdx === undefined + ? 0 + : this.mod(curState.selectedGroupIdx + incr, curState.groups.length + 1), + })) + } + break + /*case "i": + this.setState((curState) => ({ blockCells: !this.useBlocks(curState) })) + break*/ + /*case "g": + this.setState((curState) => ({ + groupHosts: !this.groupHosts(curState), + groups: !curState?.rawModel + ? curState?.groups + : this.groupBy(curState.rawModel, !this.groupHosts(curState)), + })) + break */ + } + } + }) + } + + /** We have received data from the controller */ + private readonly onData = (rawModel: UpdatePayload) => + this.setState((curState) => { + if (JSON.stringify(curState?.rawModel) === JSON.stringify(rawModel)) { + return null + } else { + return { rawModel, groups: this.groupBy(rawModel) } + } + }) + + private groupBy(model: UpdatePayload): State["groups"] { + return Object.values( + model.hosts.reduce((M, host) => { + host.jobs.forEach((job) => { + const key = job.name + if (!M[key]) { + M[key] = { + job, + ctime: Date.now(), + hosts: [], + pods: [], + stats: { min: model.stats.min, tot: { cpu: 0, mem: 0, gpu: 0 } }, + } + } + + M[key].hosts.push(host) + M[key].pods = [...M[key].pods, ...job.pods] + M[key].ctime = Math.min(M[key].ctime, ...job.pods.map((_) => _.ctime)) + M[key].stats.tot.cpu += job.pods.reduce((tot, pod) => (tot += this.mostOf(pod.cpu, defaultValueFor.cpu)), 0) + M[key].stats.tot.mem += job.pods.reduce((tot, pod) => (tot += this.mostOf(pod.mem, defaultValueFor.mem)), 0) + M[key].stats.tot.gpu += job.pods.reduce((tot, pod) => (tot += this.mostOf(pod.gpu, defaultValueFor.gpu)), 0) + }) + + return M + }, {} as Record>) + ) + .map((group, groupIdx) => Object.assign(group, { groupIdx })) + .sort(this.sorter) + } + + private readonly sorter = (a: Group, b: Group) => { + return ( + a.stats.tot.cpu + a.stats.tot.mem + a.stats.tot.gpu - (b.stats.tot.cpu + b.stats.tot.mem + b.stats.tot.gpu) || + a.job.name.localeCompare(b.job.name) + ) + } + + private longestHostName(hosts: HostRec[]) { + return hosts.reduce((max, { host }) => Math.max(max, host.length), 0) + } + + private format(quantity: number, resource: Resource) { + switch (resource) { + case "cpu": { + const formattedQuantity = + quantity < 1000 ? `${quantity}m` : (quantity / 1000).toFixed(2).replace(/0+$/, "").replace(/\.$/, "") + return formattedQuantity + " " + (formattedQuantity === "1" ? "cpu" : "cpus") + } + case "mem": + return prettyBytes(quantity, { space: false }) + default: + case "gpu": + return quantity + " gpus" + } + } + + /** Do we have a selected group? */ + private get hasSelection() { + return this.state?.selectedGroupIdx >= 0 && this.state?.selectedGroupIdx < this.state.groups.length + } + + private styleOfResource(resource: Resource): TextProps { + switch (resource) { + case "cpu": + return themes[defaultUtilizationThemes["cpu%"]][2] + + case "mem": + return themes[defaultUtilizationThemes["mem%"]][2] + + default: + case "gpu": + return themes[defaultUtilizationThemes["gpu%"]][2] + } + } + + private styleOfGroup(group: Group, baseStyle: TextProps): TextProps { + return Object.assign({}, baseStyle, { + dimColor: this.hasSelection && this.state?.selectedGroupIdx !== group.groupIdx, + }) + } + + private styleOfGroupForResource(group: Group, resource: Resource): TextProps { + return this.styleOfGroup(group, this.styleOfResource(resource)) + } + + private mostOf({ request, limit }: ResourceSpec, defaultValue: number) { + if (request === -1 && limit === -1) { + return defaultValue + } else if (request === -1) { + return limit + } else if (limit === -1) { + return request + } else { + return Math.max(request, limit) + } + } + + /** Render a number of cells in a line */ + private nCells( + N: number, + labelSingular: string, + labelPlural: string, + style: TextProps, + value: number | string = N, + key = labelSingular + ) { + return ( + + + {Array(N) + .fill(0) + .map((_, idx) => ( + + {this.block} + + ))} + + + + {value} {value === 1 ? labelSingular : labelPlural} + + + + ) + } + + /** Render cells for one `resource` for one `group` */ + private resourceLine(group: Group, resource: Resource) { + const unit = this.state.rawModel.stats.min[resource] + const tot = group.stats.tot[resource] + const amnt = tot / group.hosts.length + const N = Math.round(amnt / unit) + + return this.nCells( + N, + "", + "", + this.styleOfGroupForResource(group, resource), + this.format(amnt, resource) + "/node", + resource + ) + } + + /** Render one `group` (one job) */ + private group(group: Group, groupIdx: number) { + const isSelected = this.state.selectedGroupIdx === groupIdx + + const titleStyle: TextProps = isSelected + ? { + inverse: true, + } + : {} + + return ( + + + + + {group.job.name} + + + + {prettyMillis(Date.now() - group.ctime, { compact: true })} + + + + {this.nCells(group.hosts.length, "node", "nodes", this.styleOfGroup(group, { color: "yellow" }))} + {ValidResources.filter((resource) => group.stats.tot[resource] !== 0).map((resource) => + this.resourceLine(group, resource) + )} + {this.nCells( + group.pods.length / group.hosts.length, + "worker/node", + "workers/node", + this.styleOfGroup(group, { color: "white" }) + )} + + ) + } + + public render() { + if (!this.state?.groups || this.state.groups.length === 0) { + return No active jobs + } else { + return ( + + {this.state.groups.map((_, idx) => this.group(_, idx))} + + ) + } + } +} diff --git a/plugins/plugin-codeflare-dashboard/src/components/Top/types.ts b/plugins/plugin-codeflare-dashboard/src/components/Top/types.ts new file mode 100644 index 00000000..a909a88e --- /dev/null +++ b/plugins/plugin-codeflare-dashboard/src/components/Top/types.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2023 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type ResourceSpec = { request: number; limit: number } + +export const ValidResources = ["cpu", "mem", "gpu"] as const + +export type Resource = (typeof ValidResources)[number] + +type ResourceSpecs = Record + +/** Model for one pod */ +export type PodRec = ResourceSpecs & { + name: string + job: string + jobIdx: number + host: string + tot: Breakdown + + /** creationTimestamp in millis since epoch */ + ctime: number +} + +/** Model for one job */ +export type JobRec = { name: string; jobIdx: number; pods: PodRec[] } + +/** Model for one host */ +export type HostRec = { host: string; jobs: JobRec[] } + +/** Breakdown of consumption by resource */ +export type Breakdown = Record + +/** Updated host model */ +export type UpdatePayload = { + hosts: HostRec[] + stats: { min: Breakdown; tot: Record } +} + +export type OnData = (payload: UpdatePayload) => void diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/db.tsx b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/db.tsx index 722062a8..8595b057 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/db.tsx +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/db.tsx @@ -17,9 +17,9 @@ import React from "react" import type Options from "./options.js" -import type { GridSpec } from "../../components/Dashboard/types.js" +import type { GridSpec } from "../../components/Job/types.js" -import Dashboard from "../../components/Dashboard/index.js" +import Job from "../../components/Job/index.js" /** Wrap the `grids` in a `Dashboard` UI */ export default function db( @@ -31,8 +31,6 @@ export default function db( if (!grids || (Array.isArray(grids) && grids.length === 0)) { return null } else { - return ( - - ) + return } } diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/generic/Demo.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/generic/Demo.ts index 75f11f4a..7a64aa80 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/generic/Demo.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/generic/Demo.ts @@ -17,7 +17,7 @@ import type { TextProps } from "ink" import type HistoryConfig from "../history.js" -import type { OnData } from "../../../components/Dashboard/types.js" +import type { OnData } from "../../../components/Job/types.js" import { update } from "../history.js" diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/history.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/history.ts index 7489e4ad..2417a0ee 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/history.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/history.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Worker } from "../../components/Dashboard/types.js" +import type { Worker } from "../../components/Job/types.js" /** Configuration governining the history model of states per worker */ type HistoryConfig = { diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/index.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/job.ts similarity index 90% rename from plugins/plugin-codeflare-dashboard/src/controller/dashboard/index.ts rename to plugins/plugin-codeflare-dashboard/src/controller/dashboard/job.ts index 24b54052..16cc16d0 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/index.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/job.ts @@ -21,11 +21,13 @@ import tailf from "./tailf.js" import dashboardUI from "./db.js" import status from "./status/index.js" import utilization from "./utilization/index.js" + +import { enterAltBufferMode } from "./term.js" import { SupportedGrid, isSupportedGrid } from "./grids.js" import { KindA, isValidKindA, validKinds } from "./kinds.js" import type HistoryConfig from "./history.js" -import type { GridSpec } from "../../components/Dashboard/types.js" +import type { GridSpec } from "../../components/Job/types.js" export type Options = Arguments["parsedOptions"] & { s: number @@ -42,11 +44,6 @@ export type Options = Arguments["parsedOptions"] & { "update-frequency": number } -/** Behave like top, where the screen is cleared just for this process */ -function enterAltBufferMode() { - process.stdout.write("\x1b[?1049h") -} - export function usage(cmd: string, extraKinds: string[] = []) { return `Usage: codeflare ${cmd} ${extraKinds.concat(validKinds()).join("|")} [|-N]` } @@ -128,7 +125,7 @@ async function allGridsFor( return Promise.all(all) } -export default async function dashboard(args: Arguments, cmd: "db" | "dashboard") { +export default async function dashboard(args: Arguments) { const debug = Debug("plugin-codeflare-dashboard/controller/dashboard") debug("setup") @@ -136,18 +133,18 @@ export default async function dashboard(args: Arguments, cmd: "db" | "d const scale = args.parsedOptions.s || 1 - const jobIdOffset = args.argvNoOptions[args.argvNoOptions.indexOf(cmd) + 2] ? 2 : 1 + const jobIdOffset = args.argvNoOptions[args.argvNoOptions.indexOf("job") + 2] ? 2 : 1 const kindOffset = jobIdOffset === 2 ? 1 : 9999 - const kind = args.argvNoOptions[args.argvNoOptions.indexOf(cmd) + kindOffset] || "all" - const { jobId, profile } = await jobIdFrom(args, cmd, jobIdOffset) + const kind = args.argvNoOptions[args.argvNoOptions.indexOf("job") + kindOffset] || "all" + const { jobId, profile } = await jobIdFrom(args, "job", jobIdOffset) debug("jobId", jobId) debug("profile", profile) if (!isValidKindA(kind)) { - throw new Error(usage(cmd, ["all"])) + throw new Error(usage("top job", ["all"])) } else if (!jobId) { - throw new Error(usage(cmd, ["all"])) + throw new Error(usage("top job", ["all"])) } const gridForA = async ( @@ -173,7 +170,7 @@ export default async function dashboard(args: Arguments, cmd: "db" | "d const db = dashboardUI(profile, jobId, grids, { scale }) if (!db) { - throw new Error(usage(cmd)) + throw new Error(usage("top job")) } else { const { render } = await import("ink") diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/options.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/options.ts index 6d0ca1ea..1b1c2a20 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/options.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/options.ts @@ -34,6 +34,24 @@ type Options = { export default Options export const flags = { - boolean: ["demo"], - alias: { events: ["e"], lines: ["l"], theme: ["t"], demo: ["d"], scale: ["s"], "update-frequency": ["u"] }, + boolean: [ + // TODO these are top-specific + "redact", + + // generic? + "demo", + ], + alias: { + // TODO these are node-specific + indices: ["i"], + namespace: ["n"], + "all-namespaces": ["A"], + + events: ["e"], + lines: ["l"], + theme: ["t"], + demo: ["d"], + scale: ["s"], + "update-frequency": ["u"], + }, } diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Demo.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Demo.ts index f788d572..7b78c392 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Demo.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Demo.ts @@ -18,7 +18,7 @@ import type { TextProps } from "ink" import type HistoryConfig from "../history.js" import type { WorkerState } from "./states.js" -import type { OnData } from "../../../components/Dashboard/types.js" +import type { OnData } from "../../../components/Job/types.js" import { states } from "./states.js" import GenericDemo from "../generic/Demo.js" diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Live.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Live.ts index d99641bb..d3390d80 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Live.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Live.ts @@ -24,7 +24,7 @@ import type Options from "../options.js" import type { Tail } from "../tailf.js" import type HistoryConfig from "../history.js" import type { WorkerState } from "./states.js" -import type { OnData, Worker } from "../../../components/Dashboard/types.js" +import type { OnData, Worker } from "../../../components/Job/types.js" import { rankFor, stateFor } from "./states.js" diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/index.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/index.ts index fce29c60..2011075b 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/index.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/index.ts @@ -20,7 +20,7 @@ import type { Tail } from "../tailf.js" import type Options from "../options.js" import type HistoryConfig from "../history.js" import type { WorkerState } from "./states.js" -import type { OnData, GridSpec } from "../../../components/Dashboard/types.js" +import type { OnData, GridSpec } from "../../../components/Job/types.js" import Demo from "./Demo.js" import Live from "./Live.js" diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/term.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/term.ts new file mode 100644 index 00000000..e675b866 --- /dev/null +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/term.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2023 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Behave like top, where the screen is cleared just for this process */ +export function enterAltBufferMode() { + process.stdout.write("\x1b[?1049h") +} diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/top.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/top.ts new file mode 100644 index 00000000..c45e4f02 --- /dev/null +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/top.ts @@ -0,0 +1,316 @@ +/* + * Copyright 2023 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Arguments } from "@kui-shell/core" + +import type { Options } from "./job.js" +import type { OnData, HostRec, PodRec, ResourceSpec, UpdatePayload } from "../../components/Top/types.js" + +import { enterAltBufferMode } from "./term.js" +import defaultValueFor from "../../components/Top/defaults.js" + +export type MyOptions = Options & { + /** Don't show job names in the UI */ + redact: boolean + + /** Show jobs for one namespace */ + n: string + namespace: string + + /** Show jobs across all namespaces */ + A: boolean + "all-namespaces": boolean +} + +/** @return as milli cpus, or -1 if not specified */ +function parseCpu(amount: string): number { + try { + if (amount === "") { + return -1 + } else if (amount[amount.length - 1] === "m") { + return parseInt(amount.slice(0, amount.length - 1), 10) + } else { + return parseInt(amount, 10) * 1000 + } + } catch (err) { + console.error("Odd cpu spec " + amount) + return defaultValueFor.cpu + } +} + +const unit = { + K: 1000, + Ki: 1024, + M: Math.pow(1000, 2), + Mi: Math.pow(1024, 2), + G: Math.pow(1000, 3), + Gi: Math.pow(1024, 3), + T: Math.pow(1000, 4), + Ti: Math.pow(1024, 4), + P: Math.pow(1000, 5), + Pi: Math.pow(1024, 5), + E: Math.pow(1000, 6), + Ei: Math.pow(1024, 6), +} + +type ValidUnit = keyof typeof unit + +function isValidUnit(u: string): u is ValidUnit { + return unit[u as ValidUnit] !== undefined +} + +/** @return as bytes, or -1 if not specified */ +function parseMem(amount: string): number { + if (amount === "") { + return -1 + } else { + const match = amount.match(/^(\d+)((k|M|G|T|P|E)?i?)$/) + if (!match || (match[2] && !isValidUnit(match[2]))) { + console.error("Odd memory spec " + amount) + return -1 + } else { + return parseInt(match[1], 10) * (!match[2] ? 1 : unit[match[2] as ValidUnit]) + } + } +} + +/** @return as count, or 0 if not specified */ +function parseGpu(amount: string): number { + if (amount === "") { + return 0 + } else { + return parseInt(amount, 10) + } +} + +function leastOf({ request, limit }: ResourceSpec, defaultValue: number): number { + if (request === -1 && limit === -1) { + return defaultValue + } else if (request === -1) { + return limit + } else if (limit === -1) { + return request + } else { + return Math.min(request, limit) + } +} + +/** Map from host to map from jobname to pods */ +type Model = Record>> +// host job name + +export default async function jobsController(args: Arguments) { + const ns = args.parsedOptions.A ? "-A" : args.parsedOptions.n ? `-n ${args.parsedOptions.n}` : "" + + if (process.env.ALT !== "false") { + enterAltBufferMode() + } + + // To help us parse out one "record's" worth of output from kubectl + const recordSeparator = "-----------" + + const { spawn } = await import("child_process") + const child = spawn( + "bash", + [ + "-c", + `"while true; do kubectl get pod ${ns} --no-headers -o=custom-columns=NAME:.metadata.name,JOB:'.metadata.labels.app\\.kubernetes\\.io/instance',HOST:.status.hostIP,CPU:'.spec.containers[0].resources.requests.cpu',CPUL:'.spec.containers[0].resources.limits.cpu',MEM:'.spec.containers[0].resources.requests.memory',MEML:'.spec.containers[0].resources.limits.memory',GPU:.spec.containers[0].resources.requests.'nvidia\\.com/gpu',GPUL:.spec.containers[0].resources.limits.'nvidia\\.com/gpu',JOB2:'.metadata.labels.appwrapper\\.mcad\\.ibm\\.com',CTIME:.metadata.creationTimestamp; echo '${recordSeparator}'; sleep 2; done"`, + ], + { shell: "/bin/bash", stdio: ["ignore", "pipe", "inherit"] } + ) + + const jobIndices: Record = {} // lookup + const jobOcc: (undefined | string)[] = [] // occupancy vector + const jobIdxFor = (job: string): number => { + const jobIdx = jobIndices[job] + if (jobIdx !== undefined) { + return jobIdx + } else { + for (let idx = 0; idx < jobOcc.length; idx++) { + if (jobOcc[idx] === undefined) { + jobOcc[idx] = job + jobIndices[job] = idx + return idx + } + } + + const jobIdx = jobOcc.push(job) - 1 + jobIndices[job] = jobIdx + return jobIdx + } + } + const removeJobIdx = (job: string) => { + const jobIdx = jobIndices[job] + if (jobIdx !== undefined) { + delete jobIndices[job] + jobOcc[jobIdx] = undefined + } + } + + const trim = (extantJobs: Record) => { + Object.keys(jobIndices) + .filter((job) => !(job in extantJobs)) + .forEach(removeJobIdx) + } + + const initWatcher = (cb: OnData) => { + child.on("error", (err) => console.error(err)) + + let leftover = "" + child.stdout.on("data", (data) => { + const sofar = leftover + data.toString() + + const term = sofar.indexOf(recordSeparator) + if (term < 0) { + leftover = sofar + } else if (term >= 0) { + leftover = sofar.slice(term + recordSeparator.length) + + const lines = sofar.slice(0, term).split(/\n/).filter(Boolean) + if (lines.length === 0) { + return + } + + const byHost: Model = lines + .map((_) => _.split(/\s+/)) + .map((A) => ({ + name: A[0], + job: A[1] === "" ? A[9] : A[1], + host: A[2], + ctime: A[10] === "" ? Date.now() : new Date(A[10]).getTime(), + cpu: { request: parseCpu(A[3]), limit: parseCpu(A[4]) }, + mem: { request: parseMem(A[5]), limit: parseMem(A[6]) }, + gpu: { request: parseGpu(A[7]), limit: parseGpu(A[8]) }, + })) + .filter((_) => _.job && _.job !== "") + .map((rec) => + Object.assign(rec, { + jobIdx: jobIdxFor(rec.job), + tot: { + cpu: leastOf(rec.cpu, defaultValueFor.cpu), + mem: leastOf(rec.mem, defaultValueFor.mem), + gpu: leastOf(rec.gpu, defaultValueFor.gpu), + }, + }) + ) + .reduce((byHost, rec) => { + // pod is not yet mapped to a host? + if (rec.host !== "") { + if (!byHost[rec.host]) { + byHost[rec.host] = {} + } + const byJob = byHost[rec.host] + + if (!byJob[rec.job]) { + byJob[rec.job] = {} + } + const byName = byJob[rec.job] + + byName[rec.name] = rec + } + + return byHost + }, {} as Model) + + const extantJobs = Object.values(byHost) + .flatMap((byJob) => Object.keys(byJob)) + .reduce((jobs, job) => { + jobs[job] = true + return jobs + }, {} as Record) + trim(extantJobs) + + // turn the records of records into arrays to make the UI code + // cleaner + const hosts = Object.keys(byHost).map((host) => ({ + host, + jobs: Object.keys(byHost[host] || []).map((name) => ({ + name: args.parsedOptions.redact ? `Job ${jobIdxFor(name)}` : name, + jobIdx: jobIdxFor(name), + pods: Object.values(byHost[host][name] || []), + })), + })) + + cb(stats(hosts)) + } + }) + } + + const [{ render }, { createElement }, { default: Top }] = await Promise.all([ + import("ink"), + import("react"), + import("../../components/Top/index.js"), + ]) + const { waitUntilExit } = await render(createElement(Top, { initWatcher })) + await waitUntilExit() + return true +} + +/** @return greatest common divisor of `a` and `b` */ +//const gcd = (a: number, b: number): number => a ? gcd(b % a, a) : b +function gcd(a: number, b: number) { + a = Math.abs(a) + b = Math.abs(b) + if (b > a) { + const temp = a + a = b + b = temp + } + // eslint-disable-next-line no-constant-condition + while (true) { + if (b == 0) return a + a %= b + if (a == 0) return b + b %= a + } +} + +/** Extract min/total values for resource demand */ +function stats(hosts: HostRec[]): UpdatePayload { + // find the min cpu, total cpu, etc. + const stats = hosts + .flatMap((_) => _.jobs.flatMap((_) => _.pods)) + .reduce( + (stats, pod) => { + const cpu = leastOf(pod.cpu, defaultValueFor.cpu) + const mem = leastOf(pod.mem, defaultValueFor.mem) + const gpu = leastOf(pod.gpu, defaultValueFor.gpu) + + stats.min.cpu = stats.min.cpu === Number.MAX_VALUE ? cpu : gcd(stats.min.cpu, cpu) + //stats.min.mem = stats.min.mem === Number.MAX_VALUE ? mem / 1024 / 1024 : gcd(stats.min.mem, mem / 1024 / 1024) + stats.min.mem = Math.min(stats.min.mem, mem) + stats.min.gpu = Math.min(stats.min.gpu, gpu) + + stats.tot[pod.host].cpu += cpu + stats.tot[pod.host].mem += mem + stats.tot[pod.host].gpu += gpu + + return stats + }, + { + min: { cpu: Number.MAX_VALUE, mem: 32 * unit.Gi, gpu: Number.MAX_VALUE }, + tot: hosts.reduce((T, host) => { + T[host.host] = { cpu: 0, mem: 0, gpu: 0 } + return T + }, {} as UpdatePayload["stats"]["tot"]), + } as UpdatePayload["stats"] + ) + + return { + hosts, + stats, + } +} diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Demo.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Demo.ts index c6b97e53..61446234 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Demo.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Demo.ts @@ -18,7 +18,7 @@ import type { TextProps } from "ink" import type HistoryConfig from "../history.js" import type { WorkerState } from "./states.js" -import type { OnData } from "../../../components/Dashboard/types.js" +import type { OnData } from "../../../components/Job/types.js" import { states } from "./states.js" import GenericDemo from "../generic/Demo.js" diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Live.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Live.ts index 3939c838..ae6717f8 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Live.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/Live.ts @@ -20,7 +20,7 @@ import type { TextProps } from "ink" import type { Tail } from "../tailf.js" import type { WorkerState } from "./states.js" import type HistoryConfig from "../history.js" -import type { OnData, Worker } from "../../../components/Dashboard/types.js" +import type { OnData, Worker } from "../../../components/Job/types.js" import { states } from "./states.js" import { update as updateHistory } from "../history.js" diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/index.ts b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/index.ts index 0e3d9036..0d20c87f 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/index.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dashboard/utilization/index.ts @@ -21,7 +21,7 @@ import type { Tail } from "../tailf.js" import type Options from "../options.js" import type HistoryConfig from "../history.js" import type { WorkerState } from "./states.js" -import type { OnData, GridSpec } from "../../../components/Dashboard/types.js" +import type { OnData, GridSpec } from "../../../components/Job/types.js" import { SupportedUtilizationGrid, defaultUtilizationThemes, providerFor } from "../grids.js" import { states } from "./states.js" diff --git a/plugins/plugin-codeflare-dashboard/src/controller/dump.ts b/plugins/plugin-codeflare-dashboard/src/controller/dump.ts index 4da47dfd..60f96385 100644 --- a/plugins/plugin-codeflare-dashboard/src/controller/dump.ts +++ b/plugins/plugin-codeflare-dashboard/src/controller/dump.ts @@ -18,7 +18,7 @@ import { Arguments } from "@kui-shell/core" import { pathsFor } from "./dashboard/tailf.js" import { filepathOf, isValidKind } from "./dashboard/kinds.js" -import { Options as DashboardOptions, jobIdFrom, usage as dbUsage } from "./dashboard/index.js" +import { Options as DashboardOptions, jobIdFrom, usage as dbUsage } from "./dashboard/job.js" export type Options = DashboardOptions & { f: boolean diff --git a/plugins/plugin-codeflare-dashboard/src/plugin.ts b/plugins/plugin-codeflare-dashboard/src/plugin.ts index 73e5e341..63361300 100644 --- a/plugins/plugin-codeflare-dashboard/src/plugin.ts +++ b/plugins/plugin-codeflare-dashboard/src/plugin.ts @@ -16,18 +16,24 @@ import { KResponse, Registrar } from "@kui-shell/core" +import type { MyOptions as TopOptions } from "./controller/dashboard/top.js" +import type { Options as DashboardOptions } from "./controller/dashboard/job.js" + import { flags } from "./controller/dashboard/options.js" -import type { Options as DashboardOptions } from "./controller/dashboard/index.js" import { Options as DumpOptions, flags as dumpFlags } from "./controller/dump.js" /** Register Kui Commands */ export default function registerCodeflareCommands(registrar: Registrar) { - ["db" as const, "dashboard" as const].forEach((db) => - registrar.listen( - `/codeflare/${db}`, - (args) => import("./controller/dashboard/index.js").then((_) => _.default(args, db)), - { flags } - ) + registrar.listen( + `/codeflare/top/job`, + (args) => import("./controller/dashboard/job.js").then((_) => _.default(args)), + { flags } + ) + + registrar.listen( + `/codeflare/top`, + (args) => import("./controller/dashboard/top.js").then((_) => _.default(args)), + { flags } ) registrar.listen( diff --git a/plugins/plugin-codeflare-dashboard/tsconfig.json b/plugins/plugin-codeflare-dashboard/tsconfig.json index 3bb71ea0..d892d147 100644 --- a/plugins/plugin-codeflare-dashboard/tsconfig.json +++ b/plugins/plugin-codeflare-dashboard/tsconfig.json @@ -6,6 +6,8 @@ "composite": true, "jsx": "react", "outDir": "mdist", - "rootDir": "src" + "rootDir": "src", + "module": "node16", + "moduleResolution": "node16" } }