Enhance TTS functionality and security settings
- Integrated Google TTS languages from a separate module for better maintainability. - Updated local device voice fetching to support both macOS and Windows, improving cross-platform compatibility. - Enhanced dashboard route protection by adding dynamic settings for login requirements and tunnel access. - Introduced UI elements for managing security settings related to API key requirements and dashboard access via tunnel. - Added default TTS response example in the media provider page for better user guidance. - Updated constants to reflect changes in TTS provider configurations. This commit improves the overall user experience and security of the TTS features.
This commit is contained in:
1 parent
875a1282ea
commit
b3feb96740
10 files changed
+315
-67
No files matched your search
@@ -22,6 +22,9 @@ export default function APIPageClient({ machineId }) {
|
||||
const [createdKey, setCreatedKey] = useState(null);
|
||||
|
||||
const [requireApiKey, setRequireApiKey] = useState(false);
|
||||
const [requireLogin, setRequireLogin] = useState(true);
|
||||
const [hasPassword, setHasPassword] = useState(true);
|
||||
const [tunnelDashboardAccess, setTunnelDashboardAccess] = useState(false);
|
||||
|
||||
// Cloudflare Tunnel state
|
||||
const [tunnelChecking, setTunnelChecking] = useState(true);
|
||||
@@ -74,6 +77,9 @@ export default function APIPageClient({ machineId }) {
|
||||
if (settingsRes.ok) {
|
||||
const data = await settingsRes.json();
|
||||
setRequireApiKey(data.requireApiKey || false);
|
||||
setRequireLogin(data.requireLogin !== false);
|
||||
setHasPassword(data.hasPassword || false);
|
||||
setTunnelDashboardAccess(data.tunnelDashboardAccess || false);
|
||||
}
|
||||
if (statusRes.ok) {
|
||||
const data = await statusRes.json();
|
||||
@@ -135,6 +141,19 @@ export default function APIPageClient({ machineId }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleTunnelDashboardAccess = async (value) => {
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ tunnelDashboardAccess: value }),
|
||||
});
|
||||
if (res.ok) setTunnelDashboardAccess(value);
|
||||
} catch (error) {
|
||||
console.log("Error updating tunnelDashboardAccess:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRequireApiKey = async (value) => {
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
@@ -688,10 +707,49 @@ export default function APIPageClient({ machineId }) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Security warnings when tunnel or tailscale is active */}
|
||||
{(tunnelEnabled || tsEnabled) && (
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
{!requireApiKey && (
|
||||
<SecurityWarning
|
||||
message="Require API key is disabled — your endpoint is publicly accessible without authentication."
|
||||
action={{ label: "Enable", href: "#require-api-key" }}
|
||||
/>
|
||||
)}
|
||||
{(!requireLogin || !hasPassword) && (
|
||||
<SecurityWarning
|
||||
message={
|
||||
!requireLogin
|
||||
? "Require login is disabled — anyone can access your dashboard via tunnel."
|
||||
: "Dashboard uses the default password — change it in Profile settings."
|
||||
}
|
||||
action={{
|
||||
label: !requireLogin ? "Enable" : "Change password",
|
||||
href: "/dashboard/profile",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tunnel dashboard access option */}
|
||||
{(tunnelEnabled || tsEnabled) && (
|
||||
<div className="mt-4 pt-4 border-t border-border flex items-center gap-3">
|
||||
<Toggle
|
||||
checked={tunnelDashboardAccess}
|
||||
onChange={() => handleTunnelDashboardAccess(!tunnelDashboardAccess)}
|
||||
/>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<p className="font-medium text-sm">Allow dashboard access via tunnel</p>
|
||||
<Tooltip text="When enabled, the dashboard can be accessed through your tunnel or Tailscale URL without requiring login. Only enable if you trust everyone who can reach your tunnel URL." />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* API Keys */}
|
||||
<Card>
|
||||
<Card id="require-api-key">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold">API Keys</h2>
|
||||
<Button icon="add" onClick={() => setShowAddModal(true)}>
|
||||
@@ -1063,6 +1121,40 @@ function StatusAlert({ status, className = "" }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Inline tooltip, Claude Code CLI style */
|
||||
function Tooltip({ text }) {
|
||||
return (
|
||||
<span className="relative group inline-flex items-center">
|
||||
<span className="material-symbols-outlined text-[14px] text-text-muted cursor-help">help</span>
|
||||
<span className="pointer-events-none absolute left-5 top-1/2 -translate-y-1/2 z-50 w-64 rounded bg-gray-900 dark:bg-gray-800 text-white text-xs px-2.5 py-1.5 opacity-0 group-hover:opacity-100 transition-opacity shadow-lg">
|
||||
{text}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** Security warning banner with optional action link */
|
||||
function SecurityWarning({ message, action }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-amber-500/10 border border-amber-500/20 text-amber-700 dark:text-amber-400">
|
||||
<span className="material-symbols-outlined text-[16px] shrink-0 mt-0.5">warning</span>
|
||||
<p className="text-xs flex-1">{message}</p>
|
||||
{action && (
|
||||
<a
|
||||
href={action.href}
|
||||
className="text-xs font-medium underline shrink-0 hover:opacity-80"
|
||||
onClick={action.href.startsWith("#") ? (e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById(action.href.slice(1))?.scrollIntoView({ behavior: "smooth" });
|
||||
} : undefined}
|
||||
>
|
||||
{action.label}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
APIPageClient.propTypes = {
|
||||
machineId: PropTypes.string.isRequired,
|
||||
};
|
||||
Reference in new issue
Block a user