Skip to content

Commit

Permalink
feat: for APPLAUNCHER-27 combined app service cards with ingress cards
Browse files Browse the repository at this point in the history
  • Loading branch information
Sinjhin committed Mar 12, 2024
1 parent dede7ac commit efbf0a0
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 77 deletions.
106 changes: 42 additions & 64 deletions pkg/app-launcher/components/AppLauncherCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Card } from '@components/Card';
import ButtonDropDown from '@shell/components/ButtonDropdown';
import { isMaybeSecure } from '@shell/utils/url';
import { ingressFullPath } from '@shell/models/networking.k8s.io.ingress';
export default {
components: {
Expand All @@ -19,46 +20,11 @@ export default {
},
service: {
type: Object,
required: true,
default: () => ({
id: '',
metadata: {
labels: {
/**
* Helm chart name that created this app (if relevant).
*/
'helm.sh/chart': '',
/**
* The component of the app.
*
* See `app.kubernetes.io/component` from:
* https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/#labels
*/
'app.kubernetes.io/component': '',
/**
* The version of the app.
*
* See `app.kubernetes.io/version` from:
* https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/#labels
*/
'app.kubernetes.io/version': '',
},
/**
* The name of the app.
*
* See `app.kubernetes.io/name` from:
* https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/#labels
*/
name: '',
/**
* The namespace of the app.
*/
namespace: '',
},
spec: {
ports: [],
},
}),
default: null,
},
ingress: {
type: Object,
default: null,
},
},
methods: {
Expand All @@ -70,12 +36,15 @@ export default {
},
},
computed: {
app() {
return this.service || this.ingress;
},
endpoints() {
return (
this.service?.spec.ports?.map((port) => {
this.service?.spec?.ports?.map((port) => {
const endpoint = `${
isMaybeSecure(port.port, port.protocol) ? 'https' : 'http'
}:${this.service.metadata.name}:${port.port}`;
}:${this.service?.metadata?.name}:${port.port}`;
return {
label: `${endpoint}${port.protocol === 'UDP' ? ' (UDP)' : ''}`,
Expand All @@ -86,32 +55,35 @@ export default {
},
computedServiceName() {
return (
this.service?.metadata.labels?.['app.kubernetes.io/component'] ??
this.service?.metadata.name
this.service?.metadata?.labels?.['app.kubernetes.io/component'] ??
this.service?.metadata?.name
);
},
helmChart() {
return this.service?.metadata.labels?.['helm.sh/chart'];
return this.service?.metadata?.labels?.['helm.sh/chart'];
},
kubernetesVersion() {
return this.service?.metadata.labels?.['app.kubernetes.io/version'];
return this.service?.metadata?.labels?.['app.kubernetes.io/version'];
},
name() {
return this.service?.metadata.name;
return this.service?.metadata?.name;
},
namespace() {
return this.service?.metadata.namespace;
return this.service?.metadata?.namespace;
},
isFavorited() {
return this.favoritedServices.some(
(favoritedService) =>
favoritedService.clusterId === this.clusterId &&
favoritedService.service.id === this.service.id
favoritedService?.clusterId === this.clusterId &&
favoritedService?.service?.id === this.service?.id
);
},
isGlobalApp() {
return this.service.metadata.annotations?.['extensions.applauncher/global-app'] === 'true';
return this.service?.metadata?.annotations?.['extensions.applauncher/global-app'] === 'true';
},
ingressPath() {
return ingressFullPath(this.ingress, this.ingress?.spec?.rules?.[0]);
}
},
name: 'AppLauncherCard',
layout: 'plain',
Expand All @@ -123,35 +95,32 @@ export default {
<template #title>
<div style="width: 100%">
<p style="font-size: 1.2rem">
{{ computedServiceName }}
{{ service ? service?.metadata?.name : ingress?.metadata?.name }}
</p>
<div
style="
color: var(--input-label);
display: flex;
justify-content: space-between;
margin-top: 4px;
"
>
<p v-if="kubernetesVersion !== undefined">
<div style="color: var(--input-label); display: flex; justify-content: space-between; margin-top: 4px;">
<p v-if="app.type === 'service' && app.metadata?.labels?.['app.kubernetes.io/version'] !== undefined">
{{ kubernetesVersion }}
</p>
<p v-if="helmChart !== undefined">
<p v-if="app.type === 'service' && app.metadata?.labels?.['helm.sh/chart'] !== undefined">
{{ helmChart }}
</p>
<p v-if="app.type === 'ingress'">
Ingress
</p>
</div>
</div>
</template>
<template #body>
<p>{{ namespace }}/{{ name }}</p>
<p v-if="app.type === 'service'">{{ namespace }}/{{ name }}</p>
<p v-if="app.type === 'ingress'">{{ ingressPath }}</p>
</template>
<template #actions>
<button v-if="!isGlobalApp" class="icon-button" @click="toggleFavorite">
<i :class="['icon', isFavorited ? 'icon-star' : 'icon-star-open']" />
</button>
<i v-else class="icon icon-globe icon-only" />
<a
v-if="(endpoints?.length ?? 0) <= 1"
v-if="(endpoints?.length ?? 0) <= 1 && app.type === 'service'"
:disabled="!endpoints?.length"
:href="endpoints[0]?.value"
target="_blank"
Expand All @@ -163,6 +132,15 @@ export default {
>
{{ t('appLauncher.launch') }}
</a>
<a
v-else-if="app.type === 'ingress'"
:href="ingressPath"
target="_blank"
rel="noopener noreferrer nofollow"
class="btn role-primary"
>
{{ t('appLauncher.launch') }}
</a>
<ButtonDropDown
v-else
:button-label="t('appLauncher.launch')"
Expand Down
120 changes: 107 additions & 13 deletions pkg/app-launcher/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Loading from '@shell/components/Loading';
import SortableTable from '@shell/components/SortableTable';
import ButtonDropDown from '@shell/components/ButtonDropdown';
import { isMaybeSecure } from '@shell/utils/url';
import { ingressFullPath } from '@shell/models/networking.k8s.io.ingress';
import AppLauncherCard from '../components/AppLauncherCard.vue';
Expand All @@ -17,7 +18,7 @@ export default {
data() {
return {
servicesByCluster: [],
mockServicesByCluster: [],
ingressesByCluster: [],
clusterLoading: {},
loading: true,
selectedCluster: null,
Expand Down Expand Up @@ -59,19 +60,25 @@ export default {
async mounted() {
try {
const allClusters = await this.getClusters();
console.log('allClusters', allClusters);
this.servicesByCluster = await this.getServicesByCluster(allClusters);
this.ingressesByCluster = await this.getIngressesByCluster(allClusters);
console.log('servicesByCluster', this.servicesByCluster);
console.log('ingressesByCluster', this.ingressesByCluster);
console.log("ingress path", ingressFullPath(this.ingressesByCluster[1].ingresses[0], this.ingressesByCluster[1].ingresses[0].spec.rules?.[0]));
// Set the first cluster as the selected cluster
if (this.servicesByCluster.length > 0) {
this.selectedCluster = this.servicesByCluster[0].id;
}
this.generateClusterOptions();
console.log('servicesByCluster', this.servicesByCluster);
// Retrieve global apps based on annotations
this.servicesByCluster.forEach((cluster) => {
cluster.services.forEach((service) => {
if (service.metadata.annotations?.['extensions.applauncher/global-app'] === 'true') {
if (service.metadata?.annotations?.['extensions.applauncher/global-app'] === 'true') {
this.favoritedServices.push({
clusterId: cluster.id,
service,
Expand All @@ -93,13 +100,11 @@ export default {
},
methods: {
async getClusters() {
console.log('this.$store', this.$store);
return await this.$store.dispatch(`management/findAll`, {
type: MANAGEMENT.CLUSTER,
});
},
async getMockServicesByCluster() {
return await Promise.resolve(mockServicesByCluster);
},
getClusterName(clusterId) {
const cluster = this.servicesByCluster.find(c => c.id === clusterId);
return cluster ? cluster.name : '';
Expand All @@ -113,7 +118,7 @@ export default {
.filter((cluster) => cluster.isReady)
.map(async (cluster) => {
const clusterData = {
name: `${this.$store.getters['i18n/t']('nav.group.cluster')} ${cluster.spec.displayName}`,
name: cluster.spec.displayName,
id: cluster.id,
services: [],
loading: true,
Expand All @@ -136,9 +141,38 @@ export default {
})
);
},
async getIngressesByCluster(allClusters) {
return Promise.all(
allClusters
.filter((cluster) => cluster.isReady)
.map(async (cluster) => {
const clusterData = {
name: cluster.spec.displayName,
id: cluster.id,
ingresses: [],
loading: true,
error: false,
};
try {
const ingresses = (
await this.$store.dispatch('cluster/request', {
url: `/k8s/clusters/${cluster.id}/v1/networking.k8s.io.ingresses`,
})
).data;
clusterData.ingresses = ingresses;
} catch (error) {
console.error(`Error fetching ingresses for cluster ${cluster.id}:`, error);
clusterData.error = true;
} finally {
clusterData.loading = false;
}
return clusterData;
})
);
},
generateClusterOptions() {
this.clusterOptions = this.servicesByCluster.map((cluster) => ({
label: cluster.name.slice(8),
label: cluster.name,
value: cluster.id,
}));
},
Expand Down Expand Up @@ -189,7 +223,59 @@ export default {
},
computed: {
selectedClusterData() {
return this.getCluster(this.selectedCluster);
const cluster = this.getCluster(this.selectedCluster);
if (cluster) {
const ingresses = this.ingressesByCluster.find(
(ingressCluster) => ingressCluster.id === cluster.id
)?.ingresses || [];
const services = cluster.services.map((service) => {
const relatedIngress = ingresses.find((ingress) =>
ingress.spec.rules.some((rule) =>
rule.http.paths.some((path) =>
path.backend.service?.name === service.metadata.name
)
)
);
return {
...service,
relatedIngress,
};
});
return {
...cluster,
services,
ingresses,
};
}
return null;
},
sortedApps() {
if (this.selectedClusterData) {
const services = this.selectedClusterData.services.map((service) => ({
...service,
type: 'service',
uniqueId: `service-${service.id}`,
}));
const ingresses = this.selectedClusterData.ingresses.map((ingress) => ({
...ingress,
type: 'ingress',
uniqueId: `ingress-${ingress.id}`,
}));
return [...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);
}
});
}
return [];
},
sortedServices() {
if (this.selectedClusterData) {
Expand Down Expand Up @@ -253,14 +339,22 @@ export default {
<p>{{ $store.getters['i18n/t']('appLauncher.noServicesFound') }}</p>
</template>
<AppLauncherCard
v-else
v-for="service in sortedServices"
:key="service.id"
v-for="app in sortedApps"
:key="app.uniqueId"
:cluster-id="selectedCluster"
:service="service"
:service="app.type === 'service' ? app : null"
:ingress="app.type === 'ingress' ? app : null"
:favorited-services="favoritedServices"
@toggle-favorite="toggleFavorite"
/>
<!-- <AppLauncherCard
v-for="ingress in selectedClusterData.ingresses"
:key="ingress.id"
:cluster-id="selectedCluster"
:ingress="ingress"
:favorited-services="favoritedServices"
@toggle-favorite="toggleFavorite"
/> -->
</div>
<div v-else-if="selectedView === 'list'">
<SortableTable
Expand Down

0 comments on commit efbf0a0

Please sign in to comment.