mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-04-25 00:36:32 +02:00
Return actual sensitive values from API for visibility toggle
Backend: - Update /settings/notifications to return actual tokens/keys - Update /settings/ai to return actual API keys Frontend: - Update NotificationSettings and AISettings types - Populate form fields with actual saved values on load - Eye toggle now reveals actual stored values - Always show toggle button for consistent UX Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b885e4ef57
commit
a4da43c127
5 changed files with 63 additions and 67 deletions
|
|
@ -18,14 +18,14 @@ router.get('/notifications', async (req: AuthRequest, res: Response) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Don't expose full tokens, just indicate if they're set
|
||||
res.json({
|
||||
telegram_configured: !!(settings.telegram_bot_token && settings.telegram_chat_id),
|
||||
telegram_chat_id: settings.telegram_chat_id,
|
||||
telegram_bot_token: settings.telegram_bot_token || null,
|
||||
telegram_chat_id: settings.telegram_chat_id || null,
|
||||
telegram_enabled: settings.telegram_enabled ?? true,
|
||||
discord_configured: !!settings.discord_webhook_url,
|
||||
discord_webhook_url: settings.discord_webhook_url || null,
|
||||
discord_enabled: settings.discord_enabled ?? true,
|
||||
pushover_configured: !!(settings.pushover_user_key && settings.pushover_app_token),
|
||||
pushover_user_key: settings.pushover_user_key || null,
|
||||
pushover_app_token: settings.pushover_app_token || null,
|
||||
pushover_enabled: settings.pushover_enabled ?? true,
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -66,12 +66,13 @@ router.put('/notifications', async (req: AuthRequest, res: Response) => {
|
|||
}
|
||||
|
||||
res.json({
|
||||
telegram_configured: !!(settings.telegram_bot_token && settings.telegram_chat_id),
|
||||
telegram_chat_id: settings.telegram_chat_id,
|
||||
telegram_bot_token: settings.telegram_bot_token || null,
|
||||
telegram_chat_id: settings.telegram_chat_id || null,
|
||||
telegram_enabled: settings.telegram_enabled ?? true,
|
||||
discord_configured: !!settings.discord_webhook_url,
|
||||
discord_webhook_url: settings.discord_webhook_url || null,
|
||||
discord_enabled: settings.discord_enabled ?? true,
|
||||
pushover_configured: !!(settings.pushover_user_key && settings.pushover_app_token),
|
||||
pushover_user_key: settings.pushover_user_key || null,
|
||||
pushover_app_token: settings.pushover_app_token || null,
|
||||
pushover_enabled: settings.pushover_enabled ?? true,
|
||||
message: 'Notification settings updated successfully',
|
||||
});
|
||||
|
|
@ -196,13 +197,11 @@ router.get('/ai', async (req: AuthRequest, res: Response) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Don't expose full API keys, just indicate if they're set
|
||||
res.json({
|
||||
ai_enabled: settings.ai_enabled || false,
|
||||
ai_provider: settings.ai_provider || null,
|
||||
anthropic_configured: !!settings.anthropic_api_key,
|
||||
openai_configured: !!settings.openai_api_key,
|
||||
ollama_configured: !!(settings.ollama_base_url && settings.ollama_model),
|
||||
anthropic_api_key: settings.anthropic_api_key || null,
|
||||
openai_api_key: settings.openai_api_key || null,
|
||||
ollama_base_url: settings.ollama_base_url || null,
|
||||
ollama_model: settings.ollama_model || null,
|
||||
});
|
||||
|
|
@ -235,9 +234,8 @@ router.put('/ai', async (req: AuthRequest, res: Response) => {
|
|||
res.json({
|
||||
ai_enabled: settings.ai_enabled || false,
|
||||
ai_provider: settings.ai_provider || null,
|
||||
anthropic_configured: !!settings.anthropic_api_key,
|
||||
openai_configured: !!settings.openai_api_key,
|
||||
ollama_configured: !!(settings.ollama_base_url && settings.ollama_model),
|
||||
anthropic_api_key: settings.anthropic_api_key || null,
|
||||
openai_api_key: settings.openai_api_key || null,
|
||||
ollama_base_url: settings.ollama_base_url || null,
|
||||
ollama_model: settings.ollama_model || null,
|
||||
message: 'AI settings updated successfully',
|
||||
|
|
|
|||
|
|
@ -149,12 +149,13 @@ export const stockHistoryApi = {
|
|||
|
||||
// Settings API
|
||||
export interface NotificationSettings {
|
||||
telegram_configured: boolean;
|
||||
telegram_bot_token: string | null;
|
||||
telegram_chat_id: string | null;
|
||||
telegram_enabled: boolean;
|
||||
discord_configured: boolean;
|
||||
discord_webhook_url: string | null;
|
||||
discord_enabled: boolean;
|
||||
pushover_configured: boolean;
|
||||
pushover_user_key: string | null;
|
||||
pushover_app_token: string | null;
|
||||
pushover_enabled: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -206,9 +207,8 @@ export const settingsApi = {
|
|||
export interface AISettings {
|
||||
ai_enabled: boolean;
|
||||
ai_provider: 'anthropic' | 'openai' | 'ollama' | null;
|
||||
anthropic_configured: boolean;
|
||||
openai_configured: boolean;
|
||||
ollama_configured: boolean;
|
||||
anthropic_api_key: string | null;
|
||||
openai_api_key: string | null;
|
||||
ollama_base_url: string | null;
|
||||
ollama_model: string | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,22 @@ interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
|
|||
// All standard input props are inherited
|
||||
}
|
||||
|
||||
export default function PasswordInput({ style, value, ...props }: PasswordInputProps) {
|
||||
export default function PasswordInput({ style, ...props }: PasswordInputProps) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const hasValue = value !== undefined && value !== null && String(value).length > 0;
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<input
|
||||
{...props}
|
||||
value={value}
|
||||
type={visible ? 'text' : 'password'}
|
||||
style={{
|
||||
...style,
|
||||
width: '100%',
|
||||
paddingRight: hasValue ? '2.5rem' : '0.75rem',
|
||||
paddingRight: '2.5rem',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
/>
|
||||
{hasValue && <button
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -79,7 +77,7 @@ export default function PasswordInput({ style, value, ...props }: PasswordInputP
|
|||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
)}
|
||||
</button>}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -525,9 +525,9 @@ export default function ProductDetail() {
|
|||
<StockTimeline productId={productId} days={30} />
|
||||
|
||||
{notificationSettings && (
|
||||
(notificationSettings.telegram_configured && notificationSettings.telegram_enabled) ||
|
||||
(notificationSettings.discord_configured && notificationSettings.discord_enabled) ||
|
||||
(notificationSettings.pushover_configured && notificationSettings.pushover_enabled)
|
||||
((notificationSettings.telegram_bot_token && notificationSettings.telegram_chat_id) && notificationSettings.telegram_enabled) ||
|
||||
(notificationSettings.discord_webhook_url && notificationSettings.discord_enabled) ||
|
||||
((notificationSettings.pushover_user_key && notificationSettings.pushover_app_token) && notificationSettings.pushover_enabled)
|
||||
) && (
|
||||
<>
|
||||
<style>{`
|
||||
|
|
@ -676,13 +676,13 @@ export default function ProductDetail() {
|
|||
<span className="notification-settings-icon">🔔</span>
|
||||
<h2 className="notification-settings-title">Notification Settings</h2>
|
||||
<div className="notification-settings-channels">
|
||||
{notificationSettings.telegram_configured && notificationSettings.telegram_enabled && (
|
||||
{(notificationSettings.telegram_bot_token && notificationSettings.telegram_chat_id) && notificationSettings.telegram_enabled && (
|
||||
<span className="notification-channel-badge">Telegram</span>
|
||||
)}
|
||||
{notificationSettings.discord_configured && notificationSettings.discord_enabled && (
|
||||
{notificationSettings.discord_webhook_url && notificationSettings.discord_enabled && (
|
||||
<span className="notification-channel-badge">Discord</span>
|
||||
)}
|
||||
{notificationSettings.pushover_configured && notificationSettings.pushover_enabled && (
|
||||
{(notificationSettings.pushover_user_key && notificationSettings.pushover_app_token) && notificationSettings.pushover_enabled && (
|
||||
<span className="notification-channel-badge">Pushover</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -81,23 +81,25 @@ export default function Settings() {
|
|||
setProfile(profileRes.data);
|
||||
setProfileName(profileRes.data.name || '');
|
||||
setNotificationSettings(notificationsRes.data);
|
||||
if (notificationsRes.data.telegram_chat_id) {
|
||||
setTelegramChatId(notificationsRes.data.telegram_chat_id);
|
||||
}
|
||||
// Populate notification fields with actual values
|
||||
setTelegramBotToken(notificationsRes.data.telegram_bot_token || '');
|
||||
setTelegramChatId(notificationsRes.data.telegram_chat_id || '');
|
||||
setTelegramEnabled(notificationsRes.data.telegram_enabled ?? true);
|
||||
setDiscordWebhookUrl(notificationsRes.data.discord_webhook_url || '');
|
||||
setDiscordEnabled(notificationsRes.data.discord_enabled ?? true);
|
||||
setPushoverUserKey(notificationsRes.data.pushover_user_key || '');
|
||||
setPushoverAppToken(notificationsRes.data.pushover_app_token || '');
|
||||
setPushoverEnabled(notificationsRes.data.pushover_enabled ?? true);
|
||||
// Populate AI fields with actual values
|
||||
setAISettings(aiRes.data);
|
||||
setAIEnabled(aiRes.data.ai_enabled);
|
||||
if (aiRes.data.ai_provider) {
|
||||
setAIProvider(aiRes.data.ai_provider);
|
||||
}
|
||||
if (aiRes.data.ollama_base_url) {
|
||||
setOllamaBaseUrl(aiRes.data.ollama_base_url);
|
||||
}
|
||||
if (aiRes.data.ollama_model) {
|
||||
setOllamaModel(aiRes.data.ollama_model);
|
||||
}
|
||||
setAnthropicApiKey(aiRes.data.anthropic_api_key || '');
|
||||
setOpenaiApiKey(aiRes.data.openai_api_key || '');
|
||||
setOllamaBaseUrl(aiRes.data.ollama_base_url || '');
|
||||
setOllamaModel(aiRes.data.ollama_model || '');
|
||||
} catch {
|
||||
setError('Failed to load settings');
|
||||
} finally {
|
||||
|
|
@ -958,8 +960,8 @@ export default function Settings() {
|
|||
<div className="settings-section-header">
|
||||
<span className="settings-section-icon">📱</span>
|
||||
<h2 className="settings-section-title">Telegram Notifications</h2>
|
||||
<span className={`settings-section-status ${notificationSettings?.telegram_configured ? 'configured' : 'not-configured'}`}>
|
||||
{notificationSettings?.telegram_configured ? 'Configured' : 'Not configured'}
|
||||
<span className={`settings-section-status ${notificationSettings?.telegram_bot_token && notificationSettings?.telegram_chat_id ? 'configured' : 'not-configured'}`}>
|
||||
{notificationSettings?.telegram_bot_token && notificationSettings?.telegram_chat_id ? 'Configured' : 'Not configured'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="settings-section-description">
|
||||
|
|
@ -967,7 +969,7 @@ export default function Settings() {
|
|||
and get your chat ID.
|
||||
</p>
|
||||
|
||||
{notificationSettings?.telegram_configured && (
|
||||
{notificationSettings?.telegram_bot_token && notificationSettings?.telegram_chat_id && (
|
||||
<div className="settings-toggle">
|
||||
<div className="settings-toggle-label">
|
||||
<span className="settings-toggle-title">Enable Telegram Notifications</span>
|
||||
|
|
@ -987,7 +989,7 @@ export default function Settings() {
|
|||
<PasswordInput
|
||||
value={telegramBotToken}
|
||||
onChange={(e) => setTelegramBotToken(e.target.value)}
|
||||
placeholder={notificationSettings?.telegram_configured ? 'Saved - enter new value to replace' : 'Enter your bot token'}
|
||||
placeholder="Enter your bot token"
|
||||
/>
|
||||
<p className="hint">Create a bot via @BotFather on Telegram to get a token</p>
|
||||
</div>
|
||||
|
|
@ -1011,7 +1013,7 @@ export default function Settings() {
|
|||
>
|
||||
{isSavingNotifications ? 'Saving...' : 'Save Telegram Settings'}
|
||||
</button>
|
||||
{notificationSettings?.telegram_configured && (
|
||||
{notificationSettings?.telegram_bot_token && notificationSettings?.telegram_chat_id && (
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={handleTestTelegram}
|
||||
|
|
@ -1027,8 +1029,8 @@ export default function Settings() {
|
|||
<div className="settings-section-header">
|
||||
<span className="settings-section-icon">💬</span>
|
||||
<h2 className="settings-section-title">Discord Notifications</h2>
|
||||
<span className={`settings-section-status ${notificationSettings?.discord_configured ? 'configured' : 'not-configured'}`}>
|
||||
{notificationSettings?.discord_configured ? 'Configured' : 'Not configured'}
|
||||
<span className={`settings-section-status ${notificationSettings?.discord_webhook_url ? 'configured' : 'not-configured'}`}>
|
||||
{notificationSettings?.discord_webhook_url ? 'Configured' : 'Not configured'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="settings-section-description">
|
||||
|
|
@ -1036,7 +1038,7 @@ export default function Settings() {
|
|||
Discord server settings.
|
||||
</p>
|
||||
|
||||
{notificationSettings?.discord_configured && (
|
||||
{notificationSettings?.discord_webhook_url && (
|
||||
<div className="settings-toggle">
|
||||
<div className="settings-toggle-label">
|
||||
<span className="settings-toggle-title">Enable Discord Notifications</span>
|
||||
|
|
@ -1056,7 +1058,7 @@ export default function Settings() {
|
|||
<PasswordInput
|
||||
value={discordWebhookUrl}
|
||||
onChange={(e) => setDiscordWebhookUrl(e.target.value)}
|
||||
placeholder={notificationSettings?.discord_configured ? 'Saved - enter new value to replace' : 'https://discord.com/api/webhooks/...'}
|
||||
placeholder="https://discord.com/api/webhooks/..."
|
||||
/>
|
||||
<p className="hint">Server Settings → Integrations → Webhooks → New Webhook</p>
|
||||
</div>
|
||||
|
|
@ -1069,7 +1071,7 @@ export default function Settings() {
|
|||
>
|
||||
{isSavingNotifications ? 'Saving...' : 'Save Discord Settings'}
|
||||
</button>
|
||||
{notificationSettings?.discord_configured && (
|
||||
{notificationSettings?.discord_webhook_url && (
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={handleTestDiscord}
|
||||
|
|
@ -1085,8 +1087,8 @@ export default function Settings() {
|
|||
<div className="settings-section-header">
|
||||
<span className="settings-section-icon">🔔</span>
|
||||
<h2 className="settings-section-title">Pushover Notifications</h2>
|
||||
<span className={`settings-section-status ${notificationSettings?.pushover_configured ? 'configured' : 'not-configured'}`}>
|
||||
{notificationSettings?.pushover_configured ? 'Configured' : 'Not configured'}
|
||||
<span className={`settings-section-status ${notificationSettings?.pushover_user_key && notificationSettings?.pushover_app_token ? 'configured' : 'not-configured'}`}>
|
||||
{notificationSettings?.pushover_user_key && notificationSettings?.pushover_app_token ? 'Configured' : 'Not configured'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="settings-section-description">
|
||||
|
|
@ -1094,7 +1096,7 @@ export default function Settings() {
|
|||
and an application to get your keys.
|
||||
</p>
|
||||
|
||||
{notificationSettings?.pushover_configured && (
|
||||
{notificationSettings?.pushover_user_key && notificationSettings?.pushover_app_token && (
|
||||
<div className="settings-toggle">
|
||||
<div className="settings-toggle-label">
|
||||
<span className="settings-toggle-title">Enable Pushover Notifications</span>
|
||||
|
|
@ -1114,7 +1116,7 @@ export default function Settings() {
|
|||
<PasswordInput
|
||||
value={pushoverUserKey}
|
||||
onChange={(e) => setPushoverUserKey(e.target.value)}
|
||||
placeholder={notificationSettings?.pushover_configured ? 'Saved - enter new value to replace' : 'Enter your user key'}
|
||||
placeholder="Enter your user key"
|
||||
/>
|
||||
<p className="hint">Find your User Key on the Pushover dashboard after logging in</p>
|
||||
</div>
|
||||
|
|
@ -1124,7 +1126,7 @@ export default function Settings() {
|
|||
<PasswordInput
|
||||
value={pushoverAppToken}
|
||||
onChange={(e) => setPushoverAppToken(e.target.value)}
|
||||
placeholder={notificationSettings?.pushover_configured ? 'Saved - enter new value to replace' : 'Enter your app token'}
|
||||
placeholder="Enter your app token"
|
||||
/>
|
||||
<p className="hint">Create an application at pushover.net/apps to get an API token</p>
|
||||
</div>
|
||||
|
|
@ -1137,7 +1139,7 @@ export default function Settings() {
|
|||
>
|
||||
{isSavingNotifications ? 'Saving...' : 'Save Pushover Settings'}
|
||||
</button>
|
||||
{notificationSettings?.pushover_configured && (
|
||||
{notificationSettings?.pushover_user_key && notificationSettings?.pushover_app_token && (
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={handleTestPushover}
|
||||
|
|
@ -1208,15 +1210,14 @@ export default function Settings() {
|
|||
<PasswordInput
|
||||
value={anthropicApiKey}
|
||||
onChange={(e) => setAnthropicApiKey(e.target.value)}
|
||||
placeholder={aiSettings?.anthropic_configured ? 'Saved - enter new value to replace' : 'sk-ant-...'}
|
||||
placeholder="sk-ant-..."
|
||||
/>
|
||||
<p className="hint">
|
||||
Get your API key from{' '}
|
||||
<a href="https://console.anthropic.com/" target="_blank" rel="noopener noreferrer">
|
||||
console.anthropic.com
|
||||
</a>
|
||||
{aiSettings?.anthropic_configured && ' (key already saved)'}
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -1226,15 +1227,14 @@ export default function Settings() {
|
|||
<PasswordInput
|
||||
value={openaiApiKey}
|
||||
onChange={(e) => setOpenaiApiKey(e.target.value)}
|
||||
placeholder={aiSettings?.openai_configured ? 'Saved - enter new value to replace' : 'sk-...'}
|
||||
placeholder="sk-..."
|
||||
/>
|
||||
<p className="hint">
|
||||
Get your API key from{' '}
|
||||
<a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer">
|
||||
platform.openai.com
|
||||
</a>
|
||||
{aiSettings?.openai_configured && ' (key already saved)'}
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -1298,7 +1298,7 @@ export default function Settings() {
|
|||
? 'Select from available models or test connection to refresh list'
|
||||
: 'Enter model name or test connection to see available models'
|
||||
}
|
||||
{aiSettings?.ollama_configured && ` (currently: ${aiSettings.ollama_model})`}
|
||||
{aiSettings?.ollama_base_url && aiSettings?.ollama_model && ` (currently: ${aiSettings.ollama_model})`}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -1317,7 +1317,7 @@ export default function Settings() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{aiSettings?.ai_enabled && (aiSettings.anthropic_configured || aiSettings.openai_configured || aiSettings.ollama_configured) && (
|
||||
{aiSettings?.ai_enabled && (aiSettings.anthropic_api_key || aiSettings.openai_api_key || (aiSettings.ollama_base_url && aiSettings.ollama_model)) && (
|
||||
<div className="settings-section">
|
||||
<div className="settings-section-header">
|
||||
<span className="settings-section-icon">🧪</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue