Configure utilities API creds via Settings UI, not .env
Follows the same app_settings/DB pattern already used for the Forecasting integration: utilities_url and utilities_api_key are now editable from Reports Settings, with env vars kept only as a fallback. Avoids needing to SSH in and hand-edit .env to authenticate against the utilities app's internal API. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
dfd3a506d1
commit
203639af78
4 changed files with 22 additions and 7 deletions
|
|
@ -81,7 +81,9 @@ export async function initDb() {
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
INSERT INTO app_settings (key, value) VALUES
|
INSERT INTO app_settings (key, value) VALUES
|
||||||
('forecasting_url', ''),
|
('forecasting_url', ''),
|
||||||
('forecasting_api_key', '')
|
('forecasting_api_key', ''),
|
||||||
|
('utilities_url', ''),
|
||||||
|
('utilities_api_key', '')
|
||||||
ON CONFLICT (key) DO NOTHING
|
ON CONFLICT (key) DO NOTHING
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// Client for the `utilities` app's internal API (meter readings, tariffs, energy costs).
|
// Client for the `utilities` app's internal API (meter readings, tariffs, energy costs).
|
||||||
// Mirrors the existing forecasting integration pattern used in
|
// Mirrors the existing forecasting integration pattern used in
|
||||||
// routes/directors-forecast.js and routes/weekly-actual.js (X-API-Key header,
|
// routes/directors-forecast.js and routes/weekly-actual.js (X-API-Key header,
|
||||||
// base URL + key from env, throw on non-2xx).
|
// base URL + key from the app_settings table — set via Settings UI — with
|
||||||
|
// env vars as a fallback, throw on non-2xx).
|
||||||
|
import { getSetting } from '../db.js'
|
||||||
|
|
||||||
async function utilFetch(path) {
|
async function utilFetch(path) {
|
||||||
const apiKey = process.env.UTILITIES_API_KEY
|
const apiKey = await getSetting('utilities_api_key') || process.env.UTILITIES_API_KEY
|
||||||
const baseUrl = process.env.UTILITIES_URL || 'http://10.10.10.127:3080'
|
const baseUrl = await getSetting('utilities_url') || process.env.UTILITIES_URL || 'http://10.10.10.127:3080'
|
||||||
if (!apiKey) throw new Error('UTILITIES_API_KEY not configured')
|
if (!apiKey) throw new Error('UTILITIES_API_KEY not configured')
|
||||||
const res = await fetch(`${baseUrl}${path}`, {
|
const res = await fetch(`${baseUrl}${path}`, {
|
||||||
headers: { 'X-API-Key': apiKey },
|
headers: { 'X-API-Key': apiKey },
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { requireAuth } from '../auth.js'
|
import { requireAuth } from '../auth.js'
|
||||||
import { pool } from '../db.js'
|
import { pool } from '../db.js'
|
||||||
|
|
||||||
const ALLOWED_KEYS = new Set(['forecasting_url', 'forecasting_api_key'])
|
const ALLOWED_KEYS = new Set(['forecasting_url', 'forecasting_api_key', 'utilities_url', 'utilities_api_key'])
|
||||||
|
|
||||||
export async function settingsRoutes(fastify) {
|
export async function settingsRoutes(fastify) {
|
||||||
fastify.addHook('preHandler', requireAuth)
|
fastify.addHook('preHandler', requireAuth)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
|
||||||
import { Save } from 'lucide-react'
|
import { Save } from 'lucide-react'
|
||||||
import { getSettings, saveSettings, type AppSetting } from '../api'
|
import { getSettings, saveSettings, type AppSetting } from '../api'
|
||||||
|
|
||||||
const SETTING_LABELS: Record<string, { label: string; hint: string; secret?: boolean }> = {
|
const SETTING_LABELS: Record<string, { label: string; hint: string; secret?: boolean; placeholder?: string }> = {
|
||||||
forecasting_api_key: {
|
forecasting_api_key: {
|
||||||
label: 'Forecasting API Key',
|
label: 'Forecasting API Key',
|
||||||
hint: 'API key for the Forecasting app public API. Create one in Forecasting → Settings → API Keys.',
|
hint: 'API key for the Forecasting app public API. Create one in Forecasting → Settings → API Keys.',
|
||||||
|
|
@ -11,6 +11,17 @@ const SETTING_LABELS: Record<string, { label: string; hint: string; secret?: boo
|
||||||
forecasting_url: {
|
forecasting_url: {
|
||||||
label: 'Forecasting URL',
|
label: 'Forecasting URL',
|
||||||
hint: 'Internal URL for the Forecasting backend (e.g. http://10.10.10.113:3080). Leave blank to use the default.',
|
hint: 'Internal URL for the Forecasting backend (e.g. http://10.10.10.113:3080). Leave blank to use the default.',
|
||||||
|
placeholder: 'http://10.10.10.113:3080',
|
||||||
|
},
|
||||||
|
utilities_api_key: {
|
||||||
|
label: 'Utilities API Key',
|
||||||
|
hint: 'API key for the Utilities app internal API. Must match the key configured in the Utilities app.',
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
utilities_url: {
|
||||||
|
label: 'Utilities URL',
|
||||||
|
hint: 'Internal URL for the Utilities backend (e.g. http://10.10.10.127:3080). Leave blank to use the default.',
|
||||||
|
placeholder: 'http://10.10.10.127:3080',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +83,7 @@ export function SettingsPage() {
|
||||||
<input
|
<input
|
||||||
type={isSecret && !isRevealed ? 'password' : 'text'}
|
type={isSecret && !isRevealed ? 'password' : 'text'}
|
||||||
value={values[s.key] ?? ''}
|
value={values[s.key] ?? ''}
|
||||||
placeholder={isSecret ? 'fk_…' : 'http://10.10.10.113:3080'}
|
placeholder={isSecret ? 'fk_…' : meta.placeholder ?? 'http://10.10.10.113:3080'}
|
||||||
onChange={e => setValues(v => ({ ...v, [s.key]: e.target.value }))}
|
onChange={e => setValues(v => ({ ...v, [s.key]: e.target.value }))}
|
||||||
className="setting-input"
|
className="setting-input"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue