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
1,309 changes: 750 additions & 559 deletions backend/dist/main.js

Large diffs are not rendered by default.

35 changes: 22 additions & 13 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,8 @@ func fetchProviders(apiBase, cluster, authHeader string) ([]installedExtension,
var payload struct {
Items []struct {
Metadata struct {
Name string `json:"name"`
Name string `json:"name"`
Labels map[string]string `json:"labels"`
} `json:"metadata"`
} `json:"items"`
}
Expand All @@ -516,9 +517,14 @@ func fetchProviders(apiBase, cluster, authHeader string) ([]installedExtension,
if it.Metadata.Name == "" {
continue
}
version := ""
if it.Metadata.Labels != nil {
version = it.Metadata.Labels["app.kubernetes.io/version"]
}
out = append(out, installedExtension{
Name: it.Metadata.Name,
Type: "provider",
Name: it.Metadata.Name,
Type: "provider",
Version: version,
})
}
return out, nil
Expand All @@ -533,7 +539,8 @@ func fetchPlugins(apiBase, authHeader string) ([]installedExtension, error) {
return nil, nil
}
var items []struct {
Name string `json:"name"`
Name string `json:"name"`
Version string `json:"version"`
}
if err := json.Unmarshal(body, &items); err != nil {
return nil, fmt.Errorf("decode plugins: %w", err)
Expand All @@ -544,8 +551,9 @@ func fetchPlugins(apiBase, authHeader string) ([]installedExtension, error) {
continue
}
out = append(out, installedExtension{
Name: it.Name,
Type: "plugin",
Name: it.Name,
Type: "plugin",
Version: it.Version,
})
}
return out, nil
Expand Down Expand Up @@ -643,8 +651,11 @@ func makeSummaryHandler(cache *catalogCache, icons *iconProxy) http.HandlerFunc
out.InstalledError = installedErr.Error()
}
for _, ext := range index.Extensions {
_, ok := installedByKey[ext.Type+":"+ext.Name]
ie, ok := installedByKey[ext.Type+":"+ext.Name]
ext.Installed = ok
if ok && ie.Version != "" {
ext.InstalledVersion = ie.Version
}
ext.Icon = icons.register(ext.Icon)
out.Extensions = append(out.Extensions, ext)
}
Expand Down Expand Up @@ -699,13 +710,11 @@ type extensionSummary struct {
}

// installedExtension is a normalized view of "something currently installed".
// Type is "plugin" or "provider". We intentionally do not surface a version
// — the upstream sources (helm chart appVersion label vs. plugin manifest
// version) don't agree on a meaningful value, so the field would mislead more
// than it helps.
// Type is "plugin" or "provider".
type installedExtension struct {
Name string `json:"name"`
Type string `json:"type"`
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version,omitempty"`
}

type summaryResponse struct {
Expand Down
90 changes: 90 additions & 0 deletions src/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,93 @@ export function helmInstallCommand(entry: CatalogEntry): string {
` -n ${namespace}`,
].join('\n');
}

interface ParsedVersion {
numbers: number[];
prerelease: string[] | null;
}


function parseSemver(v: string): ParsedVersion {
const clean = v.trim().replace(/^v/i, '').split('+')[0];
const [numPart, ...preParts] = clean.split('-');
const numbers = numPart.split('.').map((n) => parseInt(n, 10) || 0);
const prerelease = preParts.length > 0 ? preParts.join('-').split('.') : null;
return { numbers, prerelease };
}


function compareSemver(v1: string, v2: string): number {
const a = parseSemver(v1);
const b = parseSemver(v2);

// compare major, minor, patch numbers
const len = Math.max(a.numbers.length, b.numbers.length);
for (let i = 0; i < len; i++) {
const numA = a.numbers[i] || 0;
const numB = b.numbers[i] || 0;
if (numA < numB) return -1;
if (numA > numB) return 1;
}

// Normal version has HIGHER precedence than a pre-release
if (!a.prerelease && b.prerelease) return 1;
if (a.prerelease && !b.prerelease) return -1;
if (!a.prerelease && !b.prerelease) return 0;

// Both are prereleases: compare identifier by identifier
const preA = a.prerelease!;
const preB = b.prerelease!;
const preLen = Math.max(preA.length, preB.length);

for (let i = 0; i < preLen; i++) {
const idA = preA[i];
const idB = preB[i];

if (idA === undefined) return -1;
if (idB === undefined) return 1;

const isNumA = /^\d+$/.test(idA);
const isNumB = /^\d+$/.test(idB);

if (isNumA && isNumB) {
const numA = parseInt(idA, 10);
const numB = parseInt(idB, 10);
if (numA < numB) return -1;
if (numA > numB) return 1;
} else if (isNumA && !isNumB) {
return -1;
} else if (!isNumA && isNumB) {
return 1;
} else {
const cmp = idA.localeCompare(idB);
if (cmp !== 0) return cmp < 0 ? -1 : 1;
}
}

return 0;
}

// Returns true if latestVersion is newer than installedVersion
export function isVersionOutdated(
installedVersion?: string,
latestVersion?: string | null,
): boolean {
if (!installedVersion || !latestVersion) return false;
return compareSemver(installedVersion, latestVersion) < 0;
}

// Generates the helm upgrade command for outdated extensions
export function helmUpgradeCommand(entry: CatalogEntry): string {
const chart = entry.artifacts?.chart;
const channel = chart?.defaultChannel ?? Object.keys(chart?.channels ?? {})[0] ?? '';
const ref = chart?.channels?.[channel]?.ref ?? '<chart-ref>';
const version = chart?.channels?.[channel]?.version ?? '<version>';
const release = entry.install?.helm?.releaseName ?? entry.name;
const namespace = entry.install?.helm?.namespace ?? 'everest-system';
return [
`helm upgrade ${release} ${ref} \\`,
` --version ${version} \\`,
` -n ${namespace}`,
].join('\n');
}
Loading
Loading