Complete working rebuild with Framer Motion animations

- Verified build works before delivery
- All animations functional (stagger, hover, fade)
- Sparkline uptime visualizations
- Grid/List view toggle
- Responsive design
- Working add/delete monitors
This commit is contained in:
OpenClaw Bot 2026-02-18 16:37:25 -06:00
parent 15e50bc9bc
commit 43022c165e
77 changed files with 13342 additions and 188 deletions

View File

@ -6,10 +6,15 @@
"static/css/app/layout.css", "static/css/app/layout.css",
"static/chunks/app/layout.js" "static/chunks/app/layout.js"
], ],
"/_not-found/page": [ "/page": [
"static/chunks/webpack.js", "static/chunks/webpack.js",
"static/chunks/main-app.js", "static/chunks/main-app.js",
"static/chunks/app/_not-found/page.js" "static/chunks/app/page.js"
],
"/api/monitor/route": [
"static/chunks/webpack.js",
"static/chunks/main-app.js",
"static/chunks/app/api/monitor/route.js"
] ]
} }
} }

View File

@ -2,7 +2,9 @@
"polyfillFiles": [ "polyfillFiles": [
"static/chunks/polyfills.js" "static/chunks/polyfills.js"
], ],
"devFiles": [], "devFiles": [
"static/chunks/react-refresh.js"
],
"ampDevFiles": [], "ampDevFiles": [],
"lowPriorityFiles": [ "lowPriorityFiles": [
"static/development/_buildManifest.js", "static/development/_buildManifest.js",
@ -14,7 +16,16 @@
], ],
"rootMainFilesTree": {}, "rootMainFilesTree": {},
"pages": { "pages": {
"/_app": [] "/_app": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/_app.js"
],
"/_error": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/_error.js"
]
}, },
"ampFirstPages": [] "ampFirstPages": []
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

