This commit is contained in:
Gabriel Peron 2025-03-22 16:59:03 +01:00
parent aa6da1d02e
commit cc98497da0
16 changed files with 439 additions and 817 deletions

View file

@ -1,3 +0,0 @@
{
"template": "bolt-vite-react-ts"
}

View file

@ -1,8 +0,0 @@
For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production.
By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them.
Use icons from lucide-react for logos.
Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags.

10
.env
View file

@ -1,10 +0,0 @@
# Database Configuration
VITE_DB_HOST=db.pandem.fr
VITE_DB_USER=proxdash
VITE_DB_PASSWORD=proxdash
VITE_DB_NAME=proxdash
# InfluxDB Configuration (if needed)
VITE_INFLUX_URL=
VITE_INFLUX_TOKEN=
VITE_INFLUX_ORG=

View file

@ -2,12 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="https://www.proxmox.com/images/proxmox/Proxmox_symbol_standard_hex_400px.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Proxmox Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
</html>

608
package-lock.json generated
View file

@ -8,11 +8,11 @@
"name": "proxmox-dashboard",
"version": "0.0.0",
"dependencies": {
"@supabase/supabase-js": "^2.39.7",
"lucide-react": "^0.344.0",
"mysql2": "^3.9.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"recharts": "^2.12.2"
"react-hot-toast": "^2.4.1"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
@ -292,17 +292,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz",
@ -1216,6 +1205,73 @@
"win32"
]
},
"node_modules/@supabase/auth-js": {
"version": "2.68.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.68.0.tgz",
"integrity": "sha512-odG7nb7aOmZPUXk6SwL2JchSsn36Ppx11i2yWMIc/meUO2B2HK9YwZHPK06utD9Ql9ke7JKDbwGin/8prHKxxQ==",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz",
"integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/node-fetch": {
"version": "2.6.15",
"resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
"integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/@supabase/postgrest-js": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.2.tgz",
"integrity": "sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz",
"integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==",
"dependencies": {
"@supabase/node-fetch": "^2.6.14",
"@types/phoenix": "^1.5.4",
"@types/ws": "^8.5.10",
"ws": "^8.18.0"
}
},
"node_modules/@supabase/storage-js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz",
"integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.49.1",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.1.tgz",
"integrity": "sha512-lKaptKQB5/juEF5+jzmBeZlz69MdHZuxf+0f50NwhL+IE//m4ZnOeWlsKRjjsM0fVayZiQKqLvYdBn0RLkhGiQ==",
"dependencies": {
"@supabase/auth-js": "2.68.0",
"@supabase/functions-js": "2.4.4",
"@supabase/node-fetch": "2.6.15",
"@supabase/postgrest-js": "1.19.2",
"@supabase/realtime-js": "2.11.2",
"@supabase/storage-js": "2.7.1"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -1257,60 +1313,6 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -1324,16 +1326,18 @@
"dev": true
},
"node_modules/@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"optional": true,
"peer": true,
"version": "22.13.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
"integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/phoenix": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A=="
},
"node_modules/@types/prop-types": {
"version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
@ -1359,6 +1363,14 @@
"@types/react": "*"
}
},
"node_modules/@types/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz",
@ -1737,14 +1749,6 @@
"postcss": "^8.1.0"
}
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -1905,14 +1909,6 @@
"node": ">= 6"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -1980,116 +1976,6 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@ -2107,25 +1993,12 @@
}
}
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -2138,15 +2011,6 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -2471,25 +2335,12 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/fast-equals": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
"integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@ -2650,14 +2501,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -2735,6 +2578,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/goober": {
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
"integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@ -2762,17 +2613,6 @@
"node": ">= 0.4"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -2807,14 +2647,6 @@
"node": ">=0.8.19"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"engines": {
"node": ">=12"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -2881,11 +2713,6 @@
"node": ">=0.12.0"
}
},
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -3027,22 +2854,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/long": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
"integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -3063,20 +2880,6 @@
"yallist": "^3.0.2"
}
},
"node_modules/lru.min": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/lucide-react": {
"version": "0.344.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.344.0.tgz",
@ -3134,25 +2937,6 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/mysql2": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.13.0.tgz",
"integrity": "sha512-M6DIQjTqKeqXH5HLbLMxwcK5XfXHw30u5ap6EZmu7QVmcF/gnh2wS/EOiQ4MTbXz/vQeoXrmycPlVRM00WSslg==",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@ -3164,25 +2948,6 @@
"thenify-all": "^1.0.0"
}
},
"node_modules/named-placeholders": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/named-placeholders/node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"engines": {
"node": ">=12"
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@ -3235,6 +3000,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -3559,21 +3325,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -3626,10 +3377,21 @@
"react": "^18.3.1"
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
"node_modules/react-hot-toast": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz",
"integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==",
"dependencies": {
"csstype": "^3.1.3",
"goober": "^2.1.16"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
@ -3640,35 +3402,6 @@
"node": ">=0.10.0"
}
},
"node_modules/react-smooth": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
"integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
"dependencies": {
"fast-equals": "^5.0.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -3690,41 +3423,6 @@
"node": ">=8.10.0"
}
},
"node_modules/recharts": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
"integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
"react-is": "^18.3.1",
"react-smooth": "^4.0.4",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@ -3819,11 +3517,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@ -3841,11 +3534,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -3888,14 +3576,6 @@
"node": ">=0.10.0"
}
},
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@ -4114,11 +3794,6 @@
"node": ">=0.8"
}
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -4140,6 +3815,11 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
@ -4209,10 +3889,7 @@
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"optional": true,
"peer": true
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
},
"node_modules/update-browserslist-db": {
"version": "1.1.1",
@ -4259,27 +3936,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/vite": {
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
@ -4339,6 +3995,20 @@
}
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -4484,6 +4154,26 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View file

