feat: UmbrelOS-style CE with iStore, Casdoor auth, and App Manager
- iStore service (port 3021): fetches app manifests from git.falahos.my/falahos org via Gitea API, 5-min cache, query/search support - UmmahID login: replaced stub form with Casdoor OAuth2 PKCE flow (RFC 7636), no client secret required; /api/auth/config and /api/auth/token endpoints added to server.cjs - App Manager service (port 3022): installs/uninstalls Docker apps via /var/run/docker.sock, pulls images, starts containers on falah-net, persists state to /data/installed.json - docker-compose.yml: orchestrates falah-app + istore + app-manager with health checks, named volumes, and shared falah-net network - install.sh: one-line curl installer — checks prereqs, clones repo, prompts for credentials, builds and starts all containers - .env.example: all env vars documented (CASDOOR_CLIENT_ID, GITEA_TOKEN, APP_MANAGER_PORT, etc.) - Frontend: IStore overlay now fetches live apps from Gitea, real install/uninstall via App Manager with 2s polling, indeterminate progress bar, Uninstall button for installed apps - app.yml.example: developer manifest spec for publishing to iStore
This commit is contained in:
+69
-99
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { useApp } from '../store'
|
||||
import { useState, useCallback } from 'react'
|
||||
import { fetchAuthConfig, initiateLogin } from '../hooks/useAuth'
|
||||
|
||||
function Logo({ size }: { size: number }) {
|
||||
return (
|
||||
@@ -25,37 +25,21 @@ function Logo({ size }: { size: number }) {
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
const { navigate } = useApp()
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [emailFocused, setEmailFocused] = useState(false)
|
||||
const [passwordFocused, setPasswordFocused] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const inputStyle = (focused: boolean): React.CSSProperties => ({
|
||||
width: '100%',
|
||||
padding: '12px 16px',
|
||||
background: focused ? 'rgba(16,185,129,0.08)' : 'rgba(255,255,255,0.06)',
|
||||
border: `1px solid ${focused ? 'var(--brand)' : 'var(--glass-border)'}`,
|
||||
borderRadius: 8,
|
||||
color: 'var(--text)',
|
||||
fontSize: 14,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
})
|
||||
|
||||
const labelStyle: React.CSSProperties = {
|
||||
display: 'block',
|
||||
fontSize: 11,
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-muted)',
|
||||
letterSpacing: 1,
|
||||
marginBottom: 6,
|
||||
}
|
||||
|
||||
const handleSignIn = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
navigate('desktop')
|
||||
}
|
||||
const handleSignIn = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
try {
|
||||
const config = await fetchAuthConfig()
|
||||
await initiateLogin(config)
|
||||
// page will redirect — no further state updates needed
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Could not reach auth server')
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -68,8 +52,7 @@ export default function Login() {
|
||||
padding: 24,
|
||||
}}
|
||||
>
|
||||
<form
|
||||
onSubmit={handleSignIn}
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
@@ -83,7 +66,6 @@ export default function Login() {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 0,
|
||||
}}
|
||||
>
|
||||
{/* Arabic */}
|
||||
@@ -100,83 +82,71 @@ export default function Login() {
|
||||
بِسْمِ اللَّهِ
|
||||
</div>
|
||||
|
||||
{/* Logo */}
|
||||
<Logo size={72} />
|
||||
|
||||
{/* Title */}
|
||||
<h1
|
||||
style={{
|
||||
fontSize: 26,
|
||||
fontWeight: 700,
|
||||
color: 'var(--text)',
|
||||
margin: '16px 0 4px',
|
||||
}}
|
||||
>
|
||||
<h1 style={{ fontSize: 26, fontWeight: 700, color: 'var(--text)', margin: '16px 0 4px' }}>
|
||||
Falah OS
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: 'var(--text-muted)',
|
||||
margin: '0 0 32px',
|
||||
}}
|
||||
>
|
||||
<p style={{ fontSize: 13, color: 'var(--text-muted)', margin: '0 0 36px', textAlign: 'center' }}>
|
||||
Sovereign Digital Economy
|
||||
</p>
|
||||
|
||||
{/* Email field */}
|
||||
<div style={{ width: '100%', marginBottom: 16 }}>
|
||||
<label style={labelStyle}>EMAIL</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="operator@falah.os"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onFocus={() => setEmailFocused(true)}
|
||||
onBlur={() => setEmailFocused(false)}
|
||||
style={inputStyle(emailFocused)}
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password field */}
|
||||
<div style={{ width: '100%', marginBottom: 28 }}>
|
||||
<label style={labelStyle}>PASSWORD</label>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onFocus={() => setPasswordFocused(true)}
|
||||
onBlur={() => setPasswordFocused(false)}
|
||||
style={inputStyle(passwordFocused)}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Sign In button */}
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
style={{ width: '100%', marginBottom: 24 }}
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
|
||||
{/* Footer */}
|
||||
<p
|
||||
{/* UmmahID sign-in block */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: 'var(--text-dim)',
|
||||
textAlign: 'center',
|
||||
margin: 0,
|
||||
width: '100%',
|
||||
background: 'rgba(16,185,129,0.06)',
|
||||
border: '1px solid rgba(16,185,129,0.18)',
|
||||
borderRadius: 16,
|
||||
padding: '20px 20px 24px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<span style={{ fontSize: 22 }}>🪪</span>
|
||||
<div>
|
||||
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>UmmahID</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-muted)' }}>Zero-knowledge identity · auth.falahos.my</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSignIn}
|
||||
disabled={loading}
|
||||
className="btn-primary"
|
||||
style={{ width: '100%', marginTop: 4, opacity: loading ? 0.6 : 1 }}
|
||||
>
|
||||
{loading ? 'Redirecting to UmmahID…' : 'Sign in with UmmahID'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
fontSize: 12,
|
||||
color: '#F87171',
|
||||
background: 'rgba(239,68,68,0.08)',
|
||||
border: '1px solid rgba(239,68,68,0.2)',
|
||||
borderRadius: 8,
|
||||
padding: '10px 14px',
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p style={{ fontSize: 11, color: 'var(--text-dim)', textAlign: 'center', margin: 0 }}>
|
||||
Falah OS CE v1.3 · Community Edition
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user