76
.next/server/_error.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,4 @@
{ {
"/_not-found/page": "app/_not-found/page.js" "/api/monitor/route": "app/api/monitor/route.js",
"/page": "app/page.js"
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

218
.next/server/app/page.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

View File

@ -3,25 +3,25 @@ self.__BUILD_MANIFEST = {
"static/chunks/polyfills.js" "static/chunks/polyfills.js"
], ],
"devFiles": [ "devFiles": [
"static/chunks/fallback/react-refresh.js" "static/chunks/react-refresh.js"
],
"ampDevFiles": [
"static/chunks/fallback/webpack.js",
"static/chunks/fallback/amp.js"
], ],
"ampDevFiles": [],
"lowPriorityFiles": [], "lowPriorityFiles": [],
"rootMainFiles": [], "rootMainFiles": [
"static/chunks/webpack.js",
"static/chunks/main-app.js"
],
"rootMainFilesTree": {}, "rootMainFilesTree": {},
"pages": { "pages": {
"/_app": [ "/_app": [
"static/chunks/fallback/webpack.js", "static/chunks/webpack.js",
"static/chunks/fallback/main.js", "static/chunks/main.js",
"static/chunks/fallback/pages/_app.js" "static/chunks/pages/_app.js"
], ],
"/_error": [ "/_error": [
"static/chunks/fallback/webpack.js", "static/chunks/webpack.js",
"static/chunks/fallback/main.js", "static/chunks/main.js",
"static/chunks/fallback/pages/_error.js" "static/chunks/pages/_error.js"
] ]
}, },
"ampFirstPages": [] "ampFirstPages": []

View File

@ -1 +1,5 @@
{} {
"/_error": "pages/_error.js",
"/_app": "pages/_app.js",
"/_document": "pages/_document.js"
}

View File

@ -0,0 +1,46 @@
"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/).
*/
(() => {
var exports = {};
exports.id = "pages/_app";
exports.ids = ["pages/_app"];
exports.modules = {
/***/ "react":
/*!************************!*\
!*** external "react" ***!
\************************/
/***/ ((module) => {
module.exports = require("react");
/***/ }),
/***/ "react/jsx-runtime":
/*!************************************!*\
!*** external "react/jsx-runtime" ***!
\************************************/
/***/ ((module) => {
module.exports = require("react/jsx-runtime");
/***/ })
};
;
// load runtime
var __webpack_require__ = require("../webpack-runtime.js");
__webpack_require__.C(exports);
var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
var __webpack_exports__ = __webpack_require__.X(0, ["vendor-chunks/next","vendor-chunks/@swc"], () => (__webpack_exec__("./node_modules/next/dist/pages/_app.js")));
module.exports = __webpack_exports__;
})();

View File

@ -0,0 +1,66 @@
"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/).
*/
(() => {
var exports = {};
exports.id = "pages/_document";
exports.ids = ["pages/_document"];
exports.modules = {
/***/ "next/dist/compiled/next-server/pages.runtime.dev.js":
/*!**********************************************************************!*\
!*** external "next/dist/compiled/next-server/pages.runtime.dev.js" ***!
\**********************************************************************/
/***/ ((module) => {
module.exports = require("next/dist/compiled/next-server/pages.runtime.dev.js");
/***/ }),
/***/ "react":
/*!************************!*\
!*** external "react" ***!
\************************/
/***/ ((module) => {
module.exports = require("react");
/***/ }),
/***/ "react/jsx-runtime":
/*!************************************!*\
!*** external "react/jsx-runtime" ***!
\************************************/
/***/ ((module) => {
module.exports = require("react/jsx-runtime");
/***/ }),
/***/ "path":
/*!***********************!*\
!*** external "path" ***!
\***********************/
/***/ ((module) => {
module.exports = require("path");
/***/ })
};
;
// load runtime
var __webpack_require__ = require("../webpack-runtime.js");
__webpack_require__.C(exports);
var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
var __webpack_exports__ = __webpack_require__.X(0, ["vendor-chunks/next","vendor-chunks/@swc"], () => (__webpack_exec__("./node_modules/next/dist/pages/_document.js")));
module.exports = __webpack_exports__;
})();

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,26 @@ exports.id = "vendor-chunks/@swc";
exports.ids = ["vendor-chunks/@swc"]; exports.ids = ["vendor-chunks/@swc"];
exports.modules = { exports.modules = {
/***/ "./node_modules/@swc/helpers/cjs/_interop_require_default.cjs":
/*!********************************************************************!*\
!*** ./node_modules/@swc/helpers/cjs/_interop_require_default.cjs ***!
\********************************************************************/
/***/ ((__unused_webpack_module, exports) => {
eval("\n\nfunction _interop_require_default(obj) {\n return obj && obj.__esModule ? obj : { default: obj };\n}\nexports._ = _interop_require_default;\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQHN3Yy9oZWxwZXJzL2Nqcy9faW50ZXJvcF9yZXF1aXJlX2RlZmF1bHQuY2pzIiwibWFwcGluZ3MiOiJBQUFhOztBQUViO0FBQ0EsMkNBQTJDO0FBQzNDO0FBQ0EsU0FBUyIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9Ac3djL2hlbHBlcnMvY2pzL19pbnRlcm9wX3JlcXVpcmVfZGVmYXVsdC5janMiXSwic291cmNlc0NvbnRlbnQiOlsiXCJ1c2Ugc3RyaWN0XCI7XG5cbmZ1bmN0aW9uIF9pbnRlcm9wX3JlcXVpcmVfZGVmYXVsdChvYmopIHtcbiAgICByZXR1cm4gb2JqICYmIG9iai5fX2VzTW9kdWxlID8gb2JqIDogeyBkZWZhdWx0OiBvYmogfTtcbn1cbmV4cG9ydHMuXyA9IF9pbnRlcm9wX3JlcXVpcmVfZGVmYXVsdDtcbiJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOlswXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./node_modules/@swc/helpers/cjs/_interop_require_default.cjs\n");
/***/ }),
/***/ "./node_modules/@swc/helpers/cjs/_interop_require_wildcard.cjs":
/*!*********************************************************************!*\
!*** ./node_modules/@swc/helpers/cjs/_interop_require_wildcard.cjs ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, exports) => {
eval("\n\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}\nexports._ = _interop_require_wildcard;\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvQHN3Yy9oZWxwZXJzL2Nqcy9faW50ZXJvcF9yZXF1aXJlX3dpbGRjYXJkLmNqcyIsIm1hcHBpbmdzIjoiQUFBYTs7QUFFYjtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQSx1RkFBdUY7O0FBRXZGOztBQUVBOztBQUVBLG1CQUFtQjtBQUNuQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0EsU0FBUyIsInNvdXJjZXMiOlsiL1VzZXJzL21hdHRicnVjZS9Eb2N1bWVudHMvUHJvamVjdHMvT3BlbkNsYXcvV2ViL2hlYXJ0YmVhdC1tb25pdG9yL25vZGVfbW9kdWxlcy9Ac3djL2hlbHBlcnMvY2pzL19pbnRlcm9wX3JlcXVpcmVfd2lsZGNhcmQuY2pzIl0sInNvdXJjZXNDb250ZW50IjpbIlwidXNlIHN0cmljdFwiO1xuXG5mdW5jdGlvbiBfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUobm9kZUludGVyb3ApIHtcbiAgICBpZiAodHlwZW9mIFdlYWtNYXAgIT09IFwiZnVuY3Rpb25cIikgcmV0dXJuIG51bGw7XG5cbiAgICB2YXIgY2FjaGVCYWJlbEludGVyb3AgPSBuZXcgV2Vha01hcCgpO1xuICAgIHZhciBjYWNoZU5vZGVJbnRlcm9wID0gbmV3IFdlYWtNYXAoKTtcblxuICAgIHJldHVybiAoX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlID0gZnVuY3Rpb24obm9kZUludGVyb3ApIHtcbiAgICAgICAgcmV0dXJuIG5vZGVJbnRlcm9wID8gY2FjaGVOb2RlSW50ZXJvcCA6IGNhY2hlQmFiZWxJbnRlcm9wO1xuICAgIH0pKG5vZGVJbnRlcm9wKTtcbn1cbmZ1bmN0aW9uIF9pbnRlcm9wX3JlcXVpcmVfd2lsZGNhcmQob2JqLCBub2RlSW50ZXJvcCkge1xuICAgIGlmICghbm9kZUludGVyb3AgJiYgb2JqICYmIG9iai5fX2VzTW9kdWxlKSByZXR1cm4gb2JqO1xuICAgIGlmIChvYmogPT09IG51bGwgfHwgdHlwZW9mIG9iaiAhPT0gXCJvYmplY3RcIiAmJiB0eXBlb2Ygb2JqICE9PSBcImZ1bmN0aW9uXCIpIHJldHVybiB7IGRlZmF1bHQ6IG9iaiB9O1xuXG4gICAgdmFyIGNhY2hlID0gX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlKG5vZGVJbnRlcm9wKTtcblxuICAgIGlmIChjYWNoZSAmJiBjYWNoZS5oYXMob2JqKSkgcmV0dXJuIGNhY2hlLmdldChvYmopO1xuXG4gICAgdmFyIG5ld09iaiA9IHsgX19wcm90b19fOiBudWxsIH07XG4gICAgdmFyIGhhc1Byb3BlcnR5RGVzY3JpcHRvciA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0eSAmJiBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yO1xuXG4gICAgZm9yICh2YXIga2V5IGluIG9iaikge1xuICAgICAgICBpZiAoa2V5ICE9PSBcImRlZmF1bHRcIiAmJiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqLCBrZXkpKSB7XG4gICAgICAgICAgICB2YXIgZGVzYyA9IGhhc1Byb3BlcnR5RGVzY3JpcHRvciA/IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqLCBrZXkpIDogbnVsbDtcbiAgICAgICAgICAgIGlmIChkZXNjICYmIChkZXNjLmdldCB8fCBkZXNjLnNldCkpIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShuZXdPYmosIGtleSwgZGVzYyk7XG4gICAgICAgICAgICBlbHNlIG5ld09ialtrZXldID0gb2JqW2tleV07XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBuZXdPYmouZGVmYXVsdCA9IG9iajtcblxuICAgIGlmIChjYWNoZSkgY2FjaGUuc2V0KG9iaiwgbmV3T2JqKTtcblxuICAgIHJldHVybiBuZXdPYmo7XG59XG5leHBvcnRzLl8gPSBfaW50ZXJvcF9yZXF1aXJlX3dpbGRjYXJkO1xuIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6WzBdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./node_modules/@swc/helpers/cjs/_interop_require_wildcard.cjs\n");
/***/ }),
/***/ "(ssr)/./node_modules/@swc/helpers/esm/_interop_require_default.js": /***/ "(ssr)/./node_modules/@swc/helpers/esm/_interop_require_default.js":
/*!*******************************************************************!*\ /*!*******************************************************************!*\
!*** ./node_modules/@swc/helpers/esm/_interop_require_default.js ***! !*** ./node_modules/@swc/helpers/esm/_interop_require_default.js ***!

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -47,6 +47,11 @@
/******/ __webpack_require__.m = __webpack_modules__; /******/ __webpack_require__.m = __webpack_modules__;
/******/ /******/
/************************************************************************/ /************************************************************************/
/******/ /* webpack/runtime/amd options */
/******/ (() => {
/******/ __webpack_require__.amdO = {};
/******/ })();
/******/
/******/ /* webpack/runtime/compat get default export */ /******/ /* webpack/runtime/compat get default export */
/******/ (() => { /******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules /******/ // getDefaultExport function for compatibility with non-harmony modules
@ -125,7 +130,7 @@
/******/ /******/
/******/ /* webpack/runtime/getFullHash */ /******/ /* webpack/runtime/getFullHash */
/******/ (() => { /******/ (() => {
/******/ __webpack_require__.h = () => ("0e336cb3c86c715c") /******/ __webpack_require__.h = () => ("a0760f453524691e")
/******/ })(); /******/ })();
/******/ /******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ /* webpack/runtime/hasOwnProperty shorthand */

View File

@ -0,0 +1,28 @@
/*
* 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["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([["/_error"],{
/***/ "./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=%2FUsers%2Fmattbruce%2FDocuments%2FProjects%2FOpenClaw%2FWeb%2Fheartbeat-monitor%2Fnode_modules%2Fnext%2Fdist%2Fpages%2F_error.js&page=%2F_error!":
/*!*****************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=%2FUsers%2Fmattbruce%2FDocuments%2FProjects%2FOpenClaw%2FWeb%2Fheartbeat-monitor%2Fnode_modules%2Fnext%2Fdist%2Fpages%2F_error.js&page=%2F_error! ***!
\*****************************************************************************************************************************************************************************************************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
eval(__webpack_require__.ts("\n (window.__NEXT_P = window.__NEXT_P || []).push([\n \"/_error\",\n function () {\n return __webpack_require__(/*! ./node_modules/next/dist/pages/_error.js */ \"./node_modules/next/dist/pages/_error.js\");\n }\n ]);\n if(true) {\n module.hot.dispose(function () {\n window.__NEXT_P.push([\"/_error\"])\n });\n }\n //# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L2J1aWxkL3dlYnBhY2svbG9hZGVycy9uZXh0LWNsaWVudC1wYWdlcy1sb2FkZXIuanM/YWJzb2x1dGVQYWdlUGF0aD0lMkZVc2VycyUyRm1hdHRicnVjZSUyRkRvY3VtZW50cyUyRlByb2plY3RzJTJGT3BlbkNsYXclMkZXZWIlMkZoZWFydGJlYXQtbW9uaXRvciUyRm5vZGVfbW9kdWxlcyUyRm5leHQlMkZkaXN0JTJGcGFnZXMlMkZfZXJyb3IuanMmcGFnZT0lMkZfZXJyb3IhIiwibWFwcGluZ3MiOiI7QUFDQTtBQUNBO0FBQ0E7QUFDQSxlQUFlLG1CQUFPLENBQUMsMEZBQTBDO0FBQ2pFO0FBQ0E7QUFDQSxPQUFPLElBQVU7QUFDakIsTUFBTSxVQUFVO0FBQ2hCO0FBQ0EsT0FBTztBQUNQO0FBQ0EiLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAod2luZG93Ll9fTkVYVF9QID0gd2luZG93Ll9fTkVYVF9QIHx8IFtdKS5wdXNoKFtcbiAgICAgIFwiL19lcnJvclwiLFxuICAgICAgZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gcmVxdWlyZShcIi4vbm9kZV9tb2R1bGVzL25leHQvZGlzdC9wYWdlcy9fZXJyb3IuanNcIik7XG4gICAgICB9XG4gICAgXSk7XG4gICAgaWYobW9kdWxlLmhvdCkge1xuICAgICAgbW9kdWxlLmhvdC5kaXNwb3NlKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgd2luZG93Ll9fTkVYVF9QLnB1c2goW1wiL19lcnJvclwiXSlcbiAgICAgIH0pO1xuICAgIH1cbiAgIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=%2FUsers%2Fmattbruce%2FDocuments%2FProjects%2FOpenClaw%2FWeb%2Fheartbeat-monitor%2Fnode_modules%2Fnext%2Fdist%2Fpages%2F_error.js&page=%2F_error!\n"));
/***/ })
},
/******/ __webpack_require__ => { // webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
/******/ __webpack_require__.O(0, ["main"], () => (__webpack_exec__("./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=%2FUsers%2Fmattbruce%2FDocuments%2FProjects%2FOpenClaw%2FWeb%2Fheartbeat-monitor%2Fnode_modules%2Fnext%2Fdist%2Fpages%2F_error.js&page=%2F_error!")));
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]);

View File

@ -0,0 +1,28 @@
/*
* 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["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([["app/api/monitor/route"],{
/***/ "(app-pages-browser)/./node_modules/next/dist/build/webpack/loaders/next-flight-client-entry-loader.js?server=false!":
/*!*******************************************************************************************************!*\
!*** ./node_modules/next/dist/build/webpack/loaders/next-flight-client-entry-loader.js?server=false! ***!
\*******************************************************************************************************/
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
/***/ })
},
/******/ __webpack_require__ => { // webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
/******/ __webpack_require__.O(0, ["main-app"], () => (__webpack_exec__("(app-pages-browser)/./node_modules/next/dist/build/webpack/loaders/next-flight-client-entry-loader.js?server=false!")));
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -117,7 +117,7 @@
/******/ /******/
/******/ /* webpack/runtime/getFullHash */ /******/ /* webpack/runtime/getFullHash */
/******/ (() => { /******/ (() => {
/******/ __webpack_require__.h = () => ("423737f34be5c1f0") /******/ __webpack_require__.h = () => ("04904af1bd77f32c")
/******/ })(); /******/ })();
/******/ /******/
/******/ /* webpack/runtime/global */ /******/ /* webpack/runtime/global */

2100
.next/static/chunks/main.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
/*
* 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["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([["pages/_app"],{
/***/ "./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_app&page=%2F_app!":
/*!********************************************************************************************************************************************!*\
!*** ./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_app&page=%2F_app! ***!
\********************************************************************************************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
eval(__webpack_require__.ts("\n (window.__NEXT_P = window.__NEXT_P || []).push([\n \"/_app\",\n function () {\n return __webpack_require__(/*! next/dist/pages/_app */ \"./node_modules/next/dist/pages/_app.js\");\n }\n ]);\n if(true) {\n module.hot.dispose(function () {\n window.__NEXT_P.push([\"/_app\"])\n });\n }\n //# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L2J1aWxkL3dlYnBhY2svbG9hZGVycy9uZXh0LWNsaWVudC1wYWdlcy1sb2FkZXIuanM/YWJzb2x1dGVQYWdlUGF0aD1uZXh0JTJGZGlzdCUyRnBhZ2VzJTJGX2FwcCZwYWdlPSUyRl9hcHAhIiwibWFwcGluZ3MiOiI7QUFDQTtBQUNBO0FBQ0E7QUFDQSxlQUFlLG1CQUFPLENBQUMsb0VBQXNCO0FBQzdDO0FBQ0E7QUFDQSxPQUFPLElBQVU7QUFDakIsTUFBTSxVQUFVO0FBQ2hCO0FBQ0EsT0FBTztBQUNQO0FBQ0EiLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAod2luZG93Ll9fTkVYVF9QID0gd2luZG93Ll9fTkVYVF9QIHx8IFtdKS5wdXNoKFtcbiAgICAgIFwiL19hcHBcIixcbiAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHJlcXVpcmUoXCJuZXh0L2Rpc3QvcGFnZXMvX2FwcFwiKTtcbiAgICAgIH1cbiAgICBdKTtcbiAgICBpZihtb2R1bGUuaG90KSB7XG4gICAgICBtb2R1bGUuaG90LmRpc3Bvc2UoZnVuY3Rpb24gKCkge1xuICAgICAgICB3aW5kb3cuX19ORVhUX1AucHVzaChbXCIvX2FwcFwiXSlcbiAgICAgIH0pO1xuICAgIH1cbiAgIl0sIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_app&page=%2F_app!\n"));
/***/ })
},
/******/ __webpack_require__ => { // webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
/******/ __webpack_require__.O(0, ["main"], () => (__webpack_exec__("./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_app&page=%2F_app!"), __webpack_exec__("./node_modules/next/dist/client/router.js")));
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]);

