diff --git a/package-lock.json b/package-lock.json index a02a7fba..4aa2bb0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.28.6", + "@carbon/icons": "^11.74.0", + "@carbon/react": "^1.100.0", "@date-io/core": "^3.2.0", "@date-io/date-fns": "^3.2.1", "@emotion/react": "^11.14.0", @@ -546,6 +548,195 @@ "node": ">=6.9.0" } }, + "node_modules/@carbon/colors": { + "version": "11.46.0", + "resolved": "https://registry.npmjs.org/@carbon/colors/-/colors-11.46.0.tgz", + "integrity": "sha512-YL4BH2hxHkUT0+wMn8cO3sYN7rb9Nnp7rGttoblM0iTy83n/urwRPcxudifRwJLtASQpravCyLHdIC9WnTtIAA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/feature-flags": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@carbon/feature-flags/-/feature-flags-0.32.0.tgz", + "integrity": "sha512-a1rFplSEFPwJ4ZsuwvOaKHgoLqPNhjCJdWY6VTgCoytRZqtgYWqwYFEqQkm9/f1mX1lHr6oK/eBpAcmi0Izuvg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/grid": { + "version": "11.49.0", + "resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.49.0.tgz", + "integrity": "sha512-zZfj/sbwJpXboduVFNUXUdV6LmsEH39fNQQMye4V+788sdvs+ErO8L3onBZFpsek5gI4ebwjpWJu2g5szu2+kQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@carbon/layout": "^11.47.0", + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/icon-helpers": { + "version": "10.71.0", + "resolved": "https://registry.npmjs.org/@carbon/icon-helpers/-/icon-helpers-10.71.0.tgz", + "integrity": "sha512-T6KcxkNIa609jPC+8A7u5husSY+mH60lCNNa3ivcOyuREoVYHwnieM7GIECigF/oaGaF5eBzrxYFx2+8mLRk1A==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/icons": { + "version": "11.74.0", + "resolved": "https://registry.npmjs.org/@carbon/icons/-/icons-11.74.0.tgz", + "integrity": "sha512-tKAUXSgzLvov2gidRb3/U1NvUWKqxfG3anmWMz7WnTj/W3aK6PXpvgfspqPMOnBsnuF5q5H3Csq6XJ99dUZ5ig==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/icons-react": { + "version": "11.74.0", + "resolved": "https://registry.npmjs.org/@carbon/icons-react/-/icons-react-11.74.0.tgz", + "integrity": "sha512-tP/ZwM3e86zDm/8mup1NoObdaBl2xqZlroWP/Z1PQ9bCYOOFelR6r34aObWiDBJVpKb5YwwZWYUrl+/98fmDRQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@carbon/icon-helpers": "^10.71.0", + "@ibm/telemetry-js": "^1.5.0", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@carbon/layout": { + "version": "11.47.0", + "resolved": "https://registry.npmjs.org/@carbon/layout/-/layout-11.47.0.tgz", + "integrity": "sha512-2XR4TVp3uf2IB0WdoZuDcBbc9C8EN/JvZAw9BdHJ3njng8FlUAQUkTFvfoUsJl10868rqA6YeClCElBS4BHofg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/motion": { + "version": "11.40.0", + "resolved": "https://registry.npmjs.org/@carbon/motion/-/motion-11.40.0.tgz", + "integrity": "sha512-QjvjMcC3G289GKYDvrf5dDuyol7SXm0TYaFltx+AkJdU6fptDCJ/qjUL5SdVrsLse3jFuI8rada9tRAL5xHS1g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/react": { + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.100.0.tgz", + "integrity": "sha512-QlJ/bqiQn3fF3EX1YfVN3+zYvbqiGuMULtsI+wtuMaoEOJcR9FBwUYSP4w7lXTCxQ7WkB/wTLrekVcGgRoNquQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.27.3", + "@carbon/feature-flags": ">=0.32.0", + "@carbon/icons-react": "^11.74.0", + "@carbon/layout": "^11.47.0", + "@carbon/styles": "^1.99.0", + "@carbon/utilities": "^0.15.0", + "@floating-ui/react": "^0.27.4", + "@ibm/telemetry-js": "^1.5.0", + "classnames": "2.5.1", + "copy-to-clipboard": "^3.3.1", + "downshift": "9.0.10", + "es-toolkit": "^1.27.0", + "flatpickr": "4.6.13", + "invariant": "^2.2.3", + "prop-types": "^15.8.1", + "react-fast-compare": "^3.2.2", + "tabbable": "^6.2.0" + }, + "peerDependencies": { + "react": "^16.8.6 || ^17.0.1 || ^18.2.0 || ^19.0.0", + "react-dom": "^16.8.6 || ^17.0.1 || ^18.2.0 || ^19.0.0", + "react-is": "^16.13.1 || ^17.0.2 || ^18.3.1 || ^19.0.0", + "sass": "^1.33.0" + } + }, + "node_modules/@carbon/styles": { + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/@carbon/styles/-/styles-1.99.0.tgz", + "integrity": "sha512-71iypyzR97h6Z94XRZyel3IEo4+n9TRylKdsYUJASNs7GNIjsIBlwKRn+upUktsyWVNTV1iQ9uzo3UkFcRiEFQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@carbon/colors": "^11.46.0", + "@carbon/feature-flags": ">=0.32.0", + "@carbon/grid": "^11.49.0", + "@carbon/layout": "^11.47.0", + "@carbon/motion": "^11.40.0", + "@carbon/themes": "^11.67.0", + "@carbon/type": "^11.53.0", + "@ibm/plex": "6.0.0-next.6", + "@ibm/plex-mono": "1.1.0", + "@ibm/plex-sans": "1.1.0", + "@ibm/plex-sans-arabic": "1.1.0", + "@ibm/plex-sans-devanagari": "1.1.0", + "@ibm/plex-sans-hebrew": "1.1.0", + "@ibm/plex-sans-thai": "1.1.0", + "@ibm/plex-sans-thai-looped": "1.1.0", + "@ibm/plex-serif": "1.1.0", + "@ibm/telemetry-js": "^1.5.0" + }, + "peerDependencies": { + "sass": "^1.33.0" + }, + "peerDependenciesMeta": { + "sass": { + "optional": true + } + } + }, + "node_modules/@carbon/themes": { + "version": "11.67.0", + "resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.67.0.tgz", + "integrity": "sha512-sCjmwxvM7nUdsDPef9g2v07Motvd4EYZJJqJyklMfhm9ZJ1oUfwecpW8rLzXylDsOBhrX9s1oCKWG/JqZF3kig==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@carbon/colors": "^11.46.0", + "@carbon/layout": "^11.47.0", + "@carbon/type": "^11.53.0", + "@ibm/telemetry-js": "^1.5.0", + "color": "^4.0.0" + } + }, + "node_modules/@carbon/type": { + "version": "11.53.0", + "resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.53.0.tgz", + "integrity": "sha512-x3GeJrkvM8wdpBwYbRr6jUsmR2wSRVbIxmPl7kamSFih32+czp7xpt/frG02EAY5xgaEk3N9YCNYspwco42raA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@carbon/grid": "^11.49.0", + "@carbon/layout": "^11.47.0", + "@ibm/telemetry-js": "^1.5.0" + } + }, + "node_modules/@carbon/utilities": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@carbon/utilities/-/utilities-0.15.0.tgz", + "integrity": "sha512-bwneNtLk8khoSIsilr6fBl115BMBrCMqxo/LjwAYiA+GiHes4URC4QYUihg+Ida5bCDpMixDx3RI9IW1UodXLQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1", + "@internationalized/number": "^3.6.1" + } + }, "node_modules/@date-io/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@date-io/core/-/core-3.2.0.tgz", @@ -721,6 +912,166 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.17", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.17.tgz", + "integrity": "sha512-LGVZKHwmWGg6MRHjLLgsfyaX2y2aCNgnD1zT/E6B+/h+vxg+nIJUqHPAlTzsHDyqdgEpJ1Np5kxWuFEErXzoGg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.7", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@ibm/plex": { + "version": "6.0.0-next.6", + "resolved": "https://registry.npmjs.org/@ibm/plex/-/plex-6.0.0-next.6.tgz", + "integrity": "sha512-B3uGruTn2rS5gweynLmfSe7yCawSRsJguJJQHVQiqf4rh2RNgJFu8YLE2Zd/JHV0ZXoVMOslcXP2k3hMkxKEyA==", + "license": "OFL-1.1", + "engines": { + "node": ">=14" + } + }, + "node_modules/@ibm/plex-mono": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-mono/-/plex-mono-1.1.0.tgz", + "integrity": "sha512-hpsdRxR3BRJkC6wGM4MZcUFD6C8M+mmK76RtAy/hlsfPro9FzpXVdIWC+G3jeQOXof109dxlUvmeKxpeKUG68A==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/plex-sans": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans/-/plex-sans-1.1.0.tgz", + "integrity": "sha512-WPgvO6Yfj2w5YbhyAr1tv95RUz4LRJlqN+CmYvBglabXteufP1D1E9BABMde+ZIKdRbFJDoKF5eQzfhpnbgZcQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/plex-sans-arabic": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-arabic/-/plex-sans-arabic-1.1.0.tgz", + "integrity": "sha512-u8wIS6szLAOFvlBjCFZmtpKIqbhuIuniG2N0J+sio8vV6INH58hP0t0QNYrSl9SZtCv2Fwb4oQGuZJY3kJ4+QA==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/plex-sans-devanagari": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-devanagari/-/plex-sans-devanagari-1.1.0.tgz", + "integrity": "sha512-IVNV9NxXZDzcGZRao/xj+kiFwkdLkcw5vNiKwY8wEzzkpjApXJnPhJ0a7mIKNAh8oIadTIF68+iGtzRKK3nXAQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/plex-sans-hebrew": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-hebrew/-/plex-sans-hebrew-1.1.0.tgz", + "integrity": "sha512-iix0rLpUD0E8dE8q+/t3B7u1or7h6gEzoy6TK9NwP41AN31WE55f2cFwQAXomBDwr0Ozc9sHYy97NutEukZXzQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/plex-sans-thai": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-thai/-/plex-sans-thai-1.1.0.tgz", + "integrity": "sha512-vk7IrjdO69eEElJpFBppCha/wvU48DFyVuDewcfIf5L6Z11s0vbROANCvKipVPRUz1LE4ron8KoitWGcl3AlfA==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/plex-sans-thai-looped": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-thai-looped/-/plex-sans-thai-looped-1.1.0.tgz", + "integrity": "sha512-9zbDGzmtscHgBRTF88y3/92zQx6lmKjz5ZxhgcljilwOpj08BAytDc3mzUl9XGUh+DmOMl0Ql1lk6ecsEYYg2w==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/plex-serif": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-serif/-/plex-serif-1.1.0.tgz", + "integrity": "sha512-ORIyWlK8t8mvpFI7AAfhVFH+sacink2l9AjLiKZscmAzLVSa2dqFckrPOXqx4dK/cax567gWwCpJNEYk7xWxBQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } + }, + "node_modules/@ibm/telemetry-js": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@ibm/telemetry-js/-/telemetry-js-1.11.0.tgz", + "integrity": "sha512-RO/9j+URJnSfseWg9ZkEX9p+a3Ousd33DBU7rOafoZB08RqdzxFVYJ2/iM50dkBuD0o7WX7GYt1sLbNgCoE+pA==", + "license": "Apache-2.0", + "bin": { + "ibmtelemetry": "dist/collect.js" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2709,7 +3060,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -2748,7 +3099,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2769,7 +3119,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2790,7 +3139,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2811,7 +3159,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2832,7 +3179,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2853,7 +3199,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2874,7 +3219,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2895,7 +3239,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2916,7 +3259,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2937,7 +3279,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2958,7 +3299,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2979,7 +3319,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3000,7 +3339,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3307,7 +3645,6 @@ "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -3558,7 +3895,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3686,6 +4023,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -3696,6 +4049,12 @@ "node": ">=6.0" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -3715,11 +4074,23 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3732,9 +4103,18 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3757,6 +4137,12 @@ "node": ">=18" } }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3773,6 +4159,15 @@ "node": ">=18" } }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/core-js-compat": { "version": "3.46.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", @@ -3976,7 +4371,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "detect-libc": "bin/detect-libc.js" @@ -4079,6 +4474,28 @@ "url": "https://dotenvx.com" } }, + "node_modules/downshift": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-9.0.10.tgz", + "integrity": "sha512-TP/iqV6bBok6eGD5tZ8boM8Xt7/+DZvnVNr8cNIhbAm2oUBd79Tudiccs2hbcV9p7xAgS/ozE7Hxy3a9QqS6Mw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.5", + "compute-scroll-into-view": "^3.1.0", + "prop-types": "^15.8.1", + "react-is": "18.2.0", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4208,7 +4625,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4223,6 +4640,12 @@ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "license": "MIT" }, + "node_modules/flatpickr": { + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", + "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -4481,6 +4904,13 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "license": "MIT", + "peer": true + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4506,6 +4936,15 @@ "node": ">=12" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4531,7 +4970,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4541,7 +4980,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4554,7 +4993,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5000,7 +5439,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5101,7 +5540,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-gyp-build-optional-packages": { @@ -5248,7 +5687,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5334,6 +5773,12 @@ "react": "^19.2.3" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, "node_modules/react-is": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", @@ -5411,6 +5856,20 @@ "react-dom": ">=16.6.0" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/recharts": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", @@ -5519,6 +5978,27 @@ ], "license": "MIT" }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "license": "MIT", + "peer": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5560,6 +6040,21 @@ "node": ">=11.0" } }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5569,6 +6064,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -5600,6 +6105,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, "node_modules/term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", @@ -5623,7 +6134,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -5632,11 +6143,16 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/type-fest": { diff --git a/package.json b/package.json index c1856034..1ddbe8f6 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.1.0", "license": "Apache-2.0", "scripts": { - "build": "npx parcel build src/index.html", + "build": "npx parcel build src/index.html && cp src/_redirects dist/_redirects", "serve": "npx parcel serve src/index.html --no-cache", "clean": "rm -rf dist/*", "test": "echo \"Error: no test specified\" && exit 1", @@ -15,6 +15,8 @@ ], "dependencies": { "@babel/runtime": "^7.28.6", + "@carbon/icons": "^11.74.0", + "@carbon/react": "^1.100.0", "@date-io/core": "^3.2.0", "@date-io/date-fns": "^3.2.1", "@emotion/react": "^11.14.0", diff --git a/src/_redirects b/src/_redirects new file mode 100644 index 00000000..ad37e2c2 --- /dev/null +++ b/src/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/src/app.js b/src/app.js index acaca9ad..9c3bf7c1 100644 --- a/src/app.js +++ b/src/app.js @@ -1,5 +1,10 @@ import { createRoot } from "react-dom/client"; import Routes from "./route"; +import { ThemeProvider } from "./context/ThemeContext"; const root = createRoot(document.getElementById("app")); -root.render(); +root.render( + + + , +); diff --git a/src/components/Assets/AssetDetailsModal.js b/src/components/Assets/AssetDetailsModal.js new file mode 100644 index 00000000..4f93b441 --- /dev/null +++ b/src/components/Assets/AssetDetailsModal.js @@ -0,0 +1,135 @@ +import React from "react"; +import { + Modal, + StructuredListWrapper, + StructuredListHead, + StructuredListBody, + StructuredListRow, + StructuredListCell, +} from "@carbon/react"; +import { formatDuration, bytesToString } from "../../util"; +import SummaryRow from "./SummaryRow"; + +const AssetDetailsModal = ({ open, onClose, asset }) => { + if (!asset) return null; + + const { properties = {}, labels = {}, ...rest } = asset; + + return ( + +
+
Overview
+ + + + + + + + + + + + + + + +
+ +
+
+ Properties ({Object.keys(properties).length}) +
+ + + + Key + Value + + + + {Object.entries(properties).length > 0 ? ( + Object.entries(properties).map(([key, value]) => ( + + + {key} + + + {String(value)} + + + )) + ) : ( + + + No properties available + + + )} + + +
+ +
+
+ Labels ({Object.keys(labels).length}) +
+ + + + Key + Value + + + + {Object.entries(labels).length > 0 ? ( + Object.entries(labels).map(([key, value]) => ( + + + {key} + + + {String(value)} + + + )) + ) : ( + + + No labels available + + + )} + + +
+
+ ); +}; + +export default AssetDetailsModal; diff --git a/src/components/Assets/AssetTable.js b/src/components/Assets/AssetTable.js new file mode 100644 index 00000000..9286ba6a --- /dev/null +++ b/src/components/Assets/AssetTable.js @@ -0,0 +1,56 @@ +import { + DataTable, + Table, + TableHead, + TableRow, + TableHeader, + TableBody, + TableCell, +} from "@carbon/react"; + +const AssetTable = ({ headers, rows, formatters = {}, onRowClick }) => { + return ( + + {({ rows, headers, getHeaderProps, getRowProps }) => ( + + + + {headers.map((header) => { + const { key, ...headerProps } = getHeaderProps({ header }); + return ( + + {header.header} + + ); + })} + + + + {rows.map((row) => { + const { key, ...rowProps } = getRowProps({ row }); + return ( + onRowClick && onRowClick(row.id)} + style={{ cursor: "pointer" }} + > + {row.cells.map((cell, index) => { + const headerKey = headers[index].key; + const formatter = formatters[headerKey]; + const displayValue = formatter + ? formatter(cell.value) + : cell.value; + return {displayValue}; + })} + + ); + })} + +
+ )} +
+ ); +}; + +export default AssetTable; diff --git a/src/components/Assets/SummaryRow.js b/src/components/Assets/SummaryRow.js new file mode 100644 index 00000000..ec429f8a --- /dev/null +++ b/src/components/Assets/SummaryRow.js @@ -0,0 +1,18 @@ +import { StructuredListRow, StructuredListCell } from "@carbon/react"; + +const SummaryRow = ({ label, value }) => { + if (value === undefined || value === null || value === "") return null; + return ( + + + {label} + + {value} + + ); +}; + +export default SummaryRow; diff --git a/src/components/Footer.js b/src/components/Footer.js index 767dd710..68f67d80 100644 --- a/src/components/Footer.js +++ b/src/components/Footer.js @@ -2,7 +2,8 @@ import { Parser as HtmlToReactParser } from "html-to-react"; // Footer could be HTML, so we need to parse it. const Footer = () => { - const content = '

