This commit is contained in:
Gabriel Peron 2025-03-18 01:30:26 +01:00
parent dd60497e5b
commit aa6da1d02e
6 changed files with 111 additions and 172 deletions

128
package-lock.json generated
View file

@ -8,7 +8,6 @@
"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",
@ -1217,73 +1216,6 @@
"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",
@ -1395,15 +1327,13 @@
"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,
"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",
@ -1429,14 +1359,6 @@
"@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",
@ -4218,11 +4140,6 @@
"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",
@ -4292,7 +4209,10 @@
"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=="
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/update-browserslist-db": {
"version": "1.1.1",
@ -4419,20 +4339,6 @@
}
}
},
"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",
@ -4578,26 +4484,6 @@
"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,7 +10,6 @@
"preview": "vite preview"
},
"dependencies": {
"@supabase/supabase-js": "^2.39.7",
"lucide-react": "^0.344.0",
"mysql2": "^3.9.2",
"react": "^18.3.1",

View file

@ -3,7 +3,7 @@ import { PlusCircle } from 'lucide-react';
import { AddServerModal } from './components/AddServerModal';
import { ServerCard } from './components/ServerCard';
import { Server, ServerMetrics } from './types';
import { supabase } from './lib/supabase';
import { getServers } from './lib/db';
function App() {
const [servers, setServers] = useState<Server[]>([]);
@ -15,20 +15,22 @@ function App() {
}, []);
const fetchServers = async () => {
const { data } = await supabase
.from('servers')
.select('*')
.order('created_at', { ascending: true });
if (data) {
setServers(data);
data.forEach(server => {
fetchMetrics(server);
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) => ({

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { X } from 'lucide-react';
import { supabase } from '../lib/supabase';
import { addServer } from '../lib/db';
interface AddServerModalProps {
onClose: () => void;
@ -13,6 +13,7 @@ export function AddServerModal({ onClose, onServerAdded }: AddServerModalProps)
model: '',
cpu_model: '',
ram_gb: '',
influx_enabled: false,
influx_org: '',
influx_token: '',
influx_url: ''
@ -21,14 +22,16 @@ export function AddServerModal({ onClose, onServerAdded }: AddServerModalProps)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const { error } = await supabase.from('servers').insert([{
...formData,
ram_gb: parseInt(formData.ram_gb)
}]);
if (!error) {
try {
await addServer({
...formData,
ram_gb: parseInt(formData.ram_gb)
});
onServerAdded();
onClose();
} catch (error) {
console.error('Failed to add server:', error);
alert('Failed to add server. Please try again.');
}
};
@ -88,39 +91,56 @@ export function AddServerModal({ onClose, onServerAdded }: AddServerModalProps)
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300">InfluxDB Organization</label>
<div className="flex items-center space-x-2">
<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
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
</label>
</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
/>
</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
/>
</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"

View file

@ -12,4 +12,35 @@ const pool = mysql.createPool({
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,12 +1,13 @@
export interface Server {
id: string;
id: number;
name: string;
model: string;
cpu_model: string;
ram_gb: number;
influx_org: string;
influx_token: string;
influx_url: string;
influx_enabled: boolean;
influx_org: string | null;
influx_token: string | null;
influx_url: string | null;
created_at: string;
}