View File

@ -0,0 +1,28 @@
/*
* 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["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([["pages/_error"],{
/***/ "./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_error&page=%2F_error!":
/*!************************************************************************************************************************************************!*\
!*** ./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_error&page=%2F_error! ***!
\************************************************************************************************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
eval(__webpack_require__.ts("\n (window.__NEXT_P = window.__NEXT_P || []).push([\n \"/_error\",\n function () {\n return __webpack_require__(/*! next/dist/pages/_error */ \"./node_modules/next/dist/pages/_error.js\");\n }\n ]);\n if(true) {\n module.hot.dispose(function () {\n window.__NEXT_P.push([\"/_error\"])\n });\n }\n //# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L2J1aWxkL3dlYnBhY2svbG9hZGVycy9uZXh0LWNsaWVudC1wYWdlcy1sb2FkZXIuanM/YWJzb2x1dGVQYWdlUGF0aD1uZXh0JTJGZGlzdCUyRnBhZ2VzJTJGX2Vycm9yJnBhZ2U9JTJGX2Vycm9yISIsIm1hcHBpbmdzIjoiO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZUFBZSxtQkFBTyxDQUFDLHdFQUF3QjtBQUMvQztBQUNBO0FBQ0EsT0FBTyxJQUFVO0FBQ2pCLE1BQU0sVUFBVTtBQUNoQjtBQUNBLE9BQU87QUFDUDtBQUNBIiwic291cmNlcyI6WyIiXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgKHdpbmRvdy5fX05FWFRfUCA9IHdpbmRvdy5fX05FWFRfUCB8fCBbXSkucHVzaChbXG4gICAgICBcIi9fZXJyb3JcIixcbiAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHJlcXVpcmUoXCJuZXh0L2Rpc3QvcGFnZXMvX2Vycm9yXCIpO1xuICAgICAgfVxuICAgIF0pO1xuICAgIGlmKG1vZHVsZS5ob3QpIHtcbiAgICAgIG1vZHVsZS5ob3QuZGlzcG9zZShmdW5jdGlvbiAoKSB7XG4gICAgICAgIHdpbmRvdy5fX05FWFRfUC5wdXNoKFtcIi9fZXJyb3JcIl0pXG4gICAgICB9KTtcbiAgICB9XG4gICJdLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_error&page=%2F_error!\n"));
/***/ })
},
/******/ __webpack_require__ => { // webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
/******/ __webpack_require__.O(0, ["pages/_app","main"], () => (__webpack_exec__("./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=next%2Fdist%2Fpages%2F_error&page=%2F_error!")));
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]);

62
.next/static/chunks/react-refresh.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -89,6 +89,18 @@
/******/ }; /******/ };
/******/ })(); /******/ })();
/******/ /******/
/******/ /* 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 */ /******/ /* webpack/runtime/create fake namespace object */
/******/ (() => { /******/ (() => {
/******/ var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__); /******/ var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__);
@ -178,7 +190,7 @@
/******/ /******/
/******/ /* webpack/runtime/getFullHash */ /******/ /* webpack/runtime/getFullHash */
/******/ (() => { /******/ (() => {
/******/ __webpack_require__.h = () => ("e53ddb03c3e18945") /******/ __webpack_require__.h = () => ("35acde78c9b1f17c")
/******/ })(); /******/ })();
/******/ /******/
/******/ /* webpack/runtime/global */ /******/ /* webpack/runtime/global */

View File

@ -1 +1 @@
self.__BUILD_MANIFEST = (function(a){return {__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:a,__routerFilterDynamic:a,sortedPages:["\u002F_app"]}}(void 0));self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB() self.__BUILD_MANIFEST = (function(a){return {__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:a,__routerFilterDynamic:a,"/_error":["static\u002Fchunks\u002Fpages\u002F_error.js"],sortedPages:["\u002F_app","\u002F_error"]}}(void 0));self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()

View File

@ -0,0 +1 @@
{"c":["app/page","webpack"],"r":["app/_not-found/page"],"m":["(app-pages-browser)/./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=%2FUsers%2Fmattbruce%2FDocuments%2FProjects%2FOpenClaw%2FWeb%2Fheartbeat-monitor%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error.js&page=%2F_not-found%2Fpage!","(app-pages-browser)/./node_modules/next/dist/client/components/http-access-fallback/error-fallback.js","(app-pages-browser)/./node_modules/next/dist/client/components/not-found-error.js"]}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"c":["webpack"],"r":[],"m":[]}

View File

@ -0,0 +1 @@
{"c":["webpack"],"r":[],"m":[]}

View File

@ -0,0 +1,18 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("35acde78c9b1f17c")
/******/ })();
/******/
/******/ }
);

View File

@ -0,0 +1,18 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("18279f93be89f6d1")
/******/ })();
/******/
/******/ }
);

View File

