Compare commits

..

No commits in common. "c25d277abf9ae5c0d212f3c7a1887e060c3975aa" and "afbc630d48280439f1b6eb2a56794c9c050b15d6" have entirely different histories.

11 changed files with 145 additions and 1169 deletions

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,6 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/base": "^5.0.0-beta.70",
"@mui/icons-material": "^7.1.1",
"@mui/material": "^7.1.1",
"axios": "^1.9.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"

View File

@ -1,17 +1,42 @@
.app-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #121212;
padding: 2rem;
#root {
max-width: 1280px;
margin: 0 auto;
max-width: 100%;
padding: 2rem;
text-align: center;
}
.title {
color: white;
text-align: center;
margin-bottom: 2rem;
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@ -1,17 +1,14 @@
import './App.css';
import './style/global.css';
import BackupForm from './components/BackupForm';
import RestoreForm from './components/RestoreForm';
function App() {
return (
<div className="app-wrapper">
<h1 className="title">🚀 Snapix Dashboard</h1>
<div className="form-sections">
<div style={{ padding: "2rem" }}>
<h1>🚀 Snapix Dashboard</h1>
<BackupForm />
<hr />
<RestoreForm />
</div>
</div>
);
}

View File

@ -0,0 +1,10 @@
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8000', // שנה לכתובת האמיתית שלך
});
export const getNamespaces = () => api.get('/namespaces');
export const getPVCs = (namespace) => api.get(`/pvcs/${namespace}`);
export const createBackup = (payload) => api.post('/backup', payload);
export const restoreBackup = (payload) => api.post('/restore', payload);

View File

