v1.7.3 #390
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+1
-1
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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() === "")
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user
The check for
credentialIdchanges has been removed from the host comparison logic. This could lead to a bug where the UI does not update when a host's associated credential is changed, causing stale data to be displayed. Was the removal of this line intentional? If so, could you please clarify the reasoning? Otherwise, it should be restored to ensure UI consistency.