@ -0,0 +1,30 @@
"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/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("c9bbb778f39c5d29")
/******/ })();
/******/
/******/ }
);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,347 @@
// File: /Users/mattbruce/Documents/Projects/OpenClaw/Web/heartbeat-monitor/src/app/api/monitor/route.ts
import * as entry from '../../../../../src/app/api/monitor/route.js'
import type { NextRequest } from 'next/server.js'
type TEntry = typeof import('../../../../../src/app/api/monitor/route.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<{
GET?: Function
HEAD?: Function
OPTIONS?: Function
POST?: Function
PUT?: Function
DELETE?: Function
PATCH?: 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
}, TEntry, ''>>()
type RouteContext = { params: Promise<SegmentParams> }
// Check the prop type of the entry function
if ('GET' in entry) {
checkFields<
Diff<
ParamCheck<Request | NextRequest>,
{
__tag__: 'GET'
__param_position__: 'first'
__param_type__: FirstArg<MaybeField<TEntry, 'GET'>>
},
'GET'
>
>()
checkFields<
Diff<
ParamCheck<RouteContext>,
{
__tag__: 'GET'
__param_position__: 'second'
__param_type__: SecondArg<MaybeField<TEntry, 'GET'>>
},
'GET'
>
>()
checkFields<
Diff<
{
__tag__: 'GET',
__return_type__: Response | void | never | Promise<Response | void | never>
},
{
__tag__: 'GET',
__return_type__: ReturnType<MaybeField<TEntry, 'GET'>>
},
'GET'
>
>()
}
// Check the prop type of the entry function
if ('HEAD' in entry) {
checkFields<
Diff<
ParamCheck<Request | NextRequest>,
{
__tag__: 'HEAD'
__param_position__: 'first'
__param_type__: FirstArg<MaybeField<TEntry, 'HEAD'>>
},
'HEAD'
>
>()
checkFields<
Diff<
ParamCheck<RouteContext>,
{
__tag__: 'HEAD'
__param_position__: 'second'
__param_type__: SecondArg<MaybeField<TEntry, 'HEAD'>>
},
'HEAD'
>
>()
checkFields<
Diff<
{
__tag__: 'HEAD',
__return_type__: Response | void | never | Promise<Response | void | never>
},
{
__tag__: 'HEAD',
__return_type__: ReturnType<MaybeField<TEntry, 'HEAD'>>
},
'HEAD'
>
>()
}
// Check the prop type of the entry function
if ('OPTIONS' in entry) {
checkFields<
Diff<
ParamCheck<Request | NextRequest>,
{
__tag__: 'OPTIONS'
__param_position__: 'first'
__param_type__: FirstArg<MaybeField<TEntry, 'OPTIONS'>>
},
'OPTIONS'
>
>()
checkFields<
Diff<
ParamCheck<RouteContext>,
{
__tag__: 'OPTIONS'
__param_position__: 'second'
__param_type__: SecondArg<MaybeField<TEntry, 'OPTIONS'>>
},
'OPTIONS'
>
>()
checkFields<
Diff<
{
__tag__: 'OPTIONS',
__return_type__: Response | void | never | Promise<Response | void | never>
},
{
__tag__: 'OPTIONS',
__return_type__: ReturnType<MaybeField<TEntry, 'OPTIONS'>>
},
'OPTIONS'
>
>()
}
// Check the prop type of the entry function
if ('POST' in entry) {
checkFields<
Diff<
ParamCheck<Request | NextRequest>,
{
__tag__: 'POST'
__param_position__: 'first'
__param_type__: FirstArg<MaybeField<TEntry, 'POST'>>
},
'POST'
>
>()
checkFields<
Diff<
ParamCheck<RouteContext>,
{
__tag__: 'POST'
__param_position__: 'second'
__param_type__: SecondArg<MaybeField<TEntry, 'POST'>>
},
'POST'
>
>()
checkFields<
Diff<
{
__tag__: 'POST',
__return_type__: Response | void | never | Promise<Response | void | never>
},
{
__tag__: 'POST',
__return_type__: ReturnType<MaybeField<TEntry, 'POST'>>
},
'POST'
>
>()
}
// Check the prop type of the entry function
if ('PUT' in entry) {
checkFields<
Diff<
ParamCheck<Request | NextRequest>,
{
__tag__: 'PUT'
__param_position__: 'first'
__param_type__: FirstArg<MaybeField<TEntry, 'PUT'>>
},
'PUT'
>
>()
checkFields<
Diff<
ParamCheck<RouteContext>,
{
__tag__: 'PUT'
__param_position__: 'second'
__param_type__: SecondArg<MaybeField<TEntry, 'PUT'>>
},
'PUT'
>
>()
checkFields<
Diff<
{
__tag__: 'PUT',
__return_type__: Response | void | never | Promise<Response | void | never>
},
{
__tag__: 'PUT',
__return_type__: ReturnType<MaybeField<TEntry, 'PUT'>>
},
'PUT'
>
>()
}
// Check the prop type of the entry function
if ('DELETE' in entry) {
checkFields<
Diff<
ParamCheck<Request | NextRequest>,
{
__tag__: 'DELETE'
__param_position__: 'first'
__param_type__: FirstArg<MaybeField<TEntry, 'DELETE'>>
},
'DELETE'
>
>()
checkFields<
Diff<
ParamCheck<RouteContext>,
{
__tag__: 'DELETE'
__param_position__: 'second'
__param_type__: SecondArg<MaybeField<TEntry, 'DELETE'>>
},
'DELETE'
>
>()
checkFields<
Diff<
{
__tag__: 'DELETE',
__return_type__: Response | void | never | Promise<Response | void | never>
},
{
__tag__: 'DELETE',
__return_type__: ReturnType<MaybeField<TEntry, 'DELETE'>>
},
'DELETE'
>
>()
}
// Check the prop type of the entry function
if ('PATCH' in entry) {
checkFields<
Diff<
ParamCheck<Request | NextRequest>,
{
__tag__: 'PATCH'
__param_position__: 'first'
__param_type__: FirstArg<MaybeField<TEntry, 'PATCH'>>
},
'PATCH'
>
>()
checkFields<
Diff<
ParamCheck<RouteContext>,
{
__tag__: 'PATCH'
__param_position__: 'second'
__param_type__: SecondArg<MaybeField<TEntry, 'PATCH'>>
},
'PATCH'
>
>()
checkFields<
Diff<
{
__tag__: 'PATCH',
__return_type__: Response | void | never | Promise<Response | void | never>
},
{
__tag__: 'PATCH',
__return_type__: ReturnType<MaybeField<TEntry, 'PATCH'>>
},
'PATCH'
>
>()
}
// 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
type ParamCheck<T> = {
__tag__: string
__param_position__: string
__param_type__: T
}
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
View 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__'

View File

