Skip to content
Draft
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
12 changes: 11 additions & 1 deletion pkg/suse-ai-rancher-ext/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,17 @@ module.exports = {
'vue/one-component-per-file': 'off',
'vue/no-deprecated-slot-attribute': 'off',
'vue/require-explicit-emits': 'off',
'vue/v-on-event-hyphenation': 'off'
'vue/v-on-event-hyphenation': 'off',
'vue/max-attributes-per-line': ['warn', {
'singleline': {
'max': 3
},
'multiline': {
'max': 1
}
}],
'vue/singleline-html-element-content-newline': 'off',
'vue/html-indent': ['warn', 2]
},
overrides: [
{
Expand Down
2 changes: 1 addition & 1 deletion pkg/suse-ai-rancher-ext/formatters/ClusterChips.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="cluster-chips" v-if="clusters.length">
<div v-if="clusters.length" class="cluster-chips">
<small v-if="showLabel" class="status-label">{{ label }}:</small>
<span
v-for="cluster in clusters"
Expand Down
10 changes: 5 additions & 5 deletions pkg/suse-ai-rancher-ext/pages/AppInstances.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<div class="breadcrumb-nav">
<button
class="btn btn-link breadcrumb-link"
@click="navigateToApps"
:aria-label="t('suseai.apps.backToApps', 'Back to Apps')"
@click="navigateToApps"
>
<i class="icon icon-arrow-left" aria-hidden="true" />
{{ t('suseai.apps.title', 'Apps') }}
Expand Down Expand Up @@ -75,23 +75,23 @@
<div class="right-actions">
<button
class="btn role-primary"
@click="onInstall"
:disabled="loading"
:title="t('suseai.instances.installNew', 'Install new instance')"
:aria-label="t('suseai.instances.installNew', 'Install new instance')"
type="button"
@click="onInstall"
>
<i class="icon icon-plus" aria-hidden="true" />
{{ t('suseai.instances.install', 'Install') }}
</button>

<button
class="btn role-secondary"
@click="refresh"
:disabled="loading"
:title="t('suseai.instances.refresh', 'Refresh instances')"
:aria-label="loading ? 'Refreshing instances...' : 'Refresh instances'"
type="button"
@click="refresh"
>
<i v-if="loading" class="icon icon-spinner icon-spin" aria-hidden="true" />
<i v-else class="icon icon-refresh" aria-hidden="true" />
Expand Down Expand Up @@ -189,20 +189,20 @@
<div class="btn-group" role="group" :aria-label="`Actions for ${instance.instanceName || instance.releaseName}`">
<button
class="btn btn-sm role-secondary"
@click="onManage(instance)"
:disabled="!canManage(instance)"
:title="t('suseai.instances.manage', 'Manage instance')"
:aria-label="`Manage ${instance.instanceName || instance.releaseName}`"
@click="onManage(instance)"
>
<i class="icon icon-edit" aria-hidden="true" />
{{ t('suseai.instances.manage', 'Manage') }}
</button>
<button
class="btn btn-sm role-secondary text-error"
@click="onDelete(instance)"
:disabled="!canDelete(instance) || deletingInstances.has(getInstanceKey(instance))"
:title="t('suseai.instances.delete', 'Delete instance')"
:aria-label="`Delete ${instance.instanceName || instance.releaseName}`"
@click="onDelete(instance)"
>
<i v-if="deletingInstances.has(getInstanceKey(instance))" class="icon icon-spinner icon-spin" aria-hidden="true" />
<i v-else class="icon icon-delete" aria-hidden="true" />
Expand Down
229 changes: 124 additions & 105 deletions pkg/suse-ai-rancher-ext/pages/Apps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,33 +56,33 @@
<div class="view-controls" role="group" aria-label="View mode selection">
<button
:class="['btn', 'btn-sm', viewMode === 'tiles' ? 'role-primary' : 'role-secondary']"
@click="viewMode = 'tiles'"
:title="t('suseai.apps.tileView', 'Tile View')"
:aria-label="t('suseai.apps.tileView', 'Tile View')"
:aria-pressed="viewMode === 'tiles'"
type="button"
@click="viewMode = 'tiles'"
>
<i class="icon icon-th view-icon-grid" aria-hidden="true" />
</button>
<button
:class="['btn', 'btn-sm', viewMode === 'list' ? 'role-primary' : 'role-secondary']"
@click="viewMode = 'list'"
:title="t('suseai.apps.listView', 'List View')"
:aria-label="t('suseai.apps.listView', 'List View')"
:aria-pressed="viewMode === 'list'"
type="button"
@click="viewMode = 'list'"
>
<i class="icon icon-th-list view-icon-list" aria-hidden="true" />
</button>
</div>

<button
class="btn role-primary"
@click="refresh"
:disabled="loading"
:title="t('suseai.apps.refresh', 'Refresh')"
:aria-label="loading ? 'Refreshing applications...' : 'Refresh applications'"
type="button"
@click="refresh"
>
<i v-if="loading" class="icon icon-spinner icon-spin" aria-hidden="true" />
<i v-else class="icon icon-refresh" aria-hidden="true" />
Expand All @@ -97,7 +97,12 @@
</div>

<!-- Search results count -->
<div v-if="search && !loading" id="search-results-count" class="sr-only" aria-live="polite">
<div
v-if="search && !loading"
id="search-results-count"
class="sr-only"
aria-live="polite"
>
{{ filteredApps.length }} {{ filteredApps.length === 1 ? 'application' : 'applications' }} found for "{{ search }}"
</div>

Expand Down Expand Up @@ -130,102 +135,47 @@
</div>

<!-- Tiles view -->
<div v-if="viewMode === 'tiles'" class="tiles-grid" role="grid" aria-label="Applications grid">
<div
v-for="app in filteredApps"
:key="app.slug_name"
class="app-tile clickable-tile"
@click="onTileClick(app)"
:aria-label="`View instances of ${app.name}`"
role="button"
tabindex="0"
@keydown.enter="onTileClick(app)"
@keydown.space.prevent="onTileClick(app)"
v-if="viewMode === 'tiles'"
class="tiles-grid"
role="grid"
aria-label="Applications grid"
>
<div class="tile-header">
<img :src="logoFor(app)" alt="" @error="onImgError($event)" class="tile-logo" />
<div class="tile-info">
<h3 class="tile-title">{{ app.name }}</h3>
<div class="tile-meta">
<span v-if="app.packaging_format" class="badge-state" :class="getBadgeClass(app.packaging_format)">
{{ formatPackagingType(app.packaging_format) }}
</span>
</div>
</div>
</div>

<div class="tile-content">
<p class="tile-description">{{ app.description || '—' }}</p>

<!-- Installation status -->
<div v-if="getInstallationInfo(app.slug_name).installed" class="install-status">
<small class="status-label">Installed in:</small>
<div class="cluster-chips">
<span
v-for="cluster in getInstallationInfo(app.slug_name).clusters"
:key="cluster"
class="cluster-chip"
:title="`${getInstallationInfo(app.slug_name).namespace}/${getInstallationInfo(app.slug_name).release}`"
>
{{ getClusterDisplayName(cluster) }}
</span>
<div
v-for="app in filteredApps"
:key="app.slug_name"
class="app-tile clickable-tile"
:aria-label="`View instances of ${app.name}`"
role="button"
tabindex="0"
@click="onTileClick(app)"
@keydown.enter="onTileClick(app)"
@keydown.space.prevent="onTileClick(app)"
>
<div class="tile-header">
<img
:src="logoFor(app)"
alt=""
class="tile-logo"
@error="onImgError($event)"
/>
<div class="tile-info">
<h3 class="tile-title">{{ app.name }}</h3>
<div class="tile-meta">
<span v-if="app.packaging_format" class="badge-state" :class="getBadgeClass(app.packaging_format)">
{{ formatPackagingType(app.packaging_format) }}
</span>
</div>
</div>
</div>
</div>

</div>
</div>

<!-- List view -->
<div v-else class="list-view">
<table class="table">
<thead>
<tr>
<th>{{ t('suseai.apps.name', 'Name') }}</th>
<th>{{ t('suseai.apps.description', 'Description') }}</th>
<th>{{ t('suseai.apps.clusters', 'Clusters') }}</th>
<th class="text-right">{{ t('suseai.apps.actions', 'Actions') }}</th>
</tr>
</thead>
<tbody>
<tr v-if="!filteredApps.length" class="empty-row">
<td colspan="4" class="text-center text-muted">{{ t('suseai.apps.noApps', 'No applications found') }}</td>
</tr>
<tr
v-else
v-for="app in filteredApps"
:key="app.slug_name"
class="main-row clickable-row"
@click="onTileClick(app)"
:aria-label="`View instances of ${app.name}`"
role="button"
tabindex="0"
@keydown.enter="onTileClick(app)"
@keydown.space.prevent="onTileClick(app)"
>
<!-- Name column with logo -->
<td class="col-name">
<div class="name-cell">
<img :src="logoFor(app)" alt="" @error="onImgError($event)" class="table-logo" />
<div class="name-info">
<div class="app-name">{{ app.name }}</div>
<div v-if="app.packaging_format" class="app-meta">
<span class="badge-state badge-sm" :class="getBadgeClass(app.packaging_format)">
{{ formatPackagingType(app.packaging_format) }}
</span>
</div>
</div>
</div>
</td>

<!-- Description -->
<td class="col-description">
<span class="text-muted">{{ app.description || '—' }}</span>
</td>
<div class="tile-content">
<p class="tile-description">{{ app.description || '—' }}</p>

<!-- Clusters -->
<td class="col-clusters">
<div v-if="getInstallationInfo(app.slug_name).installed" class="cluster-chips">
<!-- Installation status -->
<div v-if="getInstallationInfo(app.slug_name).installed" class="install-status">
<small class="status-label">Installed in:</small>
<div class="cluster-chips">
<span
v-for="cluster in getInstallationInfo(app.slug_name).clusters"
:key="cluster"
Expand All @@ -235,17 +185,86 @@
{{ getClusterDisplayName(cluster) }}
</span>
</div>
<span v-else class="text-muted">—</span>
</td>

<!-- Actions -->
<td class="col-actions text-right">
<i class="icon icon-chevron-right" aria-hidden="true"></i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

<!-- List view -->
<div v-else class="list-view">
<table class="table">
<thead>
<tr>
<th>{{ t('suseai.apps.name', 'Name') }}</th>
<th>{{ t('suseai.apps.description', 'Description') }}</th>
<th>{{ t('suseai.apps.clusters', 'Clusters') }}</th>
<th class="text-right">{{ t('suseai.apps.actions', 'Actions') }}</th>
</tr>
</thead>
<tbody>
<tr v-if="!filteredApps.length" class="empty-row">
<td colspan="4" class="text-center text-muted">{{ t('suseai.apps.noApps', 'No applications found') }}</td>
</tr>
<tr
v-for="app in filteredApps"
v-else
:key="app.slug_name"
class="main-row clickable-row"
:aria-label="`View instances of ${app.name}`"
role="button"
tabindex="0"
@click="onTileClick(app)"
@keydown.enter="onTileClick(app)"
@keydown.space.prevent="onTileClick(app)"
>
<!-- Name column with logo -->
<td class="col-name">
<div class="name-cell">
<img
:src="logoFor(app)"
alt=""
class="table-logo"
@error="onImgError($event)"
/>
<div class="name-info">
<div class="app-name">{{ app.name }}</div>
<div v-if="app.packaging_format" class="app-meta">
<span class="badge-state badge-sm" :class="getBadgeClass(app.packaging_format)">
{{ formatPackagingType(app.packaging_format) }}
</span>
</div>
</div>
</div>
</td>

<!-- Description -->
<td class="col-description">
<span class="text-muted">{{ app.description || '—' }}</span>
</td>

<!-- Clusters -->
<td class="col-clusters">
<div v-if="getInstallationInfo(app.slug_name).installed" class="cluster-chips">
<span
v-for="cluster in getInstallationInfo(app.slug_name).clusters"
:key="cluster"
class="cluster-chip"
:title="`${getInstallationInfo(app.slug_name).namespace}/${getInstallationInfo(app.slug_name).release}`"
>
{{ getClusterDisplayName(cluster) }}
</span>
</div>
<span v-else class="text-muted">—</span>
</td>

<!-- Actions -->
<td class="col-actions text-right">
<i class="icon icon-chevron-right" aria-hidden="true"></i>
</td>
</tr>
</tbody>
</table>
</div>

<!-- Empty state content -->
<div v-if="!loading && !filteredApps.length && !error" class="empty-state-content">
Expand Down
4 changes: 2 additions & 2 deletions pkg/suse-ai-rancher-ext/pages/components/AppWizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -619,9 +619,9 @@ function previousStep() {
<!-- Step: Target Cluster -->
<TargetStep
v-else-if="currentStep === 1"
:mode="props.mode"
v-model:cluster="form.cluster"
v-model:clusters="form.clusters"
:mode="props.mode"
:app-slug="props.slug"
:app-name="(route.query.n as string) || props.slug"
/>
Expand All @@ -642,6 +642,7 @@ function previousStep() {
<!-- Step: Review -->
<ReviewStep
v-else-if="currentStep === 3"
v-model:values="form.values"
:mode="props.mode"
:release="form.release"
:namespace="form.namespace"
Expand All @@ -650,7 +651,6 @@ function previousStep() {
:chart-version="form.chartVersion"
:cluster="form.cluster"
:clusters="form.clusters"
v-model:values="form.values"
@values-edited="onValuesEdited"
/>
</div>
Expand Down
Loading