Files
Enterprise-Knowledge-Base/client/system.go
T
wizardchen 6f95c75ed2 feat(system-info): surface DB migration errors with troubleshooting links
When a startup database migration fails (e.g. issue #1319: pg_trgm not
available so 000041 cannot build its trigram index), the application
intentionally keeps booting so operators can reach the UI to diagnose.
However, before this change the system info page silently dropped the
"DB Version" row because the value was empty:

  - migration.go only cached the version after a successful m.Up(); the
    error path returned early and left migrationVersionSet=false.
  - system.go used CachedMigrationVersion's ok=false to skip emitting
    db_version, and the JSON tag was already omitempty.
  - SystemInfo.vue gated the entire row on v-if="systemInfo?.db_version".

The end result was the most useful diagnostic surface vanishing in the
exact failure mode that needs it most — Wiki ingest and KG features
would silently produce nothing with no UI hint.

Changes:

  - migration.go: replace sync.Once-based setter with an RWMutex-guarded
    state struct holding {version, dirty, err}. Every failure path now
    calls captureMigrationFailure(m, err), which best-effort reads
    m.Version() so the cached value still reflects the partial state.
  - system.go (handler): always emit db_version (falling back to
    "unknown" when no version could be read), append " (failed)" when an
    error is recorded, and add db_migration_error to the response.
  - swagger / client SDK: keep the API contract in sync with the new
    response field.
  - SystemInfo.vue: render the DB version row whenever either field is
    present, show a "Migration failed" danger tag, and add a full-width
    alert below the row carrying the error message plus two links:
      1. View troubleshooting guide -> new docs/migration-troubleshooting.md
      2. Report an issue -> github.com/Tencent/WeKnora/issues/new,
         prefilled with the captured error and environment metadata.
  - docs/migration-troubleshooting.md: new self-service guide covering
    the common failure modes (missing extension, dirty state, privileges,
    out of disk, schema drift) with concrete psql / make commands.
  - i18n: add the new keys to zh-CN, en-US, ko-KR, ru-RU.

Refs #1319.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 16:34:50 +08:00

151 lines
4.9 KiB
Go

package client
import (
"context"
"encoding/json"
"net/http"
)
// SystemInfo represents system version and configuration information
type SystemInfo struct {
Version string `json:"version"`
Edition string `json:"edition"`
CommitID string `json:"commit_id,omitempty"`
BuildTime string `json:"build_time,omitempty"`
GoVersion string `json:"go_version,omitempty"`
KeywordIndexEngine string `json:"keyword_index_engine,omitempty"`
VectorStoreEngine string `json:"vector_store_engine,omitempty"`
GraphDatabaseEngine string `json:"graph_database_engine,omitempty"`
MinioEnabled bool `json:"minio_enabled,omitempty"`
DBVersion string `json:"db_version,omitempty"`
DBMigrationError string `json:"db_migration_error,omitempty"`
}
// ParserEngine represents a document parser engine
type ParserEngine struct {
Name string `json:"name"`
Label string `json:"label"`
Description string `json:"description"`
Available bool `json:"available"`
}
// StorageEngineStatusItem describes one storage engine's availability
type StorageEngineStatusItem struct {
Name string `json:"name"`
Available bool `json:"available"`
Description string `json:"description"`
}
// StorageEngineStatusResponse is the response for storage engine status
type StorageEngineStatusResponse struct {
Engines []StorageEngineStatusItem `json:"engines"`
MinioEnvAvailable bool `json:"minio_env_available"`
}
// StorageCheckRequest is the body for storage engine connectivity check
type StorageCheckRequest struct {
Provider string `json:"provider"`
MinIO json.RawMessage `json:"minio,omitempty"`
COS json.RawMessage `json:"cos,omitempty"`
TOS json.RawMessage `json:"tos,omitempty"`
S3 json.RawMessage `json:"s3,omitempty"`
}
// StorageCheckResponse is the response for storage engine check
type StorageCheckResponse struct {
OK bool `json:"ok"`
Message string `json:"message"`
BucketCreated bool `json:"bucket_created,omitempty"`
}
// GetSystemInfo gets system version and configuration information
func (c *Client) GetSystemInfo(ctx context.Context) (*SystemInfo, error) {
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/system/info", nil, nil)
if err != nil {
return nil, err
}
var result struct {
Code int `json:"code"`
Data *SystemInfo `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// ListParserEngines lists available document parser engines
func (c *Client) ListParserEngines(ctx context.Context) ([]ParserEngine, error) {
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/system/parser-engines", nil, nil)
if err != nil {
return nil, err
}
var result struct {
Code int `json:"code"`
Data []ParserEngine `json:"data"`
Connected bool `json:"connected"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// CheckParserEngines checks parser engine availability with given config overrides
func (c *Client) CheckParserEngines(ctx context.Context, config any) ([]ParserEngine, error) {
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/system/parser-engines/check", config, nil)
if err != nil {
return nil, err
}
var result struct {
Code int `json:"code"`
Data []ParserEngine `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// ReconnectDocReader reconnects the document parser service to a new address
func (c *Client) ReconnectDocReader(ctx context.Context, addr string) error {
req := map[string]string{"addr": addr}
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/system/docreader/reconnect", req, nil)
if err != nil {
return err
}
return parseResponse(resp, nil)
}
// GetStorageEngineStatus gets the availability status of all storage engines
func (c *Client) GetStorageEngineStatus(ctx context.Context) (*StorageEngineStatusResponse, error) {
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/system/storage-engine-status", nil, nil)
if err != nil {
return nil, err
}
var result struct {
Code int `json:"code"`
Data *StorageEngineStatusResponse `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// CheckStorageEngine tests connectivity for a storage engine
func (c *Client) CheckStorageEngine(ctx context.Context, req *StorageCheckRequest) (*StorageCheckResponse, error) {
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/system/storage-engine-check", req, nil)
if err != nil {
return nil, err
}
var result struct {
Code int `json:"code"`
Data *StorageCheckResponse `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}