6f95c75ed2
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>
151 lines
4.9 KiB
Go
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
|
|
}
|