Complete UI/UX redesign with modern dashboard aesthetics
- Applied dark OLED theme with slate color palette - Added Fira Code/Inter typography for technical look - Implemented glassmorphism cards with gradient accents - Added smooth area charts with gradient fills - Improved stats cards with glow effects - Added tabbed navigation (Dashboard/Settings) - Enhanced modal designs with better spacing - Added custom scrollbar styling - Implemented responsive grid layout - Added hover animations and micro-interactions - Improved accessibility with focus states
This commit is contained in:
parent
bed1169443
commit
9568bd81d1
@ -4,6 +4,17 @@
|
|||||||
"static/chunks/webpack.js",
|
"static/chunks/webpack.js",
|
||||||
"static/chunks/main-app.js",
|
"static/chunks/main-app.js",
|
||||||
"static/chunks/app/api/monitor/route.js"
|
"static/chunks/app/api/monitor/route.js"
|
||||||
|
],
|
||||||
|
"/layout": [
|
||||||
|
"static/chunks/webpack.js",
|
||||||
|
"static/chunks/main-app.js",
|
||||||
|
"static/css/app/layout.css",
|
||||||
|
"static/chunks/app/layout.js"
|
||||||
|
],
|
||||||
|
"/page": [
|
||||||
|
"static/chunks/webpack.js",
|
||||||
|
"static/chunks/main-app.js",
|
||||||
|
"static/chunks/app/page.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
Normal file
BIN
.next/cache/webpack/client-development/2.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/client-development/3.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/client-development/3.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
Normal file
BIN
.next/cache/webpack/server-development/1.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.
BIN
.next/cache/webpack/server-development/index.pack.gz.old
vendored
Normal file
BIN
.next/cache/webpack/server-development/index.pack.gz.old
vendored
Normal file
Binary file not shown.
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"/api/monitor/route": "app/api/monitor/route.js"
|
"/api/monitor/route": "app/api/monitor/route.js",
|
||||||
|
"/page": "app/page.js"
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
218
.next/server/app/page.js
Normal file
218
.next/server/app/page.js
Normal file
File diff suppressed because one or more lines are too long
1
.next/server/app/page_client-reference-manifest.js
Normal file
1
.next/server/app/page_client-reference-manifest.js
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
self.__NEXT_FONT_MANIFEST="{\"pages\":{},\"app\":{},\"appUsingSizeAdjust\":false,\"pagesUsingSizeAdjust\":false}"
|
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}"
|
||||||
@ -1 +1 @@
|
|||||||
{"pages":{},"app":{},"appUsingSizeAdjust":false,"pagesUsingSizeAdjust":false}
|
{"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}
|
||||||
65
.next/server/vendor-chunks/@babel.js
Normal file
65
.next/server/vendor-chunks/@babel.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"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/).
|
||||||
|
*/
|
||||||
|
exports.id = "vendor-chunks/@babel";
|
||||||
|
exports.ids = ["vendor-chunks/@babel"];
|
||||||
|
exports.modules = {
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js":
|
||||||
|
/*!**************************************************************************!*\
|
||||||
|
!*** ./node_modules/@babel/runtime/helpers/esm/assertThisInitialized.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 */ \"default\": () => (/* binding */ _assertThisInitialized)\n/* harmony export */ });\nfunction _assertThisInitialized(e) {\n if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");\n return e;\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vYXNzZXJ0VGhpc0luaXRpYWxpemVkLmpzIiwibWFwcGluZ3MiOiI7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9hc3NlcnRUaGlzSW5pdGlhbGl6ZWQuanMiXSwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gX2Fzc2VydFRoaXNJbml0aWFsaXplZChlKSB7XG4gIGlmICh2b2lkIDAgPT09IGUpIHRocm93IG5ldyBSZWZlcmVuY2VFcnJvcihcInRoaXMgaGFzbid0IGJlZW4gaW5pdGlhbGlzZWQgLSBzdXBlcigpIGhhc24ndCBiZWVuIGNhbGxlZFwiKTtcbiAgcmV0dXJuIGU7XG59XG5leHBvcnQgeyBfYXNzZXJ0VGhpc0luaXRpYWxpemVkIGFzIGRlZmF1bHQgfTsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js\n");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/extends.js":
|
||||||
|
/*!************************************************************!*\
|
||||||
|
!*** ./node_modules/@babel/runtime/helpers/esm/extends.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 */ \"default\": () => (/* binding */ _extends)\n/* harmony export */ });\nfunction _extends() {\n return _extends = Object.assign ? Object.assign.bind() : function (n) {\n for (var e = 1; e < arguments.length; e++) {\n var t = arguments[e];\n for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);\n }\n return n;\n }, _extends.apply(null, arguments);\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vZXh0ZW5kcy5qcyIsIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTtBQUNBLG9CQUFvQixzQkFBc0I7QUFDMUM7QUFDQSwwQkFBMEI7QUFDMUI7QUFDQTtBQUNBLEdBQUc7QUFDSCIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9leHRlbmRzLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9leHRlbmRzKCkge1xuICByZXR1cm4gX2V4dGVuZHMgPSBPYmplY3QuYXNzaWduID8gT2JqZWN0LmFzc2lnbi5iaW5kKCkgOiBmdW5jdGlvbiAobikge1xuICAgIGZvciAodmFyIGUgPSAxOyBlIDwgYXJndW1lbnRzLmxlbmd0aDsgZSsrKSB7XG4gICAgICB2YXIgdCA9IGFyZ3VtZW50c1tlXTtcbiAgICAgIGZvciAodmFyIHIgaW4gdCkgKHt9KS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHQsIHIpICYmIChuW3JdID0gdFtyXSk7XG4gICAgfVxuICAgIHJldHVybiBuO1xuICB9LCBfZXh0ZW5kcy5hcHBseShudWxsLCBhcmd1bWVudHMpO1xufVxuZXhwb3J0IHsgX2V4dGVuZHMgYXMgZGVmYXVsdCB9OyJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/extends.js\n");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js":
|
||||||
|
/*!******************************************************************!*\
|
||||||
|
!*** ./node_modules/@babel/runtime/helpers/esm/inheritsLoose.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 */ \"default\": () => (/* binding */ _inheritsLoose)\n/* harmony export */ });\n/* harmony import */ var _setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./setPrototypeOf.js */ \"(ssr)/./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js\");\n\nfunction _inheritsLoose(t, o) {\n t.prototype = Object.create(o.prototype), t.prototype.constructor = t, (0,_setPrototypeOf_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(t, o);\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vaW5oZXJpdHNMb29zZS5qcyIsIm1hcHBpbmdzIjoiOzs7OztBQUFpRDtBQUNqRDtBQUNBLHlFQUF5RSw4REFBYztBQUN2RiIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9pbmhlcml0c0xvb3NlLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBzZXRQcm90b3R5cGVPZiBmcm9tIFwiLi9zZXRQcm90b3R5cGVPZi5qc1wiO1xuZnVuY3Rpb24gX2luaGVyaXRzTG9vc2UodCwgbykge1xuICB0LnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoby5wcm90b3R5cGUpLCB0LnByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IHQsIHNldFByb3RvdHlwZU9mKHQsIG8pO1xufVxuZXhwb3J0IHsgX2luaGVyaXRzTG9vc2UgYXMgZGVmYXVsdCB9OyJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js\n");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js":
|
||||||
|
/*!*********************************************************************************!*\
|
||||||
|
!*** ./node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.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 */ \"default\": () => (/* binding */ _objectWithoutPropertiesLoose)\n/* harmony export */ });\nfunction _objectWithoutPropertiesLoose(r, e) {\n if (null == r) return {};\n var t = {};\n for (var n in r) if ({}.hasOwnProperty.call(r, n)) {\n if (-1 !== e.indexOf(n)) continue;\n t[n] = r[n];\n }\n return t;\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vb2JqZWN0V2l0aG91dFByb3BlcnRpZXNMb29zZS5qcyIsIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTtBQUNBO0FBQ0EseUJBQXlCO0FBQ3pCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIi9Vc2Vycy9tYXR0YnJ1Y2UvRG9jdW1lbnRzL1Byb2plY3RzL09wZW5DbGF3L1dlYi9oZWFydGJlYXQtbW9uaXRvci9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vb2JqZWN0V2l0aG91dFByb3BlcnRpZXNMb29zZS5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfb2JqZWN0V2l0aG91dFByb3BlcnRpZXNMb29zZShyLCBlKSB7XG4gIGlmIChudWxsID09IHIpIHJldHVybiB7fTtcbiAgdmFyIHQgPSB7fTtcbiAgZm9yICh2YXIgbiBpbiByKSBpZiAoe30uaGFzT3duUHJvcGVydHkuY2FsbChyLCBuKSkge1xuICAgIGlmICgtMSAhPT0gZS5pbmRleE9mKG4pKSBjb250aW51ZTtcbiAgICB0W25dID0gcltuXTtcbiAgfVxuICByZXR1cm4gdDtcbn1cbmV4cG9ydCB7IF9vYmplY3RXaXRob3V0UHJvcGVydGllc0xvb3NlIGFzIGRlZmF1bHQgfTsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js\n");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js":
|
||||||
|
/*!*******************************************************************!*\
|
||||||
|
!*** ./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.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 */ \"default\": () => (/* binding */ _setPrototypeOf)\n/* harmony export */ });\nfunction _setPrototypeOf(t, e) {\n return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {\n return t.__proto__ = e, t;\n }, _setPrototypeOf(t, e);\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQGJhYmVsL3J1bnRpbWUvaGVscGVycy9lc20vc2V0UHJvdG90eXBlT2YuanMiLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSCIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9AYmFiZWwvcnVudGltZS9oZWxwZXJzL2VzbS9zZXRQcm90b3R5cGVPZi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfc2V0UHJvdG90eXBlT2YodCwgZSkge1xuICByZXR1cm4gX3NldFByb3RvdHlwZU9mID0gT2JqZWN0LnNldFByb3RvdHlwZU9mID8gT2JqZWN0LnNldFByb3RvdHlwZU9mLmJpbmQoKSA6IGZ1bmN0aW9uICh0LCBlKSB7XG4gICAgcmV0dXJuIHQuX19wcm90b19fID0gZSwgdDtcbiAgfSwgX3NldFByb3RvdHlwZU9mKHQsIGUpO1xufVxuZXhwb3J0IHsgX3NldFByb3RvdHlwZU9mIGFzIGRlZmF1bHQgfTsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js\n");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
};
|
||||||
|
;
|
||||||
55
.next/server/vendor-chunks/@swc.js
Normal file
55
.next/server/vendor-chunks/@swc.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"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/).
|
||||||
|
*/
|
||||||
|
exports.id = "vendor-chunks/@swc";
|
||||||
|
exports.ids = ["vendor-chunks/@swc"];
|
||||||
|
exports.modules = {
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@swc/helpers/esm/_interop_require_default.js":
|
||||||
|
/*!*******************************************************************!*\
|
||||||
|
!*** ./node_modules/@swc/helpers/esm/_interop_require_default.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 */ _: () => (/* binding */ _interop_require_default)\n/* harmony export */ });\nfunction _interop_require_default(obj) {\n return obj && obj.__esModule ? obj : { default: obj };\n}\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQHN3Yy9oZWxwZXJzL2VzbS9faW50ZXJvcF9yZXF1aXJlX2RlZmF1bHQuanMiLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0EsMkNBQTJDO0FBQzNDO0FBQ3lDIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9PcGVuQ2xhdy9XZWIvaGVhcnRiZWF0LW1vbml0b3Ivbm9kZV9tb2R1bGVzL0Bzd2MvaGVscGVycy9lc20vX2ludGVyb3BfcmVxdWlyZV9kZWZhdWx0LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9pbnRlcm9wX3JlcXVpcmVfZGVmYXVsdChvYmopIHtcbiAgICByZXR1cm4gb2JqICYmIG9iai5fX2VzTW9kdWxlID8gb2JqIDogeyBkZWZhdWx0OiBvYmogfTtcbn1cbmV4cG9ydCB7IF9pbnRlcm9wX3JlcXVpcmVfZGVmYXVsdCBhcyBfIH07XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@swc/helpers/esm/_interop_require_default.js\n");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@swc/helpers/esm/_interop_require_wildcard.js":
|
||||||
|
/*!********************************************************************!*\
|
||||||
|
!*** ./node_modules/@swc/helpers/esm/_interop_require_wildcard.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 */ _: () => (/* binding */ _interop_require_wildcard)\n/* harmony export */ });\nfunction _getRequireWildcardCache(nodeInterop) {\n if (typeof WeakMap !== \"function\") return null;\n\n var cacheBabelInterop = new WeakMap();\n var cacheNodeInterop = new WeakMap();\n\n return (_getRequireWildcardCache = function(nodeInterop) {\n return nodeInterop ? cacheNodeInterop : cacheBabelInterop;\n })(nodeInterop);\n}\nfunction _interop_require_wildcard(obj, nodeInterop) {\n if (!nodeInterop && obj && obj.__esModule) return obj;\n if (obj === null || typeof obj !== \"object\" && typeof obj !== \"function\") return { default: obj };\n\n var cache = _getRequireWildcardCache(nodeInterop);\n\n if (cache && cache.has(obj)) return cache.get(obj);\n\n var newObj = { __proto__: null };\n var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;\n\n for (var key in obj) {\n if (key !== \"default\" && Object.prototype.hasOwnProperty.call(obj, key)) {\n var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;\n if (desc && (desc.get || desc.set)) Object.defineProperty(newObj, key, desc);\n else newObj[key] = obj[key];\n }\n }\n\n newObj.default = obj;\n\n if (cache) cache.set(obj, newObj);\n\n return newObj;\n}\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQHN3Yy9oZWxwZXJzL2VzbS9faW50ZXJvcF9yZXF1aXJlX3dpbGRjYXJkLmpzIiwibWFwcGluZ3MiOiI7Ozs7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQSx1RkFBdUY7O0FBRXZGOztBQUVBOztBQUVBLG1CQUFtQjtBQUNuQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTs7QUFFQTtBQUNBO0FBQzBDIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9PcGVuQ2xhdy9XZWIvaGVhcnRiZWF0LW1vbml0b3Ivbm9kZV9tb2R1bGVzL0Bzd2MvaGVscGVycy9lc20vX2ludGVyb3BfcmVxdWlyZV93aWxkY2FyZC5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUobm9kZUludGVyb3ApIHtcbiAgICBpZiAodHlwZW9mIFdlYWtNYXAgIT09IFwiZnVuY3Rpb25cIikgcmV0dXJuIG51bGw7XG5cbiAgICB2YXIgY2FjaGVCYWJlbEludGVyb3AgPSBuZXcgV2Vha01hcCgpO1xuICAgIHZhciBjYWNoZU5vZGVJbnRlcm9wID0gbmV3IFdlYWtNYXAoKTtcblxuICAgIHJldHVybiAoX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlID0gZnVuY3Rpb24obm9kZUludGVyb3ApIHtcbiAgICAgICAgcmV0dXJuIG5vZGVJbnRlcm9wID8gY2FjaGVOb2RlSW50ZXJvcCA6IGNhY2hlQmFiZWxJbnRlcm9wO1xuICAgIH0pKG5vZGVJbnRlcm9wKTtcbn1cbmZ1bmN0aW9uIF9pbnRlcm9wX3JlcXVpcmVfd2lsZGNhcmQob2JqLCBub2RlSW50ZXJvcCkge1xuICAgIGlmICghbm9kZUludGVyb3AgJiYgb2JqICYmIG9iai5fX2VzTW9kdWxlKSByZXR1cm4gb2JqO1xuICAgIGlmIChvYmogPT09IG51bGwgfHwgdHlwZW9mIG9iaiAhPT0gXCJvYmplY3RcIiAmJiB0eXBlb2Ygb2JqICE9PSBcImZ1bmN0aW9uXCIpIHJldHVybiB7IGRlZmF1bHQ6IG9iaiB9O1xuXG4gICAgdmFyIGNhY2hlID0gX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlKG5vZGVJbnRlcm9wKTtcblxuICAgIGlmIChjYWNoZSAmJiBjYWNoZS5oYXMob2JqKSkgcmV0dXJuIGNhY2hlLmdldChvYmopO1xuXG4gICAgdmFyIG5ld09iaiA9IHsgX19wcm90b19fOiBudWxsIH07XG4gICAgdmFyIGhhc1Byb3BlcnR5RGVzY3JpcHRvciA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0eSAmJiBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yO1xuXG4gICAgZm9yICh2YXIga2V5IGluIG9iaikge1xuICAgICAgICBpZiAoa2V5ICE9PSBcImRlZmF1bHRcIiAmJiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqLCBrZXkpKSB7XG4gICAgICAgICAgICB2YXIgZGVzYyA9IGhhc1Byb3BlcnR5RGVzY3JpcHRvciA/IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqLCBrZXkpIDogbnVsbDtcbiAgICAgICAgICAgIGlmIChkZXNjICYmIChkZXNjLmdldCB8fCBkZXNjLnNldCkpIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShuZXdPYmosIGtleSwgZGVzYyk7XG4gICAgICAgICAgICBlbHNlIG5ld09ialtrZXldID0gb2JqW2tleV07XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBuZXdPYmouZGVmYXVsdCA9IG9iajtcblxuICAgIGlmIChjYWNoZSkgY2FjaGUuc2V0KG9iaiwgbmV3T2JqKTtcblxuICAgIHJldHVybiBuZXdPYmo7XG59XG5leHBvcnQgeyBfaW50ZXJvcF9yZXF1aXJlX3dpbGRjYXJkIGFzIF8gfTtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@swc/helpers/esm/_interop_require_wildcard.js\n");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/@swc/helpers/esm/_tagged_template_literal_loose.js":
|
||||||
|
/*!*************************************************************************!*\
|
||||||
|
!*** ./node_modules/@swc/helpers/esm/_tagged_template_literal_loose.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 */ _: () => (/* binding */ _tagged_template_literal_loose)\n/* harmony export */ });\nfunction _tagged_template_literal_loose(strings, raw) {\n if (!raw) raw = strings.slice(0);\n\n strings.raw = raw;\n\n return strings;\n}\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvQHN3Yy9oZWxwZXJzL2VzbS9fdGFnZ2VkX3RlbXBsYXRlX2xpdGVyYWxfbG9vc2UuanMiLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUMrQyIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9Ac3djL2hlbHBlcnMvZXNtL190YWdnZWRfdGVtcGxhdGVfbGl0ZXJhbF9sb29zZS5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfdGFnZ2VkX3RlbXBsYXRlX2xpdGVyYWxfbG9vc2Uoc3RyaW5ncywgcmF3KSB7XG4gICAgaWYgKCFyYXcpIHJhdyA9IHN0cmluZ3Muc2xpY2UoMCk7XG5cbiAgICBzdHJpbmdzLnJhdyA9IHJhdztcblxuICAgIHJldHVybiBzdHJpbmdzO1xufVxuZXhwb3J0IHsgX3RhZ2dlZF90ZW1wbGF0ZV9saXRlcmFsX2xvb3NlIGFzIF8gfTtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/@swc/helpers/esm/_tagged_template_literal_loose.js\n");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "(rsc)/./node_modules/@swc/helpers/esm/_interop_require_default.js":
|
||||||
|
/*!*******************************************************************!*\
|
||||||
|
!*** ./node_modules/@swc/helpers/esm/_interop_require_default.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 */ _: () => (/* binding */ _interop_require_default)\n/* harmony export */ });\nfunction _interop_require_default(obj) {\n return obj && obj.__esModule ? obj : { default: obj };\n}\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHJzYykvLi9ub2RlX21vZHVsZXMvQHN3Yy9oZWxwZXJzL2VzbS9faW50ZXJvcF9yZXF1aXJlX2RlZmF1bHQuanMiLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0EsMkNBQTJDO0FBQzNDO0FBQ3lDIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9PcGVuQ2xhdy9XZWIvaGVhcnRiZWF0LW1vbml0b3Ivbm9kZV9tb2R1bGVzL0Bzd2MvaGVscGVycy9lc20vX2ludGVyb3BfcmVxdWlyZV9kZWZhdWx0LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIF9pbnRlcm9wX3JlcXVpcmVfZGVmYXVsdChvYmopIHtcbiAgICByZXR1cm4gb2JqICYmIG9iai5fX2VzTW9kdWxlID8gb2JqIDogeyBkZWZhdWx0OiBvYmogfTtcbn1cbmV4cG9ydCB7IF9pbnRlcm9wX3JlcXVpcmVfZGVmYXVsdCBhcyBfIH07XG4iXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(rsc)/./node_modules/@swc/helpers/esm/_interop_require_default.js\n");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
};
|
||||||
|
;
|
||||||
25
.next/server/vendor-chunks/clsx.js
Normal file
25
.next/server/vendor-chunks/clsx.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"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/).
|
||||||
|
*/
|
||||||
|
exports.id = "vendor-chunks/clsx";
|
||||||
|
exports.ids = ["vendor-chunks/clsx"];
|
||||||
|
exports.modules = {
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/clsx/dist/clsx.mjs":
|
||||||
|
/*!*****************************************!*\
|
||||||
|
!*** ./node_modules/clsx/dist/clsx.mjs ***!
|
||||||
|
\*****************************************/
|
||||||
|
/***/ ((__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 */ clsx: () => (/* binding */ clsx),\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nfunction r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (clsx);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvY2xzeC9kaXN0L2Nsc3gubWpzIiwibWFwcGluZ3MiOiI7Ozs7O0FBQUEsY0FBYyxhQUFhLCtDQUErQyxnREFBZ0QsZUFBZSxRQUFRLElBQUksMENBQTBDLHlDQUF5QyxTQUFnQixnQkFBZ0Isd0NBQXdDLElBQUksbURBQW1ELFNBQVMsaUVBQWUsSUFBSSIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9jbHN4L2Rpc3QvY2xzeC5tanMiXSwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gcihlKXt2YXIgdCxmLG49XCJcIjtpZihcInN0cmluZ1wiPT10eXBlb2YgZXx8XCJudW1iZXJcIj09dHlwZW9mIGUpbis9ZTtlbHNlIGlmKFwib2JqZWN0XCI9PXR5cGVvZiBlKWlmKEFycmF5LmlzQXJyYXkoZSkpe3ZhciBvPWUubGVuZ3RoO2Zvcih0PTA7dDxvO3QrKyllW3RdJiYoZj1yKGVbdF0pKSYmKG4mJihuKz1cIiBcIiksbis9Zil9ZWxzZSBmb3IoZiBpbiBlKWVbZl0mJihuJiYobis9XCIgXCIpLG4rPWYpO3JldHVybiBufWV4cG9ydCBmdW5jdGlvbiBjbHN4KCl7Zm9yKHZhciBlLHQsZj0wLG49XCJcIixvPWFyZ3VtZW50cy5sZW5ndGg7ZjxvO2YrKykoZT1hcmd1bWVudHNbZl0pJiYodD1yKGUpKSYmKG4mJihuKz1cIiBcIiksbis9dCk7cmV0dXJuIG59ZXhwb3J0IGRlZmF1bHQgY2xzeDsiXSwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbMF0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/clsx/dist/clsx.mjs\n");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
};
|
||||||
|
;
|
||||||
175
.next/server/vendor-chunks/d3-array.js
vendored
Normal file
175
.next/server/vendor-chunks/d3-array.js
vendored
Normal file
File diff suppressed because one or more lines are too long
35
.next/server/vendor-chunks/d3-color.js
vendored
Normal file
35
.next/server/vendor-chunks/d3-color.js
vendored
Normal file
File diff suppressed because one or more lines are too long
165
.next/server/vendor-chunks/d3-format.js
vendored
Normal file
165
.next/server/vendor-chunks/d3-format.js
vendored
Normal file
File diff suppressed because one or more lines are too long
155
.next/server/vendor-chunks/d3-interpolate.js
vendored
Normal file
155
.next/server/vendor-chunks/d3-interpolate.js
vendored
Normal file
File diff suppressed because one or more lines are too long
25
.next/server/vendor-chunks/d3-path.js
vendored
Normal file
25
.next/server/vendor-chunks/d3-path.js
vendored
Normal file
File diff suppressed because one or more lines are too long
245
.next/server/vendor-chunks/d3-scale.js
vendored
Normal file
245
.next/server/vendor-chunks/d3-scale.js
vendored
Normal file
File diff suppressed because one or more lines are too long
615
.next/server/vendor-chunks/d3-shape.js
vendored
Normal file
615
.next/server/vendor-chunks/d3-shape.js
vendored
Normal file
File diff suppressed because one or more lines are too long
35
.next/server/vendor-chunks/d3-time-format.js
vendored
Normal file
35
.next/server/vendor-chunks/d3-time-format.js
vendored
Normal file
File diff suppressed because one or more lines are too long
125
.next/server/vendor-chunks/d3-time.js
vendored
Normal file
125
.next/server/vendor-chunks/d3-time.js
vendored
Normal file
File diff suppressed because one or more lines are too long
25
.next/server/vendor-chunks/decimal.js-light.js
Normal file
25
.next/server/vendor-chunks/decimal.js-light.js
Normal file
File diff suppressed because one or more lines are too long
25
.next/server/vendor-chunks/eventemitter3.js
Normal file
25
.next/server/vendor-chunks/eventemitter3.js
Normal file
File diff suppressed because one or more lines are too long
25
.next/server/vendor-chunks/fast-equals.js
Normal file
25
.next/server/vendor-chunks/fast-equals.js
Normal file
File diff suppressed because one or more lines are too long
25
.next/server/vendor-chunks/internmap.js
Normal file
25
.next/server/vendor-chunks/internmap.js
Normal file
File diff suppressed because one or more lines are too long
1924
.next/server/vendor-chunks/lodash.js
Normal file
1924
.next/server/vendor-chunks/lodash.js
Normal file
File diff suppressed because one or more lines are too long
145
.next/server/vendor-chunks/lucide-react.js
Normal file
145
.next/server/vendor-chunks/lucide-react.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
25
.next/server/vendor-chunks/object-assign.js
Normal file
25
.next/server/vendor-chunks/object-assign.js
Normal file
File diff suppressed because one or more lines are too long
89
.next/server/vendor-chunks/prop-types.js
Normal file
89
.next/server/vendor-chunks/prop-types.js
Normal file
File diff suppressed because one or more lines are too long
35
.next/server/vendor-chunks/react-is.js
vendored
Normal file
35
.next/server/vendor-chunks/react-is.js
vendored
Normal file
File diff suppressed because one or more lines are too long
105
.next/server/vendor-chunks/react-smooth.js
vendored
Normal file
105
.next/server/vendor-chunks/react-smooth.js
vendored
Normal file
File diff suppressed because one or more lines are too long
85
.next/server/vendor-chunks/react-transition-group.js
vendored
Normal file
85
.next/server/vendor-chunks/react-transition-group.js
vendored
Normal file
File diff suppressed because one or more lines are too long
62
.next/server/vendor-chunks/recharts-scale.js
Normal file
62
.next/server/vendor-chunks/recharts-scale.js
Normal file
File diff suppressed because one or more lines are too long
645
.next/server/vendor-chunks/recharts.js
Normal file
645
.next/server/vendor-chunks/recharts.js
Normal file
File diff suppressed because one or more lines are too long
25
.next/server/vendor-chunks/tiny-invariant.js
Normal file
25
.next/server/vendor-chunks/tiny-invariant.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"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/).
|
||||||
|
*/
|
||||||
|
exports.id = "vendor-chunks/tiny-invariant";
|
||||||
|
exports.ids = ["vendor-chunks/tiny-invariant"];
|
||||||
|
exports.modules = {
|
||||||
|
|
||||||
|
/***/ "(ssr)/./node_modules/tiny-invariant/dist/esm/tiny-invariant.js":
|
||||||
|
/*!****************************************************************!*\
|
||||||
|
!*** ./node_modules/tiny-invariant/dist/esm/tiny-invariant.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 */ \"default\": () => (/* binding */ invariant)\n/* harmony export */ });\nvar isProduction = \"development\" === 'production';\nvar prefix = 'Invariant failed';\nfunction invariant(condition, message) {\n if (condition) {\n return;\n }\n if (isProduction) {\n throw new Error(prefix);\n }\n var provided = typeof message === 'function' ? message() : message;\n var value = provided ? \"\".concat(prefix, \": \").concat(provided) : prefix;\n throw new Error(value);\n}\n\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKHNzcikvLi9ub2RlX21vZHVsZXMvdGlueS1pbnZhcmlhbnQvZGlzdC9lc20vdGlueS1pbnZhcmlhbnQuanMiLCJtYXBwaW5ncyI6Ijs7OztBQUFBLG1CQUFtQixhQUFvQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRWdDIiwic291cmNlcyI6WyIvVXNlcnMvbWF0dGJydWNlL0RvY3VtZW50cy9Qcm9qZWN0cy9PcGVuQ2xhdy9XZWIvaGVhcnRiZWF0LW1vbml0b3Ivbm9kZV9tb2R1bGVzL3RpbnktaW52YXJpYW50L2Rpc3QvZXNtL3RpbnktaW52YXJpYW50LmpzIl0sInNvdXJjZXNDb250ZW50IjpbInZhciBpc1Byb2R1Y3Rpb24gPSBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gJ3Byb2R1Y3Rpb24nO1xudmFyIHByZWZpeCA9ICdJbnZhcmlhbnQgZmFpbGVkJztcbmZ1bmN0aW9uIGludmFyaWFudChjb25kaXRpb24sIG1lc3NhZ2UpIHtcbiAgICBpZiAoY29uZGl0aW9uKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGlzUHJvZHVjdGlvbikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IocHJlZml4KTtcbiAgICB9XG4gICAgdmFyIHByb3ZpZGVkID0gdHlwZW9mIG1lc3NhZ2UgPT09ICdmdW5jdGlvbicgPyBtZXNzYWdlKCkgOiBtZXNzYWdlO1xuICAgIHZhciB2YWx1ZSA9IHByb3ZpZGVkID8gXCJcIi5jb25jYXQocHJlZml4LCBcIjogXCIpLmNvbmNhdChwcm92aWRlZCkgOiBwcmVmaXg7XG4gICAgdGhyb3cgbmV3IEVycm9yKHZhbHVlKTtcbn1cblxuZXhwb3J0IHsgaW52YXJpYW50IGFzIGRlZmF1bHQgfTtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(ssr)/./node_modules/tiny-invariant/dist/esm/tiny-invariant.js\n");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
};
|
||||||
|
;
|
||||||
35
.next/server/vendor-chunks/victory-vendor.js
Normal file
35
.next/server/vendor-chunks/victory-vendor.js
Normal file
File diff suppressed because one or more lines are too long
@ -22,8 +22,8 @@
|
|||||||
/******/ }
|
/******/ }
|
||||||
/******/ // Create a new module (and put it into the cache)
|
/******/ // Create a new module (and put it into the cache)
|
||||||
/******/ var module = __webpack_module_cache__[moduleId] = {
|
/******/ var module = __webpack_module_cache__[moduleId] = {
|
||||||
/******/ // no module.id needed
|
/******/ id: moduleId,
|
||||||
/******/ // no module.loaded needed
|
/******/ loaded: false,
|
||||||
/******/ exports: {}
|
/******/ exports: {}
|
||||||
/******/ };
|
/******/ };
|
||||||
/******/
|
/******/
|
||||||
@ -36,6 +36,9 @@
|
|||||||
/******/ if(threw) delete __webpack_module_cache__[moduleId];
|
/******/ if(threw) delete __webpack_module_cache__[moduleId];
|
||||||
/******/ }
|
/******/ }
|
||||||
/******/
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.loaded = true;
|
||||||
|
/******/
|
||||||
/******/ // Return the exports of the module
|
/******/ // Return the exports of the module
|
||||||
/******/ return module.exports;
|
/******/ return module.exports;
|
||||||
/******/ }
|
/******/ }
|
||||||
@ -61,6 +64,36 @@
|
|||||||
/******/ };
|
/******/ };
|
||||||
/******/ })();
|
/******/ })();
|
||||||
/******/
|
/******/
|
||||||
|
/******/ /* 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/define property getters */
|
/******/ /* webpack/runtime/define property getters */
|
||||||
/******/ (() => {
|
/******/ (() => {
|
||||||
/******/ // define getter functions for harmony exports
|
/******/ // define getter functions for harmony exports
|
||||||
@ -95,6 +128,11 @@
|
|||||||
/******/ };
|
/******/ };
|
||||||
/******/ })();
|
/******/ })();
|
||||||
/******/
|
/******/
|
||||||
|
/******/ /* webpack/runtime/getFullHash */
|
||||||
|
/******/ (() => {
|
||||||
|
/******/ __webpack_require__.h = () => ("5e2e688d9298ba36")
|
||||||
|
/******/ })();
|
||||||
|
/******/
|
||||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||||
/******/ (() => {
|
/******/ (() => {
|
||||||
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
||||||
@ -111,6 +149,15 @@
|
|||||||
/******/ };
|
/******/ };
|
||||||
/******/ })();
|
/******/ })();
|
||||||
/******/
|
/******/
|
||||||
|
/******/ /* webpack/runtime/node module decorator */
|
||||||
|
/******/ (() => {
|
||||||
|
/******/ __webpack_require__.nmd = (module) => {
|
||||||
|
/******/ module.paths = [];
|
||||||
|
/******/ if (!module.children) module.children = [];
|
||||||
|
/******/ return module;
|
||||||
|
/******/ };
|
||||||
|
/******/ })();
|
||||||
|
/******/
|
||||||
/******/ /* webpack/runtime/startup entrypoint */
|
/******/ /* webpack/runtime/startup entrypoint */
|
||||||
/******/ (() => {
|
/******/ (() => {
|
||||||
/******/ __webpack_require__.X = (result, chunkIds, fn) => {
|
/******/ __webpack_require__.X = (result, chunkIds, fn) => {
|
||||||
|
|||||||
226
.next/static/chunks/app-pages-internals.js
Normal file
226
.next/static/chunks/app-pages-internals.js
Normal file
File diff suppressed because one or more lines are too long
69
.next/static/chunks/app/layout.js
Normal file
69
.next/static/chunks/app/layout.js
Normal file
File diff suppressed because one or more lines are too long
4868
.next/static/chunks/app/page.js
Normal file
4868
.next/static/chunks/app/page.js
Normal file
File diff suppressed because one or more lines are too long
@ -89,6 +89,48 @@
|
|||||||
/******/ };
|
/******/ };
|
||||||
/******/ })();
|
/******/ })();
|
||||||
/******/
|
/******/
|
||||||
|
/******/ /* 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/define property getters */
|
/******/ /* webpack/runtime/define property getters */
|
||||||
/******/ (() => {
|
/******/ (() => {
|
||||||
/******/ // define getter functions for harmony exports
|
/******/ // define getter functions for harmony exports
|
||||||
@ -148,7 +190,7 @@
|
|||||||
/******/
|
/******/
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
/******/ /* webpack/runtime/getFullHash */
|
||||||
/******/ (() => {
|
/******/ (() => {
|
||||||
/******/ __webpack_require__.h = () => ("46496372886b1561")
|
/******/ __webpack_require__.h = () => ("e0cd64fa6e96bf0c")
|
||||||
/******/ })();
|
/******/ })();
|
||||||
/******/
|
/******/
|
||||||
/******/ /* webpack/runtime/global */
|
/******/ /* webpack/runtime/global */
|
||||||
|
|||||||
93
.next/static/css/app/layout.css
Normal file
93
.next/static/css/app/layout.css
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*!*******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
|
||||||
|
!*** 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 ***!
|
||||||
|
\*****************************************************************************************************************************************************************************************************************************************************************/
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
BIN
.next/static/media/4cf2300e9c8272f7-s.p.woff2
Normal file
BIN
.next/static/media/4cf2300e9c8272f7-s.p.woff2
Normal file
Binary file not shown.
BIN
.next/static/media/747892c23ea88013-s.woff2
Normal file
BIN
.next/static/media/747892c23ea88013-s.woff2
Normal file
Binary file not shown.
BIN
.next/static/media/8d697b304b401681-s.woff2
Normal file
BIN
.next/static/media/8d697b304b401681-s.woff2
Normal file
Binary file not shown.
BIN
.next/static/media/93f479601ee12b01-s.p.woff2
Normal file
BIN
.next/static/media/93f479601ee12b01-s.p.woff2
Normal file
Binary file not shown.
BIN
.next/static/media/9610d9e46709d722-s.woff2
Normal file
BIN
.next/static/media/9610d9e46709d722-s.woff2
Normal file
Binary file not shown.
BIN
.next/static/media/ba015fad6dcf6784-s.woff2
Normal file
BIN
.next/static/media/ba015fad6dcf6784-s.woff2
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"c":["webpack"],"r":[],"m":[]}
|
||||||
60
.next/static/webpack/webpack.46496372886b1561.hot-update.js
Normal file
60
.next/static/webpack/webpack.46496372886b1561.hot-update.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"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")
|
||||||
|
/******/ })();
|
||||||
|
/******/
|
||||||
|
/******/ }
|
||||||
|
);
|
||||||
15
.next/trace
15
.next/trace
File diff suppressed because one or more lines are too long
84
.next/types/app/layout.ts
Normal file
84
.next/types/app/layout.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// File: /Users/mattbruce/Documents/Projects/OpenClaw/Web/heartbeat-monitor/src/app/layout.tsx
|
||||||
|
import * as entry from '../../../src/app/layout.js'
|
||||||
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
||||||
|
|
||||||
|
type TEntry = typeof import('../../../src/app/layout.js')
|
||||||
|
|
||||||
|
type SegmentParams<T extends Object = any> = T extends Record<string, any>
|
||||||
|
? { [K in keyof T]: T[K] extends string ? string | string[] | undefined : never }
|
||||||
|
: T
|
||||||
|
|
||||||
|
// Check that the entry is a valid entry
|
||||||
|
checkFields<Diff<{
|
||||||
|
default: Function
|
||||||
|
config?: {}
|
||||||
|
generateStaticParams?: Function
|
||||||
|
revalidate?: RevalidateRange<TEntry> | false
|
||||||
|
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
||||||
|
dynamicParams?: boolean
|
||||||
|
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
||||||
|
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
||||||
|
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
||||||
|
maxDuration?: number
|
||||||
|
|
||||||
|
metadata?: any
|
||||||
|
generateMetadata?: Function
|
||||||
|
viewport?: any
|
||||||
|
generateViewport?: Function
|
||||||
|
experimental_ppr?: boolean
|
||||||
|
|
||||||
|
}, TEntry, ''>>()
|
||||||
|
|
||||||
|
|
||||||
|
// Check the prop type of the entry function
|
||||||
|
checkFields<Diff<LayoutProps, FirstArg<TEntry['default']>, 'default'>>()
|
||||||
|
|
||||||
|
// Check the arguments and return type of the generateMetadata function
|
||||||
|
if ('generateMetadata' in entry) {
|
||||||
|
checkFields<Diff<LayoutProps, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||||
|
checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the arguments and return type of the generateViewport function
|
||||||
|
if ('generateViewport' in entry) {
|
||||||
|
checkFields<Diff<LayoutProps, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||||
|
checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the arguments and return type of the generateStaticParams function
|
||||||
|
if ('generateStaticParams' in entry) {
|
||||||
|
checkFields<Diff<{ params: SegmentParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
||||||
|
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageProps {
|
||||||
|
params?: Promise<SegmentParams>
|
||||||
|
searchParams?: Promise<any>
|
||||||
|
}
|
||||||
|
export interface LayoutProps {
|
||||||
|
children?: React.ReactNode
|
||||||
|
|
||||||
|
params?: Promise<SegmentParams>
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============
|
||||||
|
// Utility types
|
||||||
|
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
||||||
|
|
||||||
|
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
||||||
|
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
||||||
|
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
||||||
|
|
||||||
|
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
||||||
|
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
||||||
|
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
||||||
|
|
||||||
|
// https://github.com/sindresorhus/type-fest
|
||||||
|
type Numeric = number | bigint
|
||||||
|
type Zero = 0 | 0n
|
||||||
|
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
||||||
|
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
||||||
84
.next/types/app/page.ts
Normal file
84
.next/types/app/page.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// File: /Users/mattbruce/Documents/Projects/OpenClaw/Web/heartbeat-monitor/src/app/page.tsx
|
||||||
|
import * as entry from '../../../src/app/page.js'
|
||||||
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
||||||
|
|
||||||
|
type TEntry = typeof import('../../../src/app/page.js')
|
||||||
|
|
||||||
|
type SegmentParams<T extends Object = any> = T extends Record<string, any>
|
||||||
|
? { [K in keyof T]: T[K] extends string ? string | string[] | undefined : never }
|
||||||
|
: T
|
||||||
|
|
||||||
|
// Check that the entry is a valid entry
|
||||||
|
checkFields<Diff<{
|
||||||
|
default: Function
|
||||||
|
config?: {}
|
||||||
|
generateStaticParams?: Function
|
||||||
|
revalidate?: RevalidateRange<TEntry> | false
|
||||||
|
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
||||||
|
dynamicParams?: boolean
|
||||||
|
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
||||||
|
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
||||||
|
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
||||||
|
maxDuration?: number
|
||||||
|
|
||||||
|
metadata?: any
|
||||||
|
generateMetadata?: Function
|
||||||
|
viewport?: any
|
||||||
|
generateViewport?: Function
|
||||||
|
experimental_ppr?: boolean
|
||||||
|
|
||||||
|
}, TEntry, ''>>()
|
||||||
|
|
||||||
|
|
||||||
|
// Check the prop type of the entry function
|
||||||
|
checkFields<Diff<PageProps, FirstArg<TEntry['default']>, 'default'>>()
|
||||||
|
|
||||||
|
// Check the arguments and return type of the generateMetadata function
|
||||||
|
if ('generateMetadata' in entry) {
|
||||||
|
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||||
|
checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the arguments and return type of the generateViewport function
|
||||||
|
if ('generateViewport' in entry) {
|
||||||
|
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||||
|
checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the arguments and return type of the generateStaticParams function
|
||||||
|
if ('generateStaticParams' in entry) {
|
||||||
|
checkFields<Diff<{ params: SegmentParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
||||||
|
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageProps {
|
||||||
|
params?: Promise<SegmentParams>
|
||||||
|
searchParams?: Promise<any>
|
||||||
|
}
|
||||||
|
export interface LayoutProps {
|
||||||
|
children?: React.ReactNode
|
||||||
|
|
||||||
|
params?: Promise<SegmentParams>
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============
|
||||||
|
// Utility types
|
||||||
|
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
||||||
|
|
||||||
|
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
||||||
|
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
||||||
|
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
||||||
|
|
||||||
|
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
||||||
|
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
||||||
|
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
||||||
|
|
||||||
|
// https://github.com/sindresorhus/type-fest
|
||||||
|
type Numeric = number | bigint
|
||||||
|
type Zero = 0 | 0n
|
||||||
|
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
||||||
|
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
||||||
@ -1,21 +1,56 @@
|
|||||||
|
@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 base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--background: #0F172A;
|
||||||
--foreground: #171717;
|
--foreground: #F8FAFC;
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
background: var(--background);
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,9 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Heartbeat Monitor",
|
title: "Heartbeat Monitor",
|
||||||
description: "Monitor all your local web apps",
|
description: "Real-time monitoring dashboard for your web applications",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@ -23,12 +12,8 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" className="dark">
|
||||||
<body
|
<body className="antialiased">{children}</body>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
690
src/app/page.tsx
690
src/app/page.tsx
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Activity, Plus, Play, Square, Trash2, Edit2, RefreshCw, Server, Clock, TrendingUp, AlertCircle } from "lucide-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 } from "recharts";
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts";
|
||||||
|
|
||||||
interface App {
|
interface App {
|
||||||
id: string;
|
id: string;
|
||||||
@ -24,6 +24,12 @@ interface StatusEntry {
|
|||||||
responseTime?: number;
|
responseTime?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UptimeData {
|
||||||
|
time: string;
|
||||||
|
uptime: number;
|
||||||
|
responseTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default function HeartbeatMonitor() {
|
export default function HeartbeatMonitor() {
|
||||||
const [apps, setApps] = useState<App[]>([]);
|
const [apps, setApps] = useState<App[]>([]);
|
||||||
const [status, setStatus] = useState<StatusEntry[]>([]);
|
const [status, setStatus] = useState<StatusEntry[]>([]);
|
||||||
@ -31,6 +37,7 @@ export default function HeartbeatMonitor() {
|
|||||||
const [checking, setChecking] = useState<string | null>(null);
|
const [checking, setChecking] = useState<string | null>(null);
|
||||||
const [showAddApp, setShowAddApp] = useState(false);
|
const [showAddApp, setShowAddApp] = useState(false);
|
||||||
const [selectedApp, setSelectedApp] = useState<App | null>(null);
|
const [selectedApp, setSelectedApp] = useState<App | null>(null);
|
||||||
|
const [activeTab, setActiveTab] = useState("dashboard");
|
||||||
const [newApp, setNewApp] = useState<Partial<App>>({
|
const [newApp, setNewApp] = useState<Partial<App>>({
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
@ -39,13 +46,13 @@ export default function HeartbeatMonitor() {
|
|||||||
path: "",
|
path: "",
|
||||||
command: "npm run dev",
|
command: "npm run dev",
|
||||||
category: "Other",
|
category: "Other",
|
||||||
color: "#3b82f6",
|
color: "#22C55E",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
const interval = setInterval(fetchData, 30000); // Refresh every 30s
|
const interval = setInterval(fetchData, 30000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -66,7 +73,7 @@ export default function HeartbeatMonitor() {
|
|||||||
setChecking(app.id);
|
setChecking(app.id);
|
||||||
try {
|
try {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const res = await fetch(app.url, { method: "HEAD", mode: "no-cors" });
|
await fetch(app.url, { method: "HEAD", mode: "no-cors" });
|
||||||
const responseTime = Date.now() - start;
|
const responseTime = Date.now() - start;
|
||||||
|
|
||||||
const entry: StatusEntry = {
|
const entry: StatusEntry = {
|
||||||
@ -83,7 +90,7 @@ export default function HeartbeatMonitor() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (err) {
|
} catch {
|
||||||
const entry: StatusEntry = {
|
const entry: StatusEntry = {
|
||||||
appId: app.id,
|
appId: app.id,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@ -102,12 +109,6 @@ export default function HeartbeatMonitor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restartApp(app: App) {
|
|
||||||
// This would need to be implemented with a server-side process runner
|
|
||||||
// For now, just check status
|
|
||||||
await checkApp(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addApp(e: React.FormEvent) {
|
async function addApp(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!newApp.name || !newApp.url) return;
|
if (!newApp.name || !newApp.url) return;
|
||||||
@ -126,7 +127,7 @@ export default function HeartbeatMonitor() {
|
|||||||
path: "",
|
path: "",
|
||||||
command: "npm run dev",
|
command: "npm run dev",
|
||||||
category: "Other",
|
category: "Other",
|
||||||
color: "#3b82f6",
|
color: "#22C55E",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
setShowAddApp(false);
|
setShowAddApp(false);
|
||||||
@ -159,47 +160,96 @@ export default function HeartbeatMonitor() {
|
|||||||
return Math.round((upCount / entries.length) * 100);
|
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 categories = Array.from(new Set(apps.map((a) => a.category)));
|
||||||
const totalApps = apps.length;
|
const totalApps = apps.length;
|
||||||
const onlineApps = apps.filter((app) => {
|
const onlineApps = apps.filter((app) => {
|
||||||
const { latest } = getAppStatus(app.id);
|
const { latest } = getAppStatus(app.id);
|
||||||
return latest?.status === "up";
|
return latest?.status === "up";
|
||||||
}).length;
|
}).length;
|
||||||
|
const offlineApps = totalApps - onlineApps;
|
||||||
|
const overallHealth = totalApps > 0 ? Math.round((onlineApps / totalApps) * 100) : 100;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-950 flex items-center justify-center">
|
<div className="min-h-screen bg-[#0F172A] flex items-center justify-center">
|
||||||
<div className="text-slate-400">Loading...</div>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-slate-950 text-slate-100">
|
<div className="min-h-screen bg-[#0F172A] text-slate-100 font-sans">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="border-b border-slate-800 bg-slate-900/50">
|
<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 py-4">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between h-16">
|
||||||
|
{/* Logo */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Activity className="w-8 h-8 text-green-500" />
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">Heartbeat Monitor</h1>
|
<h1 className="text-lg font-semibold text-white tracking-tight">Heartbeat Monitor</h1>
|
||||||
<p className="text-sm text-slate-400">Track all your local web apps</p>
|
<p className="text-xs text-slate-400 font-mono">System Status Dashboard</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="text-right">
|
{/* Nav Tabs */}
|
||||||
<div className="text-2xl font-bold text-green-400">
|
<nav className="hidden md:flex items-center gap-1 bg-slate-800/50 rounded-lg p-1">
|
||||||
{onlineApps}/{totalApps}
|
<button
|
||||||
</div>
|
onClick={() => setActiveTab("dashboard")}
|
||||||
<div className="text-xs text-slate-400">Apps Online</div>
|
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${
|
||||||
</div>
|
activeTab === "dashboard"
|
||||||
|
? "bg-slate-700 text-white"
|
||||||
|
: "text-slate-400 hover:text-white hover:bg-slate-700/50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<LayoutDashboard className="w-4 h-4" />
|
||||||
|
Dashboard
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAddApp(true)}
|
onClick={() => setShowAddApp(true)}
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors"
|
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"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
Add App
|
<span className="hidden sm:inline">Add App</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -207,283 +257,322 @@ export default function HeartbeatMonitor() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<main className="max-w-7xl mx-auto px-4 py-6">
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{/* Stats Overview */}
|
{activeTab === "dashboard" && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
<>
|
||||||
<div className="bg-slate-900 rounded-lg p-4 border border-slate-800">
|
{/* Stats Grid */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||||
<Server className="w-5 h-5 text-blue-400" />
|
{/* Total Apps */}
|
||||||
<div>
|
<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="text-2xl font-bold">{totalApps}</div>
|
<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="text-xs text-slate-400">Total Apps</div>
|
<div className="relative">
|
||||||
</div>
|
<div className="flex items-center gap-3 mb-2">
|
||||||
</div>
|
<div className="w-10 h-10 rounded-xl bg-blue-500/20 flex items-center justify-center">
|
||||||
</div>
|
<Server className="w-5 h-5 text-blue-400" />
|
||||||
<div className="bg-slate-900 rounded-lg p-4 border border-slate-800">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Activity className="w-5 h-5 text-green-400" />
|
|
||||||
<div>
|
|
||||||
<div className="text-2xl font-bold text-green-400">{onlineApps}</div>
|
|
||||||
<div className="text-xs text-slate-400">Online</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-slate-900 rounded-lg p-4 border border-slate-800">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<AlertCircle className="w-5 h-5 text-red-400" />
|
|
||||||
<div>
|
|
||||||
<div className="text-2xl font-bold text-red-400">{totalApps - onlineApps}</div>
|
|
||||||
<div className="text-xs text-slate-400">Offline</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-slate-900 rounded-lg p-4 border border-slate-800">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Clock className="w-5 h-5 text-purple-400" />
|
|
||||||
<div>
|
|
||||||
<div className="text-2xl font-bold">
|
|
||||||
{Math.round((onlineApps / (totalApps || 1)) * 100)}%
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-slate-400">Health</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Apps by Category */}
|
|
||||||
{categories.map((category) => (
|
|
||||||
<div key={category} className="mb-8">
|
|
||||||
<h2 className="text-lg font-semibold text-slate-300 mb-4 flex items-center gap-2">
|
|
||||||
<TrendingUp className="w-5 h-5" />
|
|
||||||
{category}
|
|
||||||
</h2>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg: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";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={app.id}
|
|
||||||
className={`bg-slate-900 rounded-lg border p-4 transition-all cursor-pointer hover:border-slate-600 ${
|
|
||||||
isUp ? "border-green-900/50" : "border-red-900/50"
|
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedApp(app)}
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between mb-3">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 rounded-full"
|
|
||||||
style={{ backgroundColor: app.color }}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-white">{app.name}</h3>
|
|
||||||
<p className="text-xs text-slate-400">{app.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`w-2 h-2 rounded-full ${
|
|
||||||
isUp ? "bg-green-500 animate-pulse" : "bg-red-500"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center justify-between text-sm">
|
|
||||||
<span className="text-slate-400">URL</span>
|
|
||||||
<a
|
|
||||||
href={app.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-400 hover:underline truncate max-w-[150px]"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{app.url}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between text-sm">
|
|
||||||
<span className="text-slate-400">Port</span>
|
|
||||||
<span className="text-slate-300">{app.port}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between text-sm">
|
|
||||||
<span className="text-slate-400">Uptime</span>
|
|
||||||
<span className={uptime > 90 ? "text-green-400" : "text-yellow-400"}>
|
|
||||||
{uptime}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{latest?.responseTime && (
|
|
||||||
<div className="flex items-center justify-between text-sm">
|
|
||||||
<span className="text-slate-400">Response</span>
|
|
||||||
<span className="text-slate-300">{latest.responseTime}ms</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mini sparkline */}
|
|
||||||
{history.length > 1 && (
|
|
||||||
<div className="mt-3 h-10">
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex gap-2 mt-4">
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
checkApp(app);
|
|
||||||
}}
|
|
||||||
disabled={checking === app.id}
|
|
||||||
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 px-3 py-1.5 rounded text-sm flex items-center justify-center gap-1 transition-colors"
|
|
||||||
>
|
|
||||||
<RefreshCw className={`w-3 h-3 ${checking === app.id ? "animate-spin" : ""}`} />
|
|
||||||
Check
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
restartApp(app);
|
|
||||||
}}
|
|
||||||
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 px-3 py-1.5 rounded text-sm flex items-center justify-center gap-1 transition-colors"
|
|
||||||
>
|
|
||||||
<Play className="w-3 h-3" />
|
|
||||||
Restart
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
deleteApp(app.id);
|
|
||||||
}}
|
|
||||||
className="bg-slate-800 hover:bg-red-900/30 text-slate-400 hover:text-red-400 px-3 py-1.5 rounded text-sm transition-colors"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-3 h-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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})
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Add App Modal */}
|
{/* Add App Modal */}
|
||||||
{showAddApp && (
|
{showAddApp && (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||||
<div className="bg-slate-900 rounded-lg border border-slate-800 p-6 w-full max-w-lg">
|
<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">
|
||||||
<h2 className="text-xl font-bold mb-4">Add New App</h2>
|
<div className="flex items-center justify-between mb-6">
|
||||||
<form onSubmit={addApp} className="space-y-4">
|
<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>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Name</label>
|
<label className="block text-sm font-medium text-slate-400 mb-2">Name</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newApp.name}
|
value={newApp.name}
|
||||||
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
|
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-white"
|
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"
|
placeholder="My App"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Description</label>
|
<label className="block text-sm font-medium text-slate-400 mb-2">Description</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newApp.description}
|
value={newApp.description}
|
||||||
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
|
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-white"
|
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="What this app does"
|
placeholder="Brief description"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">URL</label>
|
<label className="block text-sm font-medium text-slate-400 mb-2">URL</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newApp.url}
|
value={newApp.url}
|
||||||
onChange={(e) => setNewApp({ ...newApp, url: e.target.value })}
|
onChange={(e) => setNewApp({ ...newApp, url: e.target.value })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-white"
|
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"
|
placeholder="http://localhost:3000"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Port</label>
|
<label className="block text-sm font-medium text-slate-400 mb-2">Port</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={newApp.port}
|
value={newApp.port}
|
||||||
onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })}
|
onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })}
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-white"
|
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"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Project Path</label>
|
<label className="block text-sm font-medium text-slate-400 mb-2">Category</label>
|
||||||
<input
|
<select
|
||||||
type="text"
|
value={newApp.category}
|
||||||
value={newApp.path}
|
onChange={(e) => setNewApp({ ...newApp, category: e.target.value })}
|
||||||
onChange={(e) => setNewApp({ ...newApp, path: 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 px-3 py-2 text-white"
|
>
|
||||||
placeholder="/Users/.../my-app"
|
<option>Productivity</option>
|
||||||
/>
|
<option>Backup</option>
|
||||||
|
<option>Monitoring</option>
|
||||||
|
<option>Development</option>
|
||||||
|
<option>Other</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-slate-400 mb-1">Start Command</label>
|
<label className="block text-sm font-medium text-slate-400 mb-2">Color</label>
|
||||||
<input
|
<div className="flex items-center gap-3">
|
||||||
type="text"
|
|
||||||
value={newApp.command}
|
|
||||||
onChange={(e) => setNewApp({ ...newApp, command: e.target.value })}
|
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-white"
|
|
||||||
placeholder="npm run dev"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm text-slate-400 mb-1">Category</label>
|
|
||||||
<select
|
|
||||||
value={newApp.category}
|
|
||||||
onChange={(e) => setNewApp({ ...newApp, category: e.target.value })}
|
|
||||||
className="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-white"
|
|
||||||
>
|
|
||||||
<option>Productivity</option>
|
|
||||||
<option>Backup</option>
|
|
||||||
<option>Monitoring</option>
|
|
||||||
<option>Development</option>
|
|
||||||
<option>Other</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm text-slate-400 mb-1">Color</label>
|
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
value={newApp.color}
|
value={newApp.color}
|
||||||
onChange={(e) => setNewApp({ ...newApp, color: e.target.value })}
|
onChange={(e) => setNewApp({ ...newApp, color: e.target.value })}
|
||||||
className="w-full h-10 bg-slate-800 border border-slate-700 rounded"
|
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>
|
</div>
|
||||||
<div className="flex gap-3 pt-4">
|
<div className="flex gap-3 pt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowAddApp(false)}
|
onClick={() => setShowAddApp(false)}
|
||||||
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 px-4 py-2 rounded transition-colors"
|
className="flex-1 bg-slate-700 hover:bg-slate-600 text-slate-300 px-4 py-3 rounded-xl font-medium transition-all"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="flex-1 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded transition-colors"
|
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"
|
||||||
>
|
>
|
||||||
Add App
|
Add App
|
||||||
</button>
|
</button>
|
||||||
@ -492,6 +581,113 @@ export default function HeartbeatMonitor() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedApp(null)}
|
||||||
|
className="text-slate-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<XCircle className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<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>
|
||||||
|
<div className="bg-slate-900/50 rounded-xl p-4">
|
||||||
|
<p className="text-sm text-slate-500 mb-1">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"
|
||||||
|
>
|
||||||
|
Check Now
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Delete App
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user