@ -1,74 +1,67 @@
import { useState, useEffect } from 'react';
import { getNamespaces, getPVCs, createBackup } from '../service';
import '../style/BackupForm.css';
import { Button, Input, Select, Option } from '@mui/base';
import { useEffect, useState } from 'react';
import { getNamespaces, getPVCs, createBackup } from '../api/snapix';
function BackupForm() {
export default function BackupForm() {
const [namespaces, setNamespaces] = useState([]);
const [pvcs, setPvcs] = useState([]);
const [namespace, setNamespace] = useState('');
const [pvc, setPvc] = useState('');
const [selectedNs, setSelectedNs] = useState('');
const [selectedPvc, setSelectedPvc] = useState('');
const [backupName, setBackupName] = useState('');
useEffect(() => {
getNamespaces().then(setNamespaces);
getNamespaces().then(res => setNamespaces(res.data));
}, []);
useEffect(() => {
if (namespace) {
getPVCs(namespace).then(setPvcs);
if (selectedNs) {
getPVCs(selectedNs).then(res => setPvcs(res.data));
} else {
setPvcs([]);
}
}, [namespace]);
}, [selectedNs]);
const handleSubmit = async () => {
try {
await createBackup({ namespace, pvcName: pvc, backupName });
alert('✅ Backup created successfully!');
} catch (error) {
console.error(error);
alert('❌ Backup failed');
const handleSubmit = () => {
if (selectedNs && selectedPvc && backupName) {
createBackup({
namespace: selectedNs,
pvc_name: selectedPvc,
backup_name: backupName,
}).then(() => {
alert("Backup started!");
setBackupName('');
});
}
};
return (
<div className="form-container BackupForm">
<h3>📦 Create Backup</h3>
<div className="form-group">
<label>Namespace</label>
<Select value={namespace} onChange={(e) => setNamespace(e.target.value)}>
<Option value="">-- Select Namespace --</Option>
{namespaces.map((ns) => (
<Option key={ns} value={ns}>{ns}</Option>
))}
</Select>
</div>
<div>
<h2>📦 Backup PVC</h2>
<div className="form-group">
<label>PVC</label>
<Select value={pvc} onChange={(e) => setPvc(e.target.value)} disabled={!namespace}>
<Option value="">-- Select PVC --</Option>
{pvcs.map((p) => (
<Option key={p} value={p}>{p}</Option>
))}
</Select>
</div>
<label>Namespace:</label>
<select onChange={e => setSelectedNs(e.target.value)} value={selectedNs}>
<option value="">-- Select Namespace --</option>
{namespaces.map(ns => <option key={ns}>{ns}</option>)}
</select>
<div className="form-group">
<label>Backup Name</label>
<Input
<label>PVC:</label>
<select
onChange={e => setSelectedPvc(e.target.value)}
value={selectedPvc}
disabled={!selectedNs}
>
<option value="">-- Select PVC --</option>
{pvcs.map(pvc => <option key={pvc}>{pvc}</option>)}
</select>
<label>Backup Name:</label>
<input
type="text"
value={backupName}
onChange={(e) => setBackupName(e.target.value)}
onChange={e => setBackupName(e.target.value)}
placeholder="e.g. git-bkp"
/>
</div>
<Button className="btn" onClick={handleSubmit} disabled={!namespace || !pvc || !backupName}>
Create Backup
</Button>
<button onClick={handleSubmit}>Create Backup</button>
</div>
);
}
export default BackupForm;

View File

@ -1,58 +1,49 @@
import { useState } from 'react';
import { restoreBackup } from '../service';
import '../style/RestoreForm.css';
import { Button, Input } from '@mui/base';
import { restoreBackup } from '../api/snapix';
function RestoreForm() {
const [namespace, setNamespace] = useState('');
const [targetPVC, setTargetPVC] = useState('');
export default function RestoreForm() {
const [backupName, setBackupName] = useState('');
const [targetNs, setTargetNs] = useState('');
const [targetPvc, setTargetPvc] = useState('');
const handleSubmit = async () => {
try {
await restoreBackup({ namespace, targetPVC, backupName });
alert('✅ Restore completed successfully!');
} catch (error) {
console.error(error);
alert('❌ Restore failed');
const handleRestore = () => {
if (backupName && targetNs && targetPvc) {
restoreBackup({
backup_name: backupName,
target_namespace: targetNs,
target_pvc: targetPvc,
}).then(() => {
alert("Restore started!");
});
}
};
return (
<div className="form-container RestoreForm">
<h3> Restore from Backup</h3>
<div className="form-group">
<label>Namespace</label>
<input
type="text"
value={namespace}
onChange={(e) => setNamespace(e.target.value)}
/>
</div>
<div>
<h2>🔁 Restore PVC</h2>
<div className="form-group">
<label>Target PVC</label>
<label>Backup Name:</label>
<input
type="text"
value={targetPVC}
onChange={(e) => setTargetPVC(e.target.value)}
/>
</div>
<div className="form-group">
<label>Backup Name</label>
<Input
type="text"
value={backupName}
onChange={(e) => setBackupName(e.target.value)}
onChange={e => setBackupName(e.target.value)}
/>
</div>
<Button className="btn" onClick={handleSubmit} disabled={!namespace || !targetPVC || !backupName}>
Restore
</Button>
<label>Target Namespace:</label>
<input
type="text"
value={targetNs}
onChange={e => setTargetNs(e.target.value)}
/>
<label>Target PVC:</label>
<input
type="text"
value={targetPvc}
onChange={e => setTargetPvc(e.target.value)}
/>
<button onClick={handleRestore}>Restore</button>
</div>
);
}
export default RestoreForm;

View File

@ -1,33 +0,0 @@
const API_BASE = "http://localhost:8000";
export async function getNamespaces() {
const res = await fetch(`${API_BASE}/namespaces`);
if (!res.ok) throw new Error("Failed to fetch namespaces");
return await res.json();
}
export async function getPVCs(namespace) {
const res = await fetch(`${API_BASE}/pvcs/${namespace}`);
if (!res.ok) throw new Error("Failed to fetch PVCs");
return await res.json();
}
export async function createBackup({ namespace, pvcName, backupName }) {
const res = await fetch(`${API_BASE}/backup`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ namespace, pvc_name: pvcName, backup_name: backupName }),
});
if (!res.ok) throw new Error("Failed to create backup");
return await res.json();
}
export async function restoreBackup({ namespace, targetPVC, backupName }) {
const res = await fetch(`${API_BASE}/restore`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ namespace, target_pvc: targetPVC, backup_name: backupName }),
});
if (!res.ok) throw new Error("Failed to restore backup");
return await res.json();
}

View File

@ -1,7 +0,0 @@
.BackupForm {
background: #e3f2fd;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
color: rgb(0, 0, 0);
}

View File

@ -1,7 +0,0 @@
.RestoreForm {
background: #f1f8e9;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
color: rgb(0, 0, 0);
}

View File

@ -1,58 +0,0 @@
/* Global button style */
.btn {
padding: 10px;
font-weight: bold;
background: #1976d2;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
/* Inputs */
input, select {
padding: 8px;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}
/* Shared spacing for forms */
.form-group {
margin-bottom: 16px;
display: flex;
flex-direction: column;
}
.form-group label {
margin-bottom: 4px;
font-weight: 500;
color: #333;
}
.form-sections {
width: 100%;
max-width: 480px;
display: flex;
flex-direction: column;
gap: 2rem;
margin: 0 auto;
}
.form-container {
display: flex;
flex-direction: column;
}
/* .input {
padding: 8px;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
background-color: transparent;
} */