Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ GITDB_AUTHOR_NAME=gitops
GITDB_AUTHOR_EMAIL=gitops@example.com
GITDB_SYNC_POLL_SECONDS=60

# Optional dedicated key for the vault secret values (AES-256-GCM).
# Falls back to GITDB_ENCRYPTION_KEY. Rotating it makes existing secrets unreadable.
# VAULT_ENCRYPTION_KEY=

# Optional HTTP(S) authentication for the GitDB repository
# GITDB_TOKEN=
# GITDB_USERNAME=
Expand Down
4 changes: 2 additions & 2 deletions e2e/specs/vault-stateiac.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test.describe('vault — secrets:read alone is enough', () => {
loginAs,
}) => {
await loginAs('vaultSecretsOnly');
await expectAllowed(page, `${base}/vault`);
await expectAllowed(page, `${base}/vault/dev`);
});
});

Expand All @@ -21,7 +21,7 @@ test.describe('vault — environments:read alone is enough', () => {
loginAs,
}) => {
await loginAs('vaultEnvironmentsOnly');
await expectAllowed(page, `${base}/vault`);
await expectAllowed(page, `${base}/vault/dev`);
});
});

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gitops",
"private": true,
"version": "1.2.3",
"version": "1.3.0",
"type": "module",
"scripts": {
"dev": "vite dev",
Expand Down
3 changes: 3 additions & 0 deletions src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.png" sizes="16x16" type="image/png" />
<link rel="icon" href="/icono_258.png" sizes="512x512" type="image/png" />
<link rel="apple-touch-icon" href="/icono_128.png" sizes="180x180" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
Expand Down
14 changes: 13 additions & 1 deletion src/lib/components/AppSidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
Lock,
PanelLeftClose,
PanelLeftOpen,
Plug,
ScrollText,
Settings,
Shield,
Expand Down Expand Up @@ -110,12 +111,23 @@
{
name: $_('sidebar.modules.vault'),
icon: Shield,
base: `${projectBase}/vault`,
items: [
{
label: $_('sidebar.modules.vault'),
label: 'Secrets',
href: `${projectBase}/vault`,
icon: Shield,
},
{
label: 'Integrations',
href: `${projectBase}/vault/integrations`,
icon: Plug,
},
{
label: 'Settings',
href: `${projectBase}/vault/settings`,
icon: Settings,
},
],
},
],
Expand Down
40 changes: 28 additions & 12 deletions src/lib/components/Dropdown.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import { createEventDispatcher } from 'svelte';
import { ChevronDown, Check } from '@lucide/svelte';

export let options: Array<{ id: string; name: string }> = [];
export let options: Array<{ id: string; name: string; disabled?: boolean; badge?: string }> = [];
export let value = '';
export let ariaLabel = 'Select option';
export let disabled = false;

let isOpen = false;

Expand All @@ -13,29 +14,32 @@
$: selected = options.find((option) => option.id === value) || options[0] || null;

function toggleOpen() {
if (disabled) return;
isOpen = !isOpen;
}

function close() {
isOpen = false;
}

