Files
objectstorage/ts_web/elements/objst-view-buckets.ts

272 lines
9.2 KiB
TypeScript
Raw Normal View History

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]}`;
}
}