This commit is contained in:
Mahad Kalam 2026-02-23 20:26:33 +00:00 committed by GitHub
parent 64849e2656
commit 1a28bc744a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -325,370 +325,346 @@
</section>
{#if ui.show_imports_and_mirrors}
<section
id="imports_and_mirrors"
class="rounded-md border border-surface-200 bg-darker p-4 sm:p-5"
>
<h2 class="text-xl font-semibold text-surface-content">
Imports & Mirrors
</h2>
<p class="mt-1 text-sm text-muted">
Connect WakaTime-compatible sources and destinations.
</p>
<div class="mt-4 space-y-7">
<section id="wakatime_import_source">
<h3 class="text-lg font-semibold text-surface-content">
Import from WakaTime
</h3>
<div class="mt-4 space-y-7">
<section id="wakatime_import_source">
<h3 class="text-lg font-semibold text-surface-content">
Import from WakaTime
</h3>
{#if importSource}
<div
class="mt-3 rounded-md border border-surface-200 bg-surface p-3"
>
<p class="text-sm text-surface-content">
Status: <span class="font-semibold">{importSource.status}</span>
</p>
<p class="mt-1 text-xs text-muted">
Last synced: {importSource.last_synced_ago || "Never"}
</p>
<p class="mt-1 text-xs text-muted">
Imported: {importSource.imported_count.toLocaleString()}
</p>
{#if importSource.last_error_message}
<p class="mt-1 text-xs text-red-300">
Last error: {importSource.last_error_message}
</p>
{/if}
</div>
{/if}
<form
method="post"
action={paths.heartbeat_import_source_path}
class="mt-3 space-y-3 rounded-md border border-surface-200 bg-surface p-3"
>
<input type="hidden" name="authenticity_token" value={csrfToken} />
{#if importSource}
<div
class="mt-3 rounded-md border border-surface-200 bg-surface p-3"
>
<p class="text-sm text-surface-content">
Status: <span class="font-semibold"
>{importSource.status}</span
>
</p>
<p class="mt-1 text-xs text-muted">
Last synced: {importSource.last_synced_ago || "Never"}
</p>
<p class="mt-1 text-xs text-muted">
Imported: {importSource.imported_count.toLocaleString()}
</p>
{#if importSource.last_error_message}
<p class="mt-1 text-xs text-red-300">
Last error: {importSource.last_error_message}
</p>
{/if}
</div>
<input type="hidden" name="_method" value="patch" />
{/if}
<form
method="post"
action={paths.heartbeat_import_source_path}
class="mt-3 space-y-3 rounded-md border border-surface-200 bg-surface p-3"
>
<div>
<label
for="import_endpoint_url"
class="mb-2 block text-sm text-surface-content"
>
Endpoint URL
</label>
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
id="import_endpoint_url"
type="url"
name="heartbeat_import_source[endpoint_url]"
required
value={importSource?.endpoint_url ||
"https://wakatime.com/api/v1"}
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
<div>
<label
for="import_api_key"
class="mb-2 block text-sm text-surface-content"
>
API Key
</label>
<input
id="import_api_key"
type="password"
name="heartbeat_import_source[encrypted_api_key]"
required={!importSource}
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
{#if importSource}
<input type="hidden" name="_method" value="patch" />
{/if}
<div>
<label
for="import_endpoint_url"
class="mb-2 block text-sm text-surface-content"
>
Endpoint URL
</label>
<input
id="import_endpoint_url"
type="url"
name="heartbeat_import_source[endpoint_url]"
required
value={importSource?.endpoint_url ||
"https://wakatime.com/api/v1"}
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
<div>
<label
for="import_api_key"
class="mb-2 block text-sm text-surface-content"
>
API Key
</label>
<input
id="import_api_key"
type="password"
name="heartbeat_import_source[encrypted_api_key]"
required={!importSource}
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
{#if importSource}
<p class="mt-1 text-xs text-muted">
Leave blank to keep the existing key.
</p>
{/if}
</div>
<div class="rounded-md border border-surface-200 bg-darker p-3">
<p class="text-sm font-semibold text-surface-content">
Backfill scope
<p class="mt-1 text-xs text-muted">
Leave blank to keep the existing key.
</p>
<div class="mt-2 flex flex-wrap items-center gap-4">
<label
class="inline-flex items-center gap-2 text-sm text-surface-content"
>
<input
type="radio"
name="backfill_mode"
value="all_time"
checked={backfillMode === "all_time"}
onchange={() => {
backfillMode = "all_time";
importStartDate = "";
importEndDate = "";
}}
class="peer sr-only"
/>
<span
class="h-4 w-4 rounded-full border border-surface-200 bg-surface ring-offset-2 ring-offset-surface transition peer-checked:border-primary peer-checked:bg-primary peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40"
></span>
All time
</label>
<label
class="inline-flex items-center gap-2 text-sm text-surface-content"
>
<input
type="radio"
name="backfill_mode"
value="date_range"
checked={backfillMode === "date_range"}
onchange={() => {
backfillMode = "date_range";
}}
class="peer sr-only"
/>
<span
class="h-4 w-4 rounded-full border border-surface-200 bg-surface ring-offset-2 ring-offset-surface transition peer-checked:border-primary peer-checked:bg-primary peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40"
></span>
Specific date range
</label>
</div>
{/if}
</div>
{#if backfillMode === "all_time"}
<div class="rounded-md border border-surface-200 bg-darker p-3">
<p class="text-sm font-semibold text-surface-content">
Backfill scope
</p>
<div class="mt-2 flex flex-wrap items-center gap-4">
<label
class="inline-flex items-center gap-2 text-sm text-surface-content"
>
<input
type="hidden"
name="heartbeat_import_source[initial_backfill_start_date]"
value=""
type="radio"
name="backfill_mode"
value="all_time"
checked={backfillMode === "all_time"}
onchange={() => {
backfillMode = "all_time";
importStartDate = "";
importEndDate = "";
}}
class="peer sr-only"
/>
<span
class="h-4 w-4 rounded-full border border-surface-200 bg-surface ring-offset-2 ring-offset-surface transition peer-checked:border-primary peer-checked:bg-primary peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40"
></span>
All time
</label>
<label
class="inline-flex items-center gap-2 text-sm text-surface-content"
>
<input
type="hidden"
name="heartbeat_import_source[initial_backfill_end_date]"
value=""
type="radio"
name="backfill_mode"
value="date_range"
checked={backfillMode === "date_range"}
onchange={() => {
backfillMode = "date_range";
}}
class="peer sr-only"
/>
{:else}
<div class="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">
<div>
<label
for="import_start_date"
class="mb-2 block text-sm text-surface-content"
>
Start date
</label>
<input
id="import_start_date"
type="date"
name="heartbeat_import_source[initial_backfill_start_date]"
bind:value={importStartDate}
required
class="w-full rounded-md border border-surface-200 bg-surface px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
<div>
<label
for="import_end_date"
class="mb-2 block text-sm text-surface-content"
>
End date
</label>
<input
id="import_end_date"
type="date"
name="heartbeat_import_source[initial_backfill_end_date]"
bind:value={importEndDate}
required
class="w-full rounded-md border border-surface-200 bg-surface px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
</div>
{/if}
<span
class="h-4 w-4 rounded-full border border-surface-200 bg-surface ring-offset-2 ring-offset-surface transition peer-checked:border-primary peer-checked:bg-primary peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40"
></span>
Specific date range
</label>
</div>
<div class="flex flex-wrap items-center gap-3">
{#if backfillMode === "all_time"}
<input
type="hidden"
name="heartbeat_import_source[sync_enabled]"
value="0"
name="heartbeat_import_source[initial_backfill_start_date]"
value=""
/>
<input
type="hidden"
name="heartbeat_import_source[initial_backfill_end_date]"
value=""
/>
{:else}
<div class="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">
<div>
<label
for="import_start_date"
class="mb-2 block text-sm text-surface-content"
>
Start date
</label>
<input
id="import_start_date"
type="date"
name="heartbeat_import_source[initial_backfill_start_date]"
bind:value={importStartDate}
required
class="w-full rounded-md border border-surface-200 bg-surface px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
<div>
<label
for="import_end_date"
class="mb-2 block text-sm text-surface-content"
>
End date
</label>
<input
id="import_end_date"
type="date"
name="heartbeat_import_source[initial_backfill_end_date]"
bind:value={importEndDate}
required
class="w-full rounded-md border border-surface-200 bg-surface px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
</div>
{/if}
</div>
<div class="flex flex-wrap items-center gap-3">
<input
type="hidden"
name="heartbeat_import_source[sync_enabled]"
value="0"
/>
<label
class="inline-flex items-center gap-2 text-sm text-surface-content"
>
<input
type="checkbox"
name="heartbeat_import_source[sync_enabled]"
value="1"
checked={importSource ? importSource.sync_enabled : true}
class="h-4 w-4 rounded border-surface-200 bg-surface text-primary"
/>
Continuous sync enabled
</label>
{#if importSource}
<label
class="inline-flex items-center gap-2 text-sm text-surface-content"
>
<input
type="checkbox"
name="heartbeat_import_source[sync_enabled]"
name="heartbeat_import_source[rerun_backfill]"
value="1"
checked={importSource ? importSource.sync_enabled : true}
class="h-4 w-4 rounded border-surface-200 bg-surface text-primary"
/>
Continuous sync enabled
Re-run backfill
</label>
{#if importSource}
<label
class="inline-flex items-center gap-2 text-sm text-surface-content"
{/if}
</div>
<div class="flex flex-wrap gap-2">
<Button type="submit" variant="primary">
{importSource ? "Update source" : "Create source"}
</Button>
</div>
</form>
{#if importSource}
<div class="mt-3 flex flex-wrap gap-2">
<form
method="post"
action={paths.heartbeat_import_source_sync_path}
>
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
/>
<Button type="submit" variant="surface">Sync now</Button>
</form>
<form
method="post"
action={paths.heartbeat_import_source_path}
onsubmit={(event) => {
if (!window.confirm("Remove import source configuration?")) {
event.preventDefault();
}
}}
>
<input type="hidden" name="_method" value="delete" />
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
/>
<Button type="submit" variant="surface">Remove source</Button>
</form>
</div>
{/if}
</section>
<section id="wakatime_mirror">
<h3 class="text-lg font-semibold text-surface-content">
Mirror to WakaTime
</h3>
{#if mirrors.length > 0}
<div class="mt-3 space-y-2">
{#each mirrors as mirror}
<div
class="rounded-md border border-surface-200 bg-surface p-3"
>
<p class="text-sm font-semibold text-surface-content">
{mirror.endpoint_url}
</p>
<p class="mt-1 text-xs text-muted">
Status: {mirror.enabled ? "enabled" : "paused"}
</p>
<p class="mt-1 text-xs text-muted">
Last synced: {mirror.last_synced_ago}
</p>
{#if mirror.last_error_message}
<p class="mt-1 text-xs text-red-300">
Last error: {mirror.last_error_message}
</p>
{/if}
{#if mirror.consecutive_failures && mirror.consecutive_failures > 0}
<p class="mt-1 text-xs text-muted">
Consecutive failures: {mirror.consecutive_failures}
</p>
{/if}
<form
method="post"
action={mirror.destroy_path}
class="mt-3"
onsubmit={(event) => {
if (!window.confirm("Delete this mirror endpoint?")) {
event.preventDefault();
}
}}
>
<input type="hidden" name="_method" value="delete" />
<input
type="checkbox"
name="heartbeat_import_source[rerun_backfill]"
value="1"
class="h-4 w-4 rounded border-surface-200 bg-surface text-primary"
type="hidden"
name="authenticity_token"
value={csrfToken}
/>
Re-run backfill
</label>
{/if}
</div>
<Button type="submit" variant="surface" size="xs">
Delete mirror
</Button>
</form>
</div>
{/each}
</div>
{/if}
<div class="flex flex-wrap gap-2">
<Button type="submit" variant="primary">
{importSource ? "Update source" : "Create source"}
</Button>
</div>
</form>
{#if importSource}
<div class="mt-3 flex flex-wrap gap-2">
<form
method="post"
action={paths.heartbeat_import_source_sync_path}
>
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
/>
<Button type="submit" variant="surface">Sync now</Button>
</form>
<form
method="post"
action={paths.heartbeat_import_source_path}
onsubmit={(event) => {
if (
!window.confirm("Remove import source configuration?")
) {
event.preventDefault();
}
}}
>
<input type="hidden" name="_method" value="delete" />
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
/>
<Button type="submit" variant="surface">Remove source</Button>
</form>
</div>
{/if}
</section>
<section id="wakatime_mirror">
<h3 class="text-lg font-semibold text-surface-content">
Mirror to WakaTime
</h3>
{#if mirrors.length > 0}
<div class="mt-3 space-y-2">
{#each mirrors as mirror}
<div
class="rounded-md border border-surface-200 bg-surface p-3"
>
<p class="text-sm font-semibold text-surface-content">
{mirror.endpoint_url}
</p>
<p class="mt-1 text-xs text-muted">
Status: {mirror.enabled ? "enabled" : "paused"}
</p>
<p class="mt-1 text-xs text-muted">
Last synced: {mirror.last_synced_ago}
</p>
{#if mirror.last_error_message}
<p class="mt-1 text-xs text-red-300">
Last error: {mirror.last_error_message}
</p>
{/if}
{#if mirror.consecutive_failures && mirror.consecutive_failures > 0}
<p class="mt-1 text-xs text-muted">
Consecutive failures: {mirror.consecutive_failures}
</p>
{/if}
<form
method="post"
action={mirror.destroy_path}
class="mt-3"
onsubmit={(event) => {
if (!window.confirm("Delete this mirror endpoint?")) {
event.preventDefault();
}
}}
>
<input type="hidden" name="_method" value="delete" />
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
/>
<Button type="submit" variant="surface" size="xs">
Delete mirror
</Button>
</form>
</div>
{/each}
</div>
{/if}
<form
method="post"
action={paths.user_wakatime_mirrors_path}
class="mt-3 space-y-3 rounded-md border border-surface-200 bg-surface p-3"
>
<form
method="post"
action={paths.user_wakatime_mirrors_path}
class="mt-3 space-y-3 rounded-md border border-surface-200 bg-surface p-3"
>
<input type="hidden" name="authenticity_token" value={csrfToken} />
<div>
<label
for="mirror_endpoint_url"
class="mb-2 block text-sm text-surface-content"
>
Endpoint URL
</label>
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
id="mirror_endpoint_url"
type="url"
name="wakatime_mirror[endpoint_url]"
value="https://wakatime.com/api/v1"
required
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
<div>
<label
for="mirror_endpoint_url"
class="mb-2 block text-sm text-surface-content"
>
Endpoint URL
</label>
<input
id="mirror_endpoint_url"
type="url"
name="wakatime_mirror[endpoint_url]"
value="https://wakatime.com/api/v1"
required
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
<div>
<label
for="mirror_key"
class="mb-2 block text-sm text-surface-content"
>
WakaTime API Key
</label>
<input
id="mirror_key"
type="password"
name="wakatime_mirror[encrypted_api_key]"
required
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
<Button type="submit" variant="primary">Add mirror</Button>
</form>
</section>
</div>
</section>
</div>
<div>
<label
for="mirror_key"
class="mb-2 block text-sm text-surface-content"
>
WakaTime API Key
</label>
<input
id="mirror_key"
type="password"
name="wakatime_mirror[encrypted_api_key]"
required
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
/>
</div>
<Button type="submit" variant="primary">Add mirror</Button>
</form>
</section>
</div>
{/if}
<section id="download_user_data">