Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0376c0251d |
@@ -159,3 +159,20 @@ HINDSIGHT_API_LOG_LEVEL=info
|
||||
# When set, visitors see a login page and must enter the key before
|
||||
# accessing the dashboard or any /api/* routes (except /api/health).
|
||||
# HINDSIGHT_CP_ACCESS_KEY=your-shared-secret-key
|
||||
|
||||
# Optional: Token the CP forwards to the dataplane admin API (/admin/*).
|
||||
# Must match HINDSIGHT_API_ADMIN_TOKEN below. Leave unset for an open admin API.
|
||||
# HINDSIGHT_CP_ADMIN_TOKEN=your-admin-token
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Admin surface (Optional, server-level)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Enable the admin API (GET /admin/config) and the Control Plane /admin page.
|
||||
# Off by default — the admin surface is invisible (404) until enabled.
|
||||
# HINDSIGHT_API_ENABLE_ADMIN_API=true
|
||||
|
||||
# Optional: Require this bearer token for the admin API. When unset, the admin
|
||||
# API is open (once enabled). When set, callers must send
|
||||
# `Authorization: Bearer <token>`. Independent of the tenant API key.
|
||||
# HINDSIGHT_API_ADMIN_TOKEN=your-admin-token
|
||||
|
||||
@@ -6,6 +6,8 @@ the FastAPI application with all API endpoints.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
@@ -79,7 +81,7 @@ def FieldWithDefault(default_factory: Callable, **kwargs) -> Any:
|
||||
return Field(default_factory=default_factory, json_schema_extra=json_extra, **kwargs)
|
||||
|
||||
|
||||
from hindsight_api.config import get_config
|
||||
from hindsight_api.config import _get_raw_config, get_config
|
||||
from hindsight_api.engine.memory_engine import Budget, _current_schema, _get_tiktoken_encoding, fq_table
|
||||
from hindsight_api.engine.providers.none_llm import LLMNotAvailableError
|
||||
from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES, MemoryFact, TokenUsage
|
||||
@@ -1206,6 +1208,33 @@ class BankConfigResponse(BaseModel):
|
||||
overrides: dict[str, Any] = Field(description="Bank-specific configuration overrides only (Python field names)")
|
||||
|
||||
|
||||
class AdminConfigResponse(BaseModel):
|
||||
"""Response model for the server-level (admin) configuration view.
|
||||
|
||||
Returns the resolved ``HindsightConfig`` as a flat dict keyed by Python field
|
||||
name. Credential fields (API keys, tokens, service-account keys, base URLs) are
|
||||
masked: present as ``"***"`` when set and ``None`` when unset, so an operator can
|
||||
see which credentials are configured without ever seeing their values.
|
||||
"""
|
||||
|
||||
config: dict[str, Any] = Field(
|
||||
description="Resolved server-level configuration (Python field names); credentials are redacted"
|
||||
)
|
||||
|
||||
|
||||
# Name suffixes that mark a config field as secret-bearing. Used in addition to
|
||||
# HindsightConfig._CREDENTIAL_FIELDS so the admin config view never leaks a provider
|
||||
# credential even if the (denylist) credential set misses a field. Suffixes are
|
||||
# singular on purpose — "_token" must not also match value-bearing "_tokens" fields
|
||||
# like recall_max_tokens.
|
||||
_SENSITIVE_FIELD_SUFFIXES = ("_api_key", "_token", "_secret", "_access_key", "_account_key", "_password")
|
||||
|
||||
|
||||
def _is_sensitive_config_field(field_name: str, credential_fields: set[str]) -> bool:
|
||||
"""Whether a config field must be redacted in the admin view."""
|
||||
return field_name in credential_fields or field_name.endswith(_SENSITIVE_FIELD_SUFFIXES)
|
||||
|
||||
|
||||
class GraphDataResponse(BaseModel):
|
||||
"""Response model for graph data endpoint."""
|
||||
|
||||
@@ -2413,6 +2442,7 @@ class FeaturesInfo(BaseModel):
|
||||
mcp: bool = Field(description="Whether MCP (Model Context Protocol) server is enabled")
|
||||
worker: bool = Field(description="Whether the background worker is enabled")
|
||||
bank_config_api: bool = Field(description="Whether per-bank configuration API is enabled")
|
||||
admin_api: bool = Field(description="Whether the admin API (/admin) is enabled")
|
||||
file_upload_api: bool = Field(description="Whether file upload/conversion API is enabled")
|
||||
document_export_api: bool = Field(description="Whether the document export endpoint is enabled")
|
||||
document_import_api: bool = Field(description="Whether the document import endpoint is enabled")
|
||||
@@ -2971,6 +3001,31 @@ def _register_routes(app: FastAPI):
|
||||
api_key = authorization.strip()
|
||||
return RequestContext(api_key=api_key)
|
||||
|
||||
def require_admin(authorization: str | None = Header(default=None)) -> None:
|
||||
"""Guard for the admin surface.
|
||||
|
||||
- 404 when the admin API is disabled (so the surface is invisible by default).
|
||||
- When ``HINDSIGHT_API_ADMIN_TOKEN`` is set, require it as a bearer token
|
||||
(or a bare token) and reject with 401 otherwise. When unset, the admin API
|
||||
is open (auth is optional) — consistent with the rest of the deployment.
|
||||
"""
|
||||
config = _get_raw_config()
|
||||
if not config.enable_admin_api:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Admin API is disabled. Set HINDSIGHT_API_ENABLE_ADMIN_API=true to enable.",
|
||||
)
|
||||
expected = config.admin_api_token
|
||||
if expected:
|
||||
token = None
|
||||
if authorization:
|
||||
if authorization.lower().startswith("bearer "):
|
||||
token = authorization[7:].strip()
|
||||
else:
|
||||
token = authorization.strip()
|
||||
if not token or not hmac.compare_digest(token, expected):
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing admin token")
|
||||
|
||||
def precheck_for(operation: str):
|
||||
"""
|
||||
Build a FastAPI dependency that runs ``OperationValidator.precheck``.
|
||||
@@ -3080,6 +3135,7 @@ def _register_routes(app: FastAPI):
|
||||
mcp=config.mcp_enabled,
|
||||
worker=config.worker_enabled,
|
||||
bank_config_api=config.enable_bank_config_api,
|
||||
admin_api=config.enable_admin_api,
|
||||
file_upload_api=config.enable_file_upload_api,
|
||||
document_export_api=config.enable_document_export_api,
|
||||
document_import_api=config.enable_document_import_api,
|
||||
@@ -3088,6 +3144,33 @@ def _register_routes(app: FastAPI):
|
||||
),
|
||||
)
|
||||
|
||||
@app.get(
|
||||
"/admin/config",
|
||||
response_model=AdminConfigResponse,
|
||||
summary="Get resolved server-level configuration",
|
||||
description="Returns the resolved server-level configuration with credentials redacted. "
|
||||
"Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.",
|
||||
tags=["Admin"],
|
||||
operation_id="get_admin_config",
|
||||
dependencies=[Depends(require_admin)],
|
||||
)
|
||||
async def admin_config_endpoint() -> AdminConfigResponse:
|
||||
"""Expose the resolved ``HindsightConfig`` for operator inspection.
|
||||
|
||||
Sensitive fields (the credential denylist plus any field whose name ends in a
|
||||
secret-bearing suffix — see ``_is_sensitive_config_field``) are masked so values
|
||||
never leave the server: ``"***"`` when set, ``None`` when unset. All other fields
|
||||
are returned as-is.
|
||||
"""
|
||||
config = _get_raw_config()
|
||||
credential_fields = type(config).get_credential_fields()
|
||||
raw = dataclasses.asdict(config)
|
||||
redacted = {
|
||||
key: ("***" if value is not None else None) if _is_sensitive_config_field(key, credential_fields) else value
|
||||
for key, value in raw.items()
|
||||
}
|
||||
return AdminConfigResponse(config=redacted)
|
||||
|
||||
@app.get(
|
||||
"/metrics",
|
||||
summary="Prometheus metrics endpoint",
|
||||
|
||||
@@ -351,6 +351,8 @@ ENV_MCP_ENABLED = "HINDSIGHT_API_MCP_ENABLED"
|
||||
ENV_MCP_ENABLED_TOOLS = "HINDSIGHT_API_MCP_ENABLED_TOOLS"
|
||||
ENV_MCP_STATELESS = "HINDSIGHT_API_MCP_STATELESS"
|
||||
ENV_ENABLE_BANK_CONFIG_API = "HINDSIGHT_API_ENABLE_BANK_CONFIG_API"
|
||||
ENV_ENABLE_ADMIN_API = "HINDSIGHT_API_ENABLE_ADMIN_API"
|
||||
ENV_ADMIN_API_TOKEN = "HINDSIGHT_API_ADMIN_TOKEN"
|
||||
ENV_DEFAULT_BANK_TEMPLATE = "HINDSIGHT_API_DEFAULT_BANK_TEMPLATE"
|
||||
ENV_GRAPH_RETRIEVER = "HINDSIGHT_API_GRAPH_RETRIEVER"
|
||||
ENV_RECALL_MAX_CONCURRENT = "HINDSIGHT_API_RECALL_MAX_CONCURRENT"
|
||||
@@ -792,6 +794,8 @@ DEFAULT_MCP_ENABLED = True
|
||||
DEFAULT_MCP_ENABLED_TOOLS: list[str] | None = None # None = all tools enabled
|
||||
DEFAULT_MCP_STATELESS = False # False = stateful (supports SSE/GET); True = stateless (POST-only)
|
||||
DEFAULT_ENABLE_BANK_CONFIG_API = True
|
||||
DEFAULT_ENABLE_ADMIN_API = False # Admin surface (server config view) is off unless explicitly enabled
|
||||
DEFAULT_ADMIN_API_TOKEN: str | None = None # None = admin API open (when enabled); set = required bearer token
|
||||
DEFAULT_DEFAULT_BANK_TEMPLATE: dict | None = None # BankTemplateManifest dict applied to newly-created banks
|
||||
DEFAULT_GRAPH_RETRIEVER = "link_expansion"
|
||||
DEFAULT_RECALL_MAX_CONCURRENT = 32 # Max concurrent recall operations per worker
|
||||
@@ -1387,6 +1391,10 @@ class HindsightConfig:
|
||||
mcp_enabled_tools: list[str] | None # None = all tools; explicit list = allowlist
|
||||
mcp_stateless: bool # True = stateless HTTP (POST-only); False = stateful (supports GET/SSE)
|
||||
enable_bank_config_api: bool
|
||||
# Admin surface (static, server-level only). enable_admin_api gates the /admin API +
|
||||
# control-plane page; admin_api_token (when set) is the required bearer token.
|
||||
enable_admin_api: bool
|
||||
admin_api_token: str | None
|
||||
# Default bank template (static, server-level only). When set, the manifest is applied
|
||||
# to every newly-created bank, overriding the env/config defaults for any fields it sets.
|
||||
default_bank_template: dict | None
|
||||
@@ -1612,6 +1620,8 @@ class HindsightConfig:
|
||||
# File parser credentials
|
||||
"file_parser_iris_token",
|
||||
"file_parser_llama_parse_api_key",
|
||||
# Admin surface token (never exposed via the admin config view itself)
|
||||
"admin_api_token",
|
||||
}
|
||||
|
||||
# CONFIGURABLE_FIELDS: Safe behavioral settings that can be customized per-tenant/bank
|
||||
@@ -2218,6 +2228,8 @@ class HindsightConfig:
|
||||
mcp_stateless=os.getenv(ENV_MCP_STATELESS, str(DEFAULT_MCP_STATELESS)).lower() == "true",
|
||||
enable_bank_config_api=os.getenv(ENV_ENABLE_BANK_CONFIG_API, str(DEFAULT_ENABLE_BANK_CONFIG_API)).lower()
|
||||
== "true",
|
||||
enable_admin_api=os.getenv(ENV_ENABLE_ADMIN_API, str(DEFAULT_ENABLE_ADMIN_API)).lower() == "true",
|
||||
admin_api_token=os.getenv(ENV_ADMIN_API_TOKEN) or DEFAULT_ADMIN_API_TOKEN,
|
||||
default_bank_template=_parse_default_bank_template(os.getenv(ENV_DEFAULT_BANK_TEMPLATE)),
|
||||
# Recall
|
||||
graph_retriever=os.getenv(ENV_GRAPH_RETRIEVER, DEFAULT_GRAPH_RETRIEVER),
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Tests for the admin surface: GET /admin/config + the admin_api feature flag.
|
||||
|
||||
These are deterministic (no LLM): the endpoint only reads server-level config. We
|
||||
toggle env vars + clear the config cache to exercise the enable flag, the optional
|
||||
admin token, and credential redaction.
|
||||
"""
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from hindsight_api.api import create_app
|
||||
from hindsight_api.config import clear_config_cache
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def admin_client(memory):
|
||||
"""Async test client for the FastAPI app (mock LLM)."""
|
||||
app = create_app(memory, initialize_memory=False)
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
yield client
|
||||
|
||||
|
||||
def _set_env(monkeypatch, **values: str | None) -> None:
|
||||
"""Set/unset env vars and reset the cached config so the next read reflects them."""
|
||||
for key, value in values.items():
|
||||
if value is None:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
else:
|
||||
monkeypatch.setenv(key, value)
|
||||
clear_config_cache()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _restore_config_cache():
|
||||
"""Ensure the global config cache is reset after each test."""
|
||||
yield
|
||||
clear_config_cache()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_config_disabled_by_default(admin_client, monkeypatch):
|
||||
"""When the admin API is disabled (default), the endpoint is invisible (404)."""
|
||||
_set_env(monkeypatch, HINDSIGHT_API_ENABLE_ADMIN_API=None, HINDSIGHT_API_ADMIN_TOKEN=None)
|
||||
|
||||
response = await admin_client.get("/admin/config")
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_config_enabled_no_token(admin_client, monkeypatch):
|
||||
"""When enabled without a token, the endpoint is open and returns config."""
|
||||
_set_env(monkeypatch, HINDSIGHT_API_ENABLE_ADMIN_API="true", HINDSIGHT_API_ADMIN_TOKEN=None)
|
||||
|
||||
response = await admin_client.get("/admin/config")
|
||||
|
||||
assert response.status_code == 200
|
||||
config = response.json()["config"]
|
||||
# A representative spread of non-credential fields should be present.
|
||||
assert "llm_provider" in config
|
||||
assert "enable_admin_api" in config
|
||||
assert config["enable_admin_api"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_config_redacts_credentials(admin_client, monkeypatch):
|
||||
"""Credential fields are masked, never returned in cleartext."""
|
||||
_set_env(
|
||||
monkeypatch,
|
||||
HINDSIGHT_API_ENABLE_ADMIN_API="true",
|
||||
HINDSIGHT_API_ADMIN_TOKEN="s3cret-token",
|
||||
HINDSIGHT_API_LLM_API_KEY="super-secret-key",
|
||||
)
|
||||
|
||||
response = await admin_client.get("/admin/config", headers={"Authorization": "Bearer s3cret-token"})
|
||||
|
||||
assert response.status_code == 200
|
||||
config = response.json()["config"]
|
||||
# The configured LLM key is present but masked.
|
||||
assert config["llm_api_key"] == "***"
|
||||
assert "super-secret-key" not in response.text
|
||||
# Provider keys that fall back to the LLM key (and aren't in the credential
|
||||
# denylist) must also be masked — the view redacts by name, not just the set.
|
||||
assert config["embeddings_openrouter_api_key"] == "***"
|
||||
assert config["reranker_openrouter_api_key"] == "***"
|
||||
# The admin token must never leak through its own config view.
|
||||
assert config["admin_api_token"] == "***"
|
||||
assert "s3cret-token" not in response.text
|
||||
# Value-bearing fields that merely contain "token" in their name (plural) are
|
||||
# NOT redacted — they carry useful config, not secrets.
|
||||
assert config["recall_max_tokens"] != "***"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_config_requires_token_when_set(admin_client, monkeypatch):
|
||||
"""With a token configured, missing/wrong tokens are rejected; the right one passes."""
|
||||
_set_env(
|
||||
monkeypatch,
|
||||
HINDSIGHT_API_ENABLE_ADMIN_API="true",
|
||||
HINDSIGHT_API_ADMIN_TOKEN="right-token",
|
||||
)
|
||||
|
||||
missing = await admin_client.get("/admin/config")
|
||||
assert missing.status_code == 401
|
||||
|
||||
wrong = await admin_client.get("/admin/config", headers={"Authorization": "Bearer wrong-token"})
|
||||
assert wrong.status_code == 401
|
||||
|
||||
bearer = await admin_client.get("/admin/config", headers={"Authorization": "Bearer right-token"})
|
||||
assert bearer.status_code == 200
|
||||
|
||||
# A bare token (no "Bearer " prefix) is also accepted.
|
||||
bare = await admin_client.get("/admin/config", headers={"Authorization": "right-token"})
|
||||
assert bare.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_version_reports_admin_api_flag(admin_client, monkeypatch):
|
||||
"""The /version feature flags track the admin enable flag."""
|
||||
_set_env(monkeypatch, HINDSIGHT_API_ENABLE_ADMIN_API="true")
|
||||
enabled = await admin_client.get("/version")
|
||||
assert enabled.json()["features"]["admin_api"] is True
|
||||
|
||||
_set_env(monkeypatch, HINDSIGHT_API_ENABLE_ADMIN_API="false")
|
||||
disabled = await admin_client.get("/version")
|
||||
assert disabled.json()["features"]["admin_api"] is False
|
||||
@@ -39,6 +39,36 @@ paths:
|
||||
summary: Get API version and feature flags
|
||||
tags:
|
||||
- Monitoring
|
||||
/admin/config:
|
||||
get:
|
||||
description: "Returns the resolved server-level configuration with credentials\
|
||||
\ redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN."
|
||||
operationId: get_admin_config
|
||||
parameters:
|
||||
- explode: false
|
||||
in: header
|
||||
name: authorization
|
||||
required: false
|
||||
schema:
|
||||
nullable: true
|
||||
type: string
|
||||
style: simple
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AdminConfigResponse'
|
||||
description: Successful Response
|
||||
"422":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
description: Validation Error
|
||||
summary: Get resolved server-level configuration
|
||||
tags:
|
||||
- Admin
|
||||
/metrics:
|
||||
get:
|
||||
description: Exports metrics in Prometheus format for scraping
|
||||
@@ -3796,6 +3826,26 @@ components:
|
||||
required:
|
||||
- content
|
||||
title: AddBackgroundRequest
|
||||
AdminConfigResponse:
|
||||
description: |-
|
||||
Response model for the server-level (admin) configuration view.
|
||||
|
||||
Returns the resolved ``HindsightConfig`` as a flat dict keyed by Python field
|
||||
name. Credential fields (API keys, tokens, service-account keys, base URLs) are
|
||||
masked: present as ``"***"`` when set and ``None`` when unset, so an operator can
|
||||
see which credentials are configured without ever seeing their values.
|
||||
example:
|
||||
config:
|
||||
key: ""
|
||||
properties:
|
||||
config:
|
||||
additionalProperties: {}
|
||||
description: Resolved server-level configuration (Python field names); credentials
|
||||
are redacted
|
||||
title: Config
|
||||
required:
|
||||
- config
|
||||
title: AdminConfigResponse
|
||||
AsyncOperationSubmitResponse:
|
||||
description: Response model for submitting an async operation.
|
||||
example:
|
||||
@@ -5406,6 +5456,10 @@ components:
|
||||
description: Whether per-bank configuration API is enabled
|
||||
title: Bank Config Api
|
||||
type: boolean
|
||||
admin_api:
|
||||
description: Whether the admin API (/admin) is enabled
|
||||
title: Admin Api
|
||||
type: boolean
|
||||
file_upload_api:
|
||||
description: Whether file upload/conversion API is enabled
|
||||
title: File Upload Api
|
||||
@@ -5427,6 +5481,7 @@ components:
|
||||
title: Llm Trace
|
||||
type: boolean
|
||||
required:
|
||||
- admin_api
|
||||
- audit_log
|
||||
- bank_config_api
|
||||
- document_export_api
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
API version: 0.8.0
|
||||
*/
|
||||
|
||||
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
|
||||
|
||||
package hindsight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
|
||||
// AdminAPIService AdminAPI service
|
||||
type AdminAPIService service
|
||||
|
||||
type ApiGetAdminConfigRequest struct {
|
||||
ctx context.Context
|
||||
ApiService *AdminAPIService
|
||||
authorization *string
|
||||
}
|
||||
|
||||
func (r ApiGetAdminConfigRequest) Authorization(authorization string) ApiGetAdminConfigRequest {
|
||||
r.authorization = &authorization
|
||||
return r
|
||||
}
|
||||
|
||||
func (r ApiGetAdminConfigRequest) Execute() (*AdminConfigResponse, *http.Response, error) {
|
||||
return r.ApiService.GetAdminConfigExecute(r)
|
||||
}
|
||||
|
||||
/*
|
||||
GetAdminConfig Get resolved server-level configuration
|
||||
|
||||
Returns the resolved server-level configuration with credentials redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.
|
||||
|
||||
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
|
||||
@return ApiGetAdminConfigRequest
|
||||
*/
|
||||
func (a *AdminAPIService) GetAdminConfig(ctx context.Context) ApiGetAdminConfigRequest {
|
||||
return ApiGetAdminConfigRequest{
|
||||
ApiService: a,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute executes the request
|
||||
// @return AdminConfigResponse
|
||||
func (a *AdminAPIService) GetAdminConfigExecute(r ApiGetAdminConfigRequest) (*AdminConfigResponse, *http.Response, error) {
|
||||
var (
|
||||
localVarHTTPMethod = http.MethodGet
|
||||
localVarPostBody interface{}
|
||||
formFiles []formFile
|
||||
localVarReturnValue *AdminConfigResponse
|
||||
)
|
||||
|
||||
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "AdminAPIService.GetAdminConfig")
|
||||
if err != nil {
|
||||
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
|
||||
}
|
||||
|
||||
localVarPath := localBasePath + "/admin/config"
|
||||
|
||||
localVarHeaderParams := make(map[string]string)
|
||||
localVarQueryParams := url.Values{}
|
||||
localVarFormParams := url.Values{}
|
||||
|
||||
// to determine the Content-Type header
|
||||
localVarHTTPContentTypes := []string{}
|
||||
|
||||
// set Content-Type header
|
||||
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
|
||||
if localVarHTTPContentType != "" {
|
||||
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
|
||||
}
|
||||
|
||||
// to determine the Accept header
|
||||
localVarHTTPHeaderAccepts := []string{"application/json"}
|
||||
|
||||
// set Accept header
|
||||
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
|
||||
if localVarHTTPHeaderAccept != "" {
|
||||
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
|
||||
}
|
||||
if r.authorization != nil {
|
||||
parameterAddToHeaderOrQuery(localVarHeaderParams, "authorization", r.authorization, "simple", "")
|
||||
}
|
||||
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
|
||||
if err != nil {
|
||||
return localVarReturnValue, nil, err
|
||||
}
|
||||
|
||||
localVarHTTPResponse, err := a.client.callAPI(req)
|
||||
if err != nil || localVarHTTPResponse == nil {
|
||||
return localVarReturnValue, localVarHTTPResponse, err
|
||||
}
|
||||
|
||||
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
|
||||
localVarHTTPResponse.Body.Close()
|
||||
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
|
||||
if err != nil {
|
||||
return localVarReturnValue, localVarHTTPResponse, err
|
||||
}
|
||||
|
||||
if localVarHTTPResponse.StatusCode >= 300 {
|
||||
newErr := &GenericOpenAPIError{
|
||||
body: localVarBody,
|
||||
error: localVarHTTPResponse.Status,
|
||||
}
|
||||
if localVarHTTPResponse.StatusCode == 422 {
|
||||
var v HTTPValidationError
|
||||
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
newErr.error = err.Error()
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
|
||||
newErr.model = v
|
||||
}
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
|
||||
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
newErr := &GenericOpenAPIError{
|
||||
body: localVarBody,
|
||||
error: err.Error(),
|
||||
}
|
||||
return localVarReturnValue, localVarHTTPResponse, newErr
|
||||
}
|
||||
|
||||
return localVarReturnValue, localVarHTTPResponse, nil
|
||||
}
|
||||
@@ -49,6 +49,8 @@ type APIClient struct {
|
||||
|
||||
// API Services
|
||||
|
||||
AdminAPI *AdminAPIService
|
||||
|
||||
AuditAPI *AuditAPIService
|
||||
|
||||
BankTemplatesAPI *BankTemplatesAPIService
|
||||
@@ -94,6 +96,7 @@ func NewAPIClient(cfg *Configuration) *APIClient {
|
||||
c.common.client = c
|
||||
|
||||
// API Services
|
||||
c.AdminAPI = (*AdminAPIService)(&c.common)
|
||||
c.AuditAPI = (*AuditAPIService)(&c.common)
|
||||
c.BankTemplatesAPI = (*BankTemplatesAPIService)(&c.common)
|
||||
c.BanksAPI = (*BanksAPIService)(&c.common)
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
API version: 0.8.0
|
||||
*/
|
||||
|
||||
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
|
||||
|
||||
package hindsight
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// checks if the AdminConfigResponse type satisfies the MappedNullable interface at compile time
|
||||
var _ MappedNullable = &AdminConfigResponse{}
|
||||
|
||||
// AdminConfigResponse Response model for the server-level (admin) configuration view. Returns the resolved ``HindsightConfig`` as a flat dict keyed by Python field name. Credential fields (API keys, tokens, service-account keys, base URLs) are masked: present as ``\"***\"`` when set and ``None`` when unset, so an operator can see which credentials are configured without ever seeing their values.
|
||||
type AdminConfigResponse struct {
|
||||
// Resolved server-level configuration (Python field names); credentials are redacted
|
||||
Config map[string]interface{} `json:"config"`
|
||||
}
|
||||
|
||||
type _AdminConfigResponse AdminConfigResponse
|
||||
|
||||
// NewAdminConfigResponse instantiates a new AdminConfigResponse object
|
||||
// This constructor will assign default values to properties that have it defined,
|
||||
// and makes sure properties required by API are set, but the set of arguments
|
||||
// will change when the set of required properties is changed
|
||||
func NewAdminConfigResponse(config map[string]interface{}) *AdminConfigResponse {
|
||||
this := AdminConfigResponse{}
|
||||
this.Config = config
|
||||
return &this
|
||||
}
|
||||
|
||||
// NewAdminConfigResponseWithDefaults instantiates a new AdminConfigResponse object
|
||||
// This constructor will only assign default values to properties that have it defined,
|
||||
// but it doesn't guarantee that properties required by API are set
|
||||
func NewAdminConfigResponseWithDefaults() *AdminConfigResponse {
|
||||
this := AdminConfigResponse{}
|
||||
return &this
|
||||
}
|
||||
|
||||
// GetConfig returns the Config field value
|
||||
func (o *AdminConfigResponse) GetConfig() map[string]interface{} {
|
||||
if o == nil {
|
||||
var ret map[string]interface{}
|
||||
return ret
|
||||
}
|
||||
|
||||
return o.Config
|
||||
}
|
||||
|
||||
// GetConfigOk returns a tuple with the Config field value
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *AdminConfigResponse) GetConfigOk() (map[string]interface{}, bool) {
|
||||
if o == nil {
|
||||
return map[string]interface{}{}, false
|
||||
}
|
||||
return o.Config, true
|
||||
}
|
||||
|
||||
// SetConfig sets field value
|
||||
func (o *AdminConfigResponse) SetConfig(v map[string]interface{}) {
|
||||
o.Config = v
|
||||
}
|
||||
|
||||
func (o AdminConfigResponse) MarshalJSON() ([]byte, error) {
|
||||
toSerialize,err := o.ToMap()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return json.Marshal(toSerialize)
|
||||
}
|
||||
|
||||
func (o AdminConfigResponse) ToMap() (map[string]interface{}, error) {
|
||||
toSerialize := map[string]interface{}{}
|
||||
toSerialize["config"] = o.Config
|
||||
return toSerialize, nil
|
||||
}
|
||||
|
||||
func (o *AdminConfigResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
// This validates that all required properties are included in the JSON object
|
||||
// by unmarshalling the object into a generic map with string keys and checking
|
||||
// that every required field exists as a key in the generic map.
|
||||
requiredProperties := []string{
|
||||
"config",
|
||||
}
|
||||
|
||||
allProperties := make(map[string]interface{})
|
||||
|
||||
err = json.Unmarshal(data, &allProperties)
|
||||
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
|
||||
for _, requiredProperty := range(requiredProperties) {
|
||||
if _, exists := allProperties[requiredProperty]; !exists {
|
||||
return fmt.Errorf("no value given for required property %v", requiredProperty)
|
||||
}
|
||||
}
|
||||
|
||||
varAdminConfigResponse := _AdminConfigResponse{}
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
err = decoder.Decode(&varAdminConfigResponse)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = AdminConfigResponse(varAdminConfigResponse)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type NullableAdminConfigResponse struct {
|
||||
value *AdminConfigResponse
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableAdminConfigResponse) Get() *AdminConfigResponse {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableAdminConfigResponse) Set(val *AdminConfigResponse) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableAdminConfigResponse) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableAdminConfigResponse) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableAdminConfigResponse(val *AdminConfigResponse) *NullableAdminConfigResponse {
|
||||
return &NullableAdminConfigResponse{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableAdminConfigResponse) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableAdminConfigResponse) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ type FeaturesInfo struct {
|
||||
Worker bool `json:"worker"`
|
||||
// Whether per-bank configuration API is enabled
|
||||
BankConfigApi bool `json:"bank_config_api"`
|
||||
// Whether the admin API (/admin) is enabled
|
||||
AdminApi bool `json:"admin_api"`
|
||||
// Whether file upload/conversion API is enabled
|
||||
FileUploadApi bool `json:"file_upload_api"`
|
||||
// Whether the document export endpoint is enabled
|
||||
@@ -47,12 +49,13 @@ type _FeaturesInfo FeaturesInfo
|
||||
// This constructor will assign default values to properties that have it defined,
|
||||
// and makes sure properties required by API are set, but the set of arguments
|
||||
// will change when the set of required properties is changed
|
||||
func NewFeaturesInfo(observations bool, mcp bool, worker bool, bankConfigApi bool, fileUploadApi bool, documentExportApi bool, documentImportApi bool, auditLog bool, llmTrace bool) *FeaturesInfo {
|
||||
func NewFeaturesInfo(observations bool, mcp bool, worker bool, bankConfigApi bool, adminApi bool, fileUploadApi bool, documentExportApi bool, documentImportApi bool, auditLog bool, llmTrace bool) *FeaturesInfo {
|
||||
this := FeaturesInfo{}
|
||||
this.Observations = observations
|
||||
this.Mcp = mcp
|
||||
this.Worker = worker
|
||||
this.BankConfigApi = bankConfigApi
|
||||
this.AdminApi = adminApi
|
||||
this.FileUploadApi = fileUploadApi
|
||||
this.DocumentExportApi = documentExportApi
|
||||
this.DocumentImportApi = documentImportApi
|
||||
@@ -165,6 +168,30 @@ func (o *FeaturesInfo) SetBankConfigApi(v bool) {
|
||||
o.BankConfigApi = v
|
||||
}
|
||||
|
||||
// GetAdminApi returns the AdminApi field value
|
||||
func (o *FeaturesInfo) GetAdminApi() bool {
|
||||
if o == nil {
|
||||
var ret bool
|
||||
return ret
|
||||
}
|
||||
|
||||
return o.AdminApi
|
||||
}
|
||||
|
||||
// GetAdminApiOk returns a tuple with the AdminApi field value
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *FeaturesInfo) GetAdminApiOk() (*bool, bool) {
|
||||
if o == nil {
|
||||
return nil, false
|
||||
}
|
||||
return &o.AdminApi, true
|
||||
}
|
||||
|
||||
// SetAdminApi sets field value
|
||||
func (o *FeaturesInfo) SetAdminApi(v bool) {
|
||||
o.AdminApi = v
|
||||
}
|
||||
|
||||
// GetFileUploadApi returns the FileUploadApi field value
|
||||
func (o *FeaturesInfo) GetFileUploadApi() bool {
|
||||
if o == nil {
|
||||
@@ -299,6 +326,7 @@ func (o FeaturesInfo) ToMap() (map[string]interface{}, error) {
|
||||
toSerialize["mcp"] = o.Mcp
|
||||
toSerialize["worker"] = o.Worker
|
||||
toSerialize["bank_config_api"] = o.BankConfigApi
|
||||
toSerialize["admin_api"] = o.AdminApi
|
||||
toSerialize["file_upload_api"] = o.FileUploadApi
|
||||
toSerialize["document_export_api"] = o.DocumentExportApi
|
||||
toSerialize["document_import_api"] = o.DocumentImportApi
|
||||
@@ -316,6 +344,7 @@ func (o *FeaturesInfo) UnmarshalJSON(data []byte) (err error) {
|
||||
"mcp",
|
||||
"worker",
|
||||
"bank_config_api",
|
||||
"admin_api",
|
||||
"file_upload_api",
|
||||
"document_export_api",
|
||||
"document_import_api",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.openapi-generator-ignore
|
||||
hindsight_client_api/__init__.py
|
||||
hindsight_client_api/api/__init__.py
|
||||
hindsight_client_api/api/admin_api.py
|
||||
hindsight_client_api/api/audit_api.py
|
||||
hindsight_client_api/api/bank_templates_api.py
|
||||
hindsight_client_api/api/banks_api.py
|
||||
@@ -21,6 +22,7 @@ hindsight_client_api/configuration.py
|
||||
hindsight_client_api/exceptions.py
|
||||
hindsight_client_api/models/__init__.py
|
||||
hindsight_client_api/models/add_background_request.py
|
||||
hindsight_client_api/models/admin_config_response.py
|
||||
hindsight_client_api/models/async_operation_submit_response.py
|
||||
hindsight_client_api/models/audit_log_entry.py
|
||||
hindsight_client_api/models/audit_log_list_response.py
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
__version__ = "0.0.7"
|
||||
|
||||
# import apis into sdk package
|
||||
from hindsight_client_api.api.admin_api import AdminApi
|
||||
from hindsight_client_api.api.audit_api import AuditApi
|
||||
from hindsight_client_api.api.bank_templates_api import BankTemplatesApi
|
||||
from hindsight_client_api.api.banks_api import BanksApi
|
||||
@@ -45,6 +46,7 @@ from hindsight_client_api.exceptions import ApiException
|
||||
|
||||
# import models into sdk package
|
||||
from hindsight_client_api.models.add_background_request import AddBackgroundRequest
|
||||
from hindsight_client_api.models.admin_config_response import AdminConfigResponse
|
||||
from hindsight_client_api.models.async_operation_submit_response import AsyncOperationSubmitResponse
|
||||
from hindsight_client_api.models.audit_log_entry import AuditLogEntry
|
||||
from hindsight_client_api.models.audit_log_list_response import AuditLogListResponse
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# flake8: noqa
|
||||
|
||||
# import apis into api package
|
||||
from hindsight_client_api.api.admin_api import AdminApi
|
||||
from hindsight_client_api.api.audit_api import AuditApi
|
||||
from hindsight_client_api.api.bank_templates_api import BankTemplatesApi
|
||||
from hindsight_client_api.api.banks_api import BanksApi
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.8.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
import warnings
|
||||
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import StrictStr
|
||||
from typing import Optional
|
||||
from hindsight_client_api.models.admin_config_response import AdminConfigResponse
|
||||
|
||||
from hindsight_client_api.api_client import ApiClient, RequestSerialized
|
||||
from hindsight_client_api.api_response import ApiResponse
|
||||
from hindsight_client_api.rest import RESTResponseType
|
||||
|
||||
|
||||
class AdminApi:
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, api_client=None) -> None:
|
||||
if api_client is None:
|
||||
api_client = ApiClient.get_default()
|
||||
self.api_client = api_client
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_admin_config(
|
||||
self,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> AdminConfigResponse:
|
||||
"""Get resolved server-level configuration
|
||||
|
||||
Returns the resolved server-level configuration with credentials redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.
|
||||
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_admin_config_serialize(
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "AdminConfigResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_admin_config_with_http_info(
|
||||
self,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[AdminConfigResponse]:
|
||||
"""Get resolved server-level configuration
|
||||
|
||||
Returns the resolved server-level configuration with credentials redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.
|
||||
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_admin_config_serialize(
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "AdminConfigResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_admin_config_without_preload_content(
|
||||
self,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Get resolved server-level configuration
|
||||
|
||||
Returns the resolved server-level configuration with credentials redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.
|
||||
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_admin_config_serialize(
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "AdminConfigResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _get_admin_config_serialize(
|
||||
self,
|
||||
authorization,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
if authorization is not None:
|
||||
_header_params['authorization'] = authorization
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='GET',
|
||||
resource_path='/admin/config',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
# import models into model package
|
||||
from hindsight_client_api.models.add_background_request import AddBackgroundRequest
|
||||
from hindsight_client_api.models.admin_config_response import AdminConfigResponse
|
||||
from hindsight_client_api.models.async_operation_submit_response import AsyncOperationSubmitResponse
|
||||
from hindsight_client_api.models.audit_log_entry import AuditLogEntry
|
||||
from hindsight_client_api.models.audit_log_list_response import AuditLogListResponse
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.8.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import pprint
|
||||
import re # noqa: F401
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from typing import Any, ClassVar, Dict, List
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
|
||||
class AdminConfigResponse(BaseModel):
|
||||
"""
|
||||
Response model for the server-level (admin) configuration view. Returns the resolved ``HindsightConfig`` as a flat dict keyed by Python field name. Credential fields (API keys, tokens, service-account keys, base URLs) are masked: present as ``\"***\"`` when set and ``None`` when unset, so an operator can see which credentials are configured without ever seeing their values.
|
||||
""" # noqa: E501
|
||||
config: Dict[str, Any] = Field(description="Resolved server-level configuration (Python field names); credentials are redacted")
|
||||
__properties: ClassVar[List[str]] = ["config"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
validate_assignment=True,
|
||||
protected_namespaces=(),
|
||||
)
|
||||
|
||||
|
||||
def to_str(self) -> str:
|
||||
"""Returns the string representation of the model using alias"""
|
||||
return pprint.pformat(self.model_dump(by_alias=True))
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Returns the JSON representation of the model using alias"""
|
||||
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> Optional[Self]:
|
||||
"""Create an instance of AdminConfigResponse from a JSON string"""
|
||||
return cls.from_dict(json.loads(json_str))
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Return the dictionary representation of the model using alias.
|
||||
|
||||
This has the following differences from calling pydantic's
|
||||
`self.model_dump(by_alias=True)`:
|
||||
|
||||
* `None` is only added to the output dict for nullable fields that
|
||||
were set at model initialization. Other fields with value `None`
|
||||
are ignored.
|
||||
"""
|
||||
excluded_fields: Set[str] = set([
|
||||
])
|
||||
|
||||
_dict = self.model_dump(
|
||||
by_alias=True,
|
||||
exclude=excluded_fields,
|
||||
exclude_none=True,
|
||||
)
|
||||
return _dict
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
||||
"""Create an instance of AdminConfigResponse from a dict"""
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return cls.model_validate(obj)
|
||||
|
||||
_obj = cls.model_validate({
|
||||
"config": obj.get("config")
|
||||
})
|
||||
return _obj
|
||||
|
||||
|
||||
@@ -30,12 +30,13 @@ class FeaturesInfo(BaseModel):
|
||||
mcp: StrictBool = Field(description="Whether MCP (Model Context Protocol) server is enabled")
|
||||
worker: StrictBool = Field(description="Whether the background worker is enabled")
|
||||
bank_config_api: StrictBool = Field(description="Whether per-bank configuration API is enabled")
|
||||
admin_api: StrictBool = Field(description="Whether the admin API (/admin) is enabled")
|
||||
file_upload_api: StrictBool = Field(description="Whether file upload/conversion API is enabled")
|
||||
document_export_api: StrictBool = Field(description="Whether the document export endpoint is enabled")
|
||||
document_import_api: StrictBool = Field(description="Whether the document import endpoint is enabled")
|
||||
audit_log: StrictBool = Field(description="Whether audit logging is enabled")
|
||||
llm_trace: StrictBool = Field(description="Whether per-bank LLM request tracing is enabled")
|
||||
__properties: ClassVar[List[str]] = ["observations", "mcp", "worker", "bank_config_api", "file_upload_api", "document_export_api", "document_import_api", "audit_log", "llm_trace"]
|
||||
__properties: ClassVar[List[str]] = ["observations", "mcp", "worker", "bank_config_api", "admin_api", "file_upload_api", "document_export_api", "document_import_api", "audit_log", "llm_trace"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
@@ -92,6 +93,7 @@ class FeaturesInfo(BaseModel):
|
||||
"mcp": obj.get("mcp"),
|
||||
"worker": obj.get("worker"),
|
||||
"bank_config_api": obj.get("bank_config_api"),
|
||||
"admin_api": obj.get("admin_api"),
|
||||
"file_upload_api": obj.get("file_upload_api"),
|
||||
"document_export_api": obj.get("document_export_api"),
|
||||
"document_import_api": obj.get("document_import_api"),
|
||||
|
||||
@@ -65,6 +65,9 @@ import type {
|
||||
FileRetainData,
|
||||
FileRetainErrors,
|
||||
FileRetainResponses,
|
||||
GetAdminConfigData,
|
||||
GetAdminConfigErrors,
|
||||
GetAdminConfigResponses,
|
||||
GetAgentStatsData,
|
||||
GetAgentStatsErrors,
|
||||
GetAgentStatsResponses,
|
||||
@@ -262,6 +265,19 @@ export const getVersion = <ThrowOnError extends boolean = false>(
|
||||
...options,
|
||||
});
|
||||
|
||||
/**
|
||||
* Get resolved server-level configuration
|
||||
*
|
||||
* Returns the resolved server-level configuration with credentials redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.
|
||||
*/
|
||||
export const getAdminConfig = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<GetAdminConfigData, ThrowOnError>
|
||||
) =>
|
||||
(options?.client ?? client).get<GetAdminConfigResponses, GetAdminConfigErrors, ThrowOnError>({
|
||||
url: "/admin/config",
|
||||
...options,
|
||||
});
|
||||
|
||||
/**
|
||||
* Prometheus metrics endpoint
|
||||
*
|
||||
|
||||
@@ -24,6 +24,27 @@ export type AddBackgroundRequest = {
|
||||
update_disposition?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* AdminConfigResponse
|
||||
*
|
||||
* Response model for the server-level (admin) configuration view.
|
||||
*
|
||||
* Returns the resolved ``HindsightConfig`` as a flat dict keyed by Python field
|
||||
* name. Credential fields (API keys, tokens, service-account keys, base URLs) are
|
||||
* masked: present as ``"***"`` when set and ``None`` when unset, so an operator can
|
||||
* see which credentials are configured without ever seeing their values.
|
||||
*/
|
||||
export type AdminConfigResponse = {
|
||||
/**
|
||||
* Config
|
||||
*
|
||||
* Resolved server-level configuration (Python field names); credentials are redacted
|
||||
*/
|
||||
config: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* AsyncOperationSubmitResponse
|
||||
*
|
||||
@@ -1658,6 +1679,12 @@ export type FeaturesInfo = {
|
||||
* Whether per-bank configuration API is enabled
|
||||
*/
|
||||
bank_config_api: boolean;
|
||||
/**
|
||||
* Admin Api
|
||||
*
|
||||
* Whether the admin API (/admin) is enabled
|
||||
*/
|
||||
admin_api: boolean;
|
||||
/**
|
||||
* File Upload Api
|
||||
*
|
||||
@@ -3783,6 +3810,37 @@ export type GetVersionResponses = {
|
||||
|
||||
export type GetVersionResponse = GetVersionResponses[keyof GetVersionResponses];
|
||||
|
||||
export type GetAdminConfigData = {
|
||||
body?: never;
|
||||
headers?: {
|
||||
/**
|
||||
* Authorization
|
||||
*/
|
||||
authorization?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: "/admin/config";
|
||||
};
|
||||
|
||||
export type GetAdminConfigErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type GetAdminConfigError = GetAdminConfigErrors[keyof GetAdminConfigErrors];
|
||||
|
||||
export type GetAdminConfigResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: AdminConfigResponse;
|
||||
};
|
||||
|
||||
export type GetAdminConfigResponse = GetAdminConfigResponses[keyof GetAdminConfigResponses];
|
||||
|
||||
export type MetricsEndpointMetricsGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Settings } from "lucide-react";
|
||||
import { AdminHeader } from "@/components/admin-header";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { FeatureNotEnabled } from "@/components/feature-not-enabled";
|
||||
import { AdminConfigView } from "@/components/admin-config-view";
|
||||
import { useFeatures } from "@/lib/features-context";
|
||||
|
||||
export default function AdminPage() {
|
||||
const t = useTranslations("admin");
|
||||
const { features, loading } = useFeatures();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex flex-col">
|
||||
<AdminHeader />
|
||||
|
||||
<div className="flex flex-1 min-h-0">
|
||||
<Sidebar
|
||||
items={[
|
||||
{ id: "configuration", label: t("configHeading"), icon: Settings, href: "/admin" },
|
||||
]}
|
||||
currentTab="configuration"
|
||||
/>
|
||||
|
||||
<main className="flex-1 px-6 py-6 overflow-y-auto">
|
||||
<div className="max-w-5xl w-full mx-auto">
|
||||
{loading ? null : features?.admin_api ? (
|
||||
<AdminConfigView />
|
||||
) : (
|
||||
<FeatureNotEnabled
|
||||
title={t("notEnabledTitle")}
|
||||
description={t.rich("notEnabledDescription", {
|
||||
envVar: () => (
|
||||
<code className="px-1 py-0.5 bg-muted rounded text-xs">
|
||||
HINDSIGHT_API_ENABLE_ADMIN_API=true
|
||||
</code>
|
||||
),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
import { BankSelector } from "@/components/bank-selector";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import type { SidebarItem } from "@/components/sidebar";
|
||||
import { DataView } from "@/components/data-view";
|
||||
import { DocumentsView } from "@/components/documents-view";
|
||||
import { EntitiesView } from "@/components/entities-view";
|
||||
@@ -42,7 +43,21 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Brain, Download, Trash2, Loader2, MoreVertical, Pencil, RotateCcw } from "lucide-react";
|
||||
import {
|
||||
Brain,
|
||||
Download,
|
||||
Trash2,
|
||||
Loader2,
|
||||
MoreVertical,
|
||||
Pencil,
|
||||
RotateCcw,
|
||||
Database,
|
||||
Search,
|
||||
Sparkles,
|
||||
FileText,
|
||||
Users,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
|
||||
type NavItem = "recall" | "reflect" | "data" | "documents" | "entities" | "profile";
|
||||
type DataSubTab = "world" | "experience" | "observations" | "mental-models";
|
||||
@@ -53,6 +68,7 @@ export default function BankPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const t = useTranslations("bank");
|
||||
const tSidebar = useTranslations("bank.sidebar");
|
||||
const tCommon = useTranslations("common");
|
||||
const { features } = useFeatures();
|
||||
const { currentBank: bankId, setCurrentBank, loadBanks } = useBank();
|
||||
@@ -80,6 +96,47 @@ export default function BankPage() {
|
||||
router.push(bankRoute(bankId, `?view=${tab}`));
|
||||
};
|
||||
|
||||
const sidebarItems: SidebarItem[] = bankId
|
||||
? [
|
||||
{
|
||||
id: "data",
|
||||
label: tSidebar("memories"),
|
||||
icon: Database,
|
||||
href: bankRoute(bankId, "?view=data"),
|
||||
},
|
||||
{
|
||||
id: "recall",
|
||||
label: tSidebar("recall"),
|
||||
icon: Search,
|
||||
href: bankRoute(bankId, "?view=recall"),
|
||||
},
|
||||
{
|
||||
id: "reflect",
|
||||
label: tSidebar("reflect"),
|
||||
icon: Sparkles,
|
||||
href: bankRoute(bankId, "?view=reflect"),
|
||||
},
|
||||
{
|
||||
id: "documents",
|
||||
label: tSidebar("documents"),
|
||||
icon: FileText,
|
||||
href: bankRoute(bankId, "?view=documents"),
|
||||
},
|
||||
{
|
||||
id: "entities",
|
||||
label: tSidebar("entities"),
|
||||
icon: Users,
|
||||
href: bankRoute(bankId, "?view=entities"),
|
||||
},
|
||||
{
|
||||
id: "profile",
|
||||
label: t("bankConfiguration"),
|
||||
icon: Settings,
|
||||
href: bankRoute(bankId, "?view=profile"),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const handleDataSubTabChange = (newSubTab: DataSubTab) => {
|
||||
if (!bankId) return;
|
||||
router.push(bankRoute(bankId, `?view=data&subTab=${newSubTab}`));
|
||||
@@ -169,7 +226,13 @@ export default function BankPage() {
|
||||
<BankSelector />
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<Sidebar currentTab={view} onTabChange={handleTabChange} />
|
||||
{bankId && (
|
||||
<Sidebar
|
||||
items={sidebarItems}
|
||||
currentTab={view}
|
||||
onTabChange={(id) => handleTabChange(id as NavItem)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="p-6">
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { localizeApiErrorPayload } from "@/lib/i18n/api-errors";
|
||||
import { DATAPLANE_URL, getAdminHeaders } from "@/lib/hindsight-client";
|
||||
|
||||
/**
|
||||
* Proxy for the dataplane admin config endpoint (`GET /admin/config`).
|
||||
*
|
||||
* Forwards the independent admin token (HINDSIGHT_CP_ADMIN_TOKEN) rather than the
|
||||
* tenant API key. The dataplane returns 404 when the admin API is disabled and 401
|
||||
* when a token is required but missing/wrong — both are surfaced to the caller.
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const response = await fetch(`${DATAPLANE_URL}/admin/config`, {
|
||||
headers: getAdminHeaders({ Accept: "application/json" }),
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
localizeApiErrorPayload(request, {
|
||||
error: "Failed to get admin config",
|
||||
errorKey: "api.errors.admin.config.fetch",
|
||||
}),
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Error getting admin config:", error);
|
||||
return NextResponse.json(
|
||||
localizeApiErrorPayload(request, {
|
||||
error: "Failed to get admin config",
|
||||
errorKey: "api.errors.admin.config.fetch",
|
||||
}),
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Loader2, Search } from "lucide-react";
|
||||
import { client } from "@/lib/api";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
/** Render a config value compactly: primitives inline, objects/arrays as JSON. */
|
||||
function formatValue(value: unknown): string {
|
||||
if (value === null || value === undefined) return "—";
|
||||
if (typeof value === "string") return value === "" ? '""' : value;
|
||||
if (typeof value === "boolean" || typeof value === "number") return String(value);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
/** Group key, derived from the first underscore-delimited token (e.g. "retain_chunk_size" -> "retain"). */
|
||||
function groupOf(key: string): string {
|
||||
const head = key.split("_")[0];
|
||||
return head || "general";
|
||||
}
|
||||
|
||||
export function AdminConfigView() {
|
||||
const t = useTranslations("admin");
|
||||
const [config, setConfig] = useState<Record<string, unknown> | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
client
|
||||
.getAdminConfig()
|
||||
.then((resp) => {
|
||||
if (!cancelled) {
|
||||
setConfig(resp.config);
|
||||
setError(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setError(true);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const groups = useMemo(() => {
|
||||
if (!config) return [];
|
||||
const needle = filter.trim().toLowerCase();
|
||||
const byGroup = new Map<string, [string, unknown][]>();
|
||||
for (const [key, value] of Object.entries(config).sort(([a], [b]) => a.localeCompare(b))) {
|
||||
if (
|
||||
needle &&
|
||||
!key.toLowerCase().includes(needle) &&
|
||||
!formatValue(value).toLowerCase().includes(needle)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const group = groupOf(key);
|
||||
const rows = byGroup.get(group) ?? [];
|
||||
rows.push([key, value]);
|
||||
byGroup.set(group, rows);
|
||||
}
|
||||
return [...byGroup.entries()].sort(([a], [b]) => a.localeCompare(b));
|
||||
}, [config, filter]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-2 py-16 text-muted-foreground">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
<span>{t("loading")}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <p className="py-16 text-center text-sm text-destructive">{t("loadError")}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">{t("configHeading")}</h2>
|
||||
<p className="text-sm text-muted-foreground">{t("configDescription")}</p>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-sm">
|
||||
<Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
placeholder={t("searchPlaceholder")}
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t("redactedHint")}</p>
|
||||
|
||||
{groups.length === 0 ? (
|
||||
<p className="py-12 text-center text-sm text-muted-foreground">{t("empty")}</p>
|
||||
) : (
|
||||
<div className="grid gap-4">
|
||||
{groups.map(([group, rows]) => (
|
||||
<Card key={group}>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-semibold capitalize text-foreground">
|
||||
{group}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<dl className="divide-y divide-border">
|
||||
{rows.map(([key, value]) => (
|
||||
<div key={key} className="grid grid-cols-1 gap-1 py-2 sm:grid-cols-3 sm:gap-4">
|
||||
<dt className="font-mono text-xs text-muted-foreground break-all">{key}</dt>
|
||||
<dd className="font-mono text-xs text-foreground break-all sm:col-span-2">
|
||||
{formatValue(value)}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, Moon, ShieldCheck, Sun, LogOut } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import { useTheme } from "@/lib/theme-context";
|
||||
import { useFeatures } from "@/lib/features-context";
|
||||
import { withBasePath } from "@/lib/base-path";
|
||||
|
||||
/**
|
||||
* Header for the global admin surface.
|
||||
*
|
||||
* Deliberately NOT the bank-scoped BankSelector: the admin area is not tied to a
|
||||
* memory bank, so it shows a back-to-app link, the admin title, and global controls
|
||||
* (theme, language, logout) instead of the bank dropdown / add-document actions.
|
||||
*/
|
||||
export function AdminHeader() {
|
||||
const tNav = useTranslations("nav");
|
||||
const t = useTranslations("admin");
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { features } = useFeatures();
|
||||
|
||||
return (
|
||||
<header className="bg-card text-card-foreground px-5 py-3 border-b-4 border-primary-gradient">
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
{/* Logo → back to app */}
|
||||
<Link href="/dashboard" title={tNav("home")}>
|
||||
<Image
|
||||
src={withBasePath("/logo.png")}
|
||||
alt="Hindsight"
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-10 w-auto"
|
||||
unoptimized
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<div className="h-8 w-px bg-border" />
|
||||
|
||||
{/* Admin title */}
|
||||
<div className="flex items-center gap-2 font-bold">
|
||||
<ShieldCheck className="h-5 w-5 text-primary" />
|
||||
<span>{t("title")}</span>
|
||||
</div>
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Back to app */}
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-accent transition-colors text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">{t("backToApp")}</span>
|
||||
</Link>
|
||||
|
||||
<div className="h-8 w-px bg-border" />
|
||||
|
||||
{/* Dark Mode Toggle */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
className="h-9 w-9"
|
||||
title={theme === "light" ? tNav("darkMode") : tNav("lightMode")}
|
||||
>
|
||||
{theme === "light" ? <Moon className="h-5 w-5" /> : <Sun className="h-5 w-5" />}
|
||||
</Button>
|
||||
|
||||
<LanguageSwitcher />
|
||||
|
||||
{features?.access_key_auth && (
|
||||
<>
|
||||
<div className="h-8 w-px bg-border" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-9 w-9"
|
||||
title="Logout"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await fetch(withBasePath("/api/auth/logout"), { method: "POST" });
|
||||
} finally {
|
||||
window.location.href = withBasePath("/login");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LogOut className="h-5 w-5" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -2,47 +2,42 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useBank } from "@/lib/bank-context";
|
||||
import { bankRoute } from "@/lib/bank-url";
|
||||
import {
|
||||
Search,
|
||||
Sparkles,
|
||||
Database,
|
||||
FileText,
|
||||
Users,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Link from "next/link";
|
||||
|
||||
type NavItem = "recall" | "reflect" | "data" | "documents" | "entities" | "profile";
|
||||
|
||||
interface SidebarProps {
|
||||
currentTab: NavItem;
|
||||
onTabChange: (tab: NavItem) => void;
|
||||
export interface SidebarItem {
|
||||
/** Stable id compared against `currentTab` to mark the active entry. */
|
||||
id: string;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
/** Destination href (Next.js applies basePath automatically). */
|
||||
href: string;
|
||||
}
|
||||
|
||||
export function Sidebar({ currentTab, onTabChange }: SidebarProps) {
|
||||
interface SidebarProps {
|
||||
/** Navigation entries to render, top to bottom. */
|
||||
items: SidebarItem[];
|
||||
/** Id of the active entry. */
|
||||
currentTab: string;
|
||||
/**
|
||||
* Optional in-app navigation handler. When provided, a plain left-click is
|
||||
* intercepted and routed via the callback (middle-click / Cmd+click still open
|
||||
* the href in a new tab). When omitted, the entry behaves as a normal link.
|
||||
*/
|
||||
onTabChange?: (id: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapsible left navigation, shared across the app (bank workspace, admin, …).
|
||||
* It is purely presentational: callers provide the items, the active id, and an
|
||||
* optional click handler.
|
||||
*/
|
||||
export function Sidebar({ items, currentTab, onTabChange }: SidebarProps) {
|
||||
const t = useTranslations("bank.sidebar");
|
||||
const tBank = useTranslations("bank");
|
||||
const { currentBank } = useBank();
|
||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||
|
||||
if (!currentBank) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const navItems = [
|
||||
{ id: "data" as NavItem, label: t("memories"), icon: Database },
|
||||
{ id: "recall" as NavItem, label: t("recall"), icon: Search },
|
||||
{ id: "reflect" as NavItem, label: t("reflect"), icon: Sparkles },
|
||||
{ id: "documents" as NavItem, label: t("documents"), icon: FileText },
|
||||
{ id: "entities" as NavItem, label: t("entities"), icon: Users },
|
||||
{ id: "profile" as NavItem, label: tBank("bankConfiguration"), icon: Settings },
|
||||
];
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
@@ -52,23 +47,22 @@ export function Sidebar({ currentTab, onTabChange }: SidebarProps) {
|
||||
>
|
||||
<nav className="flex-1 p-3 pt-4">
|
||||
<ul className="space-y-1">
|
||||
{navItems.map((item) => {
|
||||
{items.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = currentTab === item.id;
|
||||
const href = bankRoute(currentBank, `?view=${item.id}`);
|
||||
|
||||
return (
|
||||
<li key={item.id}>
|
||||
<Link
|
||||
href={href}
|
||||
href={item.href}
|
||||
onClick={(e) => {
|
||||
// For left-click, prevent default and use the callback
|
||||
// This allows the parent to handle navigation without full page reload
|
||||
if (e.button === 0 && !e.ctrlKey && !e.metaKey) {
|
||||
// For left-click, prevent default and use the callback so the
|
||||
// parent can navigate without a full page reload. Middle-click
|
||||
// or Ctrl/Cmd+click open in a new tab naturally.
|
||||
if (onTabChange && e.button === 0 && !e.ctrlKey && !e.metaKey) {
|
||||
e.preventDefault();
|
||||
onTabChange(item.id);
|
||||
}
|
||||
// Middle-click or Ctrl/Cmd+click will naturally open in new tab
|
||||
}}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-all",
|
||||
|
||||
@@ -1302,6 +1302,7 @@ export class ControlPlaneClient {
|
||||
mcp: boolean;
|
||||
worker: boolean;
|
||||
bank_config_api: boolean;
|
||||
admin_api: boolean;
|
||||
file_upload_api: boolean;
|
||||
document_export_api: boolean;
|
||||
document_import_api: boolean;
|
||||
@@ -1311,6 +1312,15 @@ export class ControlPlaneClient {
|
||||
}>("/api/version");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the resolved server-level configuration for the admin surface.
|
||||
* Credentials are redacted by the dataplane. Requires the admin API to be enabled
|
||||
* (and, if configured, the admin token to be present on the server-side proxy).
|
||||
*/
|
||||
async getAdminConfig() {
|
||||
return this.fetchApi<{ config: Record<string, unknown> }>("/api/admin/config");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export documents from a bank as a transfer ZIP archive (no LLM re-extraction).
|
||||
* Pass documentIds to export specific documents, or omit to export the whole bank.
|
||||
|
||||
@@ -8,6 +8,7 @@ interface Features {
|
||||
mcp: boolean;
|
||||
worker: boolean;
|
||||
bank_config_api: boolean;
|
||||
admin_api: boolean;
|
||||
access_key_auth: boolean;
|
||||
document_export_api: boolean;
|
||||
document_import_api: boolean;
|
||||
@@ -26,6 +27,7 @@ const defaultFeatures: Features = {
|
||||
mcp: false,
|
||||
worker: false,
|
||||
bank_config_api: false,
|
||||
admin_api: false,
|
||||
access_key_auth: false,
|
||||
document_export_api: false,
|
||||
document_import_api: false,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
|
||||
export const DATAPLANE_URL = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://localhost:8888";
|
||||
const DATAPLANE_API_KEY = process.env.HINDSIGHT_CP_DATAPLANE_API_KEY || "";
|
||||
const ADMIN_TOKEN = process.env.HINDSIGHT_CP_ADMIN_TOKEN || "";
|
||||
|
||||
/**
|
||||
* Auth headers for direct fetch calls to the dataplane API.
|
||||
@@ -25,6 +26,21 @@ export function getDataplaneHeaders(extra?: Record<string, string>): Record<stri
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth headers for the dataplane admin endpoints (`/admin/*`).
|
||||
*
|
||||
* The admin surface uses its own independent token (HINDSIGHT_CP_ADMIN_TOKEN ->
|
||||
* HINDSIGHT_API_ADMIN_TOKEN), distinct from the tenant API key. When unset, no auth
|
||||
* header is sent and the dataplane decides whether the admin API is open.
|
||||
*/
|
||||
export function getAdminHeaders(extra?: Record<string, string>): Record<string, string> {
|
||||
const headers: Record<string, string> = { ...extra };
|
||||
if (ADMIN_TOKEN) {
|
||||
headers["Authorization"] = `Bearer ${ADMIN_TOKEN}`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a dataplane URL for a bank-scoped endpoint with the bank id properly encoded.
|
||||
* Bank ids may contain `:`, `/`, `%`, etc. (e.g. openclaw `agent::channel::user`),
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "Wählen Sie eine Speicherbank aus dem Dropdown oben, um loszulegen.",
|
||||
"sidebarHint": "Die Seitenleiste erscheint, sobald Sie eine Speicherbank ausgewählt haben."
|
||||
},
|
||||
"admin": {
|
||||
"title": "Administration",
|
||||
"backToApp": "Zurück zur App",
|
||||
"configHeading": "Konfiguration",
|
||||
"configDescription": "Aufgelöste Konfiguration auf Serverebene für diese Bereitstellung. Anmeldedaten sind ausgeblendet.",
|
||||
"searchPlaceholder": "Einstellungen filtern...",
|
||||
"loading": "Konfiguration wird geladen...",
|
||||
"loadError": "Konfiguration konnte nicht geladen werden.",
|
||||
"empty": "Keine Einstellungen entsprechen dem Filter.",
|
||||
"redactedHint": "Anmeldedaten-Werte sind maskiert; „***“ bedeutet, dass ein Wert gesetzt ist.",
|
||||
"notEnabledTitle": "Administrations-API nicht aktiviert",
|
||||
"notEnabledDescription": "Aktivieren Sie den Administrationsbereich, indem Sie {envVar} auf dem API-Server setzen."
|
||||
},
|
||||
"login": {
|
||||
"title": "Hindsight Control Plane",
|
||||
"description": "Geben Sie Ihren Zugangsschlüssel ein, um fortzufahren",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "Version konnte nicht abgerufen werden"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "Administrationskonfiguration konnte nicht abgerufen werden"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "Webhooks konnten nicht aufgelistet werden",
|
||||
"create": "Webhook konnte nicht erstellt werden",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "Select a memory bank from the dropdown above to get started.",
|
||||
"sidebarHint": "The sidebar will appear once you select a memory bank."
|
||||
},
|
||||
"admin": {
|
||||
"title": "Admin",
|
||||
"backToApp": "Back to app",
|
||||
"configHeading": "Configuration",
|
||||
"configDescription": "Resolved server-level configuration for this deployment. Credentials are redacted.",
|
||||
"searchPlaceholder": "Filter settings...",
|
||||
"loading": "Loading configuration...",
|
||||
"loadError": "Failed to load configuration.",
|
||||
"empty": "No settings match your filter.",
|
||||
"redactedHint": "Credential values are masked; \"***\" means a value is set.",
|
||||
"notEnabledTitle": "Admin API Not Enabled",
|
||||
"notEnabledDescription": "Enable the admin surface by setting {envVar} on the API server."
|
||||
},
|
||||
"login": {
|
||||
"title": "Hindsight Control Plane",
|
||||
"description": "Enter your access key to continue",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "Failed to get version"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "Failed to get admin configuration"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "Failed to list webhooks",
|
||||
"create": "Failed to create webhook",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "Selecciona un banco de memoria del menú superior para comenzar.",
|
||||
"sidebarHint": "La barra lateral aparecerá una vez que selecciones un banco de memoria."
|
||||
},
|
||||
"admin": {
|
||||
"title": "Administración",
|
||||
"backToApp": "Volver a la app",
|
||||
"configHeading": "Configuración",
|
||||
"configDescription": "Configuración a nivel de servidor resuelta para esta implementación. Las credenciales están ocultas.",
|
||||
"searchPlaceholder": "Filtrar ajustes...",
|
||||
"loading": "Cargando configuración...",
|
||||
"loadError": "No se pudo cargar la configuración.",
|
||||
"empty": "Ningún ajuste coincide con el filtro.",
|
||||
"redactedHint": "Los valores de las credenciales están ocultos; \"***\" indica que hay un valor configurado.",
|
||||
"notEnabledTitle": "API de administración no habilitada",
|
||||
"notEnabledDescription": "Habilita la sección de administración configurando {envVar} en el servidor de la API."
|
||||
},
|
||||
"login": {
|
||||
"title": "Plano de Control de Hindsight",
|
||||
"description": "Introduce tu clave de acceso para continuar",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "No se pudo obtener la versión"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "No se pudo obtener la configuración de administración"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "No se pudieron listar los webhooks",
|
||||
"create": "No se pudo crear el webhook",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "Sélectionnez une banque de mémoire dans le menu ci-dessus pour commencer.",
|
||||
"sidebarHint": "La barre latérale apparaîtra une fois que vous aurez sélectionné une banque de mémoire."
|
||||
},
|
||||
"admin": {
|
||||
"title": "Administration",
|
||||
"backToApp": "Retour à l'application",
|
||||
"configHeading": "Configuration",
|
||||
"configDescription": "Configuration au niveau du serveur résolue pour ce déploiement. Les identifiants sont masqués.",
|
||||
"searchPlaceholder": "Filtrer les paramètres...",
|
||||
"loading": "Chargement de la configuration...",
|
||||
"loadError": "Échec du chargement de la configuration.",
|
||||
"empty": "Aucun paramètre ne correspond au filtre.",
|
||||
"redactedHint": "Les valeurs des identifiants sont masquées ; « *** » signifie qu'une valeur est définie.",
|
||||
"notEnabledTitle": "API d'administration non activée",
|
||||
"notEnabledDescription": "Activez la section d'administration en définissant {envVar} sur le serveur API."
|
||||
},
|
||||
"login": {
|
||||
"title": "Plan de contrôle Hindsight",
|
||||
"description": "Entrez votre clé d'accès pour continuer",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "Impossible de récupérer la version"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "Échec de la récupération de la configuration d'administration"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "Impossible de lister les webhooks",
|
||||
"create": "Impossible de créer le webhook",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "上のドロップダウンからメモリバンクを選択して始めましょう。",
|
||||
"sidebarHint": "メモリバンクを選択するとサイドバーが表示されます。"
|
||||
},
|
||||
"admin": {
|
||||
"title": "管理",
|
||||
"backToApp": "アプリに戻る",
|
||||
"configHeading": "構成",
|
||||
"configDescription": "このデプロイメントで解決されたサーバーレベルの構成です。認証情報は秘匿されています。",
|
||||
"searchPlaceholder": "設定をフィルター...",
|
||||
"loading": "構成を読み込み中...",
|
||||
"loadError": "構成の読み込みに失敗しました。",
|
||||
"empty": "フィルターに一致する設定はありません。",
|
||||
"redactedHint": "認証情報の値はマスクされています。「***」は値が設定されていることを示します。",
|
||||
"notEnabledTitle": "管理 API が有効になっていません",
|
||||
"notEnabledDescription": "API サーバーで {envVar} を設定して管理セクションを有効にしてください。"
|
||||
},
|
||||
"login": {
|
||||
"title": "Hindsight コントロールプレーン",
|
||||
"description": "続行するにはアクセスキーを入力してください",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "バージョンの取得に失敗しました"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "管理構成の取得に失敗しました"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "Webhookの一覧取得に失敗しました",
|
||||
"create": "Webhookの作成に失敗しました",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "위의 드롭다운에서 메모리 뱅크를 선택하여 시작하세요.",
|
||||
"sidebarHint": "메모리 뱅크를 선택하면 사이드바가 나타납니다."
|
||||
},
|
||||
"admin": {
|
||||
"title": "관리",
|
||||
"backToApp": "앱으로 돌아가기",
|
||||
"configHeading": "구성",
|
||||
"configDescription": "이 배포에 대해 확인된 서버 수준 구성입니다. 자격 증명은 가려져 있습니다.",
|
||||
"searchPlaceholder": "설정 필터링...",
|
||||
"loading": "구성을 불러오는 중...",
|
||||
"loadError": "구성을 불러오지 못했습니다.",
|
||||
"empty": "필터와 일치하는 설정이 없습니다.",
|
||||
"redactedHint": "자격 증명 값은 가려져 있습니다. \"***\"는 값이 설정되어 있음을 의미합니다.",
|
||||
"notEnabledTitle": "관리 API가 활성화되지 않음",
|
||||
"notEnabledDescription": "API 서버에서 {envVar}을(를) 설정하여 관리 섹션을 활성화하세요."
|
||||
},
|
||||
"login": {
|
||||
"title": "Hindsight 제어 플레인",
|
||||
"description": "계속하려면 액세스 키를 입력하세요",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "버전을 가져오지 못했습니다"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "관리 구성을 가져오지 못했습니다"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "Webhook 목록을 가져오지 못했습니다",
|
||||
"create": "Webhook을 생성하지 못했습니다",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "Selecione um banco de memória no menu acima para começar.",
|
||||
"sidebarHint": "A barra lateral aparecerá quando você selecionar um banco de memória."
|
||||
},
|
||||
"admin": {
|
||||
"title": "Administração",
|
||||
"backToApp": "Voltar ao app",
|
||||
"configHeading": "Configuração",
|
||||
"configDescription": "Configuração no nível do servidor resolvida para esta implantação. As credenciais estão ocultas.",
|
||||
"searchPlaceholder": "Filtrar configurações...",
|
||||
"loading": "Carregando configuração...",
|
||||
"loadError": "Falha ao carregar a configuração.",
|
||||
"empty": "Nenhuma configuração corresponde ao filtro.",
|
||||
"redactedHint": "Os valores das credenciais estão mascarados; \"***\" significa que há um valor definido.",
|
||||
"notEnabledTitle": "API de administração não habilitada",
|
||||
"notEnabledDescription": "Habilite a seção de administração definindo {envVar} no servidor da API."
|
||||
},
|
||||
"login": {
|
||||
"title": "Plano de Controle Hindsight",
|
||||
"description": "Insira sua chave de acesso para continuar",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "Falha ao buscar versão"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "Falha ao obter a configuração de administração"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "Falha ao listar webhooks",
|
||||
"create": "Falha ao criar webhook",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "請先在上方下拉選單選擇一個記憶庫。",
|
||||
"sidebarHint": "選擇記憶庫後,側邊欄會出現。"
|
||||
},
|
||||
"admin": {
|
||||
"title": "管理",
|
||||
"backToApp": "返回應用程式",
|
||||
"configHeading": "設定",
|
||||
"configDescription": "為呢個部署解析嘅伺服器層級設定。憑證已經隱去。",
|
||||
"searchPlaceholder": "篩選設定...",
|
||||
"loading": "正在載入設定...",
|
||||
"loadError": "載入設定失敗。",
|
||||
"empty": "冇符合篩選條件嘅設定。",
|
||||
"redactedHint": "憑證值已經遮蔽;「***」表示已經設定咗值。",
|
||||
"notEnabledTitle": "管理 API 未啟用",
|
||||
"notEnabledDescription": "喺 API 伺服器設定 {envVar} 嚟啟用管理區段。"
|
||||
},
|
||||
"login": {
|
||||
"title": "Hindsight 控制面板",
|
||||
"description": "輸入存取金鑰以繼續",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "未能載入版本"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "取得管理設定失敗"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "未能列出 Webhook",
|
||||
"create": "未能建立 Webhook",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "请从上方下拉菜单选择一个记忆库开始使用。",
|
||||
"sidebarHint": "选择记忆库后,侧边栏会出现。"
|
||||
},
|
||||
"admin": {
|
||||
"title": "管理",
|
||||
"backToApp": "返回应用",
|
||||
"configHeading": "配置",
|
||||
"configDescription": "为此部署解析的服务器级配置。凭据已被隐去。",
|
||||
"searchPlaceholder": "筛选设置...",
|
||||
"loading": "正在加载配置...",
|
||||
"loadError": "加载配置失败。",
|
||||
"empty": "没有与筛选条件匹配的设置。",
|
||||
"redactedHint": "凭据值已被屏蔽;“***”表示已设置值。",
|
||||
"notEnabledTitle": "管理 API 未启用",
|
||||
"notEnabledDescription": "在 API 服务器上设置 {envVar} 以启用管理部分。"
|
||||
},
|
||||
"login": {
|
||||
"title": "Hindsight 控制面板",
|
||||
"description": "输入访问密钥以继续",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "获取版本失败"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "获取管理配置失败"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "列出 Webhook 失败",
|
||||
"create": "创建 Webhook 失败",
|
||||
|
||||
@@ -1451,6 +1451,19 @@
|
||||
"selectBank": "請從上方下拉選單選擇一個記憶庫開始使用。",
|
||||
"sidebarHint": "選擇記憶庫後,側邊欄會出現。"
|
||||
},
|
||||
"admin": {
|
||||
"title": "管理",
|
||||
"backToApp": "返回應用程式",
|
||||
"configHeading": "設定",
|
||||
"configDescription": "為此部署解析的伺服器層級設定。憑證已被隱去。",
|
||||
"searchPlaceholder": "篩選設定...",
|
||||
"loading": "正在載入設定...",
|
||||
"loadError": "載入設定失敗。",
|
||||
"empty": "沒有符合篩選條件的設定。",
|
||||
"redactedHint": "憑證值已被遮蔽;「***」表示已設定值。",
|
||||
"notEnabledTitle": "管理 API 未啟用",
|
||||
"notEnabledDescription": "在 API 伺服器上設定 {envVar} 以啟用管理區段。"
|
||||
},
|
||||
"login": {
|
||||
"title": "Hindsight 控制面板",
|
||||
"description": "輸入存取金鑰以繼續",
|
||||
@@ -1591,6 +1604,11 @@
|
||||
"version": {
|
||||
"fetch": "取得版本失敗"
|
||||
},
|
||||
"admin": {
|
||||
"config": {
|
||||
"fetch": "取得管理設定失敗"
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"list": "列出 Webhook 失敗",
|
||||
"create": "建立 Webhook 失敗",
|
||||
|
||||
@@ -1747,6 +1747,8 @@ Configuration fields are categorized for security:
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_ENABLE_BANK_CONFIG_API` | Enable per-bank config API | `true` |
|
||||
| `HINDSIGHT_API_ENABLE_ADMIN_API` | Enable the admin API (`GET /admin/config`) and the Control Plane `/admin` page. Off by default — the surface returns `404` until enabled. Server-level (static). | `false` |
|
||||
| `HINDSIGHT_API_ADMIN_TOKEN` | Optional bearer token required by the admin API. When unset, the admin API is open (once enabled); when set, callers must send `Authorization: Bearer <token>`. Independent of the tenant API key. Server-level (static). | _(unset)_ |
|
||||
| `HINDSIGHT_API_DEFAULT_BANK_TEMPLATE` | Bank template manifest (JSON) applied automatically to every newly-created bank. See below. | _(unset)_ |
|
||||
|
||||
##### `HINDSIGHT_API_DEFAULT_BANK_TEMPLATE`
|
||||
|
||||
@@ -55,6 +55,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/config": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get resolved server-level configuration",
|
||||
"description": "Returns the resolved server-level configuration with credentials redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.",
|
||||
"operationId": "get_admin_config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "authorization",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AdminConfigResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/metrics": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -5554,6 +5604,22 @@
|
||||
"update_disposition": true
|
||||
}
|
||||
},
|
||||
"AdminConfigResponse": {
|
||||
"properties": {
|
||||
"config": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Config",
|
||||
"description": "Resolved server-level configuration (Python field names); credentials are redacted"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"config"
|
||||
],
|
||||
"title": "AdminConfigResponse",
|
||||
"description": "Response model for the server-level (admin) configuration view.\n\nReturns the resolved ``HindsightConfig`` as a flat dict keyed by Python field\nname. Credential fields (API keys, tokens, service-account keys, base URLs) are\nmasked: present as ``\"***\"`` when set and ``None`` when unset, so an operator can\nsee which credentials are configured without ever seeing their values."
|
||||
},
|
||||
"AsyncOperationSubmitResponse": {
|
||||
"properties": {
|
||||
"operation_id": {
|
||||
@@ -8215,6 +8281,11 @@
|
||||
"title": "Bank Config Api",
|
||||
"description": "Whether per-bank configuration API is enabled"
|
||||
},
|
||||
"admin_api": {
|
||||
"type": "boolean",
|
||||
"title": "Admin Api",
|
||||
"description": "Whether the admin API (/admin) is enabled"
|
||||
},
|
||||
"file_upload_api": {
|
||||
"type": "boolean",
|
||||
"title": "File Upload Api",
|
||||
@@ -8247,6 +8318,7 @@
|
||||
"mcp",
|
||||
"worker",
|
||||
"bank_config_api",
|
||||
"admin_api",
|
||||
"file_upload_api",
|
||||
"document_export_api",
|
||||
"document_import_api",
|
||||
|
||||
@@ -1747,6 +1747,8 @@ Configuration fields are categorized for security:
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_ENABLE_BANK_CONFIG_API` | Enable per-bank config API | `true` |
|
||||
| `HINDSIGHT_API_ENABLE_ADMIN_API` | Enable the admin API (`GET /admin/config`) and the Control Plane `/admin` page. Off by default — the surface returns `404` until enabled. Server-level (static). | `false` |
|
||||
| `HINDSIGHT_API_ADMIN_TOKEN` | Optional bearer token required by the admin API. When unset, the admin API is open (once enabled); when set, callers must send `Authorization: Bearer <token>`. Independent of the tenant API key. Server-level (static). | _(unset)_ |
|
||||
| `HINDSIGHT_API_DEFAULT_BANK_TEMPLATE` | Bank template manifest (JSON) applied automatically to every newly-created bank. See below. | _(unset)_ |
|
||||
|
||||
##### `HINDSIGHT_API_DEFAULT_BANK_TEMPLATE`
|
||||
|
||||
@@ -55,6 +55,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/config": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get resolved server-level configuration",
|
||||
"description": "Returns the resolved server-level configuration with credentials redacted. Gated by HINDSIGHT_API_ENABLE_ADMIN_API and, when set, HINDSIGHT_API_ADMIN_TOKEN.",
|
||||
"operationId": "get_admin_config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "authorization",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AdminConfigResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/metrics": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -5554,6 +5604,22 @@
|
||||
"update_disposition": true
|
||||
}
|
||||
},
|
||||
"AdminConfigResponse": {
|
||||
"properties": {
|
||||
"config": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Config",
|
||||
"description": "Resolved server-level configuration (Python field names); credentials are redacted"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"config"
|
||||
],
|
||||
"title": "AdminConfigResponse",
|
||||
"description": "Response model for the server-level (admin) configuration view.\n\nReturns the resolved ``HindsightConfig`` as a flat dict keyed by Python field\nname. Credential fields (API keys, tokens, service-account keys, base URLs) are\nmasked: present as ``\"***\"`` when set and ``None`` when unset, so an operator can\nsee which credentials are configured without ever seeing their values."
|
||||
},
|
||||
"AsyncOperationSubmitResponse": {
|
||||
"properties": {
|
||||
"operation_id": {
|
||||
@@ -8215,6 +8281,11 @@
|
||||
"title": "Bank Config Api",
|
||||
"description": "Whether per-bank configuration API is enabled"
|
||||
},
|
||||
"admin_api": {
|
||||
"type": "boolean",
|
||||
"title": "Admin Api",
|
||||
"description": "Whether the admin API (/admin) is enabled"
|
||||
},
|
||||
"file_upload_api": {
|
||||
"type": "boolean",
|
||||
"title": "File Upload Api",
|
||||
@@ -8247,6 +8318,7 @@
|
||||
"mcp",
|
||||
"worker",
|
||||
"bank_config_api",
|
||||
"admin_api",
|
||||
"file_upload_api",
|
||||
"document_export_api",
|
||||
"document_import_api",
|
||||
|
||||
Reference in New Issue
Block a user