mirror of
https://github.com/kennethnym/aris.git
synced 2026-04-15 22:31:19 +01:00
refactor: replace eslint/prettier with oxlint/oxfmt in admin-dashboard (#109)
Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
@@ -1,197 +1,217 @@
|
||||
import { getServerUrl } from "./server-url"
|
||||
|
||||
function apiBase() {
|
||||
return `${getServerUrl()}/api/admin`
|
||||
return `${getServerUrl()}/api/admin`
|
||||
}
|
||||
|
||||
function serverBase() {
|
||||
return `${getServerUrl()}/api`
|
||||
return `${getServerUrl()}/api`
|
||||
}
|
||||
|
||||
export interface ConfigFieldDef {
|
||||
type: "string" | "number" | "select" | "multiselect"
|
||||
label: string
|
||||
required?: boolean
|
||||
description?: string
|
||||
secret?: boolean
|
||||
defaultValue?: string | number | string[]
|
||||
options?: { label: string; value: string }[]
|
||||
type: "string" | "number" | "select" | "multiselect"
|
||||
label: string
|
||||
required?: boolean
|
||||
description?: string
|
||||
secret?: boolean
|
||||
defaultValue?: string | number | string[]
|
||||
options?: { label: string; value: string }[]
|
||||
}
|
||||
|
||||
export interface SourceDefinition {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
alwaysEnabled?: boolean
|
||||
fields: Record<string, ConfigFieldDef>
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
alwaysEnabled?: boolean
|
||||
fields: Record<string, ConfigFieldDef>
|
||||
}
|
||||
|
||||
export interface SourceConfig {
|
||||
sourceId: string
|
||||
enabled: boolean
|
||||
config: Record<string, unknown>
|
||||
sourceId: string
|
||||
enabled: boolean
|
||||
config: Record<string, unknown>
|
||||
}
|
||||
|
||||
const sourceDefinitions: SourceDefinition[] = [
|
||||
{
|
||||
id: "aelis.location",
|
||||
name: "Location",
|
||||
description: "Device location provider. Always enabled as a dependency for other sources.",
|
||||
alwaysEnabled: true,
|
||||
fields: {},
|
||||
},
|
||||
{
|
||||
id: "aelis.weather",
|
||||
name: "WeatherKit",
|
||||
description: "Apple WeatherKit weather data. Requires Apple Developer credentials.",
|
||||
fields: {
|
||||
privateKey: { type: "string", label: "Private Key", required: true, secret: true, description: "Apple WeatherKit private key (PEM format)" },
|
||||
keyId: { type: "string", label: "Key ID", required: true, secret: true },
|
||||
teamId: { type: "string", label: "Team ID", required: true, secret: true },
|
||||
serviceId: { type: "string", label: "Service ID", required: true, secret: true },
|
||||
units: { type: "select", label: "Units", options: [{ label: "Metric", value: "metric" }, { label: "Imperial", value: "imperial" }], defaultValue: "metric" },
|
||||
hourlyLimit: { type: "number", label: "Hourly Forecast Limit", defaultValue: 12, description: "Number of hourly forecasts to include" },
|
||||
dailyLimit: { type: "number", label: "Daily Forecast Limit", defaultValue: 7, description: "Number of daily forecasts to include" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "aelis.tfl",
|
||||
name: "TfL",
|
||||
description: "Transport for London tube line status alerts.",
|
||||
fields: {
|
||||
lines: {
|
||||
type: "multiselect",
|
||||
label: "Lines",
|
||||
description: "Lines to monitor. Leave empty for all lines.",
|
||||
defaultValue: [],
|
||||
options: [
|
||||
{ label: "Bakerloo", value: "bakerloo" },
|
||||
{ label: "Central", value: "central" },
|
||||
{ label: "Circle", value: "circle" },
|
||||
{ label: "District", value: "district" },
|
||||
{ label: "Hammersmith & City", value: "hammersmith-city" },
|
||||
{ label: "Jubilee", value: "jubilee" },
|
||||
{ label: "Metropolitan", value: "metropolitan" },
|
||||
{ label: "Northern", value: "northern" },
|
||||
{ label: "Piccadilly", value: "piccadilly" },
|
||||
{ label: "Victoria", value: "victoria" },
|
||||
{ label: "Waterloo & City", value: "waterloo-city" },
|
||||
{ label: "Lioness", value: "lioness" },
|
||||
{ label: "Mildmay", value: "mildmay" },
|
||||
{ label: "Windrush", value: "windrush" },
|
||||
{ label: "Weaver", value: "weaver" },
|
||||
{ label: "Suffragette", value: "suffragette" },
|
||||
{ label: "Liberty", value: "liberty" },
|
||||
{ label: "Elizabeth", value: "elizabeth" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "aelis.location",
|
||||
name: "Location",
|
||||
description: "Device location provider. Always enabled as a dependency for other sources.",
|
||||
alwaysEnabled: true,
|
||||
fields: {},
|
||||
},
|
||||
{
|
||||
id: "aelis.weather",
|
||||
name: "WeatherKit",
|
||||
description: "Apple WeatherKit weather data. Requires Apple Developer credentials.",
|
||||
fields: {
|
||||
privateKey: {
|
||||
type: "string",
|
||||
label: "Private Key",
|
||||
required: true,
|
||||
secret: true,
|
||||
description: "Apple WeatherKit private key (PEM format)",
|
||||
},
|
||||
keyId: { type: "string", label: "Key ID", required: true, secret: true },
|
||||
teamId: { type: "string", label: "Team ID", required: true, secret: true },
|
||||
serviceId: { type: "string", label: "Service ID", required: true, secret: true },
|
||||
units: {
|
||||
type: "select",
|
||||
label: "Units",
|
||||
options: [
|
||||
{ label: "Metric", value: "metric" },
|
||||
{ label: "Imperial", value: "imperial" },
|
||||
],
|
||||
defaultValue: "metric",
|
||||
},
|
||||
hourlyLimit: {
|
||||
type: "number",
|
||||
label: "Hourly Forecast Limit",
|
||||
defaultValue: 12,
|
||||
description: "Number of hourly forecasts to include",
|
||||
},
|
||||
dailyLimit: {
|
||||
type: "number",
|
||||
label: "Daily Forecast Limit",
|
||||
defaultValue: 7,
|
||||
description: "Number of daily forecasts to include",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "aelis.tfl",
|
||||
name: "TfL",
|
||||
description: "Transport for London tube line status alerts.",
|
||||
fields: {
|
||||
lines: {
|
||||
type: "multiselect",
|
||||
label: "Lines",
|
||||
description: "Lines to monitor. Leave empty for all lines.",
|
||||
defaultValue: [],
|
||||
options: [
|
||||
{ label: "Bakerloo", value: "bakerloo" },
|
||||
{ label: "Central", value: "central" },
|
||||
{ label: "Circle", value: "circle" },
|
||||
{ label: "District", value: "district" },
|
||||
{ label: "Hammersmith & City", value: "hammersmith-city" },
|
||||
{ label: "Jubilee", value: "jubilee" },
|
||||
{ label: "Metropolitan", value: "metropolitan" },
|
||||
{ label: "Northern", value: "northern" },
|
||||
{ label: "Piccadilly", value: "piccadilly" },
|
||||
{ label: "Victoria", value: "victoria" },
|
||||
{ label: "Waterloo & City", value: "waterloo-city" },
|
||||
{ label: "Lioness", value: "lioness" },
|
||||
{ label: "Mildmay", value: "mildmay" },
|
||||
{ label: "Windrush", value: "windrush" },
|
||||
{ label: "Weaver", value: "weaver" },
|
||||
{ label: "Suffragette", value: "suffragette" },
|
||||
{ label: "Liberty", value: "liberty" },
|
||||
{ label: "Elizabeth", value: "elizabeth" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function fetchSources(): Promise<SourceDefinition[]> {
|
||||
return Promise.resolve(sourceDefinitions)
|
||||
return Promise.resolve(sourceDefinitions)
|
||||
}
|
||||
|
||||
export async function fetchSourceConfig(
|
||||
sourceId: string,
|
||||
): Promise<SourceConfig | null> {
|
||||
const res = await fetch(`${serverBase()}/sources/${sourceId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
if (res.status === 404) return null
|
||||
if (!res.ok) throw new Error(`Failed to fetch source config: ${res.status}`)
|
||||
const data = (await res.json()) as { enabled: boolean; config: Record<string, unknown> }
|
||||
return { sourceId, enabled: data.enabled, config: data.config }
|
||||
export async function fetchSourceConfig(sourceId: string): Promise<SourceConfig | null> {
|
||||
const res = await fetch(`${serverBase()}/sources/${sourceId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
if (res.status === 404) return null
|
||||
if (!res.ok) throw new Error(`Failed to fetch source config: ${res.status}`)
|
||||
const data = (await res.json()) as { enabled: boolean; config: Record<string, unknown> }
|
||||
return { sourceId, enabled: data.enabled, config: data.config }
|
||||
}
|
||||
|
||||
export async function fetchConfigs(): Promise<SourceConfig[]> {
|
||||
const results = await Promise.all(
|
||||
sourceDefinitions.map((s) => fetchSourceConfig(s.id)),
|
||||
)
|
||||
return results.filter((c): c is SourceConfig => c !== null)
|
||||
const results = await Promise.all(sourceDefinitions.map((s) => fetchSourceConfig(s.id)))
|
||||
return results.filter((c): c is SourceConfig => c !== null)
|
||||
}
|
||||
|
||||
export async function replaceSource(
|
||||
sourceId: string,
|
||||
body: { enabled: boolean; config: unknown },
|
||||
sourceId: string,
|
||||
body: { enabled: boolean; config: unknown },
|
||||
): Promise<void> {
|
||||
const res = await fetch(`${serverBase()}/sources/${sourceId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = (await res.json()) as { error?: string }
|
||||
throw new Error(data.error ?? `Failed to replace source config: ${res.status}`)
|
||||
}
|
||||
const res = await fetch(`${serverBase()}/sources/${sourceId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = (await res.json()) as { error?: string }
|
||||
throw new Error(data.error ?? `Failed to replace source config: ${res.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateProviderConfig(
|
||||
sourceId: string,
|
||||
body: Record<string, unknown>,
|
||||
sourceId: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
const res = await fetch(`${apiBase()}/${sourceId}/config`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = (await res.json()) as { error?: string }
|
||||
throw new Error(data.error ?? `Failed to update provider config: ${res.status}`)
|
||||
}
|
||||
const res = await fetch(`${apiBase()}/${sourceId}/config`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = (await res.json()) as { error?: string }
|
||||
throw new Error(data.error ?? `Failed to update provider config: ${res.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
export interface LocationInput {
|
||||
lat: number
|
||||
lng: number
|
||||
accuracy: number
|
||||
lat: number
|
||||
lng: number
|
||||
accuracy: number
|
||||
}
|
||||
|
||||
export async function pushLocation(location: LocationInput): Promise<void> {
|
||||
const res = await fetch(`${serverBase()}/location`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
...location,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = (await res.json()) as { error?: string }
|
||||
throw new Error(data.error ?? `Failed to push location: ${res.status}`)
|
||||
}
|
||||
const res = await fetch(`${serverBase()}/location`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
...location,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = (await res.json()) as { error?: string }
|
||||
throw new Error(data.error ?? `Failed to push location: ${res.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
export interface FeedItemSlot {
|
||||
description: string
|
||||
content: string | null
|
||||
description: string
|
||||
content: string | null
|
||||
}
|
||||
|
||||
export interface FeedItem {
|
||||
id: string
|
||||
sourceId: string
|
||||
type: string
|
||||
timestamp: string
|
||||
data: Record<string, unknown>
|
||||
signals?: {
|
||||
urgency?: number
|
||||
timeRelevance?: string
|
||||
}
|
||||
slots?: Record<string, FeedItemSlot>
|
||||
ui?: unknown
|
||||
id: string
|
||||
sourceId: string
|
||||
type: string
|
||||
timestamp: string
|
||||
data: Record<string, unknown>
|
||||
signals?: {
|
||||
urgency?: number
|
||||
timeRelevance?: string
|
||||
}
|
||||
slots?: Record<string, FeedItemSlot>
|
||||
ui?: unknown
|
||||
}
|
||||
|
||||
export interface FeedResponse {
|
||||
items: FeedItem[]
|
||||
errors: { sourceId: string; error: string }[]
|
||||
items: FeedItem[]
|
||||
errors: { sourceId: string; error: string }[]
|
||||
}
|
||||
|
||||
export async function fetchFeed(): Promise<FeedResponse> {
|
||||
const res = await fetch(`${serverBase()}/feed`, { credentials: "include" })
|
||||
if (!res.ok) throw new Error(`Failed to fetch feed: ${res.status}`)
|
||||
return res.json() as Promise<FeedResponse>
|
||||
const res = await fetch(`${serverBase()}/feed`, { credentials: "include" })
|
||||
if (!res.ok) throw new Error(`Failed to fetch feed: ${res.status}`)
|
||||
return res.json() as Promise<FeedResponse>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user