feat(core): rebrand to @lossless.zone/objectstorage
- Rename from @lossless.zone/s3container to @lossless.zone/objectstorage - Replace @push.rocks/smarts3 with @push.rocks/smartstorage - Change env var prefix from S3_ to OBJST_ - Rename S3Container class to ObjectStorageContainer - Update web component prefix from s3c- to objst- - Update UI labels, CLI flags, documentation, and Docker config
This commit is contained in:
271
ts_web/elements/objst-view-buckets.ts
Normal file
271
ts_web/elements/objst-view-buckets.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as appstate from '../appstate.js';
|
||||
import * as shared from './shared/index.js';
|
||||
import { appRouter } from '../router.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesModal } from '@design.estate/dees-catalog';
|
||||
|
||||
@customElement('objst-view-buckets')
|
||||
export class ObjstViewBuckets extends DeesElement {
|
||||
@state()
|
||||
accessor bucketsState: appstate.IBucketsState = { buckets: [] };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const sub = appstate.bucketsStatePart
|
||||
.select((s) => s)
|
||||
.subscribe((bucketsState) => {
|
||||
this.bucketsState = bucketsState;
|
||||
});
|
||||
this.rxSubscriptions.push(sub);
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
appstate.bucketsStatePart.dispatchAction(appstate.fetchBucketsAction, null);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<objst-sectionheading>Buckets</objst-sectionheading>
|
||||
<dees-table
|
||||
heading1="Buckets"
|
||||
heading2="Manage your storage buckets"
|
||||
.data=${this.bucketsState.buckets}
|
||||
.dataName=${'bucket'}
|
||||
.searchable=${true}
|
||||
.displayFunction=${(item: any) => ({
|
||||
name: item.name,
|
||||
objects: String(item.objectCount),
|
||||
size: this.formatBytes(item.totalSizeBytes),
|
||||
created: new Date(item.creationDate).toLocaleDateString(),
|
||||
})}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'Create Bucket',
|
||||
iconName: 'lucide:plus',
|
||||
type: ['header'] as any[],
|
||||
actionFunc: async () => {
|
||||
await this.showCreateBucketModal();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Browse',
|
||||
iconName: 'lucide:folderOpen',
|
||||
type: ['inRow'] as any[],
|
||||
actionFunc: async (args: any) => {
|
||||
await appstate.objectsStatePart.dispatchAction(appstate.fetchObjectsAction, {
|
||||
bucketName: args.item.name,
|
||||
prefix: '',
|
||||
delimiter: '/',
|
||||
});
|
||||
appRouter.navigateToView('browser');
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Policy',
|
||||
iconName: 'lucide:shield',
|
||||
type: ['inRow'] as any[],
|
||||
actionFunc: async (args: any) => {
|
||||
await this.showPolicyModal(args.item.name);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
iconName: 'lucide:trash2',
|
||||
type: ['inRow', 'contextmenu'] as any[],
|
||||
actionFunc: async (args: any) => {
|
||||
await this.showDeleteBucketModal(args.item.name);
|
||||
},
|
||||
},
|
||||
]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
|
||||
private async showCreateBucketModal(): Promise<void> {
|
||||
await DeesModal.createAndShow({
|
||||
heading: 'Create Bucket',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'bucketName'} .label=${'Bucket Name'} .required=${true}></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Cancel',
|
||||
action: async (modal: any) => { modal.destroy(); },
|
||||
},
|
||||
{
|
||||
name: 'Create',
|
||||
action: async (modal: any) => {
|
||||
const form = modal.shadowRoot.querySelector('dees-form') as any;
|
||||
const data = await form.collectFormData();
|
||||
if (data.bucketName) {
|
||||
await appstate.bucketsStatePart.dispatchAction(appstate.createBucketAction, {
|
||||
bucketName: data.bucketName,
|
||||
});
|
||||
}
|
||||
modal.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private async showDeleteBucketModal(bucketName: string): Promise<void> {
|
||||
await DeesModal.createAndShow({
|
||||
heading: 'Delete Bucket',
|
||||
content: html`<p>Are you sure you want to delete bucket <strong>${bucketName}</strong>? This action cannot be undone.</p>`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Cancel',
|
||||
action: async (modal: any) => { modal.destroy(); },
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
action: async (modal: any) => {
|
||||
await appstate.bucketsStatePart.dispatchAction(appstate.deleteBucketAction, {
|
||||
bucketName,
|
||||
});
|
||||
modal.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private async showPolicyModal(bucketName: string): Promise<void> {
|
||||
const data = await appstate.getBucketNamedPolicies(bucketName);
|
||||
let modalRef: any = null;
|
||||
|
||||
const renderContent = (
|
||||
attached: typeof data.attachedPolicies,
|
||||
available: typeof data.availablePolicies,
|
||||
) => html`
|
||||
<style>
|
||||
.policy-lists { display: flex; flex-direction: column; gap: 16px; }
|
||||
.policy-section h4 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
}
|
||||
.policy-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
background: ${cssManager.bdTheme('#f5f5f5', '#1a1a2e')};
|
||||
margin-bottom: 4px;
|
||||
color: ${cssManager.bdTheme('#333', '#eee')};
|
||||
}
|
||||
.policy-item-info { display: flex; flex-direction: column; gap: 2px; }
|
||||
.policy-item-name { font-size: 14px; font-weight: 500; }
|
||||
.policy-item-desc { font-size: 12px; color: ${cssManager.bdTheme('#666', '#999')}; }
|
||||
.policy-item button {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btn-detach {
|
||||
background: ${cssManager.bdTheme('#ffebee', '#3e1a1a')};
|
||||
color: ${cssManager.bdTheme('#c62828', '#ef5350')};
|
||||
}
|
||||
.btn-detach:hover { opacity: 0.8; }
|
||||
.btn-attach {
|
||||
background: ${cssManager.bdTheme('#e3f2fd', '#1a2a3e')};
|
||||
color: ${cssManager.bdTheme('#1565c0', '#64b5f6')};
|
||||
}
|
||||
.btn-attach:hover { opacity: 0.8; }
|
||||
.empty-note {
|
||||
color: ${cssManager.bdTheme('#999', '#666')};
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
padding: 8px 0;
|
||||
}
|
||||
</style>
|
||||
<div class="policy-lists">
|
||||
<div class="policy-section">
|
||||
<h4>Attached Policies (${attached.length})</h4>
|
||||
${attached.length > 0
|
||||
? attached.map((policy) => html`
|
||||
<div class="policy-item">
|
||||
<div class="policy-item-info">
|
||||
<span class="policy-item-name">${policy.name}</span>
|
||||
${policy.description ? html`<span class="policy-item-desc">${policy.description}</span>` : ''}
|
||||
</div>
|
||||
<button class="btn-detach" @click=${async (e: Event) => {
|
||||
(e.target as HTMLButtonElement).disabled = true;
|
||||
await appstate.detachPolicyFromBucket(policy.id, bucketName);
|
||||
const fresh = await appstate.getBucketNamedPolicies(bucketName);
|
||||
if (modalRef) {
|
||||
modalRef.content = renderContent(fresh.attachedPolicies, fresh.availablePolicies);
|
||||
}
|
||||
}}>Detach</button>
|
||||
</div>
|
||||
`)
|
||||
: html`<p class="empty-note">No policies attached to this bucket.</p>`}
|
||||
</div>
|
||||
<div class="policy-section">
|
||||
<h4>Available Policies (${available.length})</h4>
|
||||
${available.length > 0
|
||||
? available.map((policy) => html`
|
||||
<div class="policy-item">
|
||||
<div class="policy-item-info">
|
||||
<span class="policy-item-name">${policy.name}</span>
|
||||
${policy.description ? html`<span class="policy-item-desc">${policy.description}</span>` : ''}
|
||||
</div>
|
||||
<button class="btn-attach" @click=${async (e: Event) => {
|
||||
(e.target as HTMLButtonElement).disabled = true;
|
||||
await appstate.attachPolicyToBucket(policy.id, bucketName);
|
||||
const fresh = await appstate.getBucketNamedPolicies(bucketName);
|
||||
if (modalRef) {
|
||||
modalRef.content = renderContent(fresh.attachedPolicies, fresh.availablePolicies);
|
||||
}
|
||||
}}>Attach</button>
|
||||
</div>
|
||||
`)
|
||||
: html`<p class="empty-note">${attached.length > 0
|
||||
? 'All policies are already attached.'
|
||||
: 'No policies defined yet. Create policies in the Policies view.'
|
||||
}</p>`}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modalRef = await DeesModal.createAndShow({
|
||||
heading: `Policies for: ${bucketName}`,
|
||||
content: renderContent(data.attachedPolicies, data.availablePolicies),
|
||||
menuOptions: [
|
||||
{ name: 'Done', action: async (modal: any) => { modal.destroy(); } },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user