mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-27 04:41:58 -06:00
Added Logout User Menu
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Paper, Box, Typography } from "@mui/material";
|
import { Paper, Box, Typography, Button } from "@mui/material";
|
||||||
|
import { GitHub as GitHubIcon, InfoOutlined as InfoIcon } from "@mui/icons-material";
|
||||||
|
import { CreditsDialog } from "../Dialogs.jsx";
|
||||||
|
|
||||||
export default function ServerInfo({ isAdmin = false }) {
|
export default function ServerInfo({ isAdmin = false }) {
|
||||||
const [serverTime, setServerTime] = useState(null);
|
const [serverTime, setServerTime] = useState(null);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const [aboutOpen, setAboutOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAdmin) return;
|
if (!isAdmin) return;
|
||||||
@@ -39,7 +42,32 @@ export default function ServerInfo({ isAdmin = false }) {
|
|||||||
{error ? `Error: ${error}` : (serverTime || 'Loading...')}
|
{error ? `Error: ${error}` : (serverTime || 'Loading...')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
|
<Typography variant="subtitle1" sx={{ color: "#58a6ff", mb: 1 }}>Project Links</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<GitHubIcon />}
|
||||||
|
onClick={() => window.open("https://github.com/bunny-lab-io/Borealis", "_blank")}
|
||||||
|
sx={{ borderColor: '#3a3a3a', color: '#7db7ff' }}
|
||||||
|
>
|
||||||
|
GitHub Project
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
startIcon={<InfoIcon />}
|
||||||
|
onClick={() => setAboutOpen(true)}
|
||||||
|
sx={{ borderColor: '#3a3a3a', color: '#ddd' }}
|
||||||
|
>
|
||||||
|
About Borealis
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<CreditsDialog open={aboutOpen} onClose={() => setAboutOpen(false)} />
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import React, { useState, useEffect, useCallback, useRef } from "react";
|
|||||||
import { ReactFlowProvider } from "reactflow";
|
import { ReactFlowProvider } from "reactflow";
|
||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
import {
|
import {
|
||||||
CloseAllDialog, CreditsDialog, RenameTabDialog, TabContextMenu, NotAuthorizedDialog
|
CloseAllDialog, RenameTabDialog, TabContextMenu, NotAuthorizedDialog
|
||||||
} from "./Dialogs";
|
} from "./Dialogs";
|
||||||
import NavigationSidebar from "./Navigation_Sidebar";
|
import NavigationSidebar from "./Navigation_Sidebar";
|
||||||
|
|
||||||
@@ -16,9 +16,7 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
KeyboardArrowDown as KeyboardArrowDownIcon,
|
KeyboardArrowDown as KeyboardArrowDownIcon,
|
||||||
InfoOutlined as InfoOutlinedIcon,
|
Logout as LogoutIcon,
|
||||||
GitHub as GitHub,
|
|
||||||
People as PeopleIcon,
|
|
||||||
NavigateNext as NavigateNextIcon
|
NavigateNext as NavigateNextIcon
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
@@ -91,8 +89,7 @@ export default function App() {
|
|||||||
const [currentPage, setCurrentPage] = useState("devices");
|
const [currentPage, setCurrentPage] = useState("devices");
|
||||||
const [selectedDevice, setSelectedDevice] = useState(null);
|
const [selectedDevice, setSelectedDevice] = useState(null);
|
||||||
|
|
||||||
const [aboutAnchorEl, setAboutAnchorEl] = useState(null);
|
const [userMenuAnchorEl, setUserMenuAnchorEl] = useState(null);
|
||||||
const [creditsDialogOpen, setCreditsDialogOpen] = useState(false);
|
|
||||||
const [confirmCloseOpen, setConfirmCloseOpen] = useState(false);
|
const [confirmCloseOpen, setConfirmCloseOpen] = useState(false);
|
||||||
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
||||||
const [renameTabId, setRenameTabId] = useState(null);
|
const [renameTabId, setRenameTabId] = useState(null);
|
||||||
@@ -102,6 +99,7 @@ export default function App() {
|
|||||||
const fileInputRef = useRef(null);
|
const fileInputRef = useRef(null);
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [userRole, setUserRole] = useState(null);
|
const [userRole, setUserRole] = useState(null);
|
||||||
|
const [userDisplayName, setUserDisplayName] = useState(null);
|
||||||
const [editingJob, setEditingJob] = useState(null);
|
const [editingJob, setEditingJob] = useState(null);
|
||||||
const [jobsRefreshToken, setJobsRefreshToken] = useState(0);
|
const [jobsRefreshToken, setJobsRefreshToken] = useState(0);
|
||||||
const [notAuthorizedOpen, setNotAuthorizedOpen] = useState(false);
|
const [notAuthorizedOpen, setNotAuthorizedOpen] = useState(false);
|
||||||
@@ -180,6 +178,7 @@ export default function App() {
|
|||||||
if (Date.now() - data.timestamp < 3600 * 1000) {
|
if (Date.now() - data.timestamp < 3600 * 1000) {
|
||||||
setUser(data.username);
|
setUser(data.username);
|
||||||
setUserRole(data.role || null);
|
setUserRole(data.role || null);
|
||||||
|
setUserDisplayName(data.display_name || data.username);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("borealis_session");
|
localStorage.removeItem("borealis_session");
|
||||||
}
|
}
|
||||||
@@ -194,9 +193,10 @@ export default function App() {
|
|||||||
const me = await resp.json();
|
const me = await resp.json();
|
||||||
setUser(me.username);
|
setUser(me.username);
|
||||||
setUserRole(me.role || null);
|
setUserRole(me.role || null);
|
||||||
|
setUserDisplayName(me.display_name || me.username);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"borealis_session",
|
"borealis_session",
|
||||||
JSON.stringify({ username: me.username, role: me.role, timestamp: Date.now() })
|
JSON.stringify({ username: me.username, display_name: me.display_name || me.username, role: me.role, timestamp: Date.now() })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -206,10 +206,25 @@ export default function App() {
|
|||||||
const handleLoginSuccess = ({ username, role }) => {
|
const handleLoginSuccess = ({ username, role }) => {
|
||||||
setUser(username);
|
setUser(username);
|
||||||
setUserRole(role || null);
|
setUserRole(role || null);
|
||||||
|
setUserDisplayName(username);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"borealis_session",
|
"borealis_session",
|
||||||
JSON.stringify({ username, role: role || null, timestamp: Date.now() })
|
JSON.stringify({ username, display_name: username, role: role || null, timestamp: Date.now() })
|
||||||
);
|
);
|
||||||
|
// Refresh full profile (to get display_name) in background
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/auth/me', { credentials: 'include' });
|
||||||
|
if (resp.ok) {
|
||||||
|
const me = await resp.json();
|
||||||
|
setUserDisplayName(me.display_name || me.username);
|
||||||
|
localStorage.setItem(
|
||||||
|
"borealis_session",
|
||||||
|
JSON.stringify({ username: me.username, display_name: me.display_name || me.username, role: me.role, timestamp: Date.now() })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -257,9 +272,17 @@ export default function App() {
|
|||||||
);
|
);
|
||||||
}, [activeTabId]);
|
}, [activeTabId]);
|
||||||
|
|
||||||
const handleAboutMenuOpen = (event) => setAboutAnchorEl(event.currentTarget);
|
const handleUserMenuOpen = (event) => setUserMenuAnchorEl(event.currentTarget);
|
||||||
const handleAboutMenuClose = () => setAboutAnchorEl(null);
|
const handleUserMenuClose = () => setUserMenuAnchorEl(null);
|
||||||
const openCreditsDialog = () => { handleAboutMenuClose(); setCreditsDialogOpen(true); };
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
|
||||||
|
} catch {}
|
||||||
|
try { localStorage.removeItem('borealis_session'); } catch {}
|
||||||
|
setUser(null);
|
||||||
|
setUserRole(null);
|
||||||
|
setUserDisplayName(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleTabRightClick = (evt, tabId) => {
|
const handleTabRightClick = (evt, tabId) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@@ -635,23 +658,19 @@ export default function App() {
|
|||||||
})}
|
})}
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
</Box>
|
</Box>
|
||||||
{/* Spacer to keep About aligned right */}
|
{/* Spacer to keep user menu aligned right */}
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
<Box sx={{ flexGrow: 1 }} />
|
||||||
<Button
|
<Button
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={handleAboutMenuOpen}
|
onClick={handleUserMenuOpen}
|
||||||
endIcon={<KeyboardArrowDownIcon />}
|
endIcon={<KeyboardArrowDownIcon />}
|
||||||
startIcon={<InfoOutlinedIcon />}
|
|
||||||
sx={{ height: "36px" }}
|
sx={{ height: "36px" }}
|
||||||
>
|
>
|
||||||
About
|
{userDisplayName || user || 'User'}
|
||||||
</Button>
|
</Button>
|
||||||
<Menu anchorEl={aboutAnchorEl} open={Boolean(aboutAnchorEl)} onClose={handleAboutMenuClose}>
|
<Menu anchorEl={userMenuAnchorEl} open={Boolean(userMenuAnchorEl)} onClose={handleUserMenuClose}>
|
||||||
<MenuItem onClick={() => { handleAboutMenuClose(); window.open("https://github.com/bunny-lab-io/Borealis", "_blank"); }}>
|
<MenuItem onClick={() => { handleUserMenuClose(); handleLogout(); }}>
|
||||||
<GitHub sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }} /> Github Project
|
<LogoutIcon sx={{ fontSize: 18, color: "#ff6b6b", mr: 1 }} /> Logout
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={openCreditsDialog}>
|
|
||||||
<PeopleIcon sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }} /> Credits
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
@@ -664,7 +683,6 @@ export default function App() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<CloseAllDialog open={confirmCloseOpen} onClose={() => setConfirmCloseOpen(false)} onConfirm={() => {}} />
|
<CloseAllDialog open={confirmCloseOpen} onClose={() => setConfirmCloseOpen(false)} onConfirm={() => {}} />
|
||||||
<CreditsDialog open={creditsDialogOpen} onClose={() => setCreditsDialogOpen(false)} />
|
|
||||||
<RenameTabDialog
|
<RenameTabDialog
|
||||||
open={renameDialogOpen}
|
open={renameDialogOpen}
|
||||||
value={renameValue}
|
value={renameValue}
|
||||||
|
|||||||
@@ -281,7 +281,33 @@ def api_me():
|
|||||||
user = _current_user()
|
user = _current_user()
|
||||||
if not user:
|
if not user:
|
||||||
return jsonify({"error": "unauthorized"}), 401
|
return jsonify({"error": "unauthorized"}), 401
|
||||||
return jsonify(user)
|
# Enrich with display_name if possible
|
||||||
|
username = (user.get('username') or '').strip()
|
||||||
|
try:
|
||||||
|
conn = _db_conn()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(
|
||||||
|
"SELECT id, username, display_name, role, last_login, created_at, updated_at FROM users WHERE LOWER(username)=LOWER(?)",
|
||||||
|
(username,)
|
||||||
|
)
|
||||||
|
row = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
if row:
|
||||||
|
info = _user_row_to_dict(row)
|
||||||
|
# Return minimal fields but include display_name
|
||||||
|
return jsonify({
|
||||||
|
"username": info['username'],
|
||||||
|
"display_name": info['display_name'],
|
||||||
|
"role": info['role']
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Fallback to original shape
|
||||||
|
return jsonify({
|
||||||
|
"username": username,
|
||||||
|
"display_name": username,
|
||||||
|
"role": user.get('role') or 'User'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/users", methods=["GET"])
|
@app.route("/api/users", methods=["GET"])
|
||||||
|
|||||||
Reference in New Issue
Block a user