Fix env not loading after restart, update translsations, fix export DB nginx conf
This commit is contained in:
@@ -90,6 +90,24 @@ http {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~ ^/database(/.*)?$ {
|
||||||
|
proxy_pass http://127.0.0.1:30001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/encryption(/.*)?$ {
|
||||||
|
proxy_pass http://127.0.0.1:30001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
location /ssh/ {
|
location /ssh/ {
|
||||||
proxy_pass http://127.0.0.1:30001;
|
proxy_pass http://127.0.0.1:30001;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|||||||
@@ -76,6 +76,24 @@ http {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~ ^/database(/.*)?$ {
|
||||||
|
proxy_pass http://127.0.0.1:30001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/encryption(/.*)?$ {
|
||||||
|
proxy_pass http://127.0.0.1:30001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
location /ssh/ {
|
location /ssh/ {
|
||||||
proxy_pass http://127.0.0.1:30001;
|
proxy_pass http://127.0.0.1:30001;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import { DataCrypto } from "../utils/data-crypto.js";
|
|||||||
import { DatabaseFileEncryption } from "../utils/database-file-encryption.js";
|
import { DatabaseFileEncryption } from "../utils/database-file-encryption.js";
|
||||||
import { DatabaseMigration } from "../utils/database-migration.js";
|
import { DatabaseMigration } from "../utils/database-migration.js";
|
||||||
import { UserDataExport } from "../utils/user-data-export.js";
|
import { UserDataExport } from "../utils/user-data-export.js";
|
||||||
import { UserDataImport } from "../utils/user-data-import.js";
|
|
||||||
import https from "https";
|
|
||||||
import { AutoSSLSetup } from "../utils/auto-ssl-setup.js";
|
import { AutoSSLSetup } from "../utils/auto-ssl-setup.js";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { users, sshData, sshCredentials, fileManagerRecent, fileManagerPinned, fileManagerShortcuts, dismissedAlerts, sshCredentialUsage, settings } from "./db/schema.js";
|
import { users, sshData, sshCredentials, fileManagerRecent, fileManagerPinned, fileManagerShortcuts, dismissedAlerts, sshCredentialUsage, settings } from "./db/schema.js";
|
||||||
@@ -447,7 +445,10 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create temporary SQLite database
|
// Create temporary SQLite database
|
||||||
const tempDir = path.join(os.tmpdir(), 'termix-exports');
|
// Use app data directory for temp files in Docker environments
|
||||||
|
const tempDir = process.env.NODE_ENV === 'production'
|
||||||
|
? path.join(process.env.DATA_DIR || './db/data', '.temp', 'exports')
|
||||||
|
: path.join(os.tmpdir(), 'termix-exports');
|
||||||
if (!fs.existsSync(tempDir)) {
|
if (!fs.existsSync(tempDir)) {
|
||||||
fs.mkdirSync(tempDir, { recursive: true });
|
fs.mkdirSync(tempDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -931,7 +931,17 @@
|
|||||||
"operationCompletedSuccessfully": "{{operation}} {{count}} items successfully",
|
"operationCompletedSuccessfully": "{{operation}} {{count}} items successfully",
|
||||||
"operationCompleted": "{{operation}} {{count}} items",
|
"operationCompleted": "{{operation}} {{count}} items",
|
||||||
"downloadFileSuccess": "File {{name}} downloaded successfully",
|
"downloadFileSuccess": "File {{name}} downloaded successfully",
|
||||||
"downloadFileFailed": "Download failed"
|
"downloadFileFailed": "Download failed",
|
||||||
|
"moveTo": "Move to {{name}}",
|
||||||
|
"diffCompareWith": "Diff compare with {{name}}",
|
||||||
|
"dragOutsideToDownload": "Drag outside window to download ({{count}} files)",
|
||||||
|
"newFolderDefault": "NewFolder",
|
||||||
|
"newFileDefault": "NewFile.txt",
|
||||||
|
"successfullyMovedItems": "Successfully moved {{count}} items to {{target}}",
|
||||||
|
"move": "Move",
|
||||||
|
"searchInFile": "Search in file (Ctrl+F)",
|
||||||
|
"showKeyboardShortcuts": "Show keyboard shortcuts",
|
||||||
|
"startWritingMarkdown": "Start writing your markdown content..."
|
||||||
},
|
},
|
||||||
"tunnels": {
|
"tunnels": {
|
||||||
"title": "SSH Tunnels",
|
"title": "SSH Tunnels",
|
||||||
|
|||||||
@@ -945,7 +945,17 @@
|
|||||||
"operationCompletedSuccessfully": "已{{operation}} {{count}} 个项目",
|
"operationCompletedSuccessfully": "已{{operation}} {{count}} 个项目",
|
||||||
"operationCompleted": "已{{operation}} {{count}} 个项目",
|
"operationCompleted": "已{{operation}} {{count}} 个项目",
|
||||||
"downloadFileSuccess": "文件 {{name}} 下载成功",
|
"downloadFileSuccess": "文件 {{name}} 下载成功",
|
||||||
"downloadFileFailed": "下载失败"
|
"downloadFileFailed": "下载失败",
|
||||||
|
"moveTo": "移动到 {{name}}",
|
||||||
|
"diffCompareWith": "与 {{name}} 对比",
|
||||||
|
"dragOutsideToDownload": "拖拽到窗口外下载 ({{count}} 个文件)",
|
||||||
|
"newFolderDefault": "新文件夹",
|
||||||
|
"newFileDefault": "新文件.txt",
|
||||||
|
"successfullyMovedItems": "成功移动 {{count}} 个项目到 {{target}}",
|
||||||
|
"move": "移动",
|
||||||
|
"searchInFile": "在文件中搜索 (Ctrl+F)",
|
||||||
|
"showKeyboardShortcuts": "显示键盘快捷键",
|
||||||
|
"startWritingMarkdown": "开始编写您的 markdown 内容..."
|
||||||
},
|
},
|
||||||
"tunnels": {
|
"tunnels": {
|
||||||
"title": "SSH 隧道",
|
"title": "SSH 隧道",
|
||||||
|
|||||||
@@ -389,6 +389,11 @@ export function AdminSettings({
|
|||||||
);
|
);
|
||||||
setImportFile(null);
|
setImportFile(null);
|
||||||
setImportPassword("");
|
setImportPassword("");
|
||||||
|
|
||||||
|
// Refresh the page to show imported data
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
toast.error(
|
||||||
`${t("admin.databaseImportFailed")}: ${result.summary?.errors?.join(", ") || "Unknown error"}`,
|
`${t("admin.databaseImportFailed")}: ${result.summary?.errors?.join(", ") || "Unknown error"}`,
|
||||||
|
|||||||
@@ -657,7 +657,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
|||||||
|
|
||||||
// Linus-style creation: pure intent, no side effects
|
// Linus-style creation: pure intent, no side effects
|
||||||
function handleCreateNewFolder() {
|
function handleCreateNewFolder() {
|
||||||
const defaultName = generateUniqueName("NewFolder", "directory");
|
const defaultName = generateUniqueName(t("fileManager.newFolderDefault"), "directory");
|
||||||
const newCreateIntent = {
|
const newCreateIntent = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
type: 'directory' as const,
|
type: 'directory' as const,
|
||||||
@@ -670,7 +670,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleCreateNewFile() {
|
function handleCreateNewFile() {
|
||||||
const defaultName = generateUniqueName("NewFile.txt", "file");
|
const defaultName = generateUniqueName(t("fileManager.newFileDefault"), "file");
|
||||||
const newCreateIntent = {
|
const newCreateIntent = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
type: 'file' as const,
|
type: 'file' as const,
|
||||||
|
|||||||
@@ -1026,13 +1026,13 @@ export function FileManagerGrid({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={confirmEditingPath}
|
onClick={confirmEditingPath}
|
||||||
className="px-2 py-1 bg-primary text-primary-foreground rounded text-xs hover:bg-primary/80"
|
className="px-2 py-1 bg-primary text-primary-foreground rounded text-sm hover:bg-primary/80"
|
||||||
>
|
>
|
||||||
{t("fileManager.confirm")}
|
{t("fileManager.confirm")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={cancelEditingPath}
|
onClick={cancelEditingPath}
|
||||||
className="px-2 py-1 bg-secondary text-secondary-foreground rounded text-xs hover:bg-secondary/80"
|
className="px-2 py-1 bg-secondary text-secondary-foreground rounded text-sm hover:bg-secondary/80"
|
||||||
>
|
>
|
||||||
{t("fileManager.cancel")}
|
{t("fileManager.cancel")}
|
||||||
</button>
|
</button>
|
||||||
@@ -1061,7 +1061,7 @@ export function FileManagerGrid({
|
|||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
onClick={startEditingPath}
|
onClick={startEditingPath}
|
||||||
className="ml-2 p-1 rounded hover:bg-dark-hover opacity-60 hover:opacity-100"
|
className="ml-2 p-1 rounded hover:bg-dark-hover opacity-60 hover:opacity-100 flex items-center justify-center"
|
||||||
title={t("fileManager.editPath")}
|
title={t("fileManager.editPath")}
|
||||||
>
|
>
|
||||||
<Edit className="w-3 h-3" />
|
<Edit className="w-3 h-3" />
|
||||||
@@ -1147,7 +1147,7 @@ export function FileManagerGrid({
|
|||||||
data-file-path={file.path}
|
data-file-path={file.path}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group p-3 rounded-lg cursor-pointer transition-all",
|
"group p-3 rounded-lg cursor-pointer",
|
||||||
"hover:bg-accent hover:text-accent-foreground border-2 border-transparent",
|
"hover:bg-accent hover:text-accent-foreground border-2 border-transparent",
|
||||||
isSelected && "bg-primary/20 border-primary",
|
isSelected && "bg-primary/20 border-primary",
|
||||||
dragState.target?.path === file.path &&
|
dragState.target?.path === file.path &&
|
||||||
@@ -1240,7 +1240,7 @@ export function FileManagerGrid({
|
|||||||
data-file-path={file.path}
|
data-file-path={file.path}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-3 p-2 rounded cursor-pointer transition-all",
|
"flex items-center gap-3 p-2 rounded cursor-pointer",
|
||||||
"hover:bg-accent hover:text-accent-foreground",
|
"hover:bg-accent hover:text-accent-foreground",
|
||||||
isSelected && "bg-primary/20",
|
isSelected && "bg-primary/20",
|
||||||
dragState.target?.path === file.path &&
|
dragState.target?.path === file.path &&
|
||||||
@@ -1378,14 +1378,14 @@ export function FileManagerGrid({
|
|||||||
<>
|
<>
|
||||||
<Move className="w-4 h-4 text-blue-500" />
|
<Move className="w-4 h-4 text-blue-500" />
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground">
|
||||||
Move to {dragState.target.name}
|
{t("fileManager.moveTo", { name: dragState.target.name })}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<GitCompare className="w-4 h-4 text-purple-500" />
|
<GitCompare className="w-4 h-4 text-purple-500" />
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground">
|
||||||
Diff compare with {dragState.target.name}
|
{t("fileManager.diffCompareWith", { name: dragState.target.name })}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -1393,7 +1393,7 @@ export function FileManagerGrid({
|
|||||||
<>
|
<>
|
||||||
<Download className="w-4 h-4 text-green-500" />
|
<Download className="w-4 h-4 text-green-500" />
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground">
|
||||||
Drag outside window to download ({files.length} files)
|
{t("fileManager.dragOutsideToDownload", { count: files.length })}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -1436,7 +1436,7 @@ function CreateIntentGridItem({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group p-3 rounded-lg border-2 border-dashed border-primary bg-primary/10 transition-all">
|
<div className="group p-3 rounded-lg border-2 border-dashed border-primary bg-primary/10">
|
||||||
<div className="flex flex-col items-center text-center">
|
<div className="flex flex-col items-center text-center">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
{intent.type === 'directory' ? (
|
{intent.type === 'directory' ? (
|
||||||
@@ -1490,7 +1490,7 @@ function CreateIntentListItem({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 p-2 rounded border-2 border-dashed border-primary bg-primary/10 transition-all">
|
<div className="flex items-center gap-3 p-2 rounded border-2 border-dashed border-primary bg-primary/10">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
{intent.type === 'directory' ? (
|
{intent.type === 'directory' ? (
|
||||||
<Folder className="w-6 h-6 text-primary" />
|
<Folder className="w-6 h-6 text-primary" />
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ export function FileViewer({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
title="Search in file (Ctrl+F)"
|
title={t("fileManager.searchInFile")}
|
||||||
>
|
>
|
||||||
<Search className="w-4 h-4" />
|
<Search className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -484,7 +484,7 @@ export function FileViewer({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowKeyboardShortcuts(!showKeyboardShortcuts)}
|
onClick={() => setShowKeyboardShortcuts(!showKeyboardShortcuts)}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
title="Show keyboard shortcuts"
|
title={t("fileManager.showKeyboardShortcuts")}
|
||||||
>
|
>
|
||||||
<Keyboard className="w-4 h-4" />
|
<Keyboard className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -928,7 +928,7 @@ export function FileViewer({
|
|||||||
onContentChange?.(e.target.value);
|
onContentChange?.(e.target.value);
|
||||||
}}
|
}}
|
||||||
className="w-full h-full resize-none border-0 bg-transparent text-foreground font-mono text-sm leading-relaxed focus:outline-none focus:ring-0"
|
className="w-full h-full resize-none border-0 bg-transparent text-foreground font-mono text-sm leading-relaxed focus:outline-none focus:ring-0"
|
||||||
placeholder="Start writing your markdown content..."
|
placeholder={t("fileManager.startWritingMarkdown")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -845,7 +845,6 @@ export function HomepageAuth({
|
|||||||
|
|
||||||
{resetStep === "verify" && (
|
{resetStep === "verify" && (
|
||||||
<>
|
<>
|
||||||
o
|
|
||||||
<div className="text-center text-muted-foreground mb-4">
|
<div className="text-center text-muted-foreground mb-4">
|
||||||
<p>
|
<p>
|
||||||
{t("auth.enterResetCode")}{" "}
|
{t("auth.enterResetCode")}{" "}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Label } from "@/components/ui/label.tsx";
|
|||||||
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert.tsx";
|
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { LanguageSwitcher } from "@/ui/Desktop/User/LanguageSwitcher.tsx";
|
import { LanguageSwitcher } from "@/ui/Desktop/User/LanguageSwitcher.tsx";
|
||||||
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
registerUser,
|
registerUser,
|
||||||
loginUser,
|
loginUser,
|
||||||
@@ -92,6 +93,12 @@ export function HomepageAuth({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!registrationAllowed && !internalLoggedIn) {
|
||||||
|
toast.warning(t("messages.registrationDisabled"));
|
||||||
|
}
|
||||||
|
}, [registrationAllowed, internalLoggedIn, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getOIDCConfig()
|
getOIDCConfig()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -116,6 +123,7 @@ export function HomepageAuth({
|
|||||||
if (res.setup_required) {
|
if (res.setup_required) {
|
||||||
setFirstUser(true);
|
setFirstUser(true);
|
||||||
setTab("signup");
|
setTab("signup");
|
||||||
|
toast.info(t("auth.firstUserMessage"));
|
||||||
} else {
|
} else {
|
||||||
setFirstUser(false);
|
setFirstUser(false);
|
||||||
}
|
}
|
||||||
@@ -124,7 +132,7 @@ export function HomepageAuth({
|
|||||||
.catch(() => {
|
.catch(() => {
|
||||||
setDbError(t("errors.databaseConnection"));
|
setDbError(t("errors.databaseConnection"));
|
||||||
});
|
});
|
||||||
}, [setDbError]);
|
}, [setDbError, t]);
|
||||||
|
|
||||||
async function handleSubmit(e: React.FormEvent) {
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -132,7 +140,7 @@ export function HomepageAuth({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
if (!localUsername.trim()) {
|
if (!localUsername.trim()) {
|
||||||
setError(t("errors.requiredField"));
|
toast.error(t("errors.requiredField"));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -143,12 +151,12 @@ export function HomepageAuth({
|
|||||||
res = await loginUser(localUsername, password);
|
res = await loginUser(localUsername, password);
|
||||||
} else {
|
} else {
|
||||||
if (password !== signupConfirmPassword) {
|
if (password !== signupConfirmPassword) {
|
||||||
setError(t("errors.passwordMismatch"));
|
toast.error(t("errors.passwordMismatch"));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (password.length < 6) {
|
if (password.length < 6) {
|
||||||
setError(t("errors.minLength", { min: 6 }));
|
toast.error(t("errors.minLength", { min: 6 }));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -185,12 +193,15 @@ export function HomepageAuth({
|
|||||||
setInternalLoggedIn(true);
|
setInternalLoggedIn(true);
|
||||||
if (tab === "signup") {
|
if (tab === "signup") {
|
||||||
setSignupConfirmPassword("");
|
setSignupConfirmPassword("");
|
||||||
|
toast.success(t("messages.registrationSuccess"));
|
||||||
|
} else {
|
||||||
|
toast.success(t("messages.loginSuccess"));
|
||||||
}
|
}
|
||||||
setTotpRequired(false);
|
setTotpRequired(false);
|
||||||
setTotpCode("");
|
setTotpCode("");
|
||||||
setTotpTempToken("");
|
setTotpTempToken("");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(
|
toast.error(
|
||||||
err?.response?.data?.error || err?.message || t("errors.unknownError"),
|
err?.response?.data?.error || err?.message || t("errors.unknownError"),
|
||||||
);
|
);
|
||||||
setInternalLoggedIn(false);
|
setInternalLoggedIn(false);
|
||||||
@@ -215,9 +226,9 @@ export function HomepageAuth({
|
|||||||
try {
|
try {
|
||||||
const result = await initiatePasswordReset(localUsername);
|
const result = await initiatePasswordReset(localUsername);
|
||||||
setResetStep("verify");
|
setResetStep("verify");
|
||||||
setError(null);
|
toast.success(t("messages.resetCodeSent"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(
|
toast.error(
|
||||||
err?.response?.data?.error ||
|
err?.response?.data?.error ||
|
||||||
err?.message ||
|
err?.message ||
|
||||||
t("errors.failedPasswordReset"),
|
t("errors.failedPasswordReset"),
|
||||||
@@ -234,9 +245,9 @@ export function HomepageAuth({
|
|||||||
const response = await verifyPasswordResetCode(localUsername, resetCode);
|
const response = await verifyPasswordResetCode(localUsername, resetCode);
|
||||||
setTempToken(response.tempToken);
|
setTempToken(response.tempToken);
|
||||||
setResetStep("newPassword");
|
setResetStep("newPassword");
|
||||||
setError(null);
|
toast.success(t("messages.codeVerified"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.response?.data?.error || t("errors.failedVerifyCode"));
|
toast.error(err?.response?.data?.error || t("errors.failedVerifyCode"));
|
||||||
} finally {
|
} finally {
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
}
|
}
|
||||||
@@ -247,13 +258,13 @@ export function HomepageAuth({
|
|||||||
setResetLoading(true);
|
setResetLoading(true);
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
setError(t("errors.passwordMismatch"));
|
toast.error(t("errors.passwordMismatch"));
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword.length < 6) {
|
if (newPassword.length < 6) {
|
||||||
setError(t("errors.minLength", { min: 6 }));
|
toast.error(t("errors.minLength", { min: 6 }));
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -269,8 +280,9 @@ export function HomepageAuth({
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
setResetSuccess(true);
|
setResetSuccess(true);
|
||||||
|
toast.success(t("messages.passwordResetSuccess"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.response?.data?.error || t("errors.failedCompleteReset"));
|
toast.error(err?.response?.data?.error || t("errors.failedCompleteReset"));
|
||||||
} finally {
|
} finally {
|
||||||
setResetLoading(false);
|
setResetLoading(false);
|
||||||
}
|
}
|
||||||
@@ -295,7 +307,7 @@ export function HomepageAuth({
|
|||||||
|
|
||||||
async function handleTOTPVerification() {
|
async function handleTOTPVerification() {
|
||||||
if (totpCode.length !== 6) {
|
if (totpCode.length !== 6) {
|
||||||
setError(t("auth.enterCode"));
|
toast.error(t("auth.enterCode"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,8 +339,9 @@ export function HomepageAuth({
|
|||||||
setTotpRequired(false);
|
setTotpRequired(false);
|
||||||
setTotpCode("");
|
setTotpCode("");
|
||||||
setTotpTempToken("");
|
setTotpTempToken("");
|
||||||
|
toast.success(t("messages.loginSuccess"));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(
|
toast.error(
|
||||||
err?.response?.data?.error ||
|
err?.response?.data?.error ||
|
||||||
err?.message ||
|
err?.message ||
|
||||||
t("errors.invalidTotpCode"),
|
t("errors.invalidTotpCode"),
|
||||||
@@ -351,7 +364,7 @@ export function HomepageAuth({
|
|||||||
|
|
||||||
window.location.replace(authUrl);
|
window.location.replace(authUrl);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(
|
toast.error(
|
||||||
err?.response?.data?.error ||
|
err?.response?.data?.error ||
|
||||||
err?.message ||
|
err?.message ||
|
||||||
t("errors.failedOidcLogin"),
|
t("errors.failedOidcLogin"),
|
||||||
@@ -367,7 +380,7 @@ export function HomepageAuth({
|
|||||||
const error = urlParams.get("error");
|
const error = urlParams.get("error");
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setError(`${t("errors.oidcAuthFailed")}: ${error}`);
|
toast.error(`${t("errors.oidcAuthFailed")}: ${error}`);
|
||||||
setOidcLoading(false);
|
setOidcLoading(false);
|
||||||
window.history.replaceState({}, document.title, window.location.pathname);
|
window.history.replaceState({}, document.title, window.location.pathname);
|
||||||
return;
|
return;
|
||||||
@@ -399,7 +412,7 @@ export function HomepageAuth({
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setError(t("errors.failedUserInfo"));
|
toast.error(t("errors.failedUserInfo"));
|
||||||
setInternalLoggedIn(false);
|
setInternalLoggedIn(false);
|
||||||
setLoggedIn(false);
|
setLoggedIn(false);
|
||||||
setIsAdmin(false);
|
setIsAdmin(false);
|
||||||
@@ -451,31 +464,6 @@ export function HomepageAuth({
|
|||||||
<AlertDescription>{dbError}</AlertDescription>
|
<AlertDescription>{dbError}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{firstUser && !dbError && !internalLoggedIn && (
|
|
||||||
<Alert variant="default" className="mb-4">
|
|
||||||
<AlertTitle>{t("auth.firstUser")}</AlertTitle>
|
|
||||||
<AlertDescription className="inline">
|
|
||||||
{t("auth.firstUserMessage")}{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/LukeGus/Termix/issues/new"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-600 underline hover:text-blue-800 inline"
|
|
||||||
>
|
|
||||||
GitHub Issue
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{!registrationAllowed && !internalLoggedIn && (
|
|
||||||
<Alert variant="destructive" className="mb-4">
|
|
||||||
<AlertTitle>{t("auth.registerTitle")}</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
{t("messages.registrationDisabled")}
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{totpRequired && (
|
{totpRequired && (
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<div className="mb-6 text-center">
|
<div className="mb-6 text-center">
|
||||||
@@ -709,14 +697,11 @@ export function HomepageAuth({
|
|||||||
|
|
||||||
{resetSuccess && (
|
{resetSuccess && (
|
||||||
<>
|
<>
|
||||||
<Alert className="mb-4">
|
<div className="text-center p-4 bg-green-500/10 rounded-lg border border-green-500/20 mb-4">
|
||||||
<AlertTitle>
|
<p className="text-green-400 text-sm">
|
||||||
{t("auth.passwordResetSuccess")}
|
|
||||||
</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
{t("auth.passwordResetSuccessDesc")}
|
{t("auth.passwordResetSuccessDesc")}
|
||||||
</AlertDescription>
|
</p>
|
||||||
</Alert>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-full h-11 text-base font-semibold"
|
className="w-full h-11 text-base font-semibold"
|
||||||
@@ -881,12 +866,6 @@ export function HomepageAuth({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{error && (
|
|
||||||
<Alert variant="destructive" className="mt-4">
|
|
||||||
<AlertTitle>Error</AlertTitle>
|
|
||||||
<AlertDescription>{error}</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, type FC } from "react";
|
||||||
import { Terminal } from "@/ui/Mobile/Apps/Terminal/Terminal.tsx";
|
import { Terminal } from "@/ui/Mobile/Apps/Terminal/Terminal.tsx";
|
||||||
import { TerminalKeyboard } from "@/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx";
|
import { TerminalKeyboard } from "@/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx";
|
||||||
import { BottomNavbar } from "@/ui/Mobile/Navigation/BottomNavbar.tsx";
|
import { BottomNavbar } from "@/ui/Mobile/Navigation/BottomNavbar.tsx";
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { getUserInfo, getCookie } from "@/ui/main-axios.ts";
|
import { getUserInfo, getCookie } from "@/ui/main-axios.ts";
|
||||||
import { HomepageAuth } from "@/ui/Mobile/Homepage/HomepageAuth.tsx";
|
import { HomepageAuth } from "@/ui/Mobile/Homepage/HomepageAuth.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Toaster } from "@/components/ui/sonner.tsx";
|
||||||
|
|
||||||
const AppContent: FC = () => {
|
const AppContent: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -159,7 +160,6 @@ const AppContent: FC = () => {
|
|||||||
ref={tab.terminalRef}
|
ref={tab.terminalRef}
|
||||||
hostConfig={tab.hostConfig}
|
hostConfig={tab.hostConfig}
|
||||||
isVisible={tab.id === currentTab}
|
isVisible={tab.id === currentTab}
|
||||||
onClose={() => removeTab(tab.id)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -207,6 +207,13 @@ const AppContent: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Toaster
|
||||||
|
position="bottom-center"
|
||||||
|
richColors={false}
|
||||||
|
closeButton
|
||||||
|
duration={5000}
|
||||||
|
offset={20}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user