Simplify UI with clean card grid layout
- Redesigned with simpler card-based grid layout - Added grid/list view toggle - Cleaner status badges (ONLINE/OFFLINE) - Simplified stats display (2 stats per card) - Removed complex animations and effects - Easier to read and scan quickly - Consistent spacing and typography
This commit is contained in:
parent
9568bd81d1
commit
08f1ff6af8
@ -1,10 +1,5 @@
|
||||
{
|
||||
"pages": {
|
||||
"/api/monitor/route": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/api/monitor/route.js"
|
||||
],
|
||||
"/layout": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
@ -15,6 +10,11 @@
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/page.js"
|
||||
],
|
||||
"/api/monitor/route": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/api/monitor/route.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
.next/cache/webpack/client-development/1.pack.gz
vendored
BIN
.next/cache/webpack/client-development/1.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/2.pack.gz
vendored
BIN
.next/cache/webpack/client-development/2.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/3.pack.gz
vendored
BIN
.next/cache/webpack/client-development/3.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/4.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/client-development/4.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/client-development/5.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/client-development/5.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
Binary file not shown.
Binary file not shown.
BIN
.next/cache/webpack/server-development/0.pack.gz
vendored
BIN
.next/cache/webpack/server-development/0.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/1.pack.gz
vendored
BIN
.next/cache/webpack/server-development/1.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/2.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/server-development/2.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/server-development/3.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/server-development/3.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/server-development/4.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/server-development/4.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
self.__NEXT_FONT_MANIFEST="{\"pages\":{},\"app\":{\"/Users/mattbruce/Documents/Projects/OpenClaw/Web/heartbeat-monitor/src/app/layout\":[\"static/media/4cf2300e9c8272f7-s.p.woff2\",\"static/media/93f479601ee12b01-s.p.woff2\"]},\"appUsingSizeAdjust\":true,\"pagesUsingSizeAdjust\":false}"
|
||||
self.__NEXT_FONT_MANIFEST="{\"pages\":{},\"app\":{},\"appUsingSizeAdjust\":false,\"pagesUsingSizeAdjust\":false}"
|
||||
@ -1 +1 @@
|
||||
{"pages":{},"app":{"/Users/mattbruce/Documents/Projects/OpenClaw/Web/heartbeat-monitor/src/app/layout":["static/media/4cf2300e9c8272f7-s.p.woff2","static/media/93f479601ee12b01-s.p.woff2"]},"appUsingSizeAdjust":true,"pagesUsingSizeAdjust":false}
|
||||
{"pages":{},"app":{},"appUsingSizeAdjust":false,"pagesUsingSizeAdjust":false}
|
||||
@ -51,33 +51,33 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-alert.js":
|
||||
/*!******************************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/circle-alert.js ***!
|
||||
\******************************************************************/
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/external-link.js":
|
||||
/*!*******************************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/external-link.js ***!
|
||||
\*******************************************************************/
|
||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ CircleAlert)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"10\",\n key: \"1mglay\"\n }\n ],\n [\n \"line\",\n {\n x1: \"12\",\n x2: \"12\",\n y1: \"8\",\n y2: \"12\",\n key: \"1pkeuh\"\n }\n ],\n [\n \"line\",\n {\n x1: \"12\",\n x2: \"12.01\",\n y1: \"16\",\n y2: \"16\",\n key: \"4dfq90\"\n }\n ]\n];\nconst CircleAlert = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"CircleAlert\", __iconNode);\n //# sourceMappingURL=circle-alert.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2NpcmNsZS1hbGVydC5qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFHTyxNQUFNLFVBQXVCO0lBQ2xDO1FBQUMsUUFBVTtRQUFBO1lBQUUsRUFBSTtZQUFNLENBQUksUUFBTTtZQUFBLENBQUc7WUFBTSxHQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQ3pEO1FBQUM7UUFBUSxDQUFFO1lBQUEsSUFBSSxDQUFNO1lBQUEsSUFBSSxDQUFNO1lBQUEsR0FBSSxJQUFLO1lBQUEsR0FBSSxLQUFNO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtJQUNqRTtRQUFDO1FBQVEsQ0FBRTtZQUFBLElBQUksQ0FBTTtZQUFBLElBQUksQ0FBUztZQUFBLEdBQUksS0FBTTtZQUFBLEdBQUksS0FBTTtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7Q0FDdkU7QUFhTSxrQkFBYyxrRUFBaUIsZ0JBQWUsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2NpcmNsZS1hbGVydC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY3JlYXRlTHVjaWRlSWNvbiBmcm9tICcuLi9jcmVhdGVMdWNpZGVJY29uJztcbmltcG9ydCB7IEljb25Ob2RlIH0gZnJvbSAnLi4vdHlwZXMnO1xuXG5leHBvcnQgY29uc3QgX19pY29uTm9kZTogSWNvbk5vZGUgPSBbXG4gIFsnY2lyY2xlJywgeyBjeDogJzEyJywgY3k6ICcxMicsIHI6ICcxMCcsIGtleTogJzFtZ2xheScgfV0sXG4gIFsnbGluZScsIHsgeDE6ICcxMicsIHgyOiAnMTInLCB5MTogJzgnLCB5MjogJzEyJywga2V5OiAnMXBrZXVoJyB9XSxcbiAgWydsaW5lJywgeyB4MTogJzEyJywgeDI6ICcxMi4wMScsIHkxOiAnMTYnLCB5MjogJzE2Jywga2V5OiAnNGRmcTkwJyB9XSxcbl07XG5cbi8qKlxuICogQGNvbXBvbmVudCBAbmFtZSBDaXJjbGVBbGVydFxuICogQGRlc2NyaXB0aW9uIEx1Y2lkZSBTVkcgaWNvbiBjb21wb25lbnQsIHJlbmRlcnMgU1ZHIEVsZW1lbnQgd2l0aCBjaGlsZHJlbi5cbiAqXG4gKiBAcHJldmlldyAhW2ltZ10oZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlBZ2VHMXNibk05SW1oMGRIQTZMeTkzZDNjdWR6TXViM0puTHpJd01EQXZjM1puSWdvZ0lIZHBaSFJvUFNJeU5DSUtJQ0JvWldsbmFIUTlJakkwSWdvZ0lIWnBaWGRDYjNnOUlqQWdNQ0F5TkNBeU5DSUtJQ0JtYVd4c1BTSnViMjVsSWdvZ0lITjBjbTlyWlQwaUl6QXdNQ0lnYzNSNWJHVTlJbUpoWTJ0bmNtOTFibVF0WTI5c2IzSTZJQ05tWm1ZN0lHSnZjbVJsY2kxeVlXUnBkWE02SURKd2VDSUtJQ0J6ZEhKdmEyVXRkMmxrZEdnOUlqSWlDaUFnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lnb2dJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaUNqNEtJQ0E4WTJseVkyeGxJR040UFNJeE1pSWdZM2s5SWpFeUlpQnlQU0l4TUNJZ0x6NEtJQ0E4YkdsdVpTQjRNVDBpTVRJaUlIZ3lQU0l4TWlJZ2VURTlJamdpSUhreVBTSXhNaUlnTHo0S0lDQThiR2x1WlNCNE1UMGlNVElpSUhneVBTSXhNaTR3TVNJZ2VURTlJakUySWlCNU1qMGlNVFlpSUM4K0Nqd3ZjM1puUGdvPSkgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvY2lyY2xlLWFsZXJ0XG4gKiBAc2VlIGh0dHBzOi8vbHVjaWRlLmRldi9ndWlkZS9wYWNrYWdlcy9sdWNpZGUtcmVhY3QgLSBEb2N1bWVudGF0aW9uXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IHByb3BzIC0gTHVjaWRlIGljb25zIHByb3BzIGFuZCBhbnkgdmFsaWQgU1ZHIGF0dHJpYnV0ZVxuICogQHJldHVybnMge0pTWC5FbGVtZW50fSBKU1ggRWxlbWVudFxuICpcbiAqL1xuY29uc3QgQ2lyY2xlQWxlcnQgPSBjcmVhdGVMdWNpZGVJY29uKCdDaXJjbGVBbGVydCcsIF9faWNvbk5vZGUpO1xuXG5leHBvcnQgZGVmYXVsdCBDaXJjbGVBbGVydDtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/circle-alert.js\n");
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ ExternalLink)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M15 3h6v6\",\n key: \"1q9fwt\"\n }\n ],\n [\n \"path\",\n {\n d: \"M10 14 21 3\",\n key: \"gplh6r\"\n }\n ],\n [\n \"path\",\n {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\",\n key: \"a6xqqp\"\n }\n ]\n];\nconst ExternalLink = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"ExternalLink\", __iconNode);\n //# sourceMappingURL=external-link.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2V4dGVybmFsLWxpbmsuanMiLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBR08sTUFBTSxVQUF1QjtJQUNsQztRQUFDLE1BQVE7UUFBQTtZQUFFLEdBQUcsQ0FBYTtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7SUFDMUM7UUFBQyxNQUFRO1FBQUE7WUFBRSxHQUFHLENBQWU7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQzVDO1FBQUMsTUFBUTtRQUFBO1lBQUUsR0FBRyxDQUE0RDtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7Q0FDM0Y7QUFhTSxtQkFBZSxrRUFBaUIsaUJBQWdCLENBQVUiLCJzb3VyY2VzIjpbIi9Vc2Vycy9tYXR0YnJ1Y2UvRG9jdW1lbnRzL1Byb2plY3RzL3NyYy9pY29ucy9leHRlcm5hbC1saW5rLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydwYXRoJywgeyBkOiAnTTE1IDNoNnY2Jywga2V5OiAnMXE5Znd0JyB9XSxcbiAgWydwYXRoJywgeyBkOiAnTTEwIDE0IDIxIDMnLCBrZXk6ICdncGxoNnInIH1dLFxuICBbJ3BhdGgnLCB7IGQ6ICdNMTggMTN2NmEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMlY4YTIgMiAwIDAgMSAyLTJoNicsIGtleTogJ2E2eHFxcCcgfV0sXG5dO1xuXG4vKipcbiAqIEBjb21wb25lbnQgQG5hbWUgRXh0ZXJuYWxMaW5rXG4gKiBAZGVzY3JpcHRpb24gTHVjaWRlIFNWRyBpY29uIGNvbXBvbmVudCwgcmVuZGVycyBTVkcgRWxlbWVudCB3aXRoIGNoaWxkcmVuLlxuICpcbiAqIEBwcmV2aWV3ICFbaW1nXShkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUFnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JZ29nSUhkcFpIUm9QU0l5TkNJS0lDQm9aV2xuYUhROUlqSTBJZ29nSUhacFpYZENiM2c5SWpBZ01DQXlOQ0F5TkNJS0lDQm1hV3hzUFNKdWIyNWxJZ29nSUhOMGNtOXJaVDBpSXpBd01DSWdjM1I1YkdVOUltSmhZMnRuY205MWJtUXRZMjlzYjNJNklDTm1abVk3SUdKdmNtUmxjaTF5WVdScGRYTTZJREp3ZUNJS0lDQnpkSEp2YTJVdGQybGtkR2c5SWpJaUNpQWdjM1J5YjJ0bExXeHBibVZqWVhBOUluSnZkVzVrSWdvZ0lITjBjbTlyWlMxc2FXNWxhbTlwYmowaWNtOTFibVFpQ2o0S0lDQThjR0YwYUNCa1BTSk5NVFVnTTJnMmRqWWlJQzgrQ2lBZ1BIQmhkR2dnWkQwaVRURXdJREUwSURJeElETWlJQzgrQ2lBZ1BIQmhkR2dnWkQwaVRURTRJREV6ZGpaaE1pQXlJREFnTUNBeExUSWdNa2cxWVRJZ01pQXdJREFnTVMweUxUSldPR0V5SURJZ01DQXdJREVnTWkweWFEWWlJQzgrQ2p3dmMzWm5QZ289KSAtIGh0dHBzOi8vbHVjaWRlLmRldi9pY29ucy9leHRlcm5hbC1saW5rXG4gKiBAc2VlIGh0dHBzOi8vbHVjaWRlLmRldi9ndWlkZS9wYWNrYWdlcy9sdWNpZGUtcmVhY3QgLSBEb2N1bWVudGF0aW9uXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IHByb3BzIC0gTHVjaWRlIGljb25zIHByb3BzIGFuZCBhbnkgdmFsaWQgU1ZHIGF0dHJpYnV0ZVxuICogQHJldHVybnMge0pTWC5FbGVtZW50fSBKU1ggRWxlbWVudFxuICpcbiAqL1xuY29uc3QgRXh0ZXJuYWxMaW5rID0gY3JlYXRlTHVjaWRlSWNvbignRXh0ZXJuYWxMaW5rJywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IEV4dGVybmFsTGluaztcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/external-link.js\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/clock.js":
|
||||
/*!***********************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/clock.js ***!
|
||||
\***********************************************************/
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/layout-grid.js":
|
||||
/*!*****************************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/layout-grid.js ***!
|
||||
\*****************************************************************/
|
||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ Clock)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"circle\",\n {\n cx: \"12\",\n cy: \"12\",\n r: \"10\",\n key: \"1mglay\"\n }\n ],\n [\n \"polyline\",\n {\n points: \"12 6 12 12 16 14\",\n key: \"68esgv\"\n }\n ]\n];\nconst Clock = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Clock\", __iconNode);\n //# sourceMappingURL=clock.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2Nsb2NrLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxRQUFVO1FBQUE7WUFBRSxFQUFJO1lBQU0sQ0FBSSxRQUFNO1lBQUEsQ0FBRztZQUFNLEdBQUs7UUFBQSxDQUFVO0tBQUE7SUFDekQ7UUFBQyxVQUFZO1FBQUE7WUFBRSxRQUFRLENBQW9CO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtDQUM1RDtBQWFNLFlBQVEsa0VBQWlCLFVBQVMsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2Nsb2NrLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydjaXJjbGUnLCB7IGN4OiAnMTInLCBjeTogJzEyJywgcjogJzEwJywga2V5OiAnMW1nbGF5JyB9XSxcbiAgWydwb2x5bGluZScsIHsgcG9pbnRzOiAnMTIgNiAxMiAxMiAxNiAxNCcsIGtleTogJzY4ZXNndicgfV0sXG5dO1xuXG4vKipcbiAqIEBjb21wb25lbnQgQG5hbWUgQ2xvY2tcbiAqIEBkZXNjcmlwdGlvbiBMdWNpZGUgU1ZHIGljb24gY29tcG9uZW50LCByZW5kZXJzIFNWRyBFbGVtZW50IHdpdGggY2hpbGRyZW4uXG4gKlxuICogQHByZXZpZXcgIVtpbWddKGRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QWdlRzFzYm5NOUltaDBkSEE2THk5M2QzY3Vkek11YjNKbkx6SXdNREF2YzNabklnb2dJSGRwWkhSb1BTSXlOQ0lLSUNCb1pXbG5hSFE5SWpJMElnb2dJSFpwWlhkQ2IzZzlJakFnTUNBeU5DQXlOQ0lLSUNCbWFXeHNQU0p1YjI1bElnb2dJSE4wY205clpUMGlJekF3TUNJZ2MzUjViR1U5SW1KaFkydG5jbTkxYm1RdFkyOXNiM0k2SUNObVptWTdJR0p2Y21SbGNpMXlZV1JwZFhNNklESndlQ0lLSUNCemRISnZhMlV0ZDJsa2RHZzlJaklpQ2lBZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW5KdmRXNWtJZ29nSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlDajRLSUNBOFkybHlZMnhsSUdONFBTSXhNaUlnWTNrOUlqRXlJaUJ5UFNJeE1DSWdMejRLSUNBOGNHOXNlV3hwYm1VZ2NHOXBiblJ6UFNJeE1pQTJJREV5SURFeUlERTJJREUwSWlBdlBnbzhMM04yWno0SykgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvY2xvY2tcbiAqIEBzZWUgaHR0cHM6Ly9sdWNpZGUuZGV2L2d1aWRlL3BhY2thZ2VzL2x1Y2lkZS1yZWFjdCAtIERvY3VtZW50YXRpb25cbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gcHJvcHMgLSBMdWNpZGUgaWNvbnMgcHJvcHMgYW5kIGFueSB2YWxpZCBTVkcgYXR0cmlidXRlXG4gKiBAcmV0dXJucyB7SlNYLkVsZW1lbnR9IEpTWCBFbGVtZW50XG4gKlxuICovXG5jb25zdCBDbG9jayA9IGNyZWF0ZUx1Y2lkZUljb24oJ0Nsb2NrJywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IENsb2NrO1xuIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/clock.js\n");
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ LayoutGrid)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"rect\",\n {\n width: \"7\",\n height: \"7\",\n x: \"3\",\n y: \"3\",\n rx: \"1\",\n key: \"1g98yp\"\n }\n ],\n [\n \"rect\",\n {\n width: \"7\",\n height: \"7\",\n x: \"14\",\n y: \"3\",\n rx: \"1\",\n key: \"6d4xhi\"\n }\n ],\n [\n \"rect\",\n {\n width: \"7\",\n height: \"7\",\n x: \"14\",\n y: \"14\",\n rx: \"1\",\n key: \"nxv5o0\"\n }\n ],\n [\n \"rect\",\n {\n width: \"7\",\n height: \"7\",\n x: \"3\",\n y: \"14\",\n rx: \"1\",\n key: \"1bb6yr\"\n }\n ]\n];\nconst LayoutGrid = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"LayoutGrid\", __iconNode);\n //# sourceMappingURL=layout-grid.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2xheW91dC1ncmlkLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxPQUFRO1FBQUE7WUFBRSxPQUFPO1lBQUssQ0FBUTtZQUFLLENBQUc7WUFBSyxHQUFHLENBQUs7WUFBQSxJQUFJLENBQUs7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQzVFO1FBQUMsT0FBUTtRQUFBO1lBQUUsT0FBTztZQUFLLENBQVE7WUFBSyxDQUFHO1lBQU0sR0FBRyxDQUFLO1lBQUEsSUFBSSxDQUFLO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtJQUM3RTtRQUFDLE9BQVE7UUFBQTtZQUFFLE9BQU87WUFBSyxDQUFRO1lBQUssQ0FBRztZQUFNLEdBQUcsQ0FBTTtZQUFBLElBQUksQ0FBSztZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7SUFDOUU7UUFBQyxPQUFRO1FBQUE7WUFBRSxPQUFPO1lBQUssQ0FBUTtZQUFLLENBQUc7WUFBSyxHQUFHLENBQU07WUFBQSxJQUFJLENBQUs7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0NBQy9FO0FBYU0saUJBQWEsa0VBQWlCLGVBQWMsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2xheW91dC1ncmlkLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydyZWN0JywgeyB3aWR0aDogJzcnLCBoZWlnaHQ6ICc3JywgeDogJzMnLCB5OiAnMycsIHJ4OiAnMScsIGtleTogJzFnOTh5cCcgfV0sXG4gIFsncmVjdCcsIHsgd2lkdGg6ICc3JywgaGVpZ2h0OiAnNycsIHg6ICcxNCcsIHk6ICczJywgcng6ICcxJywga2V5OiAnNmQ0eGhpJyB9XSxcbiAgWydyZWN0JywgeyB3aWR0aDogJzcnLCBoZWlnaHQ6ICc3JywgeDogJzE0JywgeTogJzE0Jywgcng6ICcxJywga2V5OiAnbnh2NW8wJyB9XSxcbiAgWydyZWN0JywgeyB3aWR0aDogJzcnLCBoZWlnaHQ6ICc3JywgeDogJzMnLCB5OiAnMTQnLCByeDogJzEnLCBrZXk6ICcxYmI2eXInIH1dLFxuXTtcblxuLyoqXG4gKiBAY29tcG9uZW50IEBuYW1lIExheW91dEdyaWRcbiAqIEBkZXNjcmlwdGlvbiBMdWNpZGUgU1ZHIGljb24gY29tcG9uZW50LCByZW5kZXJzIFNWRyBFbGVtZW50IHdpdGggY2hpbGRyZW4uXG4gKlxuICogQHByZXZpZXcgIVtpbWddKGRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QWdlRzFzYm5NOUltaDBkSEE2THk5M2QzY3Vkek11YjNKbkx6SXdNREF2YzNabklnb2dJSGRwWkhSb1BTSXlOQ0lLSUNCb1pXbG5hSFE5SWpJMElnb2dJSFpwWlhkQ2IzZzlJakFnTUNBeU5DQXlOQ0lLSUNCbWFXeHNQU0p1YjI1bElnb2dJSE4wY205clpUMGlJekF3TUNJZ2MzUjViR1U5SW1KaFkydG5jbTkxYm1RdFkyOXNiM0k2SUNObVptWTdJR0p2Y21SbGNpMXlZV1JwZFhNNklESndlQ0lLSUNCemRISnZhMlV0ZDJsa2RHZzlJaklpQ2lBZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW5KdmRXNWtJZ29nSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlDajRLSUNBOGNtVmpkQ0IzYVdSMGFEMGlOeUlnYUdWcFoyaDBQU0kzSWlCNFBTSXpJaUI1UFNJeklpQnllRDBpTVNJZ0x6NEtJQ0E4Y21WamRDQjNhV1IwYUQwaU55SWdhR1ZwWjJoMFBTSTNJaUI0UFNJeE5DSWdlVDBpTXlJZ2NuZzlJakVpSUM4K0NpQWdQSEpsWTNRZ2QybGtkR2c5SWpjaUlHaGxhV2RvZEQwaU55SWdlRDBpTVRRaUlIazlJakUwSWlCeWVEMGlNU0lnTHo0S0lDQThjbVZqZENCM2FXUjBhRDBpTnlJZ2FHVnBaMmgwUFNJM0lpQjRQU0l6SWlCNVBTSXhOQ0lnY25nOUlqRWlJQzgrQ2p3dmMzWm5QZ289KSAtIGh0dHBzOi8vbHVjaWRlLmRldi9pY29ucy9sYXlvdXQtZ3JpZFxuICogQHNlZSBodHRwczovL2x1Y2lkZS5kZXYvZ3VpZGUvcGFja2FnZXMvbHVjaWRlLXJlYWN0IC0gRG9jdW1lbnRhdGlvblxuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBwcm9wcyAtIEx1Y2lkZSBpY29ucyBwcm9wcyBhbmQgYW55IHZhbGlkIFNWRyBhdHRyaWJ1dGVcbiAqIEByZXR1cm5zIHtKU1guRWxlbWVudH0gSlNYIEVsZW1lbnRcbiAqXG4gKi9cbmNvbnN0IExheW91dEdyaWQgPSBjcmVhdGVMdWNpZGVJY29uKCdMYXlvdXRHcmlkJywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IExheW91dEdyaWQ7XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/layout-grid.js\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/play.js":
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/list.js":
|
||||
/*!**********************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/play.js ***!
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/list.js ***!
|
||||
\**********************************************************/
|
||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ Play)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"polygon\",\n {\n points: \"6 3 20 12 6 21 6 3\",\n key: \"1oa8hb\"\n }\n ]\n];\nconst Play = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Play\", __iconNode);\n //# sourceMappingURL=play.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL3BsYXkuanMiLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBR2EsaUJBQXVCO0lBQUM7UUFBQyxTQUFXO1FBQUEsQ0FBRTtZQUFBLE9BQVEscUJBQXNCO1lBQUEsS0FBSyxDQUFTO1FBQUEsQ0FBQztLQUFDO0NBQUE7QUFhM0YsV0FBTyxrRUFBaUIsU0FBUSxDQUFVIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9zcmMvaWNvbnMvcGxheS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY3JlYXRlTHVjaWRlSWNvbiBmcm9tICcuLi9jcmVhdGVMdWNpZGVJY29uJztcbmltcG9ydCB7IEljb25Ob2RlIH0gZnJvbSAnLi4vdHlwZXMnO1xuXG5leHBvcnQgY29uc3QgX19pY29uTm9kZTogSWNvbk5vZGUgPSBbWydwb2x5Z29uJywgeyBwb2ludHM6ICc2IDMgMjAgMTIgNiAyMSA2IDMnLCBrZXk6ICcxb2E4aGInIH1dXTtcblxuLyoqXG4gKiBAY29tcG9uZW50IEBuYW1lIFBsYXlcbiAqIEBkZXNjcmlwdGlvbiBMdWNpZGUgU1ZHIGljb24gY29tcG9uZW50LCByZW5kZXJzIFNWRyBFbGVtZW50IHdpdGggY2hpbGRyZW4uXG4gKlxuICogQHByZXZpZXcgIVtpbWddKGRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QWdlRzFzYm5NOUltaDBkSEE2THk5M2QzY3Vkek11YjNKbkx6SXdNREF2YzNabklnb2dJSGRwWkhSb1BTSXlOQ0lLSUNCb1pXbG5hSFE5SWpJMElnb2dJSFpwWlhkQ2IzZzlJakFnTUNBeU5DQXlOQ0lLSUNCbWFXeHNQU0p1YjI1bElnb2dJSE4wY205clpUMGlJekF3TUNJZ2MzUjViR1U5SW1KaFkydG5jbTkxYm1RdFkyOXNiM0k2SUNObVptWTdJR0p2Y21SbGNpMXlZV1JwZFhNNklESndlQ0lLSUNCemRISnZhMlV0ZDJsa2RHZzlJaklpQ2lBZ2MzUnliMnRsTFd4cGJtVmpZWEE5SW5KdmRXNWtJZ29nSUhOMGNtOXJaUzFzYVc1bGFtOXBiajBpY205MWJtUWlDajRLSUNBOGNHOXNlV2R2YmlCd2IybHVkSE05SWpZZ015QXlNQ0F4TWlBMklESXhJRFlnTXlJZ0x6NEtQQzl6ZG1jK0NnPT0pIC0gaHR0cHM6Ly9sdWNpZGUuZGV2L2ljb25zL3BsYXlcbiAqIEBzZWUgaHR0cHM6Ly9sdWNpZGUuZGV2L2d1aWRlL3BhY2thZ2VzL2x1Y2lkZS1yZWFjdCAtIERvY3VtZW50YXRpb25cbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gcHJvcHMgLSBMdWNpZGUgaWNvbnMgcHJvcHMgYW5kIGFueSB2YWxpZCBTVkcgYXR0cmlidXRlXG4gKiBAcmV0dXJucyB7SlNYLkVsZW1lbnR9IEpTWCBFbGVtZW50XG4gKlxuICovXG5jb25zdCBQbGF5ID0gY3JlYXRlTHVjaWRlSWNvbignUGxheScsIF9faWNvbk5vZGUpO1xuXG5leHBvcnQgZGVmYXVsdCBQbGF5O1xuIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/play.js\n");
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ List)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M3 12h.01\",\n key: \"nlz23k\"\n }\n ],\n [\n \"path\",\n {\n d: \"M3 18h.01\",\n key: \"1tta3j\"\n }\n ],\n [\n \"path\",\n {\n d: \"M3 6h.01\",\n key: \"1rqtza\"\n }\n ],\n [\n \"path\",\n {\n d: \"M8 12h13\",\n key: \"1za7za\"\n }\n ],\n [\n \"path\",\n {\n d: \"M8 18h13\",\n key: \"1lx6n3\"\n }\n ],\n [\n \"path\",\n {\n d: \"M8 6h13\",\n key: \"ik3vkj\"\n }\n ]\n];\nconst List = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"List\", __iconNode);\n //# sourceMappingURL=list.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL2xpc3QuanMiLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBR08sTUFBTSxVQUF1QjtJQUNsQztRQUFDLE1BQVE7UUFBQTtZQUFFLEdBQUcsQ0FBYTtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7SUFDMUM7UUFBQyxNQUFRO1FBQUE7WUFBRSxHQUFHLENBQWE7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQzFDO1FBQUMsTUFBUTtRQUFBO1lBQUUsR0FBRyxDQUFZO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtJQUN6QztRQUFDLE1BQVE7UUFBQTtZQUFFLEdBQUcsQ0FBWTtZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7SUFDekM7UUFBQyxNQUFRO1FBQUE7WUFBRSxHQUFHLENBQVk7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0lBQ3pDO1FBQUMsTUFBUTtRQUFBO1lBQUUsR0FBRyxDQUFXO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtDQUMxQztBQWFNLFdBQU8sa0VBQWlCLFNBQVEsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL2xpc3QudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGNyZWF0ZUx1Y2lkZUljb24gZnJvbSAnLi4vY3JlYXRlTHVjaWRlSWNvbic7XG5pbXBvcnQgeyBJY29uTm9kZSB9IGZyb20gJy4uL3R5cGVzJztcblxuZXhwb3J0IGNvbnN0IF9faWNvbk5vZGU6IEljb25Ob2RlID0gW1xuICBbJ3BhdGgnLCB7IGQ6ICdNMyAxMmguMDEnLCBrZXk6ICdubHoyM2snIH1dLFxuICBbJ3BhdGgnLCB7IGQ6ICdNMyAxOGguMDEnLCBrZXk6ICcxdHRhM2onIH1dLFxuICBbJ3BhdGgnLCB7IGQ6ICdNMyA2aC4wMScsIGtleTogJzFycXR6YScgfV0sXG4gIFsncGF0aCcsIHsgZDogJ004IDEyaDEzJywga2V5OiAnMXphN3phJyB9XSxcbiAgWydwYXRoJywgeyBkOiAnTTggMThoMTMnLCBrZXk6ICcxbHg2bjMnIH1dLFxuICBbJ3BhdGgnLCB7IGQ6ICdNOCA2aDEzJywga2V5OiAnaWszdmtqJyB9XSxcbl07XG5cbi8qKlxuICogQGNvbXBvbmVudCBAbmFtZSBMaXN0XG4gKiBAZGVzY3JpcHRpb24gTHVjaWRlIFNWRyBpY29uIGNvbXBvbmVudCwgcmVuZGVycyBTVkcgRWxlbWVudCB3aXRoIGNoaWxkcmVuLlxuICpcbiAqIEBwcmV2aWV3ICFbaW1nXShkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUFnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JZ29nSUhkcFpIUm9QU0l5TkNJS0lDQm9aV2xuYUhROUlqSTBJZ29nSUhacFpYZENiM2c5SWpBZ01DQXlOQ0F5TkNJS0lDQm1hV3hzUFNKdWIyNWxJZ29nSUhOMGNtOXJaVDBpSXpBd01DSWdjM1I1YkdVOUltSmhZMnRuY205MWJtUXRZMjlzYjNJNklDTm1abVk3SUdKdmNtUmxjaTF5WVdScGRYTTZJREp3ZUNJS0lDQnpkSEp2YTJVdGQybGtkR2c5SWpJaUNpQWdjM1J5YjJ0bExXeHBibVZqWVhBOUluSnZkVzVrSWdvZ0lITjBjbTlyWlMxc2FXNWxhbTlwYmowaWNtOTFibVFpQ2o0S0lDQThjR0YwYUNCa1BTSk5NeUF4TW1ndU1ERWlJQzgrQ2lBZ1BIQmhkR2dnWkQwaVRUTWdNVGhvTGpBeElpQXZQZ29nSUR4d1lYUm9JR1E5SWsweklEWm9MakF4SWlBdlBnb2dJRHh3WVhSb0lHUTlJazA0SURFeWFERXpJaUF2UGdvZ0lEeHdZWFJvSUdROUlrMDRJREU0YURFeklpQXZQZ29nSUR4d1lYUm9JR1E5SWswNElEWm9NVE1pSUM4K0Nqd3ZjM1puUGdvPSkgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvbGlzdFxuICogQHNlZSBodHRwczovL2x1Y2lkZS5kZXYvZ3VpZGUvcGFja2FnZXMvbHVjaWRlLXJlYWN0IC0gRG9jdW1lbnRhdGlvblxuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBwcm9wcyAtIEx1Y2lkZSBpY29ucyBwcm9wcyBhbmQgYW55IHZhbGlkIFNWRyBhdHRyaWJ1dGVcbiAqIEByZXR1cm5zIHtKU1guRWxlbWVudH0gSlNYIEVsZW1lbnRcbiAqXG4gKi9cbmNvbnN0IExpc3QgPSBjcmVhdGVMdWNpZGVJY29uKCdMaXN0JywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IExpc3Q7XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/list.js\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
@ -101,16 +101,6 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/server.js":
|
||||
/*!************************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/server.js ***!
|
||||
\************************************************************/
|
||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ Server)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"rect\",\n {\n width: \"20\",\n height: \"8\",\n x: \"2\",\n y: \"2\",\n rx: \"2\",\n ry: \"2\",\n key: \"ngkwjq\"\n }\n ],\n [\n \"rect\",\n {\n width: \"20\",\n height: \"8\",\n x: \"2\",\n y: \"14\",\n rx: \"2\",\n ry: \"2\",\n key: \"iecqi9\"\n }\n ],\n [\n \"line\",\n {\n x1: \"6\",\n x2: \"6.01\",\n y1: \"6\",\n y2: \"6\",\n key: \"16zg32\"\n }\n ],\n [\n \"line\",\n {\n x1: \"6\",\n x2: \"6.01\",\n y1: \"18\",\n y2: \"18\",\n key: \"nzw8ys\"\n }\n ]\n];\nconst Server = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Server\", __iconNode);\n //# sourceMappingURL=server.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL3NlcnZlci5qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFHTyxNQUFNLFVBQXVCO0lBQ2xDO1FBQUMsTUFBUTtRQUFBO1lBQUUsS0FBTyxPQUFNO1lBQUEsT0FBUSxJQUFLO1lBQUEsR0FBRyxDQUFLO1lBQUEsR0FBRztZQUFLLENBQUksT0FBSztZQUFBLEdBQUksSUFBSztZQUFBLElBQUs7UUFBQSxDQUFVO0tBQUE7SUFDdEY7UUFBQyxNQUFRO1FBQUE7WUFBRSxLQUFPLE9BQU07WUFBQSxPQUFRLElBQUs7WUFBQSxHQUFHLENBQUs7WUFBQSxHQUFHO1lBQU0sQ0FBSSxPQUFLO1lBQUEsR0FBSSxJQUFLO1lBQUEsSUFBSztRQUFBLENBQVU7S0FBQTtJQUN2RjtRQUFDO1FBQVEsQ0FBRTtZQUFBLElBQUksQ0FBSztZQUFBLElBQUksQ0FBUTtZQUFBLEdBQUksSUFBSztZQUFBLEdBQUksSUFBSztZQUFBLEtBQUs7UUFBQSxDQUFVO0tBQUE7SUFDakU7UUFBQztRQUFRLENBQUU7WUFBQSxJQUFJLENBQUs7WUFBQSxJQUFJLENBQVE7WUFBQSxHQUFJLEtBQU07WUFBQSxHQUFJLEtBQU07WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0NBQ3JFO0FBYU0sYUFBUyxrRUFBaUIsV0FBVSxDQUFVIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9zcmMvaWNvbnMvc2VydmVyLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydyZWN0JywgeyB3aWR0aDogJzIwJywgaGVpZ2h0OiAnOCcsIHg6ICcyJywgeTogJzInLCByeDogJzInLCByeTogJzInLCBrZXk6ICduZ2t3anEnIH1dLFxuICBbJ3JlY3QnLCB7IHdpZHRoOiAnMjAnLCBoZWlnaHQ6ICc4JywgeDogJzInLCB5OiAnMTQnLCByeDogJzInLCByeTogJzInLCBrZXk6ICdpZWNxaTknIH1dLFxuICBbJ2xpbmUnLCB7IHgxOiAnNicsIHgyOiAnNi4wMScsIHkxOiAnNicsIHkyOiAnNicsIGtleTogJzE2emczMicgfV0sXG4gIFsnbGluZScsIHsgeDE6ICc2JywgeDI6ICc2LjAxJywgeTE6ICcxOCcsIHkyOiAnMTgnLCBrZXk6ICduenc4eXMnIH1dLFxuXTtcblxuLyoqXG4gKiBAY29tcG9uZW50IEBuYW1lIFNlcnZlclxuICogQGRlc2NyaXB0aW9uIEx1Y2lkZSBTVkcgaWNvbiBjb21wb25lbnQsIHJlbmRlcnMgU1ZHIEVsZW1lbnQgd2l0aCBjaGlsZHJlbi5cbiAqXG4gKiBAcHJldmlldyAhW2ltZ10oZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlBZ2VHMXNibk05SW1oMGRIQTZMeTkzZDNjdWR6TXViM0puTHpJd01EQXZjM1puSWdvZ0lIZHBaSFJvUFNJeU5DSUtJQ0JvWldsbmFIUTlJakkwSWdvZ0lIWnBaWGRDYjNnOUlqQWdNQ0F5TkNBeU5DSUtJQ0JtYVd4c1BTSnViMjVsSWdvZ0lITjBjbTlyWlQwaUl6QXdNQ0lnYzNSNWJHVTlJbUpoWTJ0bmNtOTFibVF0WTI5c2IzSTZJQ05tWm1ZN0lHSnZjbVJsY2kxeVlXUnBkWE02SURKd2VDSUtJQ0J6ZEhKdmEyVXRkMmxrZEdnOUlqSWlDaUFnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lnb2dJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaUNqNEtJQ0E4Y21WamRDQjNhV1IwYUQwaU1qQWlJR2hsYVdkb2REMGlPQ0lnZUQwaU1pSWdlVDBpTWlJZ2NuZzlJaklpSUhKNVBTSXlJaUF2UGdvZ0lEeHlaV04wSUhkcFpIUm9QU0l5TUNJZ2FHVnBaMmgwUFNJNElpQjRQU0l5SWlCNVBTSXhOQ0lnY25nOUlqSWlJSEo1UFNJeUlpQXZQZ29nSUR4c2FXNWxJSGd4UFNJMklpQjRNajBpTmk0d01TSWdlVEU5SWpZaUlIa3lQU0kySWlBdlBnb2dJRHhzYVc1bElIZ3hQU0kySWlCNE1qMGlOaTR3TVNJZ2VURTlJakU0SWlCNU1qMGlNVGdpSUM4K0Nqd3ZjM1puUGdvPSkgLSBodHRwczovL2x1Y2lkZS5kZXYvaWNvbnMvc2VydmVyXG4gKiBAc2VlIGh0dHBzOi8vbHVjaWRlLmRldi9ndWlkZS9wYWNrYWdlcy9sdWNpZGUtcmVhY3QgLSBEb2N1bWVudGF0aW9uXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IHByb3BzIC0gTHVjaWRlIGljb25zIHByb3BzIGFuZCBhbnkgdmFsaWQgU1ZHIGF0dHJpYnV0ZVxuICogQHJldHVybnMge0pTWC5FbGVtZW50fSBKU1ggRWxlbWVudFxuICpcbiAqL1xuY29uc3QgU2VydmVyID0gY3JlYXRlTHVjaWRlSWNvbignU2VydmVyJywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IFNlcnZlcjtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/server.js\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/trash-2.js":
|
||||
/*!*************************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/trash-2.js ***!
|
||||
@ -121,16 +111,6 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/icons/trending-up.js":
|
||||
/*!*****************************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/icons/trending-up.js ***!
|
||||
\*****************************************************************/
|
||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ __iconNode: () => (/* binding */ __iconNode),\n/* harmony export */ \"default\": () => (/* binding */ TrendingUp)\n/* harmony export */ });\n/* harmony import */ var _createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../createLucideIcon.js */ \"(ssr)/./node_modules/lucide-react/dist/esm/createLucideIcon.js\");\n/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */ \nconst __iconNode = [\n [\n \"polyline\",\n {\n points: \"22 7 13.5 15.5 8.5 10.5 2 17\",\n key: \"126l90\"\n }\n ],\n [\n \"polyline\",\n {\n points: \"16 7 22 7 22 13\",\n key: \"kwv8wd\"\n }\n ]\n];\nconst TrendingUp = (0,_createLucideIcon_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"TrendingUp\", __iconNode);\n //# sourceMappingURL=trending-up.js.map\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvbHVjaWRlLXJlYWN0L2Rpc3QvZXNtL2ljb25zL3RyZW5kaW5nLXVwLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUdPLE1BQU0sVUFBdUI7SUFDbEM7UUFBQyxVQUFZO1FBQUE7WUFBRSxRQUFRLENBQWdDO1lBQUEsS0FBSztRQUFBLENBQVU7S0FBQTtJQUN0RTtRQUFDLFVBQVk7UUFBQTtZQUFFLFFBQVEsQ0FBbUI7WUFBQSxLQUFLO1FBQUEsQ0FBVTtLQUFBO0NBQzNEO0FBYU0saUJBQWEsa0VBQWlCLGVBQWMsQ0FBVSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvc3JjL2ljb25zL3RyZW5kaW5nLXVwLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcmVhdGVMdWNpZGVJY29uIGZyb20gJy4uL2NyZWF0ZUx1Y2lkZUljb24nO1xuaW1wb3J0IHsgSWNvbk5vZGUgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBjb25zdCBfX2ljb25Ob2RlOiBJY29uTm9kZSA9IFtcbiAgWydwb2x5bGluZScsIHsgcG9pbnRzOiAnMjIgNyAxMy41IDE1LjUgOC41IDEwLjUgMiAxNycsIGtleTogJzEyNmw5MCcgfV0sXG4gIFsncG9seWxpbmUnLCB7IHBvaW50czogJzE2IDcgMjIgNyAyMiAxMycsIGtleTogJ2t3djh3ZCcgfV0sXG5dO1xuXG4vKipcbiAqIEBjb21wb25lbnQgQG5hbWUgVHJlbmRpbmdVcFxuICogQGRlc2NyaXB0aW9uIEx1Y2lkZSBTVkcgaWNvbiBjb21wb25lbnQsIHJlbmRlcnMgU1ZHIEVsZW1lbnQgd2l0aCBjaGlsZHJlbi5cbiAqXG4gKiBAcHJldmlldyAhW2ltZ10oZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlBZ2VHMXNibk05SW1oMGRIQTZMeTkzZDNjdWR6TXViM0puTHpJd01EQXZjM1puSWdvZ0lIZHBaSFJvUFNJeU5DSUtJQ0JvWldsbmFIUTlJakkwSWdvZ0lIWnBaWGRDYjNnOUlqQWdNQ0F5TkNBeU5DSUtJQ0JtYVd4c1BTSnViMjVsSWdvZ0lITjBjbTlyWlQwaUl6QXdNQ0lnYzNSNWJHVTlJbUpoWTJ0bmNtOTFibVF0WTI5c2IzSTZJQ05tWm1ZN0lHSnZjbVJsY2kxeVlXUnBkWE02SURKd2VDSUtJQ0J6ZEhKdmEyVXRkMmxrZEdnOUlqSWlDaUFnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lnb2dJSE4wY205clpTMXNhVzVsYW05cGJqMGljbTkxYm1RaUNqNEtJQ0E4Y0c5c2VXeHBibVVnY0c5cGJuUnpQU0l5TWlBM0lERXpMalVnTVRVdU5TQTRMalVnTVRBdU5TQXlJREUzSWlBdlBnb2dJRHh3YjJ4NWJHbHVaU0J3YjJsdWRITTlJakUySURjZ01qSWdOeUF5TWlBeE15SWdMejRLUEM5emRtYytDZz09KSAtIGh0dHBzOi8vbHVjaWRlLmRldi9pY29ucy90cmVuZGluZy11cFxuICogQHNlZSBodHRwczovL2x1Y2lkZS5kZXYvZ3VpZGUvcGFja2FnZXMvbHVjaWRlLXJlYWN0IC0gRG9jdW1lbnRhdGlvblxuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBwcm9wcyAtIEx1Y2lkZSBpY29ucyBwcm9wcyBhbmQgYW55IHZhbGlkIFNWRyBhdHRyaWJ1dGVcbiAqIEByZXR1cm5zIHtKU1guRWxlbWVudH0gSlNYIEVsZW1lbnRcbiAqXG4gKi9cbmNvbnN0IFRyZW5kaW5nVXAgPSBjcmVhdGVMdWNpZGVJY29uKCdUcmVuZGluZ1VwJywgX19pY29uTm9kZSk7XG5cbmV4cG9ydCBkZWZhdWx0IFRyZW5kaW5nVXA7XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/lucide-react/dist/esm/icons/trending-up.js\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/lucide-react/dist/esm/shared/src/utils.js":
|
||||
/*!****************************************************************!*\
|
||||
!*** ./node_modules/lucide-react/dist/esm/shared/src/utils.js ***!
|
||||
|
||||
@ -10,26 +10,6 @@ exports.id = "vendor-chunks/next";
|
||||
exports.ids = ["vendor-chunks/next"];
|
||||
exports.modules = {
|
||||
|
||||
/***/ "(rsc)/./node_modules/next/font/google/target.css?{\"path\":\"src/app/layout.tsx\",\"import\":\"Geist\",\"arguments\":[{\"variable\":\"--font-geist-sans\",\"subsets\":[\"latin\"]}],\"variableName\":\"geistSans\"}":
|
||||
/*!***********************************************************************************************************************************************************************************************!*\
|
||||
!*** ./node_modules/next/font/google/target.css?{"path":"src/app/layout.tsx","import":"Geist","arguments":[{"variable":"--font-geist-sans","subsets":["latin"]}],"variableName":"geistSans"} ***!
|
||||
\***********************************************************************************************************************************************************************************************/
|
||||
/***/ ((module) => {
|
||||
|
||||
eval("// Exports\nmodule.exports = {\n\t\"style\": {\"fontFamily\":\"'Geist', 'Geist Fallback'\",\"fontStyle\":\"normal\"},\n\t\"className\": \"__className_188709\",\n\t\"variable\": \"__variable_188709\"\n};\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHJzYykvLi9ub2RlX21vZHVsZXMvbmV4dC9mb250L2dvb2dsZS90YXJnZXQuY3NzP3tcInBhdGhcIjpcInNyYy9hcHAvbGF5b3V0LnRzeFwiLFwiaW1wb3J0XCI6XCJHZWlzdFwiLFwiYXJndW1lbnRzXCI6W3tcInZhcmlhYmxlXCI6XCItLWZvbnQtZ2Vpc3Qtc2Fuc1wiLFwic3Vic2V0c1wiOltcImxhdGluXCJdfV0sXCJ2YXJpYWJsZU5hbWVcIjpcImdlaXN0U2Fuc1wifSIsIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0EsV0FBVyw4REFBOEQ7QUFDekU7QUFDQTtBQUNBIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9PcGVuQ2xhdy9XZWIvaGVhcnRiZWF0LW1vbml0b3Ivbm9kZV9tb2R1bGVzL25leHQvZm9udC9nb29nbGUvdGFyZ2V0LmNzcz97XCJwYXRoXCI6XCJzcmMvYXBwL2xheW91dC50c3hcIixcImltcG9ydFwiOlwiR2Vpc3RcIixcImFyZ3VtZW50c1wiOlt7XCJ2YXJpYWJsZVwiOlwiLS1mb250LWdlaXN0LXNhbnNcIixcInN1YnNldHNcIjpbXCJsYXRpblwiXX1dLFwidmFyaWFibGVOYW1lXCI6XCJnZWlzdFNhbnNcIn0iXSwic291cmNlc0NvbnRlbnQiOlsiLy8gRXhwb3J0c1xubW9kdWxlLmV4cG9ydHMgPSB7XG5cdFwic3R5bGVcIjoge1wiZm9udEZhbWlseVwiOlwiJ0dlaXN0JywgJ0dlaXN0IEZhbGxiYWNrJ1wiLFwiZm9udFN0eWxlXCI6XCJub3JtYWxcIn0sXG5cdFwiY2xhc3NOYW1lXCI6IFwiX19jbGFzc05hbWVfMTg4NzA5XCIsXG5cdFwidmFyaWFibGVcIjogXCJfX3ZhcmlhYmxlXzE4ODcwOVwiXG59O1xuIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6WzBdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(rsc)/./node_modules/next/font/google/target.css?{\"path\":\"src/app/layout.tsx\",\"import\":\"Geist\",\"arguments\":[{\"variable\":\"--font-geist-sans\",\"subsets\":[\"latin\"]}],\"variableName\":\"geistSans\"}\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(rsc)/./node_modules/next/font/google/target.css?{\"path\":\"src/app/layout.tsx\",\"import\":\"Geist_Mono\",\"arguments\":[{\"variable\":\"--font-geist-mono\",\"subsets\":[\"latin\"]}],\"variableName\":\"geistMono\"}":
|
||||
/*!****************************************************************************************************************************************************************************************************!*\
|
||||
!*** ./node_modules/next/font/google/target.css?{"path":"src/app/layout.tsx","import":"Geist_Mono","arguments":[{"variable":"--font-geist-mono","subsets":["latin"]}],"variableName":"geistMono"} ***!
|
||||
\****************************************************************************************************************************************************************************************************/
|
||||
/***/ ((module) => {
|
||||
|
||||
eval("// Exports\nmodule.exports = {\n\t\"style\": {\"fontFamily\":\"'Geist Mono', 'Geist Mono Fallback'\",\"fontStyle\":\"normal\"},\n\t\"className\": \"__className_9a8899\",\n\t\"variable\": \"__variable_9a8899\"\n};\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHJzYykvLi9ub2RlX21vZHVsZXMvbmV4dC9mb250L2dvb2dsZS90YXJnZXQuY3NzP3tcInBhdGhcIjpcInNyYy9hcHAvbGF5b3V0LnRzeFwiLFwiaW1wb3J0XCI6XCJHZWlzdF9Nb25vXCIsXCJhcmd1bWVudHNcIjpbe1widmFyaWFibGVcIjpcIi0tZm9udC1nZWlzdC1tb25vXCIsXCJzdWJzZXRzXCI6W1wibGF0aW5cIl19XSxcInZhcmlhYmxlTmFtZVwiOlwiZ2Vpc3RNb25vXCJ9IiwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQSxXQUFXLHdFQUF3RTtBQUNuRjtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIi9Vc2Vycy9tYXR0YnJ1Y2UvRG9jdW1lbnRzL1Byb2plY3RzL09wZW5DbGF3L1dlYi9oZWFydGJlYXQtbW9uaXRvci9ub2RlX21vZHVsZXMvbmV4dC9mb250L2dvb2dsZS90YXJnZXQuY3NzP3tcInBhdGhcIjpcInNyYy9hcHAvbGF5b3V0LnRzeFwiLFwiaW1wb3J0XCI6XCJHZWlzdF9Nb25vXCIsXCJhcmd1bWVudHNcIjpbe1widmFyaWFibGVcIjpcIi0tZm9udC1nZWlzdC1tb25vXCIsXCJzdWJzZXRzXCI6W1wibGF0aW5cIl19XSxcInZhcmlhYmxlTmFtZVwiOlwiZ2Vpc3RNb25vXCJ9Il0sInNvdXJjZXNDb250ZW50IjpbIi8vIEV4cG9ydHNcbm1vZHVsZS5leHBvcnRzID0ge1xuXHRcInN0eWxlXCI6IHtcImZvbnRGYW1pbHlcIjpcIidHZWlzdCBNb25vJywgJ0dlaXN0IE1vbm8gRmFsbGJhY2snXCIsXCJmb250U3R5bGVcIjpcIm5vcm1hbFwifSxcblx0XCJjbGFzc05hbWVcIjogXCJfX2NsYXNzTmFtZV85YTg4OTlcIixcblx0XCJ2YXJpYWJsZVwiOiBcIl9fdmFyaWFibGVfOWE4ODk5XCJcbn07XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(rsc)/./node_modules/next/font/google/target.css?{\"path\":\"src/app/layout.tsx\",\"import\":\"Geist_Mono\",\"arguments\":[{\"variable\":\"--font-geist-mono\",\"subsets\":[\"latin\"]}],\"variableName\":\"geistMono\"}\n");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "(ssr)/./node_modules/next/dist/client/app-build-id.js":
|
||||
/*!*******************************************************!*\
|
||||
!*** ./node_modules/next/dist/client/app-build-id.js ***!
|
||||
|
||||
@ -130,7 +130,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("5e2e688d9298ba36")
|
||||
/******/ __webpack_require__.h = () => ("8a3e399d90687866")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -190,7 +190,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("e0cd64fa6e96bf0c")
|
||||
/******/ __webpack_require__.h = () => ("c48572fb170ff111")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/global */
|
||||
|
||||
@ -1,93 +1,60 @@
|
||||
/*!*******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
|
||||
!*** css ./node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[13].oneOf[2].use[1]!./node_modules/next/dist/build/webpack/loaders/next-font-loader/index.js??ruleSet[1].rules[13].oneOf[2].use[2]!./node_modules/next/font/google/target.css?{"path":"src/app/layout.tsx","import":"Geist","arguments":[{"variable":"--font-geist-sans","subsets":["latin"]}],"variableName":"geistSans"} ***!
|
||||
\*******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(/_next/static/media/8d697b304b401681-s.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(/_next/static/media/ba015fad6dcf6784-s.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(/_next/static/media/4cf2300e9c8272f7-s.p.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}@font-face {font-family: 'Geist Fallback';src: local("Arial");ascent-override: 95.94%;descent-override: 28.16%;line-gap-override: 0.00%;size-adjust: 104.76%
|
||||
}.__className_188709 {font-family: 'Geist', 'Geist Fallback';font-style: normal
|
||||
}.__variable_188709 {--font-geist-sans: 'Geist', 'Geist Fallback'
|
||||
}
|
||||
|
||||
/*!************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
|
||||
!*** css ./node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[13].oneOf[2].use[1]!./node_modules/next/dist/build/webpack/loaders/next-font-loader/index.js??ruleSet[1].rules[13].oneOf[2].use[2]!./node_modules/next/font/google/target.css?{"path":"src/app/layout.tsx","import":"Geist_Mono","arguments":[{"variable":"--font-geist-mono","subsets":["latin"]}],"variableName":"geistMono"} ***!
|
||||
\************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(/_next/static/media/9610d9e46709d722-s.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(/_next/static/media/747892c23ea88013-s.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(/_next/static/media/93f479601ee12b01-s.p.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}@font-face {font-family: 'Geist Mono Fallback';src: local("Arial");ascent-override: 74.67%;descent-override: 21.92%;line-gap-override: 0.00%;size-adjust: 134.59%
|
||||
}.__className_9a8899 {font-family: 'Geist Mono', 'Geist Mono Fallback';font-style: normal
|
||||
}.__variable_9a8899 {--font-geist-mono: 'Geist Mono', 'Geist Mono Fallback'
|
||||
}
|
||||
|
||||
/*!*****************************************************************************************************************************************************************************************************************************************************************!*\
|
||||
!*** css ./node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[13].oneOf[10].use[2]!./node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[13].oneOf[10].use[3]!./src/app/globals.css ***!
|
||||
\*****************************************************************************************************************************************************************************************************************************************************************/
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
--background: #0F172A;
|
||||
--foreground: #F8FAFC;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1E293B;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #334155;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #475569;
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
* {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Focus visible styles */
|
||||
:focus-visible {
|
||||
outline: 2px solid #22C55E;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Selection color */
|
||||
::selection {
|
||||
background: rgba(34, 197, 94, 0.3);
|
||||
color: #F8FAFC;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"c":["app/page","webpack"],"r":[],"m":["(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/circle-check.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/circle-x.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/layout-dashboard.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/server.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/settings.js","(app-pages-browser)/./node_modules/lucide-react/dist/esm/icons/trending-up.js","(app-pages-browser)/./node_modules/recharts/es6/cartesian/Area.js","(app-pages-browser)/./node_modules/recharts/es6/cartesian/CartesianGrid.js","(app-pages-browser)/./node_modules/recharts/es6/chart/AreaChart.js"]}
|
||||
62
.next/static/webpack/app/page.591979329fc83864.hot-update.js
Normal file
62
.next/static/webpack/app/page.591979329fc83864.hot-update.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,60 +0,0 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/compat get default export */
|
||||
/******/ (() => {
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = (module) => {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ () => (module['default']) :
|
||||
/******/ () => (module);
|
||||
/******/ __webpack_require__.d(getter, { a: getter });
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/create fake namespace object */
|
||||
/******/ (() => {
|
||||
/******/ var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__);
|
||||
/******/ var leafPrototypes;
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 16: return value when it's Promise-like
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = this(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if(typeof value === 'object' && value) {
|
||||
/******/ if((mode & 4) && value.__esModule) return value;
|
||||
/******/ if((mode & 16) && typeof value.then === 'function') return value;
|
||||
/******/ }
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ var def = {};
|
||||
/******/ leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];
|
||||
/******/ for(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {
|
||||
/******/ Object.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key])));
|
||||
/******/ }
|
||||
/******/ def['default'] = () => (value);
|
||||
/******/ __webpack_require__.d(ns, def);
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("e0cd64fa6e96bf0c")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
18
.next/static/webpack/webpack.591979329fc83864.hot-update.js
Normal file
18
.next/static/webpack/webpack.591979329fc83864.hot-update.js
Normal file
@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("c48572fb170ff111")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
18
.next/static/webpack/webpack.a5a6469a7f87f9b7.hot-update.js
Normal file
18
.next/static/webpack/webpack.a5a6469a7f87f9b7.hot-update.js
Normal file
@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("591979329fc83864")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
30
.next/trace
30
.next/trace
File diff suppressed because one or more lines are too long
105
data/status.json
Normal file
105
data/status.json
Normal file
@ -0,0 +1,105 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:37:28.239Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "blog-backup",
|
||||
"timestamp": "2026-02-18T19:37:30.279Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "heartbeat-monitor",
|
||||
"timestamp": "2026-02-18T19:37:33.658Z",
|
||||
"status": "up",
|
||||
"responseTime": 23
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:56.164Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:56.921Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:57.231Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:57.415Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:57.587Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:57.805Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:58.012Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:58.264Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:58.542Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:58.856Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:59.358Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:59.798Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:38:59.960Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "project-hub",
|
||||
"timestamp": "2026-02-18T19:39:00.220Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "blog-backup",
|
||||
"timestamp": "2026-02-18T19:39:05.997Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "blog-backup",
|
||||
"timestamp": "2026-02-18T19:39:06.652Z",
|
||||
"status": "down"
|
||||
},
|
||||
{
|
||||
"appId": "blog-backup",
|
||||
"timestamp": "2026-02-18T19:39:06.827Z",
|
||||
"status": "down"
|
||||
}
|
||||
]
|
||||
}
|
||||
636
src/app/page.tsx
636
src/app/page.tsx
@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Activity, Plus, RefreshCw, Trash2, Server, Clock, TrendingUp, AlertCircle, CheckCircle2, XCircle, ExternalLink, LayoutDashboard, Settings, Bell } from "lucide-react";
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts";
|
||||
import { Activity, Plus, RefreshCw, Trash2, ExternalLink, LayoutGrid, List, Settings } from "lucide-react";
|
||||
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
@ -24,20 +24,14 @@ interface StatusEntry {
|
||||
responseTime?: number;
|
||||
}
|
||||
|
||||
interface UptimeData {
|
||||
time: string;
|
||||
uptime: number;
|
||||
responseTime: number;
|
||||
}
|
||||
|
||||
export default function HeartbeatMonitor() {
|
||||
const [apps, setApps] = useState<App[]>([]);
|
||||
const [status, setStatus] = useState<StatusEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [checking, setChecking] = useState<string | null>(null);
|
||||
const [showAddApp, setShowAddApp] = useState(false);
|
||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
||||
const [selectedApp, setSelectedApp] = useState<App | null>(null);
|
||||
const [activeTab, setActiveTab] = useState("dashboard");
|
||||
const [newApp, setNewApp] = useState<Partial<App>>({
|
||||
name: "",
|
||||
description: "",
|
||||
@ -147,109 +141,63 @@ export default function HeartbeatMonitor() {
|
||||
function getAppStatus(appId: string) {
|
||||
const appStatus = status.filter((s) => s.appId === appId);
|
||||
const latest = appStatus[appStatus.length - 1];
|
||||
return {
|
||||
latest,
|
||||
history: appStatus.slice(-20),
|
||||
uptime: calculateUptime(appStatus),
|
||||
};
|
||||
const isUp = latest?.status === "up";
|
||||
const uptime = appStatus.length > 0
|
||||
? Math.round((appStatus.filter(s => s.status === "up").length / appStatus.length) * 100)
|
||||
: 100;
|
||||
|
||||
return { latest, isUp, uptime, history: appStatus.slice(-10) };
|
||||
}
|
||||
|
||||
function calculateUptime(entries: StatusEntry[]) {
|
||||
if (entries.length === 0) return 100;
|
||||
const upCount = entries.filter((e) => e.status === "up").length;
|
||||
return Math.round((upCount / entries.length) * 100);
|
||||
}
|
||||
|
||||
function generateUptimeData(appId: string): UptimeData[] {
|
||||
const appStatus = status.filter((s) => s.appId === appId).slice(-24);
|
||||
return appStatus.map((s, i) => ({
|
||||
time: new Date(s.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
||||
uptime: s.status === "up" ? 100 : 0,
|
||||
responseTime: s.responseTime || 0,
|
||||
}));
|
||||
}
|
||||
|
||||
const categories = Array.from(new Set(apps.map((a) => a.category)));
|
||||
const totalApps = apps.length;
|
||||
const onlineApps = apps.filter((app) => {
|
||||
const { latest } = getAppStatus(app.id);
|
||||
return latest?.status === "up";
|
||||
}).length;
|
||||
const onlineApps = apps.filter((app) => getAppStatus(app.id).isUp).length;
|
||||
const offlineApps = totalApps - onlineApps;
|
||||
const overallHealth = totalApps > 0 ? Math.round((onlineApps / totalApps) * 100) : 100;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0F172A] flex items-center justify-center">
|
||||
<div className="flex items-center gap-3 text-slate-400">
|
||||
<div className="w-8 h-8 border-2 border-emerald-500/20 border-t-emerald-500 rounded-full animate-spin" />
|
||||
<span className="font-mono">Loading monitor...</span>
|
||||
</div>
|
||||
<div className="min-h-screen bg-slate-950 flex items-center justify-center">
|
||||
<div className="text-slate-400">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0F172A] text-slate-100 font-sans">
|
||||
<div className="min-h-screen bg-slate-950 text-slate-100">
|
||||
{/* Header */}
|
||||
<header className="border-b border-slate-800/50 bg-slate-900/50 backdrop-blur-xl sticky top-0 z-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Logo */}
|
||||
<header className="bg-slate-900 border-b border-slate-800">
|
||||
<div className="max-w-7xl mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 flex items-center justify-center shadow-lg shadow-emerald-500/20">
|
||||
<Activity className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-emerald-400 rounded-full animate-pulse" />
|
||||
<div className="w-10 h-10 bg-emerald-500 rounded-lg flex items-center justify-center">
|
||||
<Activity className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-white tracking-tight">Heartbeat Monitor</h1>
|
||||
<p className="text-xs text-slate-400 font-mono">System Status Dashboard</p>
|
||||
<h1 className="text-xl font-bold text-white">Heartbeat Monitor</h1>
|
||||
<p className="text-sm text-slate-400">
|
||||
{onlineApps} of {totalApps} services online
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Nav Tabs */}
|
||||
<nav className="hidden md:flex items-center gap-1 bg-slate-800/50 rounded-lg p-1">
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setActiveTab("dashboard")}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${
|
||||
activeTab === "dashboard"
|
||||
? "bg-slate-700 text-white"
|
||||
: "text-slate-400 hover:text-white hover:bg-slate-700/50"
|
||||
}`}
|
||||
onClick={() => setViewMode(viewMode === "grid" ? "list" : "grid")}
|
||||
className="p-2 bg-slate-800 rounded-lg text-slate-400 hover:text-white"
|
||||
>
|
||||
<LayoutDashboard className="w-4 h-4" />
|
||||
Dashboard
|
||||
{viewMode === "grid" ? <List className="w-5 h-5" /> : <LayoutGrid className="w-5 h-5" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("settings")}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${
|
||||
activeTab === "settings"
|
||||
? "bg-slate-700 text-white"
|
||||
: "text-slate-400 hover:text-white hover:bg-slate-700/50"
|
||||
}`}
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={fetchData}
|
||||
className="p-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-all"
|
||||
title="Refresh"
|
||||
className="p-2 bg-slate-800 rounded-lg text-slate-400 hover:text-white"
|
||||
>
|
||||
<RefreshCw className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAddApp(true)}
|
||||
className="flex items-center gap-2 bg-emerald-500 hover:bg-emerald-600 text-white px-4 py-2 rounded-lg font-medium transition-all shadow-lg shadow-emerald-500/20"
|
||||
className="flex items-center gap-2 bg-emerald-500 hover:bg-emerald-600 text-white px-4 py-2 rounded-lg font-medium"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Add App</span>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -257,322 +205,224 @@ export default function HeartbeatMonitor() {
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{activeTab === "dashboard" && (
|
||||
<>
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
{/* Total Apps */}
|
||||
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-slate-800/80 to-slate-900/80 border border-slate-700/50 p-6">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-blue-500/10 rounded-full blur-3xl -mr-16 -mt-16" />
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-xl bg-blue-500/20 flex items-center justify-center">
|
||||
<Server className="w-5 h-5 text-blue-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-400">Total Apps</span>
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-white font-mono">{totalApps}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Online */}
|
||||
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-slate-800/80 to-slate-900/80 border border-slate-700/50 p-6">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-emerald-500/10 rounded-full blur-3xl -mr-16 -mt-16" />
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-xl bg-emerald-500/20 flex items-center justify-center">
|
||||
<CheckCircle2 className="w-5 h-5 text-emerald-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-400">Online</span>
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-emerald-400 font-mono">{onlineApps}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Offline */}
|
||||
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-slate-800/80 to-slate-900/80 border border-slate-700/50 p-6">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-red-500/10 rounded-full blur-3xl -mr-16 -mt-16" />
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-xl bg-red-500/20 flex items-center justify-center">
|
||||
<XCircle className="w-5 h-5 text-red-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-400">Offline</span>
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-red-400 font-mono">{offlineApps}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Health */}
|
||||
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-slate-800/80 to-slate-900/80 border border-slate-700/50 p-6">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl -mr-16 -mt-16" />
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-xl bg-purple-500/20 flex items-center justify-center">
|
||||
<TrendingUp className="w-5 h-5 text-purple-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-400">Health</span>
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-purple-400 font-mono">{overallHealth}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Apps List */}
|
||||
<div className="space-y-6">
|
||||
{categories.map((category) => (
|
||||
<section key={category}>
|
||||
<h2 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500" />
|
||||
{category}
|
||||
<span className="text-sm font-normal text-slate-500">
|
||||
({apps.filter((a) => a.category === category).length})
|
||||
<main className="max-w-7xl mx-auto px-4 py-6">
|
||||
{viewMode === "grid" ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{apps.map((app) => {
|
||||
const { isUp, uptime, history, latest } = getAppStatus(app.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={app.id}
|
||||
className={`bg-slate-900 rounded-xl border-2 p-5 transition-all hover:scale-[1.02] cursor-pointer ${
|
||||
isUp ? "border-emerald-500/30" : "border-red-500/30"
|
||||
}`}
|
||||
onClick={() => setSelectedApp(app)}
|
||||
>
|
||||
{/* Status Badge */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-bold ${
|
||||
isUp
|
||||
? "bg-emerald-500/20 text-emerald-400"
|
||||
: "bg-red-500/20 text-red-400"
|
||||
}`}>
|
||||
{isUp ? "● ONLINE" : "● OFFLINE"}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||
{apps
|
||||
.filter((app) => app.category === category)
|
||||
.map((app) => {
|
||||
const { latest, history, uptime } = getAppStatus(app.id);
|
||||
const isUp = latest?.status === "up";
|
||||
const uptimeData = generateUptimeData(app.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={app.id}
|
||||
className={`group relative rounded-2xl border p-5 transition-all hover:scale-[1.02] cursor-pointer ${
|
||||
isUp
|
||||
? "bg-slate-800/50 border-slate-700/50 hover:border-emerald-500/30"
|
||||
: "bg-slate-800/50 border-red-500/30 hover:border-red-500/50"
|
||||
}`}
|
||||
onClick={() => setSelectedApp(app)}
|
||||
>
|
||||
{/* Status Indicator */}
|
||||
<div className="absolute top-4 right-4 flex items-center gap-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
isUp ? "bg-emerald-500 animate-pulse" : "bg-red-500"
|
||||
}`}
|
||||
/>
|
||||
<span className={`text-xs font-mono ${isUp ? "text-emerald-400" : "text-red-400"}`}>
|
||||
{isUp ? "ONLINE" : "OFFLINE"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* App Header */}
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div
|
||||
className="w-12 h-12 rounded-xl flex items-center justify-center text-xl"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${app.color}20, ${app.color}40)`,
|
||||
border: `1px solid ${app.color}30`,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: app.color }}>
|
||||
{app.name.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 pr-16">
|
||||
<h3 className="font-semibold text-white truncate">{app.name}</h3>
|
||||
<p className="text-sm text-slate-400 truncate">{app.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Metrics */}
|
||||
<div className="grid grid-cols-3 gap-4 mb-4">
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 mb-1">Uptime</p>
|
||||
<p className={`text-lg font-mono font-semibold ${uptime >= 90 ? "text-emerald-400" : "text-yellow-400"}`}>
|
||||
{uptime}%
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 mb-1">Port</p>
|
||||
<p className="text-lg font-mono font-semibold text-white">{app.port}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 mb-1">Response</p>
|
||||
<p className="text-lg font-mono font-semibold text-white">
|
||||
{latest?.responseTime ? `${latest.responseTime}ms` : "--"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sparkline Chart */}
|
||||
{uptimeData.length > 1 && (
|
||||
<div className="h-16 mb-4">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={uptimeData}>
|
||||
<defs>
|
||||
<linearGradient id={`gradient-${app.id}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={isUp ? "#22C55E" : "#EF4444"} stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor={isUp ? "#22C55E" : "#EF4444"} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="uptime"
|
||||
stroke={isUp ? "#22C55E" : "#EF4444"}
|
||||
strokeWidth={2}
|
||||
fill={`url(#gradient-${app.id})`}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
checkApp(app);
|
||||
}}
|
||||
disabled={checking === app.id}
|
||||
className="flex-1 bg-slate-700/50 hover:bg-slate-700 text-slate-300 px-3 py-2 rounded-lg text-sm font-medium transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${checking === app.id ? "animate-spin" : ""}`} />
|
||||
Check
|
||||
</button>
|
||||
<a
|
||||
href={app.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="p-2 bg-slate-700/50 hover:bg-slate-700 text-slate-300 rounded-lg transition-all"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteApp(app.id);
|
||||
}}
|
||||
className="p-2 bg-slate-700/50 hover:bg-red-500/20 hover:text-red-400 text-slate-300 rounded-lg transition-all"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<span className="text-slate-500 text-sm">{app.port}</span>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === "settings" && (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="bg-slate-800/50 rounded-2xl border border-slate-700/50 p-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-6">Settings</h2>
|
||||
<div className="space-y-4 text-slate-400">
|
||||
<p>Monitor settings and configuration options will be available here.</p>
|
||||
<p className="text-sm">Current version: 1.0.0</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* App Info */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-bold text-white mb-1">{app.name}</h3>
|
||||
<p className="text-slate-400 text-sm">{app.description || "No description"}</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
<div className="bg-slate-800 rounded-lg p-3">
|
||||
<p className="text-xs text-slate-500 mb-1">Uptime</p>
|
||||
<p className={`text-xl font-bold ${uptime >= 90 ? "text-emerald-400" : "text-yellow-400"}`}>
|
||||
{uptime}%
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 rounded-lg p-3">
|
||||
<p className="text-xs text-slate-500 mb-1">Response</p>
|
||||
<p className="text-xl font-bold text-white">
|
||||
{latest?.responseTime ? `${latest.responseTime}ms` : "--"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mini Chart */}
|
||||
{history.length > 1 && (
|
||||
<div className="h-16 mb-4">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={history.map((h, i) => ({ i, status: h.status === "up" ? 1 : 0 }))}>
|
||||
<Line
|
||||
type="step"
|
||||
dataKey="status"
|
||||
stroke={isUp ? "#22C55E" : "#EF4444"}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
checkApp(app);
|
||||
}}
|
||||
disabled={checking === app.id}
|
||||
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 py-2 rounded-lg text-sm font-medium transition-colors"
|
||||
>
|
||||
{checking === app.id ? "Checking..." : "Check Now"}
|
||||
</button>
|
||||
<a
|
||||
href={app.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="p-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
/* List View */
|
||||
<div className="space-y-2">
|
||||
{apps.map((app) => {
|
||||
const { isUp, uptime, latest } = getAppStatus(app.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={app.id}
|
||||
className={`flex items-center gap-4 bg-slate-900 rounded-lg border-l-4 p-4 ${
|
||||
isUp ? "border-l-emerald-500" : "border-l-red-500"
|
||||
}`}
|
||||
>
|
||||
<div className={`w-3 h-3 rounded-full ${isUp ? "bg-emerald-500" : "bg-red-500"}`} />
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-white">{app.name}</h3>
|
||||
<p className="text-sm text-slate-400 truncate">{app.url}</p>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex items-center gap-6 text-sm">
|
||||
<div className="text-center">
|
||||
<p className="text-slate-500">Port</p>
|
||||
<p className="font-mono text-white">{app.port}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-slate-500">Uptime</p>
|
||||
<p className={`font-mono ${uptime >= 90 ? "text-emerald-400" : "text-yellow-400"}`}>
|
||||
{uptime}%
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-slate-500">Response</p>
|
||||
<p className="font-mono text-white">
|
||||
{latest?.responseTime ? `${latest.responseTime}ms` : "--"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => checkApp(app)}
|
||||
disabled={checking === app.id}
|
||||
className="p-2 text-slate-400 hover:text-white"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${checking === app.id ? "animate-spin" : ""}`} />
|
||||
</button>
|
||||
<a
|
||||
href={app.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2 text-slate-400 hover:text-white"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
<button
|
||||
onClick={() => deleteApp(app.id)}
|
||||
className="p-2 text-slate-400 hover:text-red-400"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* Add App Modal */}
|
||||
{showAddApp && (
|
||||
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-slate-800 rounded-2xl border border-slate-700/50 p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-white">Add New App</h2>
|
||||
<button
|
||||
onClick={() => setShowAddApp(false)}
|
||||
className="text-slate-400 hover:text-white transition-colors"
|
||||
>
|
||||
<XCircle className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={addApp} className="space-y-5">
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-slate-900 rounded-xl p-6 w-full max-w-md">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Add New App</h2>
|
||||
<form onSubmit={addApp} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-400 mb-2">Name</label>
|
||||
<label className="block text-sm text-slate-400 mb-1">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newApp.name}
|
||||
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-emerald-500 transition-colors"
|
||||
placeholder="My App"
|
||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-400 mb-2">Description</label>
|
||||
<label className="block text-sm text-slate-400 mb-1">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newApp.description}
|
||||
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-emerald-500 transition-colors"
|
||||
placeholder="Brief description"
|
||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-400 mb-2">URL</label>
|
||||
<label className="block text-sm text-slate-400 mb-1">URL</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newApp.url}
|
||||
onChange={(e) => setNewApp({ ...newApp, url: e.target.value })}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-emerald-500 transition-colors"
|
||||
placeholder="http://localhost:3000"
|
||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-400 mb-2">Port</label>
|
||||
<label className="block text-sm text-slate-400 mb-1">Port</label>
|
||||
<input
|
||||
type="number"
|
||||
value={newApp.port}
|
||||
onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-emerald-500 transition-colors"
|
||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-400 mb-2">Category</label>
|
||||
<select
|
||||
value={newApp.category}
|
||||
onChange={(e) => setNewApp({ ...newApp, category: e.target.value })}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-emerald-500 transition-colors"
|
||||
>
|
||||
<option>Productivity</option>
|
||||
<option>Backup</option>
|
||||
<option>Monitoring</option>
|
||||
<option>Development</option>
|
||||
<option>Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-400 mb-2">Color</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
value={newApp.color}
|
||||
onChange={(e) => setNewApp({ ...newApp, color: e.target.value })}
|
||||
className="w-12 h-12 rounded-xl bg-slate-900 border border-slate-700 cursor-pointer"
|
||||
/>
|
||||
<span className="text-slate-500 font-mono">{newApp.color}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3 pt-4">
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddApp(false)}
|
||||
className="flex-1 bg-slate-700 hover:bg-slate-600 text-slate-300 px-4 py-3 rounded-xl font-medium transition-all"
|
||||
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 py-2 rounded-lg"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white px-4 py-3 rounded-xl font-medium transition-all shadow-lg shadow-emerald-500/20"
|
||||
className="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white py-2 rounded-lg font-medium"
|
||||
>
|
||||
Add App
|
||||
</button>
|
||||
@ -584,93 +434,37 @@ export default function HeartbeatMonitor() {
|
||||
|
||||
{/* App Detail Modal */}
|
||||
{selectedApp && (
|
||||
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-slate-800 rounded-2xl border border-slate-700/50 p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="w-14 h-14 rounded-xl flex items-center justify-center text-2xl font-bold"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${selectedApp.color}20, ${selectedApp.color}40)`,
|
||||
border: `1px solid ${selectedApp.color}30`,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: selectedApp.color }}>
|
||||
{selectedApp.name.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-white">{selectedApp.name}</h2>
|
||||
<p className="text-slate-400">{selectedApp.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-slate-900 rounded-xl p-6 w-full max-w-lg">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-bold text-white">{selectedApp.name}</h2>
|
||||
<button
|
||||
onClick={() => setSelectedApp(null)}
|
||||
className="text-slate-400 hover:text-white transition-colors"
|
||||
className="text-slate-400 hover:text-white"
|
||||
>
|
||||
<XCircle className="w-6 h-6" />
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="bg-slate-900/50 rounded-xl p-4">
|
||||
<p className="text-sm text-slate-500 mb-1">URL</p>
|
||||
<a
|
||||
href={selectedApp.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-emerald-400 hover:text-emerald-300 font-mono text-sm break-all"
|
||||
>
|
||||
{selectedApp.url}
|
||||
</a>
|
||||
<div className="bg-slate-800 rounded-lg p-3">
|
||||
<p className="text-sm text-slate-500">URL</p>
|
||||
<p className="text-emerald-400 font-mono text-sm break-all">{selectedApp.url}</p>
|
||||
</div>
|
||||
<div className="bg-slate-900/50 rounded-xl p-4">
|
||||
<p className="text-sm text-slate-500 mb-1">Port</p>
|
||||
<div className="bg-slate-800 rounded-lg p-3">
|
||||
<p className="text-sm text-slate-500">Port</p>
|
||||
<p className="text-white font-mono">{selectedApp.port}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-slate-400 mb-3">Uptime History</h3>
|
||||
<div className="h-48 bg-slate-900/50 rounded-xl p-4">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={generateUptimeData(selectedApp.id)}>
|
||||
<defs>
|
||||
<linearGradient id="detailGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#22C55E" stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor="#22C55E" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||
<XAxis dataKey="time" stroke="#64748B" fontSize={12} />
|
||||
<YAxis stroke="#64748B" fontSize={12} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: "#1E293B",
|
||||
border: "1px solid #334155",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="uptime"
|
||||
stroke="#22C55E"
|
||||
strokeWidth={2}
|
||||
fill="url(#detailGradient)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
checkApp(selectedApp);
|
||||
setSelectedApp(null);
|
||||
}}
|
||||
className="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white px-4 py-3 rounded-xl font-medium transition-all"
|
||||
className="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white py-2 rounded-lg font-medium"
|
||||
>
|
||||
Check Now
|
||||
</button>
|
||||
@ -679,9 +473,9 @@ export default function HeartbeatMonitor() {
|
||||
deleteApp(selectedApp.id);
|
||||
setSelectedApp(null);
|
||||
}}
|
||||
className="flex-1 bg-red-500/20 hover:bg-red-500/30 text-red-400 px-4 py-3 rounded-xl font-medium transition-all"
|
||||
className="flex-1 bg-red-500/20 hover:bg-red-500/30 text-red-400 py-2 rounded-lg font-medium"
|
||||
>
|
||||
Delete App
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user