@ -10,11 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
"@supabase/supabase-js": "^2.39.7",
"lucide-react": "^0.344.0",
"mysql2": "^3.9.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"recharts": "^2.12.2"
"react-hot-toast": "^2.4.1"
},
"devDependencies": {
"@eslint/js": "^9.9.1",

View file

@ -1,83 +1,75 @@
import React, { useEffect, useState } from 'react';
import { PlusCircle } from 'lucide-react';
import React, { useState, useEffect } from 'react';
import { Plus, Server as ServerIcon } from 'lucide-react';
import { Toaster } from 'react-hot-toast';
import { AddServerModal } from './components/AddServerModal';
import { ServerCard } from './components/ServerCard';
import { Server, ServerMetrics } from './types';
import { getServers } from './lib/db';
import { storage } from './utils/storage';
import type { Server } from './types';
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [servers, setServers] = useState<Server[]>([]);
const [showAddModal, setShowAddModal] = useState(false);
const [metrics, setMetrics] = useState<Record<string, ServerMetrics[]>>({});
const fetchServers = () => {
const data = storage.getServers();
setServers(data);
};
useEffect(() => {
fetchServers();
}, []);
const fetchServers = async () => {
try {
const data = await getServers();
setServers(data as Server[]);
data.forEach((server: Server) => {
if (server.influx_enabled) {
fetchMetrics(server);
}
});
} catch (error) {
console.error('Failed to fetch servers:', error);
}
};
const fetchMetrics = async (server: Server) => {
if (!server.influx_enabled) return;
// In a real implementation, this would fetch from InfluxDB
// For demo purposes, we'll generate mock data
const mockMetrics: ServerMetrics[] = Array.from({ length: 20 }).map((_, i) => ({
cpu_usage: Math.random() * 100,
ram_usage: Math.random() * 100,
timestamp: new Date(Date.now() - (19 - i) * 60000).toISOString()
}));
setMetrics(prev => ({
...prev,
[server.id]: mockMetrics
}));
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">Proxmox Dashboard</h1>
<div className="min-h-screen gradient-bg">
<Toaster position="top-right" />
<div className="container mx-auto px-4 py-8">
<div className="flex flex-col md:flex-row justify-between items-center mb-12 gap-4">
<div className="flex items-center gap-3">
<ServerIcon size={32} className="text-purple-400" />
<h1 className="text-4xl font-bold text-white bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-pink-300">
Proxmox Dashboard
</h1>
</div>
<button
onClick={() => setShowAddModal(true)}
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
onClick={() => setIsModalOpen(true)}
className="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg flex items-center gap-2 transform hover:scale-105 transition-all duration-200 shadow-lg hover:shadow-purple-500/20"
>
<PlusCircle size={20} />
<span>Add Server</span>
<Plus size={20} />
Add Server
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{servers.map(server => (
<ServerCard
key={server.id}
server={server}
metrics={metrics[server.id] || []}
/>
))}
</div>
{showAddModal && (
<AddServerModal
onClose={() => setShowAddModal(false)}
onServerAdded={fetchServers}
/>
{servers.length === 0 ? (
<div className="text-center py-16">
<ServerIcon size={48} className="text-purple-400 mx-auto mb-4 opacity-50" />
<h2 className="text-2xl font-semibold text-gray-300 mb-2">No servers yet</h2>
<p className="text-gray-400">Add your first Proxmox server to get started</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{servers.map((server) => (
<ServerCard
key={server.id}
server={server}
onDelete={(id) => {
storage.deleteServer(id);
fetchServers();
}}
/>
))}
</div>
)}
</div>
<AddServerModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onServerAdded={fetchServers}
/>
</div>
);
}
export default App;
export default App

View file

@ -1,150 +1,159 @@
import React, { useState } from 'react';
import { X } from 'lucide-react';
import { addServer } from '../lib/db';
import { X, Server, Cpu, MemoryStick as Memory, Link } from 'lucide-react';
import { storage } from '../utils/storage';
import toast from 'react-hot-toast';
interface AddServerModalProps {
isOpen: boolean;
onClose: () => void;
onServerAdded: () => void;
}
export function AddServerModal({ onClose, onServerAdded }: AddServerModalProps) {
export function AddServerModal({ isOpen, onClose, onServerAdded }: AddServerModalProps) {
const [formData, setFormData] = useState({
name: '',
model: '',
cpu_model: '',
cpu_cores: '',
ram_gb: '',
influx_enabled: false,
influx_org: '',
influx_token: '',
influx_url: ''
proxmox_url: ''
});
if (!isOpen) return null;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await addServer({
...formData,
ram_gb: parseInt(formData.ram_gb)
storage.addServer({
name: formData.name,
model: formData.model,
cpu_model: formData.cpu_model,
cpu_cores: parseInt(formData.cpu_cores),
ram_gb: parseInt(formData.ram_gb),
proxmox_url: formData.proxmox_url
});
toast.success('Server added successfully');
onServerAdded();
onClose();
} catch (error) {
console.error('Failed to add server:', error);
alert('Failed to add server. Please try again.');
toast.error('Failed to add server');
console.error('Error:', error);
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-gray-800 p-6 rounded-lg w-full max-w-md">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-white">Add New Server</h2>
<button onClick={onClose} className="text-gray-400 hover:text-white">
<X size={24} />
<div className="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-gray-800/90 backdrop-blur rounded-xl p-6 w-full max-w-md border border-gray-700/50">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-semibold text-white flex items-center gap-2">
<Server className="text-purple-400" size={24} />
Add New Server
</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-white hover:bg-gray-700/50 p-2 rounded-lg transition-colors"
>
<X size={20} />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-5">
<div>
<label className="block text-sm font-medium text-gray-300">Server Name</label>
<label className="block text-sm font-medium text-gray-300 mb-1.5">
<div className="flex items-center gap-2 mb-2">
<Server size={16} className="text-purple-400" />
Server Name
</div>
</label>
<input
type="text"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full bg-gray-700/50 text-white rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-purple-500 focus:outline-none transition-all duration-200"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300">Server Model</label>
<label className="block text-sm font-medium text-gray-300 mb-1.5">Model</label>
<input
type="text"
placeholder="e.g. Dell R730"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
value={formData.model}
onChange={(e) => setFormData({ ...formData, model: e.target.value })}
className="w-full bg-gray-700/50 text-white rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-purple-500 focus:outline-none transition-all duration-200"
placeholder="e.g. Dell R720"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300">CPU Model</label>
<label className="block text-sm font-medium text-gray-300 mb-1.5">
<div className="flex items-center gap-2 mb-2">
<Cpu size={16} className="text-purple-400" />
CPU Model
</div>
</label>
<input
type="text"
placeholder="e.g. Intel Xeon E5-2680 v3"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
value={formData.cpu_model}
onChange={(e) => setFormData({ ...formData, cpu_model: e.target.value })}
className="w-full bg-gray-700/50 text-white rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-purple-500 focus:outline-none transition-all duration-200"
placeholder="e.g. Intel Xeon E5-2680"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300">RAM (GB)</label>
<input
type="number"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
value={formData.ram_gb}
onChange={(e) => setFormData({ ...formData, ram_gb: e.target.value })}
required
/>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1.5">CPU Cores</label>
<input
type="number"
value={formData.cpu_cores}
onChange={(e) => setFormData({ ...formData, cpu_cores: e.target.value })}
className="w-full bg-gray-700/50 text-white rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-purple-500 focus:outline-none transition-all duration-200"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1.5">
<div className="flex items-center gap-2 mb-2">
<Memory size={16} className="text-purple-400" />
RAM (GB)
</div>
</label>
<input
type="number"
value={formData.ram_gb}
onChange={(e) => setFormData({ ...formData, ram_gb: e.target.value })}
className="w-full bg-gray-700/50 text-white rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-purple-500 focus:outline-none transition-all duration-200"
required
/>
</div>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="influxEnabled"
className="rounded bg-gray-700 border-gray-600 text-blue-600"
checked={formData.influx_enabled}
onChange={(e) => setFormData({ ...formData, influx_enabled: e.target.checked })}
/>
<label htmlFor="influxEnabled" className="text-sm font-medium text-gray-300">
Enable InfluxDB Metrics
<div>
<label className="block text-sm font-medium text-gray-300 mb-1.5">
<div className="flex items-center gap-2 mb-2">
<Link size={16} className="text-purple-400" />
Proxmox UI URL
</div>
</label>
<input
type="url"
value={formData.proxmox_url}
onChange={(e) => setFormData({ ...formData, proxmox_url: e.target.value })}
className="w-full bg-gray-700/50 text-white rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-purple-500 focus:outline-none transition-all duration-200"
placeholder="https://proxmox.example.com:8006"
required
/>
</div>
{formData.influx_enabled && (
<>
<div>
<label className="block text-sm font-medium text-gray-300">InfluxDB Organization</label>
<input
type="text"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
value={formData.influx_org}
onChange={(e) => setFormData({ ...formData, influx_org: e.target.value })}
required={formData.influx_enabled}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300">InfluxDB Token</label>
<input
type="password"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
value={formData.influx_token}
onChange={(e) => setFormData({ ...formData, influx_token: e.target.value })}
required={formData.influx_enabled}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300">InfluxDB URL</label>
<input
type="url"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
value={formData.influx_url}
onChange={(e) => setFormData({ ...formData, influx_url: e.target.value })}
required={formData.influx_enabled}
/>
</div>
</>
)}
<button
type="submit"
className="w-full bg-blue-600 text-white rounded-md py-2 hover:bg-blue-700 transition-colors"
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-medium py-3 px-4 rounded-lg transform hover:scale-102 transition-all duration-200 mt-6"
>
Add Server
</button>

View file

@ -1,79 +1,71 @@
import React from 'react';
import { Server, ServerMetrics } from '../types';
import { Cpu, MemoryStick as Memory, Server as ServerIcon } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { Server, Cpu, MemoryStick as Memory, Trash2, ExternalLink } from 'lucide-react';
import type { Server as ServerType } from '../types';
interface ServerCardProps {
server: Server;
metrics: ServerMetrics[];
server: ServerType;
onDelete: (id: string) => void;
}
export function ServerCard({ server, metrics }: ServerCardProps) {
const lastMetric = metrics[metrics.length - 1] || { cpu_usage: 0, ram_usage: 0 };
export function ServerCard({ server, onDelete }: ServerCardProps) {
const handleProxmoxClick = () => {
window.open(server.proxmox_url, '_blank');
};
return (
<div className="bg-gray-800 rounded-lg p-6 shadow-lg">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<ServerIcon className="text-blue-500" size={24} />
<h3 className="text-xl font-semibold text-white">{server.name}</h3>
</div>
<span className="text-gray-400 text-sm">{server.model}</span>
<div
className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 hover:bg-gray-700/50 transition-all duration-300 cursor-pointer transform hover:scale-102 hover:shadow-xl hover:shadow-purple-500/10 border border-gray-700/50"
onClick={handleProxmoxClick}
>
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-semibold text-white flex items-center gap-2 group">
<Server className="text-purple-400 group-hover:text-purple-300 transition-colors" size={24} />
<span className="group-hover:text-purple-300 transition-colors">{server.name}</span>
<ExternalLink size={16} className="text-purple-400 group-hover:text-purple-300 transition-colors" />
</h3>
<button
onClick={(e) => {
e.stopPropagation();
onDelete(server.id);
}}
className="text-gray-400 hover:text-red-400 transition-colors p-2 hover:bg-red-400/10 rounded-lg"
title="Delete server"
>
<Trash2 size={18} />
</button>
</div>
<div className="grid grid-cols-2 gap-4 mb-6">
<div className="bg-gray-700 p-4 rounded-lg">
<div className="flex items-center space-x-2 mb-2">
<Cpu className="text-green-500" size={20} />
<span className="text-gray-300">CPU</span>
</div>
<p className="text-white font-semibold">{server.cpu_model}</p>
<p className="text-blue-400 mt-2">{lastMetric.cpu_usage.toFixed(1)}% Usage</p>
<div className="space-y-4">
<div className="flex items-center gap-2 text-gray-300 bg-gray-800/50 p-3 rounded-lg">
<span className="text-gray-400 min-w-[4rem]">Model:</span>
<span className="font-medium">{server.model}</span>
</div>
<div className="bg-gray-700 p-4 rounded-lg">
<div className="flex items-center space-x-2 mb-2">
<Memory className="text-purple-500" size={20} />
<span className="text-gray-300">Memory</span>
<div className="bg-gray-800/50 p-4 rounded-lg space-y-3">
<div className="flex items-center gap-2 mb-2">
<Cpu size={18} className="text-purple-400" />
<span className="text-gray-300 font-medium">CPU Information</span>
</div>
<div className="pl-7 space-y-2">
<div className="flex items-center gap-2">
<span className="text-gray-400">Model:</span>
<span className="text-gray-200 font-medium">{server.cpu_model}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-gray-400">Cores:</span>
<span className="text-gray-200 font-medium">{server.cpu_cores} cores</span>
</div>
</div>
</div>
<div className="flex items-center gap-2 text-gray-300 bg-gray-800/50 p-3 rounded-lg">
<Memory size={18} className="text-purple-400 min-w-[1.5rem]" />
<div>
<div className="font-medium">{server.ram_gb} GB</div>
<div className="text-sm text-gray-400">RAM</div>
</div>
<p className="text-white font-semibold">{server.ram_gb} GB</p>
<p className="text-blue-400 mt-2">{lastMetric.ram_usage.toFixed(1)}% Usage</p>
</div>
</div>
<div className="h-40">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={metrics}>
<XAxis
dataKey="timestamp"
stroke="#4B5563"
tickFormatter={(value) => new Date(value).toLocaleTimeString()}
/>
<YAxis stroke="#4B5563" />
<Tooltip
contentStyle={{
backgroundColor: '#1F2937',
border: 'none',
borderRadius: '0.5rem',
color: '#F3F4F6'
}}
/>
<Line
type="monotone"
dataKey="cpu_usage"
stroke="#10B981"
strokeWidth={2}
dot={false}
/>
<Line
type="monotone"
dataKey="ram_usage"
stroke="#8B5CF6"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
);

View file

@ -1,3 +1,31 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
color-scheme: dark;
}
body {
background: linear-gradient(to bottom right, #1a1a2e, #16213e);
color: #ffffff;
min-height: 100vh;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.gradient-bg {
background: linear-gradient(-45deg, #2d1b69, #1a1a2e, #16213e, #1f2937);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
}

View file

@ -1,46 +0,0 @@
import mysql from 'mysql2/promise';
const pool = mysql.createPool({
host: import.meta.env.VITE_DB_HOST || 'db.pandem.fr',
user: import.meta.env.VITE_DB_USER || 'proxdash',
password: import.meta.env.VITE_DB_PASSWORD || 'proxdash',
database: import.meta.env.VITE_DB_NAME || 'proxdash',
waitForConnections: true,
connectionLimit: 10,
maxIdle: 10,
idleTimeout: 60000,
queueLimit: 0,
});
export async function getServers() {
const [rows] = await pool.query('SELECT * FROM servers ORDER BY created_at ASC');
return rows;
}
export async function addServer(server: {
name: string;
model: string;
cpu_model: string;
ram_gb: number;
influx_enabled: boolean;
influx_org?: string;
influx_token?: string;
influx_url?: string;
}) {
const [result] = await pool.query(
'INSERT INTO servers (name, model, cpu_model, ram_gb, influx_enabled, influx_org, influx_token, influx_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[
server.name,
server.model,
server.cpu_model,
server.ram_gb,
server.influx_enabled,
server.influx_org || null,
server.influx_token || null,
server.influx_url || null,
]
);
return result;
}
export default pool;

View file

@ -1,6 +0,0 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);

7
src/supabase.ts Normal file
View file

@ -0,0 +1,7 @@
import { createClient } from '@supabase/supabase-js';
import type { Database } from './database.types';
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
export const supabase = createClient<Database>(supabaseUrl, supabaseKey);

View file

@ -1,18 +1,11 @@
export interface Server {
id: number;
id: string;
name: string;
model: string;
cpu_model: string;
cpu_cores: number;
ram_gb: number;
influx_enabled: boolean;
influx_org: string | null;
influx_token: string | null;
influx_url: string | null;
proxmox_url: string;
created_at: string;
}
export interface ServerMetrics {
cpu_usage: number;
ram_usage: number;
timestamp: string;
updated_at: string;
}

43
src/utils/storage.ts Normal file
View file

@ -0,0 +1,43 @@
import { Server } from '../types';
const STORAGE_FILE = 'servers.json';
export const storage = {
getServers: (): Server[] => {
try {
const data = localStorage.getItem(STORAGE_FILE);
return data ? JSON.parse(data) : [];
} catch (error) {
console.error('Error reading servers:', error);
return [];
}
},
saveServers: (servers: Server[]): void => {
try {
localStorage.setItem(STORAGE_FILE, JSON.stringify(servers));
} catch (error) {
console.error('Error saving servers:', error);
}
},
addServer: (server: Omit<Server, 'id' | 'created_at' | 'updated_at'>): Server => {
const servers = storage.getServers();
const newServer: Server = {
...server,
id: crypto.randomUUID(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
servers.push(newServer);
storage.saveServers(servers);
return newServer;
},
deleteServer: (id: string): void => {
const servers = storage.getServers();
const filteredServers = servers.filter(server => server.id !== id);
storage.saveServers(filteredServers);
}
};

View file

@ -1,59 +0,0 @@
/*
# Create servers table for Proxmox Dashboard
1. New Tables
- `servers`
- `id` (uuid, primary key)
- `name` (text, server name)
- `model` (text, server model like Dell R730)
- `cpu_model` (text, CPU model name)
- `ram_gb` (integer, RAM amount in GB)
- `influx_org` (text, InfluxDB organization)
- `influx_token` (text, InfluxDB API token)
- `influx_url` (text, InfluxDB server URL)
- `created_at` (timestamp with timezone)
2. Security
- Enable RLS on `servers` table
- Add policies for authenticated users to manage their servers
*/
CREATE TABLE servers (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
model text NOT NULL,
cpu_model text NOT NULL,
ram_gb integer NOT NULL,
influx_org text NOT NULL,
influx_token text NOT NULL,
influx_url text NOT NULL,
created_at timestamptz DEFAULT now()
);
-- Enable Row Level Security
ALTER TABLE servers ENABLE ROW LEVEL SECURITY;
-- Create policies
CREATE POLICY "Users can view their servers"
ON servers
FOR SELECT
TO authenticated
USING (true);
CREATE POLICY "Users can insert their servers"
ON servers
FOR INSERT
TO authenticated
WITH CHECK (true);
CREATE POLICY "Users can update their servers"
ON servers
FOR UPDATE
TO authenticated
USING (true);
CREATE POLICY "Users can delete their servers"
ON servers
FOR DELETE
TO authenticated
USING (true);