feat: add S3-compatible storage provider (MinIO, Ceph, R2, etc.)
Adds a new 'S3-Compatible Storage' provider that works with any
S3-API-compatible object storage service, including MinIO, Ceph,
Cloudflare R2, Backblaze B2, and others.
Changes:
- New provider class: classes/providers/storage/s3-compatible-provider.php
- Provider key: s3compatible
- Reads user-configured endpoint URL from settings
- Uses path-style URL access (required by most S3-compatible services)
- Supports credentials via AS3CF_S3COMPAT_ACCESS_KEY_ID /
AS3CF_S3COMPAT_SECRET_ACCESS_KEY wp-config.php constants
- Disables AWS-specific features (Block Public Access, Object Ownership)
- New provider SVG icons (s3compatible.svg, -link.svg, -round.svg)
- Registered provider in main plugin class with endpoint setting support
- Updated StorageProviderSubPage to show endpoint URL input for S3-compatible
- Built pro settings bundle with rollup (Svelte 4.2.19)
- Added package.json and updated rollup.config.mjs for pro-only builds
This commit is contained in:
191
ui/components/OffloadStatusFlyout.svelte
Normal file
191
ui/components/OffloadStatusFlyout.svelte
Normal file
@@ -0,0 +1,191 @@
|
||||
<script>
|
||||
import {
|
||||
counts,
|
||||
offloadRemainingUpsell,
|
||||
summaryCounts,
|
||||
strings,
|
||||
urls,
|
||||
api,
|
||||
state
|
||||
} from "../js/stores";
|
||||
import {numToString} from "../js/numToString";
|
||||
import {delayMin} from "../js/delay";
|
||||
import Button from "./Button.svelte";
|
||||
import Panel from "./Panel.svelte";
|
||||
import PanelRow from "./PanelRow.svelte";
|
||||
|
||||
export let expanded = false;
|
||||
export let buttonRef = {};
|
||||
export let panelRef = {};
|
||||
export let hasFocus = false;
|
||||
export let refreshing = false;
|
||||
|
||||
/**
|
||||
* Keep track of when a child control gets mouse focus.
|
||||
*/
|
||||
function handleMouseEnter() {
|
||||
hasFocus = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of when a child control loses mouse focus.
|
||||
*/
|
||||
function handleMouseLeave() {
|
||||
hasFocus = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the panel is clicked, select the first focusable element
|
||||
* so that clicking outside the panel triggers a lost focus event.
|
||||
*/
|
||||
function handlePanelClick() {
|
||||
hasFocus = true;
|
||||
|
||||
const firstFocusable = panelRef.querySelector( "a:not([tabindex='-1']),button:not([tabindex='-1'])" );
|
||||
|
||||
if ( firstFocusable ) {
|
||||
firstFocusable.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When either the button or panel completely lose focus, close the flyout.
|
||||
*
|
||||
* @param {FocusEvent} event
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
function handleFocusOut( event ) {
|
||||
if ( !expanded ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mouse click and OffloadStatus control/children no longer have mouse focus.
|
||||
if ( event.relatedTarget === null && !hasFocus ) {
|
||||
expanded = false;
|
||||
}
|
||||
|
||||
// Keyboard focus change and new focused control isn't within OffloadStatus/Flyout.
|
||||
if (
|
||||
event.relatedTarget !== null &&
|
||||
event.relatedTarget !== buttonRef &&
|
||||
!panelRef.contains( event.relatedTarget )
|
||||
) {
|
||||
expanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cancel event from panel and button.
|
||||
*/
|
||||
function handleCancel() {
|
||||
buttonRef.focus();
|
||||
expanded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually refresh the media counts.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function handleRefresh() {
|
||||
let start = Date.now();
|
||||
refreshing = true;
|
||||
|
||||
let params = {
|
||||
refreshMediaCounts: true
|
||||
};
|
||||
|
||||
let json = await api.get( "state", params );
|
||||
await delayMin( start, 1000 );
|
||||
state.updateState( json );
|
||||
|
||||
refreshing = false;
|
||||
buttonRef.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button
|
||||
expandable
|
||||
{expanded}
|
||||
on:click={() => expanded = !expanded}
|
||||
title={expanded ? $strings.hide_details : $strings.show_details}
|
||||
bind:ref={buttonRef}
|
||||
on:focusout={handleFocusOut}
|
||||
on:cancel={handleCancel}
|
||||
/>
|
||||
|
||||
{#if expanded}
|
||||
<Panel
|
||||
multi
|
||||
flyout
|
||||
refresh
|
||||
{refreshing}
|
||||
heading={$strings.offload_status_title}
|
||||
refreshDesc={$strings.refresh_media_counts_desc}
|
||||
bind:ref={panelRef}
|
||||
on:focusout={handleFocusOut}
|
||||
on:mouseenter={handleMouseEnter}
|
||||
on:mouseleave={handleMouseLeave}
|
||||
on:mousedown={handleMouseEnter}
|
||||
on:click={handlePanelClick}
|
||||
on:cancel={handleCancel}
|
||||
on:refresh={handleRefresh}
|
||||
>
|
||||
<PanelRow class="summary">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{$strings.summary_type_title}</th>
|
||||
<th class="numeric">{$strings.summary_offloaded_title}</th>
|
||||
<th class="numeric">{$strings.summary_not_offloaded_title}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{#each $summaryCounts as summary (summary.type)}
|
||||
<tr>
|
||||
<td>{summary.name}</td>
|
||||
{#if summary.offloaded_url}
|
||||
<td class="numeric">
|
||||
<a href="{summary.offloaded_url}">{numToString( summary.offloaded )}</a>
|
||||
</td>
|
||||
{:else}
|
||||
<td class="numeric">{numToString( summary.offloaded )}</td>
|
||||
{/if}
|
||||
{#if summary.not_offloaded_url}
|
||||
<td class="numeric">
|
||||
<a href="{summary.not_offloaded_url}">{numToString( summary.not_offloaded )}</a>
|
||||
</td>
|
||||
{:else}
|
||||
<td class="numeric">{numToString( summary.not_offloaded )}</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
|
||||
{#if $summaryCounts.length > 1}
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>{$strings.summary_total_row_title}</td>
|
||||
<td class="numeric">{numToString( $counts.offloaded )}</td>
|
||||
<td class="numeric">{numToString( $counts.not_offloaded )}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
{/if}
|
||||
</table>
|
||||
</PanelRow>
|
||||
|
||||
<slot name="footer">
|
||||
<PanelRow footer class="upsell">
|
||||
{#if $offloadRemainingUpsell}
|
||||
<p>{@html $offloadRemainingUpsell}</p>
|
||||
{/if}
|
||||
<a href={$urls.upsell_discount} class="button btn-sm btn-primary licence" target="_blank">
|
||||
<img src={$urls.assets + "img/icon/stars.svg"} alt="stars icon" style="margin-right: 5px;">
|
||||
{$strings.offload_remaining_upsell_cta}
|
||||
</a>
|
||||
</PanelRow>
|
||||
</slot>
|
||||
</Panel>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user