phase 16: add registry/risk nginx proxies, add company form, network policies
This commit is contained in:
@@ -41,3 +41,10 @@ output/
|
|||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.coverage
|
.coverage
|
||||||
htmlcov/
|
htmlcov/
|
||||||
|
|
||||||
|
|
||||||
|
# API secret files
|
||||||
|
polygon.io.key
|
||||||
|
alpaca.key
|
||||||
|
alpaca.secret
|
||||||
|
alpaca.url
|
||||||
|
|||||||
@@ -18,6 +18,24 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Proxy Symbol Registry (companies CRUD, sources, watchlists, aliases)
|
||||||
|
location /registry/ {
|
||||||
|
proxy_pass http://symbol-registry:8000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy Risk Engine
|
||||||
|
location /risk/ {
|
||||||
|
proxy_pass http://risk:8000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
# Cache static assets
|
# Cache static assets
|
||||||
location /assets/ {
|
location /assets/ {
|
||||||
expires 1y;
|
expires 1y;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const QUERY_API_BASE = import.meta.env.VITE_QUERY_API_URL ?? '';
|
const QUERY_API_BASE = import.meta.env.VITE_QUERY_API_URL ?? '';
|
||||||
const SYMBOL_REGISTRY_BASE = import.meta.env.VITE_SYMBOL_REGISTRY_URL ?? '';
|
const SYMBOL_REGISTRY_BASE = import.meta.env.VITE_SYMBOL_REGISTRY_URL ?? '/registry';
|
||||||
const RISK_ENGINE_BASE = import.meta.env.VITE_RISK_ENGINE_URL ?? '';
|
const RISK_ENGINE_BASE = import.meta.env.VITE_RISK_ENGINE_URL ?? '/risk';
|
||||||
|
|
||||||
export type ApiBase = 'query' | 'registry' | 'risk';
|
export type ApiBase = 'query' | 'registry' | 'risk';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useNavigate } from '@tanstack/react-router';
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
import { useCompanies } from '../api/hooks';
|
import { useCompanies, useCreateCompany } from '../api/hooks';
|
||||||
import { DataTable, type Column } from '../components/DataTable';
|
import { DataTable, type Column } from '../components/DataTable';
|
||||||
import { StatusBadge, LoadingSpinner } from '../components/ui';
|
import { StatusBadge, LoadingSpinner, Card } from '../components/ui';
|
||||||
import type { Company } from '../api/hooks';
|
import type { Company } from '../api/hooks';
|
||||||
|
|
||||||
const columns: Column<Company>[] = [
|
const columns: Column<Company>[] = [
|
||||||
@@ -23,13 +24,25 @@ const columns: Column<Company>[] = [
|
|||||||
export function CompaniesPage() {
|
export function CompaniesPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isLoading, error } = useCompanies();
|
const { data, isLoading, error } = useCompanies();
|
||||||
|
const [showAdd, setShowAdd] = useState(false);
|
||||||
|
|
||||||
if (isLoading) return <LoadingSpinner />;
|
if (isLoading) return <LoadingSpinner />;
|
||||||
if (error) return <div className="text-red-400">Failed to load companies</div>;
|
if (error) return <div className="text-red-400">Failed to load companies</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="mb-4 text-xl font-semibold text-gray-100">Companies</h1>
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<h1 className="text-xl font-semibold text-gray-100">Companies</h1>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAdd(true)}
|
||||||
|
className="rounded-md bg-brand-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-brand-700"
|
||||||
|
>
|
||||||
|
Add Company
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showAdd && <AddCompanyForm onClose={() => setShowAdd(false)} />}
|
||||||
|
|
||||||
<DataTable<Company>
|
<DataTable<Company>
|
||||||
data={data ?? []}
|
data={data ?? []}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -47,3 +60,61 @@ export function CompaniesPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AddCompanyForm({ onClose }: { onClose: () => void }) {
|
||||||
|
const [ticker, setTicker] = useState('');
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [sector, setSector] = useState('');
|
||||||
|
const [exchange, setExchange] = useState('');
|
||||||
|
const mutation = useCreateCompany();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="mb-4">
|
||||||
|
<form
|
||||||
|
className="space-y-3"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (ticker.trim() && name.trim()) {
|
||||||
|
mutation.mutate(
|
||||||
|
{
|
||||||
|
ticker: ticker.trim().toUpperCase(),
|
||||||
|
legal_name: name.trim(),
|
||||||
|
sector: sector || undefined,
|
||||||
|
exchange: exchange || undefined,
|
||||||
|
},
|
||||||
|
{ onSuccess: onClose },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs text-gray-500" htmlFor="add-ticker">Ticker</label>
|
||||||
|
<input id="add-ticker" type="text" value={ticker} onChange={(e) => setTicker(e.target.value)} required placeholder="AAPL" className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500 uppercase" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs text-gray-500" htmlFor="add-name">Legal Name</label>
|
||||||
|
<input id="add-name" type="text" value={name} onChange={(e) => setName(e.target.value)} required placeholder="Apple Inc." className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs text-gray-500" htmlFor="add-sector">Sector</label>
|
||||||
|
<input id="add-sector" type="text" value={sector} onChange={(e) => setSector(e.target.value)} placeholder="Technology" className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs text-gray-500" htmlFor="add-exchange">Exchange</label>
|
||||||
|
<input id="add-exchange" type="text" value={exchange} onChange={(e) => setExchange(e.target.value)} placeholder="NASDAQ" className="w-full rounded-md border border-surface-700 bg-surface-900 px-2 py-1.5 text-sm text-gray-200 placeholder-gray-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mutation.error && <div className="text-xs text-red-400">Failed to create company</div>}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button type="submit" disabled={mutation.isPending} className="rounded-md bg-brand-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50">
|
||||||
|
{mutation.isPending ? 'Creating…' : 'Create'}
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={onClose} className="rounded-md border border-surface-700 px-3 py-1.5 text-sm text-gray-400 hover:bg-surface-800">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ spec:
|
|||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
kubernetes.io/metadata.name: kube-system
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: dashboard
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 8000
|
port: 8000
|
||||||
@@ -71,6 +74,9 @@ spec:
|
|||||||
- podSelector:
|
- podSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: query-api
|
app: query-api
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: dashboard
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 8000
|
port: 8000
|
||||||
|
|||||||
+9
-1
@@ -12,6 +12,10 @@ MINIO_ACCESS_KEY="AKIA6V7J3N9B5P0D2YQH"
|
|||||||
MINIO_SECRET_KEY='8fG3!v2rJ7$wN@9mLpQ6zXbC4tKdPqW1'
|
MINIO_SECRET_KEY='8fG3!v2rJ7$wN@9mLpQ6zXbC4tKdPqW1'
|
||||||
PG_PASSWORD='St0nks0racl3!'
|
PG_PASSWORD='St0nks0racl3!'
|
||||||
REDIS_PASSWORD='PSCh4ng3me!'
|
REDIS_PASSWORD='PSCh4ng3me!'
|
||||||
|
POLYGON_API_KEY=$(cat "$REPO_DIR/polygon.io.key" | tr -d '[:space:]')
|
||||||
|
ALPACA_API_KEY=$(cat "$REPO_DIR/alpaca.key" | tr -d '[:space:]')
|
||||||
|
ALPACA_API_SECRET=$(cat "$REPO_DIR/alpaca.secret" | tr -d '[:space:]')
|
||||||
|
ALPACA_BASE_URL=$(cat "$REPO_DIR/alpaca.url" | tr -d '[:space:]')
|
||||||
|
|
||||||
echo "=== Stonks Oracle Deployment ==="
|
echo "=== Stonks Oracle Deployment ==="
|
||||||
|
|
||||||
@@ -66,7 +70,11 @@ helm upgrade --install stonks-oracle "$CHART_DIR" \
|
|||||||
--set "secrets.core.POSTGRES_PASSWORD=$PG_PASSWORD" \
|
--set "secrets.core.POSTGRES_PASSWORD=$PG_PASSWORD" \
|
||||||
--set "secrets.core.MINIO_ACCESS_KEY=$MINIO_ACCESS_KEY" \
|
--set "secrets.core.MINIO_ACCESS_KEY=$MINIO_ACCESS_KEY" \
|
||||||
--set "secrets.core.MINIO_SECRET_KEY=$MINIO_SECRET_KEY" \
|
--set "secrets.core.MINIO_SECRET_KEY=$MINIO_SECRET_KEY" \
|
||||||
--set "secrets.core.REDIS_PASSWORD=$REDIS_PASSWORD"
|
--set "secrets.core.REDIS_PASSWORD=$REDIS_PASSWORD" \
|
||||||
|
--set "secrets.market.MARKET_DATA_API_KEY=$POLYGON_API_KEY" \
|
||||||
|
--set "secrets.broker.BROKER_API_KEY=$ALPACA_API_KEY" \
|
||||||
|
--set "secrets.broker.BROKER_API_SECRET=$ALPACA_API_SECRET" \
|
||||||
|
--set "secrets.broker.BROKER_BASE_URL=$ALPACA_BASE_URL"
|
||||||
|
|
||||||
# --- Rolling restart to pick up secrets ---
|
# --- Rolling restart to pick up secrets ---
|
||||||
echo "Rolling restart..."
|
echo "Rolling restart..."
|
||||||
|
|||||||
Reference in New Issue
Block a user