feat: add NixServer and NixClient React UIs, wire into installer
- nixserver/: React 18 + TypeScript + Vite + Tailwind - Login page + placeholder admin dashboard with card grid - Blue accent color (#3b82f6) - Full Tailwind + PostCSS + TypeScript setup - nixclient/: identical structure, user-facing panel - Green accent color (#10b981) - Same build tooling as NixServer - install.sh: new 'Building NixServer & NixClient UIs' step - npm install + npm run build for each - Outputs to /opt/nixpanel/nixserver/dist and /opt/nixpanel/nixclient/dist - Both panels immediately accessible via Apache on 2087 and 2083 - Skips gracefully if source not present (early dev)
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function App() {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false)
|
||||
|
||||
const handleLogin = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (email && password) {
|
||||
setIsLoggedIn(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoggedIn) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950 text-white">
|
||||
<nav className="border-b border-gray-800 bg-gray-950/80 backdrop-blur-sm sticky top-0 z-40">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold">Nix<span className="text-emerald-500">Client</span></h1>
|
||||
<button
|
||||
onClick={() => setIsLoggedIn(false)}
|
||||
className="px-4 py-2 rounded-lg border border-gray-700 text-gray-300 hover:border-gray-500 hover:text-white transition-colors text-sm"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-20">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{[
|
||||
{ title: 'My Domains', icon: '🌐', count: '0' },
|
||||
{ title: 'Email Accounts', icon: '📧', count: '0' },
|
||||
{ title: 'Databases', icon: '💾', count: '0' },
|
||||
{ title: 'Backups', icon: '💾', count: '0' },
|
||||
].map((item) => (
|
||||
<div key={item.title} className="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
|
||||
<div className="text-4xl mb-3">{item.icon}</div>
|
||||
<h3 className="font-semibold text-white mb-2">{item.title}</h3>
|
||||
<p className="text-3xl font-bold text-emerald-500">{item.count}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-12 bg-emerald-900/20 border border-emerald-500/20 rounded-2xl p-8">
|
||||
<h2 className="text-2xl font-bold text-white mb-2">Welcome to Your Account</h2>
|
||||
<p className="text-gray-400">
|
||||
Manage your domains, emails, databases, and more — all from one dashboard.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950">
|
||||
<div className="w-full max-w-md px-4">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">
|
||||
Nix<span className="text-emerald-500">Client</span>
|
||||
</h1>
|
||||
<p className="text-gray-400">Your Hosting Control Panel</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-8">
|
||||
<form onSubmit={handleLogin} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm text-gray-400 mb-1.5">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full bg-gray-900 border border-gray-700 text-white rounded-lg px-4 py-2.5 text-sm placeholder-gray-500 focus:outline-none focus:border-emerald-500 transition-colors"
|
||||
placeholder="[email protected]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-gray-400 mb-1.5">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="w-full bg-gray-900 border border-gray-700 text-white rounded-lg px-4 py-2.5 text-sm placeholder-gray-500 focus:outline-none focus:border-emerald-500 transition-colors"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white font-semibold text-sm transition-colors mt-6"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
NixPanel Client — Manage your hosting account
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgb(3, 7, 30);
|
||||
color: rgb(229, 231, 235);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
Reference in New Issue
Block a user