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:
OpenClaw Bot 2026-02-18 13:39:41 -06:00
parent 9568bd81d1
commit 08f1ff6af8
42 changed files with 529 additions and 728 deletions

View File

@ -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"
]
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

View File

@ -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}"

View File

@ -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}

View File

@ -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 ***!

View File

@ -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 ***!

View File

@ -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

View File

@ -190,7 +190,7 @@
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("e0cd64fa6e96bf0c")
/******/ __webpack_require__.h = () => ("c48572fb170ff111")
/******/ })();
/******/
/******/ /* webpack/runtime/global */

View File

@ -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;
}

View File

@ -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"]}

File diff suppressed because one or more lines are too long

View File

@ -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")
/******/ })();
/******/
/******/ }
);

View 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")
/******/ })();
/******/
/******/ }
);

View 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")
/******/ })();
/******/
/******/ }
);

File diff suppressed because one or more lines are too long

105
data/status.json Normal file
View 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"
}
]
}

View File

@ -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>