feat: complete per-user integration config isolation and UI initialization
Some checks failed
Release Binaries / release (push) Has been cancelled
Some checks failed
Release Binaries / release (push) Has been cancelled
This commit is contained in:
@@ -100,6 +100,11 @@ const App: Component = () => {
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
// Initialize user context from Electron IPC
|
||||
import("./lib/user-context").then(({ initializeUserContext }) => {
|
||||
initializeUserContext()
|
||||
})
|
||||
|
||||
updateInstanceTabBarHeight()
|
||||
const handleResize = () => updateInstanceTabBarHeight()
|
||||
window.addEventListener("resize", handleResize)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, createSignal, onMount, For, Show, createEffect, on } from "solid-js"
|
||||
import { CheckCircle, XCircle, Loader, RefreshCw, Settings, AlertTriangle } from "lucide-solid"
|
||||
import { userFetch } from "../../lib/user-context"
|
||||
|
||||
interface ApiStatus {
|
||||
id: string
|
||||
@@ -28,7 +29,7 @@ const API_CHECKS: ApiStatusCheck[] = [
|
||||
checkEnabled: async () => true, // Always available
|
||||
testConnection: async () => {
|
||||
try {
|
||||
const res = await fetch("/api/opencode-zen/test")
|
||||
const res = await userFetch("/api/opencode-zen/test")
|
||||
if (!res.ok) return false
|
||||
const data = await res.json()
|
||||
return data.connected === true
|
||||
@@ -43,7 +44,7 @@ const API_CHECKS: ApiStatusCheck[] = [
|
||||
icon: "🦙",
|
||||
checkEnabled: async () => {
|
||||
try {
|
||||
const res = await fetch("/api/ollama/config")
|
||||
const res = await userFetch("/api/ollama/config")
|
||||
if (!res.ok) return false
|
||||
const data = await res.json()
|
||||
return data.config?.enabled === true
|
||||
@@ -53,7 +54,7 @@ const API_CHECKS: ApiStatusCheck[] = [
|
||||
},
|
||||
testConnection: async () => {
|
||||
try {
|
||||
const res = await fetch("/api/ollama/test", { method: "POST" })
|
||||
const res = await userFetch("/api/ollama/test", { method: "POST" })
|
||||
if (!res.ok) return false
|
||||
const data = await res.json()
|
||||
return data.connected === true
|
||||
@@ -68,7 +69,7 @@ const API_CHECKS: ApiStatusCheck[] = [
|
||||
icon: "🧠",
|
||||
checkEnabled: async () => {
|
||||
try {
|
||||
const res = await fetch("/api/zai/config")
|
||||
const res = await userFetch("/api/zai/config")
|
||||
if (!res.ok) return false
|
||||
const data = await res.json()
|
||||
return data.config?.enabled === true
|
||||
@@ -78,7 +79,7 @@ const API_CHECKS: ApiStatusCheck[] = [
|
||||
},
|
||||
testConnection: async () => {
|
||||
try {
|
||||
const res = await fetch("/api/zai/test", { method: "POST" })
|
||||
const res = await userFetch("/api/zai/test", { method: "POST" })
|
||||
if (!res.ok) return false
|
||||
const data = await res.json()
|
||||
return data.connected === true
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button } from '@suid/material'
|
||||
import { Cloud, CheckCircle, XCircle, Loader } from 'lucide-solid'
|
||||
import { instances } from '../../stores/instances'
|
||||
import { fetchProviders } from '../../stores/session-api'
|
||||
import { userFetch } from '../../lib/user-context'
|
||||
|
||||
interface OllamaCloudConfig {
|
||||
enabled: boolean
|
||||
@@ -34,7 +35,7 @@ const OllamaCloudSettings: Component = () => {
|
||||
// Load config on mount
|
||||
onMount(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/ollama/config')
|
||||
const response = await userFetch('/api/ollama/config')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
const maskedKey = typeof data.config?.apiKey === "string" && /^\*+$/.test(data.config.apiKey)
|
||||
@@ -62,7 +63,7 @@ const OllamaCloudSettings: Component = () => {
|
||||
delete payload.apiKey
|
||||
}
|
||||
|
||||
const response = await fetch('/api/ollama/config', {
|
||||
const response = await userFetch('/api/ollama/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
@@ -101,7 +102,7 @@ const OllamaCloudSettings: Component = () => {
|
||||
setConnectionStatus('testing')
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ollama/test', {
|
||||
const response = await userFetch('/api/ollama/test', {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
@@ -140,7 +141,7 @@ const OllamaCloudSettings: Component = () => {
|
||||
const loadModels = async () => {
|
||||
setIsLoadingModels(true)
|
||||
try {
|
||||
const response = await fetch('/api/ollama/models')
|
||||
const response = await userFetch('/api/ollama/models')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
// Handle different response formats
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, createSignal, onMount, Show } from 'solid-js'
|
||||
import toast from 'solid-toast'
|
||||
import { Button } from '@suid/material'
|
||||
import { Cpu, CheckCircle, XCircle, Loader, Key, ExternalLink } from 'lucide-solid'
|
||||
import { userFetch } from '../../lib/user-context'
|
||||
|
||||
interface ZAIConfig {
|
||||
enabled: boolean
|
||||
@@ -19,7 +20,7 @@ const ZAISettings: Component = () => {
|
||||
// Load config on mount
|
||||
onMount(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/zai/config')
|
||||
const response = await userFetch('/api/zai/config')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setConfig(data.config)
|
||||
@@ -37,7 +38,7 @@ const ZAISettings: Component = () => {
|
||||
const saveConfig = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const response = await fetch('/api/zai/config', {
|
||||
const response = await userFetch('/api/zai/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config())
|
||||
@@ -66,7 +67,7 @@ const ZAISettings: Component = () => {
|
||||
setConnectionStatus('testing')
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/zai/test', {
|
||||
const response = await userFetch('/api/zai/test', {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
@@ -104,7 +105,7 @@ const ZAISettings: Component = () => {
|
||||
|
||||
const loadModels = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/zai/models')
|
||||
const response = await userFetch('/api/zai/models')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setModels(data.models.map((m: any) => m.name))
|
||||
|
||||
96
packages/ui/src/lib/user-context.ts
Normal file
96
packages/ui/src/lib/user-context.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* User Context utilities for frontend
|
||||
* Handles active user ID and passes it in API requests
|
||||
*/
|
||||
|
||||
// Storage key for active user
|
||||
const ACTIVE_USER_KEY = "codenomad_active_user_id"
|
||||
|
||||
/**
|
||||
* Set the active user ID
|
||||
*/
|
||||
export function setActiveUserId(userId: string | null): void {
|
||||
if (userId) {
|
||||
localStorage.setItem(ACTIVE_USER_KEY, userId)
|
||||
console.log(`[UserContext] Active user set to: ${userId}`)
|
||||
} else {
|
||||
localStorage.removeItem(ACTIVE_USER_KEY)
|
||||
console.log(`[UserContext] Active user cleared`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active user ID
|
||||
*/
|
||||
export function getActiveUserId(): string | null {
|
||||
return localStorage.getItem(ACTIVE_USER_KEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers with user ID for API requests
|
||||
*/
|
||||
export function getUserHeaders(): Record<string, string> {
|
||||
const userId = getActiveUserId()
|
||||
if (userId) {
|
||||
return { "X-User-Id": userId }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create fetch options with user headers
|
||||
*/
|
||||
export function withUserHeaders(options: RequestInit = {}): RequestInit {
|
||||
const userHeaders = getUserHeaders()
|
||||
return {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
...userHeaders,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch wrapper that automatically includes user headers
|
||||
*/
|
||||
export async function userFetch(url: string, options: RequestInit = {}): Promise<Response> {
|
||||
return fetch(url, withUserHeaders(options))
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize user context from Electron IPC
|
||||
* Call this on app startup
|
||||
*/
|
||||
export async function initializeUserContext(): Promise<void> {
|
||||
try {
|
||||
// Check if we're in Electron environment
|
||||
const ipcRenderer = (window as any).electron?.ipcRenderer
|
||||
if (ipcRenderer) {
|
||||
const activeUser = await ipcRenderer.invoke("users:active")
|
||||
if (activeUser?.id) {
|
||||
setActiveUserId(activeUser.id)
|
||||
console.log(`[UserContext] Initialized with user: ${activeUser.id} (${activeUser.name})`)
|
||||
} else {
|
||||
console.log(`[UserContext] No active user from IPC`)
|
||||
}
|
||||
} else {
|
||||
// Web mode - try to get from localStorage or use default
|
||||
const existingId = getActiveUserId()
|
||||
if (existingId) {
|
||||
console.log(`[UserContext] Using cached user ID: ${existingId}`)
|
||||
} else {
|
||||
// Set a default user ID for web mode
|
||||
const defaultUserId = "default"
|
||||
setActiveUserId(defaultUserId)
|
||||
console.log(`[UserContext] Web mode - using default user ID`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[UserContext] Failed to initialize:`, error)
|
||||
// Fall back to default
|
||||
if (!getActiveUserId()) {
|
||||
setActiveUserId("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user