mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-15 00:09:00 +01:00
* vector: expose request metrics and registration-aware health endpoints * vector: report immutable read cache and storage fetch metrics * docs: describe vector engine health, metrics, and maintenance * vector: count canceled requests when their handler is dropped * vector: account for uncached large immutable storage reads * vector: reliably release the stalled credential reader in tests * vector: observe large reads as their streamed body is consumed * vector: bound heartbeats on established master registration streams * test: exercise hardened vector maintenance on Go and Rust volumes * vector: register engines in their configured filer group * vector: make retried index creation preserve compatible datasets * vector: allow registration to advertise a separate address * test: release stalled credential readers without blocking FIFO cleanup * test: allow hardened vector coverage in a named filer group * vector: discover engines in the same filer group as their gateway * vector: fall back when the advertised address is empty * admin: expose vector readiness, maintenance, and management tools (#12520) * s3vectors: share vector discovery with trusted management clients * admin: show vector engine readiness and recent maintenance activity * admin: suggest S3 Vectors IAM actions from the supported operations * admin: manage vector indexes, policies, tags, encryption, and query data * admin: validate SSE-S3 support before creating encrypted vector buckets * admin: discover vector engines in their registration group * admin: probe vector engines concurrently with independent time budgets * admin: verify encryption capability on the remote S3 gateway * admin: defer discovery until after the HTTP server can start * admin: distinguish unknown vector health services from failed engines * admin: count workers that can execute vector index maintenance * admin: allow read-only users to query vectors and inspect tags * vector: scope gateway and admin discovery to the configured filer group * admin: refresh policy state after mutations and serialize policy controls * vector: drain active RPCs before closing replaced discovery connections * vector: respect request cancellation while waiting for discovery * vector: prefer another registered engine after a transport failure * admin: check all masters before reporting no registered vector engines * iam: honor attached vector policies without broadening legacy grants * vector: allow short-lived discovery clients to close their connections * vector: refresh cached master addresses for each discovery lookup * iam: enforce vector bucket tag conditions in attached policies * iam: authorize vector bucket creation with the requested tags * test: exercise vector IAM grants and tag denies on Go and Rust volumes * admin: reject authenticated vector mutations without an admin role * test: run vector IAM assertions before the shared engine is stopped * vector: add native mini startup, shell tools, and a runnable Compose example (#12522) * s3vectors: share vector discovery with trusted management clients * admin: show vector engine readiness and recent maintenance activity * admin: suggest S3 Vectors IAM actions from the supported operations * admin: manage vector indexes, policies, tags, encryption, and query data * admin: validate SSE-S3 support before creating encrypted vector buckets * admin: discover vector engines in their registration group * admin: probe vector engines concurrently with independent time budgets * admin: verify encryption capability on the remote S3 gateway * admin: defer discovery until after the HTTP server can start * admin: distinguish unknown vector health services from failed engines * admin: count workers that can execute vector index maintenance * admin: allow read-only users to query vectors and inspect tags * vector: scope gateway and admin discovery to the configured filer group * admin: refresh policy state after mutations and serialize policy controls * vector: drain active RPCs before closing replaced discovery connections * vector: respect request cancellation while waiting for discovery * vector: prefer another registered engine after a transport failure * admin: check all masters before reporting no registered vector engines * iam: honor attached vector policies without broadening legacy grants * vector: allow short-lived discovery clients to close their connections * vector: refresh cached master addresses for each discovery lookup * iam: enforce vector bucket tag conditions in attached policies * iam: authorize vector bucket creation with the requested tags * test: exercise vector IAM grants and tag denies on Go and Rust volumes * admin: reject authenticated vector mutations without an admin role * test: run vector IAM assertions before the shared engine is stopped * mini: optionally start and supervise the vector engine * mini: bootstrap vector buckets with existing Lance catalog helpers * shell: manage vector buckets, indexes, and vector data * docs: provide a runnable vector and maintenance worker Compose stack * shell: scope vector discovery to the session and close its connections * mini: honor the bind address for vector and metrics listeners * mini: reserve vector ports before assigning gRPC and compatibility listeners * mini: wait for vector storage setup and master registration * mini: create requested vector buckets without requiring the Lance HTTP listener * shell: refresh master addresses during vector discovery * mini: resolve sibling vector executables with platform extensions * vector: preserve uncertain creates and fix mini listener allocation (#12525) * vector: retain catalog records when index creation has an unknown outcome * mini: keep chosen listener ports reserved throughout allocation * mini: resolve hostname binds before starting vector listeners * vector: fix integration test imports and recover interrupted index creation (#12533) * shell: register vector operations from the command package * s3vectors: resume interrupted index initialization safely * mini: exclude disabled WebDAV from listener allocation * s3vectors: retain uncertain creates across refused retries * s3vectors: claim pending indexes before retrying initialization * s3vectors: compare resolved distance metrics during recovery * s3vectors: preserve metadata keys when retrying initialization * s3vectors: verify ownership before initializing new catalog records
213 lines
6.7 KiB
Templ
213 lines
6.7 KiB
Templ
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
|
|
)
|
|
|
|
templ S3VectorsBucketDetails(data dash.S3VectorsBucketData) {
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<div>
|
|
<h1 class="h2 mb-0">
|
|
<i class="bi bi-bounding-box-circles me-2"></i>{ data.Bucket.Name }
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0 mt-1">
|
|
<li class="breadcrumb-item"><a href={ dash.PUrl(ctx, "/object-store/vector-buckets") }>Vector Buckets</a></li>
|
|
<li class="breadcrumb-item active" aria-current="page">{ data.Bucket.Name }</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
@VectorStatus(data.Operations)
|
|
@VectorTools(data.Bucket.Name,data.Bucket.ARN,data.Policy)
|
|
<div class="card shadow mb-4">
|
|
<div class="card-body">
|
|
<dl class="row mb-0">
|
|
<dt class="col-sm-2">ARN</dt>
|
|
<dd class="col-sm-10"><code>{ data.Bucket.ARN }</code></dd>
|
|
<dt class="col-sm-2">Created</dt>
|
|
<dd class="col-sm-10">{ data.Bucket.CreatedAt.Format("2006-01-02 15:04:05") }</dd>
|
|
<dt class="col-sm-2">Encryption</dt>
|
|
<dd class="col-sm-10">
|
|
if data.Bucket.Encryption != "" {
|
|
<span class="badge bg-success">{ data.Bucket.Encryption }</span>
|
|
} else {
|
|
<span class="text-muted">None</span>
|
|
}
|
|
</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow mb-4">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 font-weight-bold text-primary">
|
|
<i class="bi bi-list-ul me-2"></i>{ fmt.Sprintf("%d indexes", data.TotalIndexes) }
|
|
if data.Truncated {
|
|
<span class="text-muted small ms-2">{ fmt.Sprintf("showing the first %d; list the rest with an S3 Vectors client", data.TotalIndexes) }</span>
|
|
}
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" width="100%" cellspacing="0" id="s3vectorsIndexesTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Data Type</th>
|
|
<th>Dimension</th>
|
|
<th>Distance Metric</th>
|
|
<th>Non-filterable Metadata Keys</th>
|
|
<th>Created</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
for _, index := range data.Indexes {
|
|
<tr>
|
|
<td>
|
|
{ index.IndexName }
|
|
<div class="text-muted small">{ index.IndexArn }</div>
|
|
</td>
|
|
<td>{ index.DataType }</td>
|
|
<td>{ fmt.Sprintf("%d", index.Dimension) }</td>
|
|
<td>{ index.DistanceMetric }</td>
|
|
<td>
|
|
if index.MetadataConfiguration != nil && len(index.MetadataConfiguration.NonFilterableMetadataKeys) > 0 {
|
|
<code>{ strings.Join(index.MetadataConfiguration.NonFilterableMetadataKeys, ", ") }</code>
|
|
} else {
|
|
<span class="text-muted">None</span>
|
|
}
|
|
</td>
|
|
<td>{ index.CreationTime.Format("2006-01-02 15:04") }</td>
|
|
<td>
|
|
<button type="button" class="btn btn-outline-danger btn-sm s3vectors-delete-index-btn" data-index-name={ index.IndexName } title="Delete">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
if len(data.Indexes) == 0 {
|
|
<tr>
|
|
<td colspan="7" class="text-center text-muted py-4">
|
|
No indexes. Create one with the form above.
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow mb-4">
|
|
<div class="card-header py-3">
|
|
<h6 class="m-0 font-weight-bold text-primary">
|
|
<i class="bi bi-shield me-2"></i>Bucket Policy
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
if data.Policy != "" {
|
|
<pre class="bg-light p-3 border rounded mb-0" id="s3vectorsBucketPolicy">{ indentJSON(data.Policy) }</pre>
|
|
} else {
|
|
<span class="text-muted">No policy. Set one with <code>PutVectorBucketPolicy</code>.</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="deleteS3VectorsIndexModal" tabindex="-1" aria-labelledby="deleteS3VectorsIndexModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="deleteS3VectorsIndexModalLabel">
|
|
<i class="bi bi-exclamation-triangle me-2 text-warning"></i>Delete Index
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Delete the index <strong id="deleteS3VectorsIndexName"></strong> and all its vectors?</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-danger" id="deleteS3VectorsIndexBtn">
|
|
<i class="bi bi-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="s3vectorsBucketDetails" data-bucket-name={ data.Bucket.Name }></div>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const bucket = document.getElementById('s3vectorsBucketDetails').dataset.bucketName;
|
|
const modalEl = document.getElementById('deleteS3VectorsIndexModal');
|
|
const modal = new bootstrap.Modal(modalEl);
|
|
document.querySelectorAll('.s3vectors-delete-index-btn').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
modalEl.dataset.indexName = this.dataset.indexName || '';
|
|
document.getElementById('deleteS3VectorsIndexName').textContent = this.dataset.indexName || '';
|
|
modal.show();
|
|
});
|
|
});
|
|
document.getElementById('deleteS3VectorsIndexBtn').addEventListener('click', async function () {
|
|
const index = modalEl.dataset.indexName;
|
|
if (!bucket || !index) return;
|
|
try {
|
|
const response = await fetch(s3tBasePath('/api/s3vectors/indexes?bucket=' + encodeURIComponent(bucket) + '&index=' + encodeURIComponent(index)), { method: 'DELETE', headers: s3tWriteHeaders() });
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
alert(data.error || 'Failed to delete index');
|
|
return;
|
|
}
|
|
location.reload();
|
|
} catch (error) {
|
|
alert('Failed to delete index: ' + error.message);
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
}
|
|
|
|
const maxIndentedJSONDepth = 32
|
|
|
|
func indentJSON(s string) string {
|
|
if jsonDepth(s) > maxIndentedJSONDepth {
|
|
return s
|
|
}
|
|
var out bytes.Buffer
|
|
if json.Indent(&out, []byte(s), "", " ") != nil {
|
|
return s
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
func jsonDepth(s string) int {
|
|
depth, maxDepth := 0, 0
|
|
inString, escaped := false, false
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
switch {
|
|
case escaped:
|
|
escaped = false
|
|
case inString:
|
|
if c == '\\' {
|
|
escaped = true
|
|
} else if c == '"' {
|
|
inString = false
|
|
}
|
|
case c == '"':
|
|
inString = true
|
|
case c == '{' || c == '[':
|
|
depth++
|
|
if depth > maxDepth {
|
|
maxDepth = depth
|
|
}
|
|
case c == '}' || c == ']':
|
|
depth--
|
|
}
|
|
}
|
|
return maxDepth
|
|
}
|