@ -2,14 +2,23 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { Activity, Plus, RefreshCw, Trash2, ExternalLink, Server, TrendingUp, Zap, AlertCircle, Globe } from "lucide-react"; import {
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; Activity,
import { Button } from "@/components/ui/button"; Plus,
import { Badge } from "@/components/ui/badge"; RefreshCw,
import { Progress } from "@/components/ui/progress"; Trash2,
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; ExternalLink,
import { Input } from "@/components/ui/input"; Server,
import { Label } from "@/components/ui/label"; TrendingUp,
AlertCircle,
Globe,
CheckCircle2,
XCircle,
Clock,
MoreHorizontal,
LayoutGrid,
List
} from "lucide-react";
interface App { interface App {
id: string; id: string;
@ -35,7 +44,9 @@ export default function HeartbeatMonitor() {
const [apps, setApps] = useState<App[]>([]); const [apps, setApps] = useState<App[]>([]);
const [status, setStatus] = useState<StatusEntry[]>([]); const [status, setStatus] = useState<StatusEntry[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [showAddApp, setShowAddApp] = useState(false); const [showAddApp, setShowAddApp] = useState(false);
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
const [newApp, setNewApp] = useState<Partial<App>>({ const [newApp, setNewApp] = useState<Partial<App>>({
name: "", name: "",
description: "", description: "",
@ -64,9 +75,15 @@ export default function HeartbeatMonitor() {
console.error("Failed to fetch data:", err); console.error("Failed to fetch data:", err);
} finally { } finally {
setLoading(false); setLoading(false);
setRefreshing(false);
} }
} }
async function handleRefresh() {
setRefreshing(true);
await fetchData();
}
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;
@ -76,6 +93,17 @@ export default function HeartbeatMonitor() {
body: JSON.stringify({ action: "addApp", app: newApp }), body: JSON.stringify({ action: "addApp", app: newApp }),
}); });
setShowAddApp(false); setShowAddApp(false);
setNewApp({
name: "",
description: "",
url: "http://localhost:",
port: 3000,
path: "",
command: "npm run dev",
category: "Other",
color: "#22C55E",
enabled: true,
});
fetchData(); fetchData();
} }
@ -96,24 +124,76 @@ export default function HeartbeatMonitor() {
const uptime = appStatus.length > 0 const uptime = appStatus.length > 0
? Math.round((appStatus.filter(s => s.status === "up").length / appStatus.length) * 100) ? Math.round((appStatus.filter(s => s.status === "up").length / appStatus.length) * 100)
: 100; : 100;
return { latest, isUp, uptime }; const avgResponseTime = appStatus.length > 0
? Math.round(appStatus.filter(s => s.responseTime).reduce((acc, s) => acc + (s.responseTime || 0), 0) / appStatus.filter(s => s.responseTime).length) || 0
: 0;
const last24Hours = appStatus.filter(s => {
const hoursAgo = (Date.now() - new Date(s.timestamp).getTime()) / (1000 * 60 * 60);
return hoursAgo <= 24;
});
return { latest, isUp, uptime, avgResponseTime, history: last24Hours };
} }
const stats = { const stats = {
total: apps.length, total: apps.length,
online: apps.filter((app) => getAppStatus(app.id).isUp).length, online: apps.filter((app) => getAppStatus(app.id).isUp).length,
offline: apps.filter((app) => !getAppStatus(app.id).isUp).length,
avgUptime: apps.length > 0 avgUptime: apps.length > 0
? Math.round(apps.reduce((acc, app) => acc + getAppStatus(app.id).uptime, 0) / apps.length) ? Math.round(apps.reduce((acc, app) => acc + getAppStatus(app.id).uptime, 0) / apps.length)
: 0, : 0,
avgResponseTime: apps.length > 0
? Math.round(apps.reduce((acc, app) => acc + getAppStatus(app.id).avgResponseTime, 0) / apps.length)
: 0,
incidents: status.filter(s => s.status === "down").length,
}; };
const allUp = stats.online === stats.total && stats.total > 0;
function Sparkline({ data }: { data: StatusEntry[] }) {
if (data.length === 0) {
return (
<div className="h-8 w-20 bg-slate-800/50 rounded flex items-end justify-center gap-0.5 p-1">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="w-1 bg-slate-700 rounded-sm" style={{ height: '30%' }} />
))}
</div>
);
}
const bars = data.slice(-8).map((entry, i) => {
const height = entry.status === "up"
? Math.max(20, Math.min(100, 100 - ((entry.responseTime || 100) / 10)))
: 10;
return (
<div
key={i}
className={`w-1.5 rounded-sm ${entry.status === "up" ? "bg-emerald-500/80" : "bg-red-500/80"}`}
style={{ height: `${height}%` }}
/>
);
});
return (
<div className="h-8 w-20 bg-slate-800/50 rounded flex items-end justify-center gap-0.5 p-1">
{bars}
</div>
);
}
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-slate-950 flex items-center justify-center">
<div className="flex flex-col items-center gap-4"> <motion.div
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-cyan-500 animate-pulse" /> initial={{ opacity: 0, scale: 0.9 }}
<p className="text-slate-400">Loading dashboard...</p> animate={{ opacity: 1, scale: 1 }}
</div> className="flex flex-col items-center gap-4"
>
<div className="relative">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-cyan-500 animate-pulse" />
<div className="absolute inset-0 w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-cyan-500 blur-xl opacity-50" />
</div>
<p className="text-slate-400 animate-pulse">Loading dashboard...</p>
</motion.div>
</div> </div>
); );
} }
@ -129,21 +209,38 @@ export default function HeartbeatMonitor() {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500 to-cyan-500 flex items-center justify-center shadow-lg shadow-emerald-500/20"> <motion.div
<Activity className="w-5 h-5 text-white" /> className="relative"
</div> whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500 to-cyan-500 flex items-center justify-center shadow-lg shadow-emerald-500/20">
<Activity className="w-5 h-5 text-white" />
</div>
</motion.div>
<div> <div>
<h1 className="text-xl font-bold text-white">Heartbeat</h1> <h1 className="text-xl font-bold text-white">Heartbeat</h1>
<p className="text-xs text-slate-400">Infrastructure Monitor</p> <p className="text-xs text-slate-400">Infrastructure Monitor</p>
</div> </div>
</div> </div>
<Button
onClick={() => setShowAddApp(true)} <div className="flex items-center gap-3">
className="gap-2 bg-gradient-to-r from-emerald-600 to-emerald-500 hover:from-emerald-500 hover:to-emerald-400 text-white" <button
> onClick={handleRefresh}
<Plus className="w-4 h-4" /> disabled={refreshing}
Add Monitor className="p-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors"
</Button> >
<RefreshCw className={`w-5 h-5 ${refreshing ? 'animate-spin' : ''}`} />
</button>
<button
onClick={() => setShowAddApp(true)}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-emerald-600 to-emerald-500 hover:from-emerald-500 hover:to-emerald-400 text-white rounded-lg shadow-lg shadow-emerald-500/20 transition-all"
>
<Plus className="w-4 h-4" />
<span className="hidden sm:inline">Add Monitor</span>
</button>
</div>
</div> </div>
</div> </div>
</motion.header> </motion.header>
@ -151,163 +248,348 @@ export default function HeartbeatMonitor() {
{/* Main Content */} {/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1 }}
transition={{ staggerChildren: 0.1 }}
className="space-y-8" className="space-y-8"
> >
{/* Status Overview */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="flex items-center gap-3"
>
<div className={`w-3 h-3 rounded-full ${allUp ? 'bg-emerald-500 animate-pulse' : 'bg-red-500'}`} />
<span className="text-lg font-medium">
{allUp ? 'All Systems Operational' : `${stats.offline} Service${stats.offline > 1 ? 's' : ''} Down`}
</span>
<span className="text-slate-500"> Last updated {new Date().toLocaleTimeString()}</span>
</motion.div>
{/* Stats Grid */} {/* Stats Grid */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<Card className="bg-slate-900/50 border-slate-800 border-l-4 border-l-emerald-500"> <motion.div
<CardContent className="p-6"> initial={{ opacity: 0, y: 20 }}
<div className="flex items-center justify-between"> animate={{ opacity: 1, y: 0 }}
<div className="p-3 rounded-xl bg-emerald-500/10"> transition={{ delay: 0.1 }}
<Server className="w-6 h-6 text-emerald-500" /> whileHover={{ y: -4 }}
</div> className="bg-slate-900/50 border border-slate-800 border-l-4 border-l-emerald-500 rounded-xl p-6 cursor-pointer hover:bg-slate-800/50 transition-colors"
<div className="text-right"> >
<p className="text-3xl font-bold">{stats.online}/{stats.total}</p> <div className="flex items-center justify-between">
<p className="text-sm text-slate-400">Services Online</p> <div className="p-3 rounded-xl bg-emerald-500/10">
</div> <Server className="w-6 h-6 text-emerald-500" />
</div> </div>
</CardContent> <div className="text-right">
</Card> <p className="text-3xl font-bold">{stats.online}<span className="text-slate-500 text-lg">/{stats.total}</span></p>
<p className="text-sm text-slate-400">Services Online</p>
</div>
</div>
</motion.div>
<Card className="bg-slate-900/50 border-slate-800 border-l-4 border-l-blue-500"> <motion.div
<CardContent className="p-6"> initial={{ opacity: 0, y: 20 }}
<div className="flex items-center justify-between"> animate={{ opacity: 1, y: 0 }}
<div className="p-3 rounded-xl bg-blue-500/10"> transition={{ delay: 0.15 }}
<TrendingUp className="w-6 h-6 text-blue-500" /> whileHover={{ y: -4 }}
</div> className="bg-slate-900/50 border border-slate-800 border-l-4 border-l-blue-500 rounded-xl p-6 cursor-pointer hover:bg-slate-800/50 transition-colors"
<div className="text-right"> >
<p className="text-3xl font-bold">{stats.avgUptime}%</p> <div className="flex items-center justify-between">
<p className="text-sm text-slate-400">Avg Uptime</p> <div className="p-3 rounded-xl bg-blue-500/10">
</div> <TrendingUp className="w-6 h-6 text-blue-500" />
</div> </div>
<Progress value={stats.avgUptime} className="h-2 mt-4" /> <div className="text-right">
</CardContent> <p className="text-3xl font-bold">{stats.avgUptime}%</p>
</Card> <p className="text-sm text-slate-400">Avg Uptime</p>
</div>
</div>
<div className="mt-4 w-full bg-slate-800 rounded-full h-2">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${stats.avgUptime}%` }}
transition={{ duration: 1, delay: 0.5 }}
className="bg-blue-500 h-2 rounded-full"
/>
</div>
</motion.div>
<Card className="bg-slate-900/50 border-slate-800 border-l-4 border-l-amber-500"> <motion.div
<CardContent className="p-6"> initial={{ opacity: 0, y: 20 }}
<div className="flex items-center justify-between"> animate={{ opacity: 1, y: 0 }}
<div className="p-3 rounded-xl bg-amber-500/10"> transition={{ delay: 0.2 }}
<AlertCircle className="w-6 h-6 text-amber-500" /> whileHover={{ y: -4 }}
</div> className="bg-slate-900/50 border border-slate-800 border-l-4 border-l-purple-500 rounded-xl p-6 cursor-pointer hover:bg-slate-800/50 transition-colors"
<div className="text-right"> >
<p className="text-3xl font-bold">{status.filter(s => s.status === "down").length}</p> <div className="flex items-center justify-between">
<p className="text-sm text-slate-400">Incidents</p> <div className="p-3 rounded-xl bg-purple-500/10">
</div> <Clock className="w-6 h-6 text-purple-500" />
</div> </div>
</CardContent> <div className="text-right">
</Card> <p className="text-3xl font-bold">{stats.avgResponseTime}<span className="text-slate-500 text-lg">ms</span></p>
<p className="text-sm text-slate-400">Avg Response</p>
</div>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.25 }}
whileHover={{ y: -4 }}
className="bg-slate-900/50 border border-slate-800 border-l-4 border-l-amber-500 rounded-xl p-6 cursor-pointer hover:bg-slate-800/50 transition-colors"
>
<div className="flex items-center justify-between">
<div className="p-3 rounded-xl bg-amber-500/10">
<AlertCircle className="w-6 h-6 text-amber-500" />
</div>
<div className="text-right">
<p className="text-3xl font-bold">{stats.incidents}</p>
<p className="text-sm text-slate-400">Incidents</p>
</div>
</div>
</motion.div>
</div> </div>
{/* Monitors */} {/* Monitors Section */}
<Card className="bg-slate-900/50 border-slate-800 overflow-hidden"> <motion.div
<CardHeader> initial={{ opacity: 0, y: 20 }}
<CardTitle className="flex items-center gap-2"> animate={{ opacity: 1, y: 0 }}
<Globe className="w-5 h-5 text-slate-400" /> transition={{ delay: 0.3 }}
Monitored Services className="bg-slate-900/50 border border-slate-800 rounded-xl overflow-hidden"
</CardTitle> >
<CardDescription>{stats.total} active monitors</CardDescription> <div className="px-6 py-4 border-b border-slate-800 flex items-center justify-between">
</CardHeader> <div>
<div className="flex items-center gap-2">
<CardContent className="p-0"> <Globe className="w-5 h-5 text-slate-400" />
{apps.length === 0 ? ( <h2 className="text-xl font-bold">Monitored Services</h2>
<div className="px-6 py-16 text-center">
<Activity className="w-16 h-16 text-slate-600 mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">No monitors yet</h3>
<Button onClick={() => setShowAddApp(true)}>
<Plus className="w-4 h-4 mr-2" />
Add Your First Monitor
</Button>
</div> </div>
) : ( <p className="text-sm text-slate-400 mt-1">{stats.total} active monitor{stats.total !== 1 ? 's' : ''}</p>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 p-6"> </div>
<div className="flex items-center gap-2">
<button
onClick={() => setViewMode(viewMode === "grid" ? "list" : "grid")}
className="p-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors"
>
{viewMode === "grid" ? <List className="w-4 h-4" /> : <LayoutGrid className="w-4 h-4" />}
</button>
</div>
</div>
<div className="p-6">
{apps.length === 0 ? (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center py-16"
>
<div className="w-20 h-20 bg-gradient-to-br from-slate-800 to-slate-900 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-inner">
<Activity className="w-10 h-10 text-slate-600" />
</div>
<h3 className="text-lg font-semibold mb-2">No monitors yet</h3>
<p className="text-slate-400 mb-6">Start monitoring your services by adding your first monitor</p>
<button
onClick={() => setShowAddApp(true)}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg transition-colors"
>
Add Your First Monitor
</button>
</motion.div>
) : viewMode === "grid" ? (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<AnimatePresence> <AnimatePresence>
{apps.map((app) => { {apps.map((app, index) => {
const { isUp, uptime, latest } = getAppStatus(app.id); const { isUp, uptime, avgResponseTime, history, latest } = getAppStatus(app.id);
return ( return (
<motion.div <motion.div
key={app.id} key={app.id}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95 }}
transition={{ delay: index * 0.05 }}
whileHover={{ y: -4 }} whileHover={{ y: -4 }}
className={`bg-slate-900 border border-slate-800 rounded-xl border-l-4 ${isUp ? 'border-l-emerald-500' : 'border-l-red-500'} p-5 group`}
> >
<Card className={`bg-slate-900 border-slate-800 border-l-4 ${isUp ? 'border-l-emerald-500' : 'border-l-red-500'}`}> <div className="flex items-start justify-between">
<CardContent className="p-5"> <div className="flex items-center gap-3">
<div className="flex items-start justify-between"> <div className={`w-10 h-10 rounded-xl flex items-center justify-center ${isUp ? 'bg-emerald-500/10' : 'bg-red-500/10'}`}>
<div className="flex items-center gap-3"> <Server className={`w-5 h-5 ${isUp ? 'text-emerald-500' : 'text-red-500'}`} />
<div className={`w-10 h-10 rounded-xl flex items-center justify-center ${isUp ? 'bg-emerald-500/10' : 'bg-red-500/10'}`}>
<Server className={`w-5 h-5 ${isUp ? 'text-emerald-500' : 'text-red-500'}`} />
</div>
<div>
<h3 className="font-semibold text-lg">{app.name}</h3>
<a href={app.url} target="_blank" rel="noopener noreferrer" className="text-sm text-slate-400 hover:text-emerald-400">
{app.url}
</a>
</div>
</div>
<Badge variant="outline" className={isUp ? "border-emerald-500/30 bg-emerald-500/10 text-emerald-400" : "border-red-500/30 bg-red-500/10 text-red-400"}>
<span className={`w-2 h-2 rounded-full mr-1.5 ${isUp ? 'bg-emerald-500' : 'bg-red-500'}`} />
{isUp ? "Operational" : "Down"}
</Badge>
</div> </div>
<div className="mt-4 flex items-center justify-between"> <div>
<div> <h3 className="font-semibold text-lg">{app.name}</h3>
<p className="text-2xl font-bold">{uptime}%</p> <a
<p className="text-xs text-slate-400">Uptime</p> href={app.url}
</div> target="_blank"
<div className="text-right"> rel="noopener noreferrer"
<p className="text-xs text-slate-400">Last check</p> className="text-sm text-slate-400 hover:text-emerald-400 transition-colors flex items-center gap-1"
<p className="text-sm">{latest ? new Date(latest.timestamp).toLocaleTimeString() : 'Never'}</p> >
</div> {app.url}
<Button variant="ghost" size="icon" onClick={() => deleteApp(app.id)} className="text-red-400"> <ExternalLink className="w-3 h-3" />
<Trash2 className="w-4 h-4" /> </a>
</Button>
</div> </div>
</CardContent> </div>
</Card> <span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border ${isUp ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400' : 'border-red-500/30 bg-red-500/10 text-red-400'}`}>
<span className={`relative flex h-2 w-2`}>
<span className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${isUp ? 'bg-emerald-400' : 'bg-red-400'}`}></span>
<span className={`relative inline-flex rounded-full h-2 w-2 ${isUp ? 'bg-emerald-500' : 'bg-red-500'}`}></span>
</span>
{isUp ? "Operational" : "Down"}
</span>
</div>
<div className="mt-4 grid grid-cols-3 gap-4">
<div>
<p className="text-2xl font-bold">{uptime}%</p>
<p className="text-xs text-slate-400">Uptime</p>
</div>
<div>
<p className="text-2xl font-bold">{avgResponseTime > 0 ? `${avgResponseTime}ms` : '—'}</p>
<p className="text-xs text-slate-400">Response</p>
</div>
<div className="flex items-center justify-end">
<Sparkline data={history} />
</div>
</div>
<div className="mt-4 pt-4 border-t border-slate-800 flex items-center justify-between">
<span className="text-xs text-slate-500">
Last check: {latest ? new Date(latest.timestamp).toLocaleTimeString() : 'Never'}
</span>
<button
onClick={() => deleteApp(app.id)}
className="p-2 text-slate-500 hover:text-red-400 hover:bg-red-500/10 rounded-lg opacity-0 group-hover:opacity-100 transition-all"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</motion.div>
);
})}
</AnimatePresence>
</div>
) : (
<div className="divide-y divide-slate-800">
<AnimatePresence>
{apps.map((app) => {
const { isUp, uptime, avgResponseTime, history, latest } = getAppStatus(app.id);
return (
<motion.div
key={app.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="py-4 flex items-center justify-between group hover:bg-slate-800/30 transition-colors px-2 rounded-lg"
>
<div className="flex items-center gap-4">
<div className={`w-2 h-12 rounded-full ${isUp ? 'bg-emerald-500' : 'bg-red-500'}`} />
<div>
<h3 className="font-semibold">{app.name}</h3>
<a href={app.url} target="_blank" rel="noopener noreferrer" className="text-sm text-slate-400 hover:text-emerald-400 transition-colors">
{app.url}
</a>
</div>
</div>
<div className="flex items-center gap-8">
<div className="text-right">
<p className="text-xl font-bold">{uptime}%</p>
<p className="text-xs text-slate-400">Uptime</p>
</div>
<Sparkline data={history} />
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border ${isUp ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400' : 'border-red-500/30 bg-red-500/10 text-red-400'}`}>
<span className={`w-2 h-2 rounded-full ${isUp ? 'bg-emerald-500' : 'bg-red-500'}`} />
{isUp ? "Operational" : "Down"}
</span>
<button
onClick={() => deleteApp(app.id)}
className="p-2 text-slate-500 hover:text-red-400 hover:bg-red-500/10 rounded-lg opacity-0 group-hover:opacity-100 transition-all"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</motion.div> </motion.div>
); );
})} })}
</AnimatePresence> </AnimatePresence>
</div> </div>
)} )}
</CardContent> </div>
</Card> </motion.div>
</motion.div> </motion.div>
</main> </main>
{/* Add Monitor Dialog */} {/* Add Monitor Dialog */}
<Dialog open={showAddApp} onOpenChange={setShowAddApp}> <AnimatePresence>
<DialogContent className="bg-slate-900 border-slate-800 text-white"> {showAddApp && (
<DialogHeader> <motion.div
<DialogTitle>Add New Monitor</DialogTitle> initial={{ opacity: 0 }}
<DialogDescription>Monitor a new service</DialogDescription> animate={{ opacity: 1 }}
</DialogHeader> exit={{ opacity: 0 }}
<form onSubmit={addApp} className="space-y-4"> className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4"
<div> >
<Label>Name</Label> <motion.div
<Input value={newApp.name} onChange={(e) => setNewApp({ ...newApp, name: e.target.value })} className="bg-slate-950 border-slate-800" required /> initial={{ opacity: 0, scale: 0.95, y: 20 }}
</div> animate={{ opacity: 1, scale: 1, y: 0 }}
<div className="grid grid-cols-2 gap-4"> exit={{ opacity: 0, scale: 0.95, y: 20 }}
<div> className="bg-slate-900 border border-slate-800 rounded-xl p-6 w-full max-w-md shadow-2xl"
<Label>URL</Label> >
<Input type="url" value={newApp.url} onChange={(e) => setNewApp({ ...newApp, url: e.target.value })} className="bg-slate-950 border-slate-800" required /> <h2 className="text-xl font-bold mb-2">Add New Monitor</h2>
</div> <p className="text-slate-400 text-sm mb-6">Monitor a new service by providing its details below.</p>
<div> <form onSubmit={addApp} className="space-y-4">
<Label>Port</Label> <div>
<Input type="number" value={newApp.port} onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })} className="bg-slate-950 border-slate-800" required /> <label className="block text-sm font-medium mb-2">Monitor Name</label>
</div> <input
</div> type="text"
<div className="flex gap-3"> value={newApp.name}
<Button type="button" variant="outline" onClick={() => setShowAddApp(false)} className="flex-1">Cancel</Button> onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
<Button type="submit" className="flex-1 bg-emerald-600 hover:bg-emerald-500">Add Monitor</Button> className="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
</div> placeholder="My Application"
</form> required
</DialogContent> />
</Dialog> </div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-2">URL</label>
<input
type="url"
value={newApp.url}
onChange={(e) => setNewApp({ ...newApp, url: e.target.value })}
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
placeholder="http://localhost:3000"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Port</label>
<input
type="number"
value={newApp.port}
onChange={(e) => setNewApp({ ...newApp, port: parseInt(e.target.value) })}
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-white placeholder-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors"
required
/>
</div>
</div>
<div className="flex gap-3 pt-4">
<button
type="button"
onClick={() => setShowAddApp(false)}
className="flex-1 px-4 py-2.5 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors"
>
Cancel
</button>
<button
type="submit"
className="flex-1 px-4 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg transition-colors font-medium"
>
Add Monitor
</button>
</div>
</form>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
); );
} }