db de con a changer

This commit is contained in:
Gabriel Peron 2025-03-16 22:36:59 +01:00
parent 23842800c4
commit 98918d33b3
6 changed files with 286 additions and 11 deletions

3
.env Normal file
View file

@ -0,0 +1,3 @@
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZwZmJzY2psanBtcGJjb2hteHJtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDIxNTk1NjIsImV4cCI6MjA1NzczNTU2Mn0.bMHEDr8XS2fFVr38JuCJOF80sWU3P4isQ1DStRuwUiw
VITE_SUPABASE_URL=https://vpfbscjljpmpbcohmxrm.supabase.co

133
package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "vite-react-typescript-starter",
"version": "0.0.0",
"dependencies": {
"@supabase/supabase-js": "^2.39.7",
"framer-motion": "^11.0.8",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
@ -1204,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,6 +1325,19 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"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==",
"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",
@ -1282,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",
@ -3742,6 +3831,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",
@ -3813,6 +3907,11 @@
}
}
},
"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=="
},
"node_modules/update-browserslist-db": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
@ -3917,6 +4016,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",
@ -4062,6 +4175,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,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@supabase/supabase-js": "^2.39.7",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",

View file

@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { Plus, ExternalLink, Trash2, Activity } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { supabase } from './supabase';
// Custom Proxmox Logo SVG component
const ProxmoxLogo = ({ className }: { className?: string }) => (
@ -45,6 +46,28 @@ function App() {
}
});
// Load servers from Supabase
useEffect(() => {
const loadServers = async () => {
const { data, error } = await supabase
.from('servers')
.select('*');
if (error) {
console.error('Error loading servers:', error);
return;
}
setServers(data.map(server => ({
...server,
specs: server.specs,
lastPing: server.last_ping
})));
};
loadServers();
}, []);
const pingServer = async (server: ProxmoxServer) => {
setServers(current =>
current.map(s =>
@ -56,15 +79,34 @@ function App() {
const startTime = Date.now();
const response = await fetch(server.url, { mode: 'no-cors' });
const endTime = Date.now();
const pingTime = endTime - startTime;
// Update server status in Supabase
await supabase
.from('servers')
.update({
status: 'online',
last_ping: pingTime
})
.eq('id', server.id);
setServers(current =>
current.map(s =>
s.id === server.id
? { ...s, status: 'online', lastPing: endTime - startTime }
? { ...s, status: 'online', lastPing: pingTime }
: s
)
);
} catch (error) {
// Update server status in Supabase
await supabase
.from('servers')
.update({
status: 'offline',
last_ping: null
})
.eq('id', server.id);
setServers(current =>
current.map(s =>
s.id === server.id ? { ...s, status: 'offline' } : s
@ -73,14 +115,28 @@ function App() {
}
};
const addServer = () => {
const addServer = async () => {
if (newServer.name && newServer.url) {
const { data: serverData, error } = await supabase
.from('servers')
.insert([{
name: newServer.name,
url: newServer.url,
specs: newServer.specs,
status: 'checking'
}])
.select()
.single();
if (error) {
console.error('Error adding server:', error);
return;
}
const server: ProxmoxServer = {
id: Date.now().toString(),
name: newServer.name,
url: newServer.url,
status: 'checking',
specs: newServer.specs
...serverData,
specs: serverData.specs,
lastPing: serverData.last_ping
};
setServers([...servers, server]);
@ -94,7 +150,17 @@ function App() {
}
};
const deleteServer = (id: string) => {
const deleteServer = async (id: string) => {
const { error } = await supabase
.from('servers')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting server:', error);
return;
}
setServers(servers.filter(server => server.id !== id));
};
@ -190,8 +256,8 @@ function App() {
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="absolute invisible group-hover:visible -top-2 left-1/2 -translate-x-1/2 -translate-y-full
bg-gray-700 rounded-lg shadow-lg p-4 w-64 z-10 transform origin-bottom"
className="absolute invisible group-hover:visible bottom-full left-1/2 -translate-x-1/2 mb-2
bg-gray-700 rounded-lg shadow-lg p-4 w-64 z-10 transform origin-bottom pointer-events-none"
>
<div className="space-y-2">
<p className="text-sm"><span className="font-semibold">CPU:</span> {server.specs.cpu}</p>
@ -199,7 +265,7 @@ function App() {
<p className="text-sm"><span className="font-semibold">GPU:</span> {server.specs.gpu}</p>
<p className="text-sm"><span className="font-semibold">Type:</span> {server.specs.type}</p>
</div>
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2
<div className="absolute top-full left-1/2 -translate-x-1/2 -translate-y-1/2
border-8 border-transparent border-t-gray-700" />
</motion.div>
</motion.div>

10
src/supabase.ts Normal file
View file

@ -0,0 +1,10 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
if (!supabaseUrl || !supabaseKey) {
throw new Error('Missing Supabase environment variables');
}
export const supabase = createClient(supabaseUrl, supabaseKey);

View file

@ -0,0 +1,62 @@
/*
# Create servers table for Proxmox dashboard
1. New Tables
- `servers`
- `id` (uuid, primary key)
- `name` (text)
- `url` (text)
- `status` (text)
- `specs` (jsonb)
- `last_ping` (integer)
- `user_id` (uuid, foreign key to auth.users)
- `created_at` (timestamp with time zone)
2. Security
- Enable RLS on `servers` table
- Add policies for authenticated users to:
- Read their own servers
- Insert their own servers
- Update their own servers
- Delete their own servers
*/
CREATE TABLE IF NOT EXISTS servers (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
url text NOT NULL,
status text NOT NULL DEFAULT 'checking',
specs jsonb NOT NULL DEFAULT '{}'::jsonb,
last_ping integer,
user_id uuid REFERENCES auth.users(id) NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL
);
-- Enable Row Level Security
ALTER TABLE servers ENABLE ROW LEVEL SECURITY;
-- Create policies
CREATE POLICY "Users can read their own servers"
ON servers
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own servers"
ON servers
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update their own servers"
ON servers
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can delete their own servers"
ON servers
FOR DELETE
TO authenticated
USING (auth.uid() = user_id);