mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-04 02:57:44 +01:00
* s3vectors: run an operation for trusted local tooling Like s3tables.Manager.Execute, Server.Execute runs one S3 Vectors operation through its handler as a given identity, without a signed request. The admin console can then manage vector buckets through the same validation, authorization and catalog steps as the API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * admin: list, inspect, create and delete vector buckets over the API /api/s3vectors runs the S3 Vectors operations through Server.Execute as the admin account: list vector buckets with their encryption, read one bucket with its policy and each index's data type, dimension, distance metric and non-filterable keys, create and delete a bucket, and delete an index. Going through the operations keeps the API's rules, such as ICEBERG buckets not being vector buckets and the implicit index namespace not blocking a delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3vectors: export the rule for which table buckets are vector buckets IsVectorBucketFormat names the check ListVectorBuckets and isVectorBucket already make, so the admin console can tell the same buckets apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * admin: vector bucket pages Object Store > Vector Buckets lists the vector buckets with their ARN, encryption and creation time, creates and deletes them, and opens one bucket with its indexes (data type, dimension, distance metric, non-filterable keys) and its policy, where an index can be deleted. A LANCE row on the Table Buckets page links to the same bucket's vector page, since both APIs serve it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * admin: stop the vector bucket and index lists at the list limit A page cut short by filtered ICEBERG buckets let the next page carry the list past s3TablesAdminListLimit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * admin: say when a vector bucket or index list is cut at the limit The lists stop at s3TablesAdminListLimit while more remain, so mark them truncated and point to an S3 Vectors client for the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * admin: indent the vector bucket policy A policy stored as one line ran off the right edge of the details page. Policies that are not valid JSON are shown as stored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * admin: show a deeply nested vector bucket policy as stored Indenting multiplies the size of a deeply nested document, so a policy past 32 levels of nesting is shown without indenting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
190 lines
7.4 KiB
Templ
190 lines
7.4 KiB
Templ
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
|
|
)
|
|
|
|
templ S3VectorsBuckets(data dash.S3VectorsBucketsData) {
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">
|
|
<i class="bi bi-bounding-box-circles me-2"></i>Vector Buckets
|
|
</h1>
|
|
<div class="btn-toolbar mb-2 mb-md-0">
|
|
<div class="btn-group me-2">
|
|
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#createS3VectorsBucketModal">
|
|
<i class="bi bi-plus me-1"></i>Create Bucket
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="alert alert-info mb-4">
|
|
<i class="bi bi-info-circle me-2"></i>
|
|
Vector buckets serve the Amazon S3 Vectors API on the S3 port. Each one is a LANCE table bucket that keeps its indexes in its <code>vector</code> namespace.
|
|
Indexes are created and filled with an S3 Vectors client, which needs a running <code>weed-vector</code> engine.
|
|
</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-bounding-box-circles me-2"></i>{ fmt.Sprintf("%d vector buckets", data.TotalBuckets) }
|
|
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.TotalBuckets) }</span>
|
|
}
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" width="100%" cellspacing="0" id="s3vectorsBucketsTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>ARN</th>
|
|
<th>Encryption</th>
|
|
<th>Created</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
for _, bucket := range data.Buckets {
|
|
<tr>
|
|
<td>
|
|
<a href={ dash.PUrl(ctx, "/object-store/vector-buckets/"+url.PathEscape(bucket.Name)) }>{ bucket.Name }</a>
|
|
</td>
|
|
<td class="text-muted small">{ bucket.ARN }</td>
|
|
<td>
|
|
if bucket.Encryption != "" {
|
|
<span class="badge bg-success">{ bucket.Encryption }</span>
|
|
} else {
|
|
<span class="text-muted">None</span>
|
|
}
|
|
</td>
|
|
<td>{ bucket.CreatedAt.Format("2006-01-02 15:04") }</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<a class="btn btn-outline-primary btn-sm" href={ dash.PUrl(ctx, "/object-store/vector-buckets/"+url.PathEscape(bucket.Name)) } title="Indexes">
|
|
<i class="bi bi-folder2-open"></i>
|
|
</a>
|
|
<button type="button" class="btn btn-outline-danger btn-sm s3vectors-delete-bucket-btn" data-bucket-name={ bucket.Name } title="Delete">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
if len(data.Buckets) == 0 {
|
|
<tr>
|
|
<td colspan="5" class="text-center text-muted py-4">
|
|
<i class="bi bi-bounding-box-circles icon-3x mb-3 text-muted"></i>
|
|
<div>
|
|
<h5>No vector buckets found</h5>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="createS3VectorsBucketModal" tabindex="-1" aria-labelledby="createS3VectorsBucketModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="createS3VectorsBucketModalLabel">
|
|
<i class="bi bi-plus me-2"></i>Create Vector Bucket
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form id="createS3VectorsBucketForm">
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="s3vectorsBucketName" class="form-label">Bucket Name</label>
|
|
<input type="text" class="form-control" id="s3vectorsBucketName" name="name" placeholder="vector-bucket-name" required/>
|
|
<div class="form-text">3 to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit.</div>
|
|
</div>
|
|
<div class="form-text">
|
|
The bucket is created without encryption. To encrypt it, create it with an S3 Vectors client and <code>encryptionConfiguration</code>, which the S3 gateway checks against its SSE-S3 key.
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-plus me-1"></i>Create
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="deleteS3VectorsBucketModal" tabindex="-1" aria-labelledby="deleteS3VectorsBucketModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="deleteS3VectorsBucketModalLabel">
|
|
<i class="bi bi-exclamation-triangle me-2 text-warning"></i>Delete Vector Bucket
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Delete the vector bucket <strong id="deleteS3VectorsBucketName"></strong>? A bucket that still has indexes is not deleted.</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="deleteS3VectorsBucketBtn">
|
|
<i class="bi bi-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const deleteModalEl = document.getElementById('deleteS3VectorsBucketModal');
|
|
const deleteModal = new bootstrap.Modal(deleteModalEl);
|
|
document.querySelectorAll('.s3vectors-delete-bucket-btn').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
deleteModalEl.dataset.bucketName = this.dataset.bucketName || '';
|
|
document.getElementById('deleteS3VectorsBucketName').textContent = this.dataset.bucketName || '';
|
|
deleteModal.show();
|
|
});
|
|
});
|
|
document.getElementById('deleteS3VectorsBucketBtn').addEventListener('click', async function () {
|
|
const bucket = deleteModalEl.dataset.bucketName;
|
|
if (!bucket) return;
|
|
try {
|
|
const response = await fetch(s3tBasePath('/api/s3vectors/buckets?bucket=' + encodeURIComponent(bucket)), { method: 'DELETE', headers: s3tWriteHeaders() });
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
alert(data.error || 'Failed to delete vector bucket');
|
|
return;
|
|
}
|
|
location.reload();
|
|
} catch (error) {
|
|
alert('Failed to delete vector bucket: ' + error.message);
|
|
}
|
|
});
|
|
document.getElementById('createS3VectorsBucketForm').addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
const name = document.getElementById('s3vectorsBucketName').value.trim();
|
|
try {
|
|
const response = await fetch(s3tBasePath('/api/s3vectors/buckets'), {
|
|
method: 'POST',
|
|
headers: s3tWriteHeaders({ 'Content-Type': 'application/json' }),
|
|
body: JSON.stringify({ name: name })
|
|
});
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
alert(data.error || 'Failed to create vector bucket');
|
|
return;
|
|
}
|
|
location.reload();
|
|
} catch (error) {
|
|
alert('Failed to create vector bucket: ' + error.message);
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
}
|