function selectOption(id: string) {
dispatch('change', { id });
function selectOption(option: { id: string; disabled?: boolean }) {
if (option.disabled) return;
dispatch('change', { id: option.id });
close();
}
</script>

<div class="relative inline-block text-left">
<button
type="button"
class="btn-secondary inline-flex items-center gap-2 rounded-md px-2.5 py-1 text-sm font-medium text-slate-800"
class="btn-secondary inline-flex items-center gap-2 rounded-md px-2.5 py-1 text-sm font-medium text-slate-800 disabled:cursor-not-allowed disabled:opacity-50"
aria-haspopup="listbox"
aria-expanded={isOpen}
aria-label={ariaLabel}
{disabled}
on:click={toggleOpen}
>
<span class="max-w-[220px] truncate">{selected?.name || 'Select'}</span>
<span class="max-w-55 truncate">{selected?.name || 'Select'}</span>
<ChevronDown class="h-4 w-4 text-slate-500" />
</button>

Expand All @@ -53,17 +57,29 @@
<li>
<button
type="button"
class="btn-ghost flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm transition-colors {option.id === value
? 'bg-slate-100 text-slate-900'
: 'text-slate-700 hover:bg-slate-50'}"
class="btn-ghost flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm transition-colors {option.disabled
? 'cursor-not-allowed text-slate-400'
: option.id === value
? 'bg-slate-100 text-slate-900'
: 'text-slate-700 hover:bg-slate-50'}"
role="option"
aria-selected={option.id === value}
on:click={() => selectOption(option.id)}
aria-disabled={option.disabled}
on:click={() => selectOption(option)}
>
<span class="truncate">{option.name}</span>
{#if option.id === value}
<Check class="h-4 w-4 text-slate-600" />
{/if}
<span class="flex shrink-0 items-center gap-2">
{#if option.badge}
<span
class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-slate-500"
>
{option.badge}
</span>
{/if}
{#if option.id === value}
<Check class="h-4 w-4 text-slate-600" />
{/if}
</span>
</button>
</li>
{/each}
Expand Down
71 changes: 65 additions & 6 deletions src/lib/components/Footer.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
import { BookOpen, Heart, MessageCircle } from '@lucide/svelte';
import { BookOpen, Info, MessageCircle } from '@lucide/svelte';
import GitDbStatusBadge from './GitDbStatusBadge.svelte';
import { _ } from '$lib/i18n';
import packageJson from '../../../package.json';

const POLL_MS = 15_000;

Expand All @@ -13,6 +14,27 @@
state: 'unconfigured',
label: 'Checking sync status...',
};
let latestRelease: { tag_name: string; html_url: string } | null = null;

function versionParts(version: string) {
return version
.replace(/^v/i, '')
.split(/[.-]/, 3)
.map((part) => Number.parseInt(part, 10) || 0);
}

function isNewerVersion(candidate: string, current: string) {
const candidateParts = versionParts(candidate);
const currentParts = versionParts(current);

for (let index = 0; index < 3; index += 1) {
if (candidateParts[index] !== currentParts[index]) {
return candidateParts[index] > currentParts[index];
}
}

return false;
}

async function refresh() {
try {
Expand All @@ -24,23 +46,60 @@
}
}

async function checkForUpdates() {
try {
const response = await fetch('https://api.github.com/repos/getgitops/gitops/releases/latest');
if (!response.ok) return;

const release = (await response.json()) as { tag_name?: string; html_url?: string };
if (
release.tag_name &&
release.html_url &&
isNewerVersion(release.tag_name, packageJson.version)
) {
latestRelease = { tag_name: release.tag_name, html_url: release.html_url };
}
} catch {
latestRelease = null;
}
}

onMount(() => {
void refresh();
void checkForUpdates();
const timer = setInterval(refresh, POLL_MS);
return () => clearInterval(timer);
});
</script>

<footer class="app-shell-footer mt-auto border-t border-[#142236] bg-[#05101d]/82 backdrop-blur">
<div class="mx-auto flex w-full max-w-[1600px] flex-col gap-4 px-6 py-6 sm:px-8 lg:flex-row lg:items-center lg:justify-between">
<div class="flex items-center gap-3 text-sm text-slate-400">
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-[#1b2b42] bg-[#071323] text-[#0d7dff]">
<Heart class="h-4 w-4" />
</span>
<p>{$_('footer.developedWithLove')} <span class="font-semibold text-slate-200">{$_('footer.community')}</span></p>
<div class="flex items-center text-sm text-slate-400">
<p>
{$_('footer.currentVersion')}:
<a
href="https://getgitops.com/changelog"
target="_blank"
rel="noopener noreferrer"
class="font-semibold text-slate-200 transition-colors hover:text-white"
>v{packageJson.version}</a>
</p>
</div>

<div class="flex flex-wrap items-center gap-3 sm:gap-4">
{#if latestRelease}
<a
href={latestRelease.html_url}
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 rounded-lg border border-[#1b2b42] bg-[#071323] px-3 py-2 text-sm font-medium text-slate-300 transition-colors hover:border-[#294467] hover:bg-[#102139] hover:text-white"
aria-label={`${$_('footer.updateAvailable')}: ${latestRelease.tag_name}`}
>
<Info class="h-4 w-4" />
{$_('footer.update')}
</a>
{/if}

{#if showSyncStatus}
{#if syncStatusHref}
<a
Expand Down
4 changes: 4 additions & 0 deletions src/lib/config/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ const permissions = {
resource: 'environments',
permissions: grantsFor('project:vault', 'environments'),
},
settings: {
resource: 'settings',
permissions: grantsFor('project:vault', 'settings'),
},
},
},
codereport: {
Expand Down
Loading
Loading