Code cleanup for 1.7.0

This commit is contained in:
LukeGus
2025-10-01 14:56:03 -05:00
parent 66c9937be9
commit 129e683f0c
19 changed files with 264 additions and 248 deletions
+5 -5
View File
@@ -496,7 +496,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
process.env.NODE_ENV === "production"
? path.join(process.env.DATA_DIR || "./db/data", ".temp", "exports")
: path.join(os.tmpdir(), "termix-exports");
try {
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
@@ -861,7 +861,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
const fileStream = fs.createReadStream(tempPath);
fileStream.on("error", (streamError) => {
apiLogger.error("File stream error during export", streamError, {
operation: "export_file_stream_error",
@@ -882,13 +882,13 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
userId,
filename,
});
fs.unlink(tempPath, (err) => {
if (err) {
apiLogger.warn("Failed to clean up export file", {
apiLogger.warn("Failed to clean up export file", {
operation: "export_cleanup_failed",
path: tempPath,
error: err.message
error: err.message,
});
}
});
+3 -1
View File
@@ -569,7 +569,9 @@ async function connectSSHTunnel(
if (tunnelConfig.endpointCredentialId && tunnelConfig.endpointUserId) {
try {
const userDataKey = DataCrypto.getUserDataKey(tunnelConfig.endpointUserId);
const userDataKey = DataCrypto.getUserDataKey(
tunnelConfig.endpointUserId,
);
if (userDataKey) {
const credentials = await SimpleDBOps.select(
getDb()
+3 -2
View File
@@ -56,10 +56,11 @@ export class DatabaseMigration {
if (hasEncryptedDb && hasUnencryptedDb) {
const unencryptedSize = fs.statSync(this.unencryptedDbPath).size;
const encryptedSize = fs.statSync(this.encryptedDbPath).size;
if (unencryptedSize === 0) {
needsMigration = false;
reason = "Empty unencrypted database found alongside encrypted database. Removing empty file.";
reason =
"Empty unencrypted database found alongside encrypted database. Removing empty file.";
try {
fs.unlinkSync(this.unencryptedDbPath);
databaseLogger.info("Removed empty unencrypted database file", {
+6 -9
View File
@@ -28,7 +28,7 @@ class SystemCrypto {
const dataDir = process.env.DATA_DIR || "./db/data";
const envPath = path.join(dataDir, ".env");
try {
const envContent = await fs.readFile(envPath, "utf8");
const jwtMatch = envContent.match(/^JWT_SECRET=(.+)$/m);
@@ -37,8 +37,7 @@ class SystemCrypto {
process.env.JWT_SECRET = jwtMatch[1];
return;
}
} catch {
}
} catch {}
await this.generateAndGuideUser();
} catch (error) {
@@ -66,7 +65,7 @@ class SystemCrypto {
const dataDir = process.env.DATA_DIR || "./db/data";
const envPath = path.join(dataDir, ".env");
try {
const envContent = await fs.readFile(envPath, "utf8");
const dbKeyMatch = envContent.match(/^DATABASE_KEY=(.+)$/m);
@@ -75,8 +74,7 @@ class SystemCrypto {
process.env.DATABASE_KEY = dbKeyMatch[1];
return;
}
} catch {
}
} catch {}
await this.generateAndGuideDatabaseKey();
} catch (error) {
@@ -104,7 +102,7 @@ class SystemCrypto {
const dataDir = process.env.DATA_DIR || "./db/data";
const envPath = path.join(dataDir, ".env");
try {
const envContent = await fs.readFile(envPath, "utf8");
const tokenMatch = envContent.match(/^INTERNAL_AUTH_TOKEN=(.+)$/m);
@@ -113,8 +111,7 @@ class SystemCrypto {
process.env.INTERNAL_AUTH_TOKEN = tokenMatch[1];
return;
}
} catch {
}
} catch {}
await this.generateAndGuideInternalAuthToken();
} catch (error) {
+3 -2
View File
@@ -71,7 +71,7 @@ class UserCrypto {
async setupOIDCUserEncryption(userId: string): Promise<void> {
const DEK = crypto.randomBytes(UserCrypto.DEK_LENGTH);
const now = Date.now();
this.userSessions.set(userId, {
dataKey: Buffer.from(DEK),
@@ -319,7 +319,8 @@ class UserCrypto {
}
private deriveOIDCSystemKey(userId: string): Buffer {
const systemSecret = process.env.OIDC_SYSTEM_SECRET || "termix-oidc-system-secret-default";
const systemSecret =
process.env.OIDC_SYSTEM_SECRET || "termix-oidc-system-secret-default";
const salt = Buffer.from(userId, "utf8");
return crypto.pbkdf2Sync(
systemSecret,
+4 -2
View File
@@ -271,7 +271,8 @@ export function AdminSettings({
setExportLoading(true);
try {
const isDev = process.env.NODE_ENV === "development" &&
const isDev =
process.env.NODE_ENV === "development" &&
(window.location.port === "3000" ||
window.location.port === "5173" ||
window.location.port === "" ||
@@ -340,7 +341,8 @@ export function AdminSettings({
setImportLoading(true);
try {
const isDev = process.env.NODE_ENV === "development" &&
const isDev =
process.env.NODE_ENV === "development" &&
(window.location.port === "3000" ||
window.location.port === "5173" ||
window.location.port === "" ||
@@ -87,7 +87,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
initialHost || null,
);
const [currentPath, setCurrentPath] = useState(
initialHost?.defaultPath || "/"
initialHost?.defaultPath || "/",
);
const [files, setFiles] = useState<FileItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
@@ -184,7 +184,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
const handleCloseWithError = useCallback(
(errorMessage: string) => {
if (isClosing) return; // Prevent duplicate calls
if (isClosing) return;
setIsClosing(true);
toast.error(errorMessage);
if (onClose) {
@@ -758,10 +758,10 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
const windowCount = Date.now() % 10;
const baseOffsetX = 120 + windowCount * 30;
const baseOffsetY = 120 + windowCount * 30;
const maxOffsetX = Math.max(0, window.innerWidth - 800 - 100);
const maxOffsetY = Math.max(0, window.innerHeight - 600 - 100);
const offsetX = Math.min(baseOffsetX, maxOffsetX);
const offsetY = Math.min(baseOffsetY, maxOffsetY);
@@ -355,11 +355,22 @@ export function FileViewer({
setShowLargeFileWarning(false);
}
if (fileTypeInfo.type === "image" && file.name.toLowerCase().endsWith('.svg') && content) {
if (
fileTypeInfo.type === "image" &&
file.name.toLowerCase().endsWith(".svg") &&
content
) {
setImageLoading(false);
setImageLoadError(false);
}
}, [content, savedContent, fileTypeInfo.type, isLargeFile, forceShowAsText, file.name]);
}, [
content,
savedContent,
fileTypeInfo.type,
isLargeFile,
forceShowAsText,
file.name,
]);
const handleContentChange = (newContent: string) => {
setEditedContent(newContent);
@@ -706,8 +717,8 @@ export function FileViewer({
</Button>
)}
</div>
) : file.name.toLowerCase().endsWith('.svg') ? (
<div
) : file.name.toLowerCase().endsWith(".svg") ? (
<div
className="max-w-full max-h-full flex items-center justify-center"
style={{ maxHeight: "calc(100vh - 200px)" }}
dangerouslySetInnerHTML={{ __html: content }}
@@ -9,15 +9,15 @@ interface DragAndDropState {
interface UseDragAndDropProps {
onFilesDropped: (files: FileList) => void;
onError?: (error: string) => void;
maxFileSize?: number; // in MB
maxFileSize?: number;
allowedTypes?: string[];
}
export function useDragAndDrop({
onFilesDropped,
onError,
maxFileSize = 5120, // 5GB default - much more reasonable
allowedTypes = [], // empty means all types allowed
maxFileSize = 5120,
allowedTypes = [],
}: UseDragAndDropProps) {
const [state, setState] = useState<DragAndDropState>({
isDragging: false,
@@ -32,28 +32,23 @@ export function useDragAndDrop({
for (let i = 0; i < files.length; i++) {
const file = files[i];
// Check file size
if (file.size > maxSizeBytes) {
return `File "${file.name}" is too large. Maximum size is ${maxFileSize}MB.`;
}
// Check file type if restrictions exist
if (allowedTypes.length > 0) {
const fileExt = file.name.split(".").pop()?.toLowerCase();
const mimeType = file.type.toLowerCase();
const isAllowed = allowedTypes.some((type) => {
// Check by extension
if (type.startsWith(".")) {
return fileExt === type.slice(1);
}
// Check by MIME type
if (type.includes("/")) {
return (
mimeType === type || mimeType.startsWith(type.replace("*", ""))
);
}
// Check by category
switch (type) {
case "image":
return mimeType.startsWith("image/");
@@ -114,7 +109,6 @@ export function useDragAndDrop({
e.preventDefault();
e.stopPropagation();
// Set dropEffect to indicate what operation is allowed
e.dataTransfer.dropEffect = "copy";
}, []);
+5 -1
View File
@@ -214,7 +214,11 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
);
reconnectTimeoutRef.current = setTimeout(() => {
if (isUnmountingRef.current || shouldNotReconnectRef.current || wasDisconnectedBySSH.current) {
if (
isUnmountingRef.current ||
shouldNotReconnectRef.current ||
wasDisconnectedBySSH.current
) {
isReconnectingRef.current = false;
return;
}
+1 -1
View File
@@ -143,7 +143,7 @@ function AppContent() {
isAdmin={isAdmin}
username={username}
>
<div
<div
className="h-screen w-full visible pointer-events-auto static overflow-hidden"
style={{ display: showTerminalView ? "block" : "none" }}
>
+4 -10
View File
@@ -82,7 +82,6 @@ export function HomepageAuth({
const [registrationAllowed, setRegistrationAllowed] = useState(true);
const [oidcConfigured, setOidcConfigured] = useState(false);
// Legacy reset states (kept for compatibility)
const [resetStep, setResetStep] = useState<
"initiate" | "verify" | "newPassword"
>("initiate");
@@ -106,8 +105,7 @@ export function HomepageAuth({
const clearJWTOnLoad = async () => {
try {
await logoutUser();
} catch (error) {
}
} catch (error) {}
};
clearJWTOnLoad();
@@ -242,7 +240,6 @@ export function HomepageAuth({
setIsAdmin(false);
setUsername(null);
setUserId(null);
// HttpOnly cookies cannot be cleared from JavaScript - backend handles this
if (err?.response?.data?.error?.includes("Database")) {
setDbConnectionFailed(true);
} else {
@@ -253,8 +250,6 @@ export function HomepageAuth({
}
}
// ===== Legacy password reset functions (deprecated) =====
async function handleInitiatePasswordReset() {
setError(null);
setResetLoading(true);
@@ -317,7 +312,6 @@ export function HomepageAuth({
setResetSuccess(true);
toast.success(t("messages.passwordResetSuccess"));
// Immediately redirect to login after successful reset
setTab("login");
resetPasswordState();
} catch (err: any) {
@@ -372,7 +366,7 @@ export function HomepageAuth({
setUsername(res.username || null);
setUserId(res.userId || null);
setDbError(null);
setTimeout(() => {
onAuthSuccess({
isAdmin: !!res.is_admin,
@@ -380,7 +374,7 @@ export function HomepageAuth({
userId: res.userId || null,
});
}, 100);
setInternalLoggedIn(true);
setTotpRequired(false);
setTotpCode("");
@@ -392,7 +386,7 @@ export function HomepageAuth({
err?.response?.data?.error ||
err?.message ||
t("errors.invalidTotpCode");
if (errorCode === "SESSION_EXPIRED") {
setTotpRequired(false);
setTotpCode("");
+9 -5
View File
@@ -352,7 +352,7 @@ export function HomepageAuth({
setUsername(res.username || null);
setUserId(res.userId || null);
setDbError(null);
setTimeout(() => {
onAuthSuccess({
isAdmin: !!res.is_admin,
@@ -360,7 +360,7 @@ export function HomepageAuth({
userId: res.userId || null,
});
}, 100);
setInternalLoggedIn(true);
setTotpRequired(false);
setTotpCode("");
@@ -372,7 +372,7 @@ export function HomepageAuth({
err?.response?.data?.error ||
err?.message ||
t("errors.invalidTotpCode");
if (errorCode === "SESSION_EXPIRED") {
setTotpRequired(false);
setTotpCode("");
@@ -566,7 +566,9 @@ export function HomepageAuth({
type="button"
variant="outline"
className="w-full h-11 text-base font-semibold"
onClick={() => window.open("https://docs.termix.site/install", "_blank")}
onClick={() =>
window.open("https://docs.termix.site/install", "_blank")
}
>
{t("mobile.viewMobileAppDocs")}
</Button>
@@ -900,7 +902,9 @@ export function HomepageAuth({
type="button"
variant="outline"
className="w-full h-11 text-base font-semibold"
onClick={() => window.open("https://docs.termix.site/install", "_blank")}
onClick={() =>
window.open("https://docs.termix.site/install", "_blank")
}
>
{t("mobile.viewMobileAppDocs")}
</Button>
+3 -1
View File
@@ -164,7 +164,9 @@ const AppContent: FC = () => {
</p>
<button
className="mt-4 px-6 py-3 bg-primary text-primary-foreground rounded-lg font-semibold hover:bg-primary/90 transition-colors"
onClick={() => window.open("https://docs.termix.site/install", "_blank")}
onClick={() =>
window.open("https://docs.termix.site/install", "_blank")
}
>
{t("mobile.viewMobileAppDocs")}
</button>
-4
View File
@@ -58,14 +58,10 @@ interface LeftSidebarProps {
async function handleLogout() {
try {
// Call backend logout endpoint to clear HttpOnly cookie and data session
await logoutUser();
// Reload the page to reset the application state
window.location.reload();
} catch (error) {
console.error("Logout failed:", error);
// Even if logout fails, reload the page to reset state
window.location.reload();
}
}