Skip to content

Commit

Permalink
Merge pull request #28 from krumIO/feat/APPLAUNCHER-34__global_filter
Browse files Browse the repository at this point in the history
feat: for APPLAUNCHER-34, APPLAUNCHER-32 filter changes
  • Loading branch information
Sinjhin authored Apr 10, 2024
2 parents b478b48 + 84ddee7 commit 0fa04a3
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 112 deletions.
2 changes: 1 addition & 1 deletion pkg/app-launcher/components/AppLauncherCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default {
</script>
<template>
<Card class="app-launcher-card" :show-highlight-border="false" :sticky="true">
<Card class="app-launcher-card" :show-highlight-border="false" :sticky="true" v-if="app">
<template #title>
<div style="width: 100%">
<p style="font-size: 1.2rem">
Expand Down
250 changes: 141 additions & 109 deletions pkg/app-launcher/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default {
// Set the first cluster as the selected cluster
if (this.servicesByCluster.length > 0) {
this.selectedCluster = this.servicesByCluster[0].id;
this.selectedCluster = "ALL_CLUSTERS";
}
this.generateClusterOptions();
Expand Down Expand Up @@ -165,10 +165,10 @@ export default {
);
},
generateClusterOptions() {
this.clusterOptions = this.servicesByCluster.map((cluster) => ({
this.clusterOptions = [{ label: 'All Clusters', value: 'ALL_CLUSTERS' }, ...this.servicesByCluster.map(cluster => ({
label: cluster.name,
value: cluster.id,
}));
}))];
},
toggleSortOrder() {
this.tableHeaders[0].sortOrder = this.tableHeaders[0].sortOrder === 'asc' ? 'desc' : 'asc';
Expand Down Expand Up @@ -236,15 +236,29 @@ export default {
relatedIngress,
};
});
const filteredApps = this.filteredApps(services, ingresses);
return {
...cluster,
services,
ingresses,
filteredApps,
};
}
return null;
},
displayedClusterData() {
if (this.selectedCluster === 'ALL_CLUSTERS') {
return this.servicesByCluster.map(cluster => ({
...cluster,
ingresses: this.ingressesByCluster.find(ingressCluster => ingressCluster.id === cluster.id)?.ingresses || [],
filteredApps: this.filteredApps(cluster.services, this.ingressesByCluster.find(ingressCluster => ingressCluster.id === cluster.id)?.ingresses || []),
}));
} else {
return [this.selectedClusterData]; // This just remakes use of selectedClusterData for single cluster view
}
},
sortedApps() {
if (this.selectedClusterData) {
const services = this.selectedClusterData.services.map((service) => ({
Expand Down Expand Up @@ -272,18 +286,30 @@ export default {
return [];
},
filteredApps() {
if (this.searchQuery.trim() === '') {
return this.sortedApps;
} else {
const searchTerm = this.searchQuery.trim().toLowerCase();
return this.sortedApps.filter(app => {
return (app.metadata.name.toLowerCase().includes(searchTerm) ||
app.metadata.namespace.toLowerCase().includes(searchTerm) ||
app.metadata.labels?.['app.kubernetes.io/version']?.toLowerCase().includes(searchTerm) ||
app.metadata.labels?.['helm.sh/chart']?.toLowerCase().includes(searchTerm) ||
app.metadata.fields.includes(searchTerm));
return (services, ingresses) => {
const sortedApps = [...(services || []), ...(ingresses || {})].sort((a, b) => {
const nameA = a.metadata.name.toLowerCase();
const nameB = b.metadata.name.toLowerCase();
if (this.tableHeaders[0].sortOrder === 'asc') {
return nameA.localeCompare(nameB);
} else {
return nameB.localeCompare(nameA);
}
});
}
if (this.searchQuery.trim() === '') {
return sortedApps;
} else {
const searchTerm = this.searchQuery.trim().toLowerCase();
return sortedApps.filter(app => {
return (app.metadata.name.toLowerCase().includes(searchTerm) ||
app.metadata.namespace.toLowerCase().includes(searchTerm) ||
app.metadata.labels?.['app.kubernetes.io/version']?.toLowerCase().includes(searchTerm) ||
app.metadata.labels?.['helm.sh/chart']?.toLowerCase().includes(searchTerm) ||
app.metadata.fields.includes(searchTerm));
});
}
};
},
sortedServices() {
if (this.selectedClusterData) {
Expand All @@ -306,108 +332,103 @@ export default {
<template>
<Loading v-if="loading" :label="$store.getters['i18n/t']('appLauncher.loading')" />
<div v-else>
<div v-if="favoritedServices.length > 0">
<h2>{{ t('appLauncher.globalApps') }}</h2>
<div class="services-by-cluster-grid">
<AppLauncherCard
v-for="favoritedService in favoritedServices"
:key="`${favoritedService.clusterId}-${favoritedService.service.id}`"
:cluster-id="favoritedService.clusterId"
:service="favoritedService.service"
:favorited-services="favoritedServices"
@toggle-favorite="toggleFavorite"
/>
<div v-else class="main-container">
<div class="cluster-actions">
<div class="search-input">
<input v-model="searchQuery" :placeholder="$store.getters['i18n/t']('appLauncher.filter')" />
</div>
<button class="icon-button" @click="toggleSortOrder" v-if="selectedView === 'grid'">
<i class="icon icon-sort" />
</button>
<div class="select-wrapper">
<select v-model="selectedCluster" class="cluster-select">
<option v-for="option in clusterOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
<button class="icon-button" @click="selectedView = 'grid'">
<i class="icon icon-apps" />
</button>
<button class="icon-button" @click="selectedView = 'list'">
<i class="icon icon-list-flat" />
</button>
</div>
<div class="cluster-header">
<h1>{{ selectedCluster ? getClusterName(selectedCluster) : $store.getters['i18n/t']('appLauncher.selectCluster') }}</h1>
<div class="cluster-actions">
<button class="icon-button" @click="toggleSortOrder" v-if="selectedView === 'grid'">
<i class="icon icon-sort" />
</button>
<div class="select-wrapper">
<select v-model="selectedCluster" class="cluster-select">
<option v-for="option in clusterOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
<button class="icon-button" @click="selectedView = 'grid'">
<i class="icon icon-apps" />
</button>
<button class="icon-button" @click="selectedView = 'list'">
<i class="icon icon-list-flat" />
</button>
<div v-if="favoritedServices.length > 0">
<div class="cluster-header">
<h2>{{ t('appLauncher.globalApps') }}</h2>
</div>
<div class="services-by-cluster-grid">
<AppLauncherCard v-for="favoritedService in favoritedServices"
:key="`${favoritedService.clusterId}-${favoritedService.service.id}`" :cluster-id="favoritedService.clusterId"
:service="favoritedService.service" :favorited-services="favoritedServices"
@toggle-favorite="toggleFavorite" />
</div>
</div>
<div v-if="selectedCluster">
<div v-if="selectedView === 'grid'">
<div class="search-input">
<input v-model="searchQuery" :placeholder="$store.getters['i18n/t']('appLauncher.filter')" />
</div>
<div class="services-by-cluster-grid">
<template v-if="filteredApps.length === 0">
<p>{{ $store.getters['i18n/t']('appLauncher.noAppsFound') }}</p>
</template>
<AppLauncherCard
v-else
v-for="app in filteredApps"
:key="app.uniqueId"
:cluster-id="selectedCluster"
:service="app.type === 'service' ? app : null"
:ingress="app.type === 'ingress' ? app : null"
:favorited-services="favoritedServices"
@toggle-favorite="toggleFavorite"
/>
<div v-for="clusterData in displayedClusterData" :key="clusterData.id">
<div class="cluster-header">
<h1>
{{ clusterData.name }}
</h1>
</div>
<div class="services-by-cluster-grid">
<AppLauncherCard
v-for="app in clusterData.filteredApps"
:key="app.uniqueId"
:cluster-id="clusterData.id"
:service="app.type === 'service' ? app : null"
:ingress="app.type === 'ingress' ? app : null"
:favorited-services="favoritedServices"
@toggle-favorite="toggleFavorite"
/>
</div>
</div>
</div>
<div v-else-if="selectedView === 'list'">
<SortableTable
:rows="sortedServices"
:headers="tableHeaders"
:row-key="'id'"
:search="true"
:table-actions="false"
:row-actions="false"
:no-rows-text="$store.getters['i18n/t']('appLauncher.noServicesFound')"
>
<template #cell:name="{row}">
{{ row.metadata.name }}
</template>
<template #cell:namespace="{row}">
{{ row.metadata.namespace }}
</template>
<template #cell:version="{row}">
{{ row.metadata.labels?.['app.kubernetes.io/version'] }}
</template>
<template #cell:helmChart="{row}">
{{ row.metadata.labels?.['helm.sh/chart'] }}
</template>
<template #cell:actions="{row}">
<div style="display: flex; justify-content: flex-end;">
<button class="icon-button favorite-icon" @click="toggleFavorite(row)">
<i :class="['icon', isFavorited(row, favoritedServices) ? 'icon-star' : 'icon-star-open']" />
</button>
<a
v-if="getEndpoints(row)?.length <= 1"
:href="getEndpoints(row)[0]?.value"
target="_blank"
rel="noopener noreferrer nofollow"
class="btn role-primary"
>
{{ t('appLauncher.launch') }}
</a>
<ButtonDropDown
v-else
:button-label="t('appLauncher.launch')"
:dropdown-options="getEndpoints(row)"
:title="t('appLauncher.launchAnEndpointFromSelection')"
@click-action="(o) => openLink(o.value)"
/>
</div>
</template>
</SortableTable>
<div v-for="clusterData in displayedClusterData" :key="clusterData.id">
<div class="cluster-header">
<h1>
{{ clusterData.name }}
</h1>
</div>
<SortableTable
:rows="clusterData.filteredApps"
:headers="tableHeaders"
:row-key="'uniqueId'"
:search="false"
:table-actions="false"
:row-actions="false"
:no-rows-text="$store.getters['i18n/t']('appLauncher.noAppsFound')"
>
<template #cell:name="{row}">
{{ row.metadata.name }}
</template>
<template #cell:namespace="{row}">
{{ row.metadata.namespace }}
</template>
<template #cell:version="{row}">
{{ row.metadata.labels?.['app.kubernetes.io/version'] }}
</template>
<template #cell:helmChart="{row}">
{{ row.metadata.labels?.['helm.sh/chart'] }}
</template>
<template #cell:actions="{row}">
<div style="display: flex; justify-content: flex-end;">
<button class="icon-button favorite-icon" @click="toggleFavorite(row)">
<i :class="['icon', isFavorited(row, favoritedServices) ? 'icon-star' : 'icon-star-open']" />
</button>
<a v-if="getEndpoints(row)?.length <= 1" :href="getEndpoints(row)[0]?.value" target="_blank"
rel="noopener noreferrer nofollow" class="btn role-primary">
{{ t('appLauncher.launch') }}
</a>
<ButtonDropDown v-else :button-label="t('appLauncher.launch')" :dropdown-options="getEndpoints(row)"
:title="t('appLauncher.launchAnEndpointFromSelection')" @click-action="(o) => openLink(o.value)" />
</div>
</template>
</SortableTable>
</div>
</div>
</div>
<div v-else>
Expand All @@ -419,6 +440,10 @@ export default {
<style lang="scss" scoped>
@import "@shell/assets/styles/fonts/_icons.scss";
.main-container {
margin-top: -2.425rem;
}
.services-by-cluster-grid {
display: grid;
grid-gap: 1rem;
Expand All @@ -429,7 +454,7 @@ export default {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
margin: 1rem 0;
background: var(--header-bg);
border-bottom: var(--header-border-size) solid var(--header-border);
height: var(--header-height);
Expand All @@ -442,6 +467,14 @@ export default {
display: flex;
align-items: center;
gap: 1rem;
position: fixed;
right: 6rem;
top: 4.4rem;
z-index: 2;
padding-bottom: 0.425rem;
padding-right: 4.4rem;
background: rgb(27, 28, 33);
border-bottom: var(--header-border-size) solid var(--header-border);
}
.icon-button {
Expand All @@ -462,7 +495,6 @@ export default {
}
.search-input {
margin-bottom: 1rem;
text-align: right;
justify-content: flex-end;
display: flex;
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14901,9 +14901,9 @@ __metadata:
languageName: node
linkType: hard

"krum-rancher-extensions-demo@workspace:.":
"krum-rancher-extensions@workspace:.":
version: 0.0.0-use.local
resolution: "krum-rancher-extensions-demo@workspace:."
resolution: "krum-rancher-extensions@workspace:."
dependencies:
"@babel/plugin-proposal-private-property-in-object": ^7.21.11
"@rancher/components": 0.1.3
Expand Down

0 comments on commit 0fa04a3

Please sign in to comment.