PLACEHOLDER_FOOTER_CONTENT
'; + const content = + '

PLACEHOLDER_FOOTER_CONTENT
'; const htmlToReactParser = new HtmlToReactParser(); const parsedContent = htmlToReactParser.parse(content); return parsedContent; diff --git a/src/components/Header.js b/src/components/Header.js index 54e9bd59..e94b25be 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -1,6 +1,4 @@ -import Breadcrumbs from "@mui/material/Breadcrumbs"; -import Link from "@mui/material/Link"; -import Typography from "@mui/material/Typography"; +import { Heading, Breadcrumb, BreadcrumbItem, Link } from "@carbon/react"; const Header = (props) => { const { title, breadcrumbs, headerTitle } = props; @@ -15,22 +13,20 @@ const Header = (props) => { marginTop: "10px", }} > - - {headerTitle} - + {headerTitle}
- {title && {title}} + {title} {breadcrumbs && breadcrumbs.length > 0 && ( - + {breadcrumbs.slice(0, breadcrumbs.length - 1).map((b) => ( - - {b.name} - + + {b.name} + ))} - + {breadcrumbs[breadcrumbs.length - 1].name} - - + + )}
{props.children}
diff --git a/src/components/Nav/NavItem.js b/src/components/Nav/NavItem.js deleted file mode 100644 index da24e71c..00000000 --- a/src/components/Nav/NavItem.js +++ /dev/null @@ -1,55 +0,0 @@ -import { ListItem, ListItemIcon, ListItemText } from "@mui/material"; -import { Link } from "react-router"; - -const NavItem = ({ active, href, name, onClick, secondary, title, icon }) => { - const renderListItemCore = () => ( - { - if (onClick) { - onClick(); - e.stopPropagation(); - } - }} - title={title} - > - - {icon} - - - - ); - - return href && !active ? ( - - {renderListItemCore()} - - ) : ( - renderListItemCore() - ); -}; - -export { NavItem }; diff --git a/src/components/Nav/SidebarNav.js b/src/components/Nav/SidebarNav.js index 781184d7..db1829ab 100644 --- a/src/components/Nav/SidebarNav.js +++ b/src/components/Nav/SidebarNav.js @@ -1,59 +1,69 @@ -import * as React from "react"; -import { Drawer, List } from "@mui/material"; - -import { NavItem } from "./NavItem"; -import { BarChart, Cloud } from "@mui/icons-material"; +import { SideNav, SideNavLink, SideNavItems } from "@carbon/react"; +import { + ChartColumn, + CloudDataOps, + LayersExternal, + IbmCloudBareMetalServer, + Asleep, + Light, +} from "@carbon/icons-react"; +import { useTheme } from "../../context/ThemeContext"; const logo = new URL("../../images/logo.png", import.meta.url).href; -const DRAWER_WIDTH = 200; - const SidebarNav = ({ active }) => { - const [init, setInit] = React.useState(false); - - React.useEffect(() => { - if (!init) { - setInit(true); - } - }, [init]); - - const top = [ - { - name: "Cost Allocation", - href: "/allocation", - icon: , - }, - { name: "Cloud Costs", href: "/cloud", icon: }, - { name: "External Costs", href: "/external-costs", icon: }, - ]; + const { theme, toggleTheme } = useTheme(); return ( - - OpenCost - - {top.map((l) => ( - - ))} - - + <> + + OpenCost + + {[ + { + icon: ChartColumn, + href: "/allocation", + label: "Cost Allocation", + }, + { + icon: CloudDataOps, + href: "/cloud", + label: "Cloud Costs", + }, + { + icon: LayersExternal, + href: "/external-costs", + label: "External Costs", + }, + { + icon: IbmCloudBareMetalServer, + href: "/assets", + label: "Assets", + }, + ].map(({ icon, href, label }) => ( + + {label} + + ))} + + {theme === "g10" ? "Dark Mode" : "Light Mode"} + + + + ); }; diff --git a/src/components/Page.js b/src/components/Page.js index 769dcc67..71671a78 100644 --- a/src/components/Page.js +++ b/src/components/Page.js @@ -1,49 +1,57 @@ import { useLocation } from "react-router"; +import { Theme } from "@carbon/react"; import { SidebarNav } from "./Nav/SidebarNav"; +import { useTheme } from "../context/ThemeContext"; const Page = (props) => { const { pathname } = useLocation(); + const { theme } = useTheme(); return ( -
- +
+
- {props.children} +
+ {props.children} +
-
+ ); }; diff --git a/src/constants/windowsOptions.js b/src/constants/windowsOptions.js new file mode 100644 index 00000000..d42cd2fb --- /dev/null +++ b/src/constants/windowsOptions.js @@ -0,0 +1,12 @@ +const windowOptions = [ + { name: "Today", value: "today" }, + { name: "Yesterday", value: "yesterday" }, + { name: "Last 24h", value: "24h" }, + { name: "Last 48h", value: "48h" }, + { name: "Week-to-date", value: "week" }, + { name: "Last week", value: "lastweek" }, + { name: "Last 7 days", value: "7d" }, + { name: "Last 14 days", value: "14d" }, +]; + +export { windowOptions }; diff --git a/src/context/ThemeContext.js b/src/context/ThemeContext.js new file mode 100644 index 00000000..066a78a4 --- /dev/null +++ b/src/context/ThemeContext.js @@ -0,0 +1,35 @@ +import React, { createContext, useContext, useState, useEffect } from "react"; + +const ThemeContext = createContext(); + +export const useTheme = () => useContext(ThemeContext); + +export const ThemeProvider = ({ children }) => { + const [theme, setTheme] = useState("g10"); // light theme by default + + useEffect(() => { + const savedTheme = localStorage.getItem("opencost-ui-theme"); + if (savedTheme) { + setTheme(savedTheme); + } + }, []); + + useEffect(() => { + document.body.classList.remove("cds--g10", "cds--g100"); + document.body.classList.add(`cds--${theme}`); + }, [theme]); + + const toggleTheme = () => { + setTheme((prevTheme) => { + const newTheme = prevTheme === "g10" ? "g100" : "g10"; + localStorage.setItem("opencost-ui-theme", newTheme); + return newTheme; + }); + }; + + return ( + + {children} + + ); +}; diff --git a/src/css/index.css b/src/css/index.css index 8ace1e31..89dbee86 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -1,10 +1,11 @@ @import "../../node_modules/material-design-icons-iconfont/dist/material-design-icons.css"; +@import "../../node_modules/@carbon/styles/css/styles.css"; body { background-color: #f3f3f3; display: flex; flex-flow: column; - font-family: "Roboto", sans-serif; + font-family: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif; margin: 0px; overflow-y: scroll; } @@ -18,3 +19,19 @@ body .page-container { .recharts-tooltip-wrapper { z-index: 1000; } + +.cds--side-nav { + display: flex !important; + flex-direction: column !important; +} + +.cds--side-nav__items { + display: flex !important; + flex-direction: column !important; + flex-grow: 1 !important; +} + +.cds--side-nav__items > .cds--side-nav__item:last-child { + margin-top: auto !important; + margin-bottom: 1rem; +} diff --git a/src/pages/Allocations.js b/src/pages/Allocations.js index e82ad847..b712aa86 100644 --- a/src/pages/Allocations.js +++ b/src/pages/Allocations.js @@ -25,17 +25,7 @@ import { toVerboseTimeRange, } from "../util"; import { currencyCodes } from "../constants/currencyCodes"; - -const windowOptions = [ - { name: "Today", value: "today" }, - { name: "Yesterday", value: "yesterday" }, - { name: "Last 24h", value: "24h" }, - { name: "Last 48h", value: "48h" }, - { name: "Week-to-date", value: "week" }, - { name: "Last week", value: "lastweek" }, - { name: "Last 7 days", value: "7d" }, - { name: "Last 14 days", value: "14d" }, -]; +import { windowOptions } from "../constants/windowsOptions"; const aggregationOptions = [ { name: "Cluster", value: "cluster" }, diff --git a/src/pages/Assets.js b/src/pages/Assets.js new file mode 100644 index 00000000..3ce3e263 --- /dev/null +++ b/src/pages/Assets.js @@ -0,0 +1,480 @@ +import * as React from "react"; +import Page from "../components/Page"; +import Header from "../components/Header"; +import Footer from "../components/Footer"; +import { + Button, + Loading, + InlineNotification, + FluidDatePicker, + DatePickerInput, + Tabs, + TabList, + Tab, + TabPanels, + TabPanel, + Tile, + SelectItem, + FluidSelect, +} from "@carbon/react"; +import { + Renew, + Dashboard, + VirtualMachine, + StoragePool, + Network_4, + Settings, + Money, +} from "@carbon/icons-react"; +import { useLocation, useNavigate } from "react-router"; +import AssetsService from "../services/assets"; +import { toCurrency, formatDuration, bytesToString } from "../util"; +import AssetTable from "../components/Assets/AssetTable"; +import AssetDetailsModal from "../components/Assets/AssetDetailsModal"; +import { currencyCodes } from "../constants/currencyCodes"; +import { windowOptions } from "../constants/windowsOptions"; + +const AssetsPage = () => { + const [assetsData, setAssetsData] = React.useState({}); + const [loading, setLoading] = React.useState(true); + const [errors, setErrors] = React.useState([]); + const [dateRange, setDateRange] = React.useState([null, null]); + const [selectedAsset, setSelectedAsset] = React.useState(null); + const [isModalOpen, setIsModalOpen] = React.useState(false); + + const routerLocation = useLocation(); + const navigate = useNavigate(); + const searchParams = new URLSearchParams(routerLocation.search); + + const winParam = searchParams.get("window") || "7d"; + const currencyParam = searchParams.get("currency") || "USD"; + + const formatCurrency = (value) => toCurrency(value, currencyParam, 2); + + React.useEffect(() => { + fetchData(); + }, [winParam]); + + async function fetchData() { + setLoading(true); + setErrors([]); + + try { + const resp = await AssetsService.fetchAssets(winParam); + if (resp && resp.data) { + setAssetsData(resp.data); + } else { + setAssetsData({}); + } + } catch (err) { + setErrors([ + { + primary: "Failed to load assets data", + secondary: err.message || "Please try again later.", + }, + ]); + } + + setLoading(false); + } + + const handleDateChange = (dates) => { + setDateRange(dates); + if (dates && dates.length === 2 && dates[0] && dates[1]) { + // RFC3339 without fractional seconds + // Regex replaces .000Z (or any millisecond precision) with Z + const start = dates[0].toISOString().replace(/\.\d{3}Z$/, "Z"); + const end = dates[1].toISOString().replace(/\.\d{3}Z$/, "Z"); + searchParams.set("window", `${start},${end}`); + navigate(`?${searchParams.toString()}`); + } + }; + + const handleRowClick = (rowId) => { + const asset = assetsData[rowId]; + if (asset) { + setSelectedAsset(asset); + setIsModalOpen(true); + } + }; + + const processData = React.useMemo(() => { + const all = []; + const compute = []; + const storage = []; + const network = []; + const management = []; + let totalCost = 0; + + Object.entries(assetsData).forEach(([id, asset]) => { + const category = asset.properties?.category || "Unknown"; + const cost = asset.totalCost || 0; + totalCost += cost; + + const common = { + id, + category, + type: asset.type, + duration: asset.minutes || 0, + cost: cost, + raw: asset, + cluster: asset.properties?.cluster, + }; + + all.push(common); + + if (category === "Compute") { + compute.push({ + ...common, + name: asset.properties?.name || asset.name, + cpu: asset.cpuCores, + ram: asset.ramBytes, + specs: `${asset.cpuCores} Cores / ${bytesToString(asset.ramBytes)}`, + cpuCost: asset.cpuCost || 0, + ramCost: asset.ramCost || 0, + gpuCount: asset.gpuCount || 0, + gpuCost: asset.gpuCost || 0, + preemptible: asset.preemptible ? "Yes" : "No", + }); + } else if (category === "Storage") { + storage.push({ + ...common, + volumeName: asset.volumeName || asset.properties?.name, + claim: asset.claimName, + storageClass: asset.storageClass, + size: asset.bytes, + namespace: asset.claimNamespace, + }); + } else if (category === "Network") { + network.push({ + ...common, + name: asset.properties?.name, + ip: asset.ip, + }); + } else if (category === "Management") { + management.push({ + ...common, + service: asset.properties?.service, + cluster: asset.properties?.cluster, + }); + } + }); + + return { + all, + compute, + storage, + network, + management, + totalCost, + categoryTotals: { + Total: totalCost, + Compute: compute.reduce((acc, item) => acc + item.cost, 0), + Storage: storage.reduce((acc, item) => acc + item.cost, 0), + Network: network.reduce((acc, item) => acc + item.cost, 0), + Management: management.reduce((acc, item) => acc + item.cost, 0), + }, + }; + }, [assetsData]); + + return ( + +
+ +
+ + {/* error notifications */} + {!loading && errors.length > 0 && ( +
+ {errors.map((error, index) => ( + + ))} +
+ )} + + {/* Tiles */} +
+
+ {[ + { name: "Total", icon: Money }, + { name: "Compute", icon: VirtualMachine }, + { name: "Storage", icon: StoragePool }, + { name: "Network", icon: Network_4 }, + { name: "Management", icon: Settings }, + ].map((cat) => ( + +
+ +

{cat.name} Cost

+
+

+ {formatCurrency(processData.categoryTotals[cat.name])} +

+
+ ))} +
+ + {/* Tabs and Tables */} + {loading ? ( +
+ +
+ ) : ( + +
+ + All + Compute + Storage + Network + Management + +
+
+ { + searchParams.set("currency", e.target.value); + navigate(`?${searchParams.toString()}`); + }} + > + {currencyCodes.map((code) => ( + + ))} + +
+
+ o.value === winParam) + ? winParam + : "" + } + onChange={(e) => { + setDateRange([null, null]); + searchParams.set("window", e.target.value); + navigate(`?${searchParams.toString()}`); + }} + > + + {windowOptions.map((opt) => ( + + ))} + +
+
+ + + + +
+
+
+ + + {/* All Assets Table */} + + + + {/* Compute Assets Table */} + + ({ + id: r.id, + name: r.name, + type: r.type, + cluster: r.cluster, + specs: r.specs, + gpuCount: r.gpuCount, + preemptible: r.preemptible, + duration: r.duration, + cpuCost: r.cpuCost, + ramCost: r.ramCost, + gpuCost: r.gpuCost, + totalCost: r.cost, + }))} + formatters={{ + duration: formatDuration, + cpuCost: formatCurrency, + ramCost: formatCurrency, + gpuCost: formatCurrency, + totalCost: formatCurrency, + }} + onRowClick={handleRowClick} + /> + + + {/* Storage Assets Table */} + + ({ + id: r.id, + volumeName: r.volumeName, + claim: r.claim, + namespace: r.namespace, + cluster: r.cluster, + storageClass: r.storageClass, + size: r.size, + duration: r.duration, + cost: r.cost, + }))} + formatters={{ + size: bytesToString, + duration: formatDuration, + cost: formatCurrency, + }} + onRowClick={handleRowClick} + /> + + + {/* Network Assets Table */} + + ({ + id: r.id, + name: r.name, + cluster: r.cluster, + ip: r.ip, + duration: r.duration, + cost: r.cost, + }))} + formatters={{ + duration: formatDuration, + cost: formatCurrency, + }} + onRowClick={handleRowClick} + /> + + + {/* Management Assets Table */} + + ({ + id: r.id, + service: r.service, + cluster: r.cluster, + duration: r.duration, + cost: r.cost, + }))} + formatters={{ + duration: formatDuration, + cost: formatCurrency, + }} + onRowClick={handleRowClick} + /> + + +
+ )} +
+ +