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", "name": "proxmox-dashboard",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^2.39.7",
"lucide-react": "^0.344.0", "lucide-react": "^0.344.0",
"mysql2": "^3.9.2", "mysql2": "^3.9.2",
"react": "^18.3.1", "react": "^18.3.1",
@ -1217,73 +1216,6 @@
"win32" "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": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -1395,15 +1327,13 @@
"version": "22.13.10", "version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.20.0" "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": { "node_modules/@types/prop-types": {
"version": "15.7.13", "version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
@ -1429,14 +1359,6 @@
"@types/react": "*" "@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": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.8.1", "version": "8.8.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz",
@ -4218,11 +4140,6 @@
"node": ">=8.0" "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": { "node_modules/ts-api-utils": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
@ -4292,7 +4209,10 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.20.0", "version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "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": { "node_modules/update-browserslist-db": {
"version": "1.1.1", "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -4578,26 +4484,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "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": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View file

@ -10,7 +10,6 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^2.39.7",
"lucide-react": "^0.344.0", "lucide-react": "^0.344.0",
"mysql2": "^3.9.2", "mysql2": "^3.9.2",
"react": "^18.3.1", "react": "^18.3.1",

View file

@ -3,7 +3,7 @@ import { PlusCircle } from 'lucide-react';
import { AddServerModal } from './components/AddServerModal'; import { AddServerModal } from './components/AddServerModal';
import { ServerCard } from './components/ServerCard'; import { ServerCard } from './components/ServerCard';
import { Server, ServerMetrics } from './types'; import { Server, ServerMetrics } from './types';
import { supabase } from './lib/supabase'; import { getServers } from './lib/db';
function App() { function App() {
const [servers, setServers] = useState<Server[]>([]); const [servers, setServers] = useState<Server[]>([]);
@ -15,20 +15,22 @@ function App() {
}, []); }, []);
const fetchServers = async () => { const fetchServers = async () => {
const { data } = await supabase try {
.from('servers') const data = await getServers();
.select('*') setServers(data as Server[]);
.order('created_at', { ascending: true }); data.forEach((server: Server) => {
if (server.influx_enabled) {
if (data) { fetchMetrics(server);
setServers(data); }
data.forEach(server => {
fetchMetrics(server);
}); });
} catch (error) {
console.error('Failed to fetch servers:', error);
} }
}; };
const fetchMetrics = async (server: Server) => { const fetchMetrics = async (server: Server) => {
if (!server.influx_enabled) return;
// In a real implementation, this would fetch from InfluxDB // In a real implementation, this would fetch from InfluxDB
// For demo purposes, we'll generate mock data // For demo purposes, we'll generate mock data
const mockMetrics: ServerMetrics[] = Array.from({ length: 20 }).map((_, i) => ({ const mockMetrics: ServerMetrics[] = Array.from({ length: 20 }).map((_, i) => ({

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { supabase } from '../lib/supabase'; import { addServer } from '../lib/db';
interface AddServerModalProps { interface AddServerModalProps {
onClose: () => void; onClose: () => void;
@ -13,6 +13,7 @@ export function AddServerModal({ onClose, onServerAdded }: AddServerModalProps)
model: '', model: '',
cpu_model: '', cpu_model: '',
ram_gb: '', ram_gb: '',
influx_enabled: false,
influx_org: '', influx_org: '',
influx_token: '', influx_token: '',
influx_url: '' influx_url: ''
@ -21,14 +22,16 @@ export function AddServerModal({ onClose, onServerAdded }: AddServerModalProps)
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const { error } = await supabase.from('servers').insert([{ try {
...formData, await addServer({
ram_gb: parseInt(formData.ram_gb) ...formData,
}]); ram_gb: parseInt(formData.ram_gb)
});
if (!error) {
onServerAdded(); onServerAdded();
onClose(); 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 required
/> />
</div> </div>
<div> <div className="flex items-center space-x-2">
<label className="block text-sm font-medium text-gray-300">InfluxDB Organization</label>
<input <input
type="text" type="checkbox"
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white" id="influxEnabled"
value={formData.influx_org} className="rounded bg-gray-700 border-gray-600 text-blue-600"
onChange={(e) => setFormData({ ...formData, influx_org: e.target.value })} checked={formData.influx_enabled}
required 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>
<div> {formData.influx_enabled && (
<label className="block text-sm font-medium text-gray-300">InfluxDB Token</label> <>
<input <div>
type="password" <label className="block text-sm font-medium text-gray-300">InfluxDB Organization</label>
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white" <input
value={formData.influx_token} type="text"
onChange={(e) => setFormData({ ...formData, influx_token: e.target.value })} className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
required value={formData.influx_org}
/> onChange={(e) => setFormData({ ...formData, influx_org: e.target.value })}
</div> required={formData.influx_enabled}
/>
<div> </div>
<label className="block text-sm font-medium text-gray-300">InfluxDB URL</label>
<input <div>
type="url" <label className="block text-sm font-medium text-gray-300">InfluxDB Token</label>
className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white" <input
value={formData.influx_url} type="password"
onChange={(e) => setFormData({ ...formData, influx_url: e.target.value })} className="mt-1 block w-full rounded-md bg-gray-700 border-gray-600 text-white"
required value={formData.influx_token}
/> onChange={(e) => setFormData({ ...formData, influx_token: e.target.value })}
</div> 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 <button
type="submit" type="submit"

View file

@ -12,4 +12,35 @@ const pool = mysql.createPool({
queueLimit: 0, 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; export default pool;

View file

@ -1,12 +1,13 @@
export interface Server { export interface Server {
id: string; id: number;
name: string; name: string;
model: string; model: string;
cpu_model: string; cpu_model: string;
ram_gb: number; ram_gb: number;
influx_org: string; influx_enabled: boolean;
influx_token: string; influx_org: string | null;
influx_url: string; influx_token: string | null;
influx_url: string | null;
created_at: string; created_at: string;
} }