Run code cleanup, add sidebar persistence, fix OIDC credentials, force SSH password.

This commit is contained in:
LukeGus
2025-10-05 21:48:32 -05:00
parent aaec940b1b
commit 75e973f3a2
9 changed files with 31 additions and 18 deletions

View File

@@ -23,7 +23,6 @@ http {
return 301 https://$host:${SSL_PORT}$request_uri; return 301 https://$host:${SSL_PORT}$request_uri;
} }
# HTTPS Server
server { server {
listen ${SSL_PORT} ssl; listen ${SSL_PORT} ssl;
server_name _; server_name _;
@@ -41,7 +40,6 @@ http {
index index.html index.htm; index index.html index.htm;
} }
# Handle missing source map files gracefully
location ~* \.map$ { location ~* \.map$ {
return 404; return 404;
access_log off; access_log off;

View File

@@ -29,7 +29,6 @@ http {
index index.html index.htm; index index.html index.htm;
} }
# Handle missing source map files gracefully
location ~* \.map$ { location ~* \.map$ {
return 404; return 404;
access_log off; access_log off;

View File

@@ -1,7 +1,7 @@
{ {
"name": "termix", "name": "termix",
"private": true, "private": true,
"version": "1.7.1", "version": "1.7.2",
"description": "A web-based server management platform with SSH terminal, tunneling, and file editing capabilities", "description": "A web-based server management platform with SSH terminal, tunneling, and file editing capabilities",
"author": "Karmaa", "author": "Karmaa",
"main": "electron/main.cjs", "main": "electron/main.cjs",

View File

@@ -27,12 +27,7 @@ class FieldCrypto {
"oidc_identifier", "oidc_identifier",
"oidcIdentifier", "oidcIdentifier",
]), ]),
ssh_data: new Set([ ssh_data: new Set(["password", "key", "key_password", "keyPassword"]),
"password",
"key",
"key_password",
"keyPassword",
]),
ssh_credentials: new Set([ ssh_credentials: new Set([
"password", "password",
"private_key", "private_key",

View File

@@ -6,7 +6,7 @@ export class LazyFieldEncryption {
key_password: "keyPassword", key_password: "keyPassword",
private_key: "privateKey", private_key: "privateKey",
public_key: "publicKey", public_key: "publicKey",
// Reverse mappings for Drizzle ORM (camelCase -> snake_case)
keyPassword: "key_password", keyPassword: "key_password",
privateKey: "private_key", privateKey: "private_key",
publicKey: "public_key", publicKey: "public_key",

View File

@@ -589,7 +589,6 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
async function handleDeleteFiles(files: FileItem[]) { async function handleDeleteFiles(files: FileItem[]) {
if (!sshSessionId || files.length === 0) return; if (!sshSessionId || files.length === 0) return;
// Determine the confirmation message based on file count and type
let confirmMessage: string; let confirmMessage: string;
if (files.length === 1) { if (files.length === 1) {
const file = files[0]; const file = files[0];
@@ -613,10 +612,8 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
}); });
} }
// Add permanent deletion warning
const fullMessage = `${confirmMessage}\n\n${t("fileManager.permanentDeleteWarning")}`; const fullMessage = `${confirmMessage}\n\n${t("fileManager.permanentDeleteWarning")}`;
// Show confirmation dialog
confirmWithToast( confirmWithToast(
fullMessage, fullMessage,
async () => { async () => {

View File

@@ -210,7 +210,18 @@ export function HostManagerEditor({
defaultPath: z.string().optional(), defaultPath: z.string().optional(),
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
if (data.authType === "key") { if (data.authType === "password") {
if (
!data.password ||
(typeof data.password === "string" && data.password.trim() === "")
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("hosts.passwordRequired"),
path: ["password"],
});
}
} else if (data.authType === "key") {
if ( if (
!data.key || !data.key ||
(typeof data.key === "string" && data.key.trim() === "") (typeof data.key === "string" && data.key.trim() === "")

View File

@@ -24,7 +24,10 @@ function AppContent() {
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [authLoading, setAuthLoading] = useState(true); const [authLoading, setAuthLoading] = useState(true);
const [showVersionCheck, setShowVersionCheck] = useState(true); const [showVersionCheck, setShowVersionCheck] = useState(true);
const [isTopbarOpen, setIsTopbarOpen] = useState<boolean>(true); const [isTopbarOpen, setIsTopbarOpen] = useState<boolean>(() => {
const saved = localStorage.getItem("topNavbarOpen");
return saved !== null ? JSON.parse(saved) : true;
});
const { currentTab, tabs } = useTabs(); const { currentTab, tabs } = useTabs();
useEffect(() => { useEffect(() => {
@@ -64,6 +67,10 @@ function AppContent() {
return () => window.removeEventListener("storage", handleStorageChange); return () => window.removeEventListener("storage", handleStorageChange);
}, []); }, []);
useEffect(() => {
localStorage.setItem("topNavbarOpen", JSON.stringify(isTopbarOpen));
}, [isTopbarOpen]);
const handleSelectView = (nextView: string) => { const handleSelectView = (nextView: string) => {
setMountedViews((prev) => { setMountedViews((prev) => {
if (prev.has(nextView)) return prev; if (prev.has(nextView)) return prev;

View File

@@ -101,7 +101,10 @@ export function LeftSidebar({
const [deleteLoading, setDeleteLoading] = React.useState(false); const [deleteLoading, setDeleteLoading] = React.useState(false);
const [deleteError, setDeleteError] = React.useState<string | null>(null); const [deleteError, setDeleteError] = React.useState<string | null>(null);
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(true); const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(() => {
const saved = localStorage.getItem("leftSidebarOpen");
return saved !== null ? JSON.parse(saved) : true;
});
const { const {
tabs: tabList, tabs: tabList,
@@ -181,7 +184,6 @@ export function LeftSidebar({
newHost.key !== existingHost.key || newHost.key !== existingHost.key ||
newHost.keyPassword !== existingHost.keyPassword || newHost.keyPassword !== existingHost.keyPassword ||
newHost.keyType !== existingHost.keyType || newHost.keyType !== existingHost.keyType ||
newHost.credentialId !== existingHost.credentialId ||
newHost.defaultPath !== existingHost.defaultPath || newHost.defaultPath !== existingHost.defaultPath ||
JSON.stringify(newHost.tags) !== JSON.stringify(newHost.tags) !==
JSON.stringify(existingHost.tags) || JSON.stringify(existingHost.tags) ||
@@ -247,6 +249,10 @@ export function LeftSidebar({
return () => clearTimeout(handler); return () => clearTimeout(handler);
}, [search]); }, [search]);
React.useEffect(() => {
localStorage.setItem("leftSidebarOpen", JSON.stringify(isSidebarOpen));
}, [isSidebarOpen]);
const filteredHosts = React.useMemo(() => { const filteredHosts = React.useMemo(() => {
if (!debouncedSearch.trim()) return hosts; if (!debouncedSearch.trim()) return hosts;
const q = debouncedSearch.trim().toLowerCase(); const q = debouncedSearch.trim().toLowerCase();