ldelalande+alexhancock/shared-config-client (#1219)
Co-authored-by: Lily Delalande <ldelalande@squareup.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ConfigAPI, ConfigResponse } from './api';
|
||||
|
||||
export const ConfigManager: React.FC = () => {
|
||||
const [config, setConfig] = useState<ConfigResponse | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [newKey, setNewKey] = useState('');
|
||||
const [newValue, setNewValue] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadConfig();
|
||||
}, []);
|
||||
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const data = await ConfigAPI.readAllConfig();
|
||||
setConfig(data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError('Failed to load configuration');
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpsert = async () => {
|
||||
try {
|
||||
await ConfigAPI.upsertConfig({
|
||||
key: newKey,
|
||||
value: newValue as any, // You might want to add proper parsing here
|
||||
});
|
||||
await loadConfig();
|
||||
setNewKey('');
|
||||
setNewValue('');
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError('Failed to update configuration');
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = async (key: string) => {
|
||||
try {
|
||||
await ConfigAPI.removeConfig(key);
|
||||
await loadConfig();
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError('Failed to remove configuration');
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="text-2xl font-bold mb-4">Configuration Manager</h2>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold mb-2">Add/Update Configuration</h3>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newKey}
|
||||
onChange={(e) => setNewKey(e.target.value)}
|
||||
placeholder="Key"
|
||||
className="border p-2 rounded"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={newValue}
|
||||
onChange={(e) => setNewValue(e.target.value)}
|
||||
placeholder="Value"
|
||||
className="border p-2 rounded"
|
||||
/>
|
||||
<button onClick={handleUpsert} className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-2">Current Configuration</h3>
|
||||
{config && (
|
||||
<div className="border rounded">
|
||||
{Object.entries(config.config).map(([key, value]) => (
|
||||
<div key={key} className="p-2 border-b flex justify-between items-center">
|
||||
<div>
|
||||
<span className="font-medium">{key}:</span> <span>{JSON.stringify(value)}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleRemove(key)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Value } from 'yaml';
|
||||
|
||||
export interface UpsertConfigQuery {
|
||||
key: string;
|
||||
value: Value;
|
||||
isSecret?: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigKeyQuery {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ExtensionQuery {
|
||||
name: string;
|
||||
config: Value;
|
||||
}
|
||||
|
||||
export interface ConfigResponse {
|
||||
config: Record<string, Value>;
|
||||
}
|
||||
|
||||
const API_BASE = 'http://localhost:3000'; // Update this with your actual API base URL
|
||||
|
||||
export class ConfigAPI {
|
||||
static async readAllConfig(): Promise<ConfigResponse> {
|
||||
const response = await fetch(`${API_BASE}/config`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch config');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
static async upsertConfig(query: UpsertConfigQuery): Promise<string> {
|
||||
const params = new URLSearchParams({
|
||||
key: query.key,
|
||||
value: JSON.stringify(query.value),
|
||||
...(query.isSecret && { is_secret: String(query.isSecret) }),
|
||||
});
|
||||
|
||||
const response = await fetch(`${API_BASE}/config/upsert?${params}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to upsert config');
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
|
||||
static async removeConfig(key: string): Promise<string> {
|
||||
const params = new URLSearchParams({ key });
|
||||
const response = await fetch(`${API_BASE}/config/remove?${params}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to remove config');
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
|
||||
static async readConfig(key: string): Promise<Value> {
|
||||
const params = new URLSearchParams({ key });
|
||||
const response = await fetch(`${API_BASE}/config/read?${params}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to read config');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
static async addExtension(extension: ExtensionQuery): Promise<string> {
|
||||
const response = await fetch(`${API_BASE}/config/extension`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(extension),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to add extension');
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
|
||||
static async removeExtension(name: string): Promise<string> {
|
||||
const params = new URLSearchParams({ key: name });
|
||||
const response = await fetch(`${API_BASE}/config/extension?${params}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to remove extension');
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user