Skip to content

Commit

Permalink
Merge pull request #10 from krumIO/feat/APPLAUNCHER-6__k8_annotation
Browse files Browse the repository at this point in the history
feat: for APPLAUNCHER-6 added k8 annotations to global
  • Loading branch information
Sinjhin authored Mar 13, 2024
2 parents 3f47e03 + 932eacd commit e1e7960
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 86 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<h1 align="center">Krum Rancher Extensions Demo</h1>

The App Launcher is intended to fill a large gap in functionality for non-operator users of Rancher. Currently, if a user wants to access a software application running in Rancher/kubernetes, they need to dig deep into the services/ingress section in Rancher, or learn how to build the proxy URL or on their own. Otherwise, operators need to use an ingress to create convenient access for users.

However, the proxy URL can be a powerful tool in Rancher. It allows a user with appropriate access to a namespaced resource to access that resource/application without ingress.

The App Launcher will expose a top-level directory of service/ingress objects, and pre-build proxy URLs. It will also provide an option to launch that ingress, if available.

## Setup

### Node
Expand All @@ -19,6 +25,6 @@ We use [corepack](https://nodejs.org/api/corepack.html) (comes with Node.js) to
corepack enable
```

### Misc
## Misc

For more info, refer to the [rancher extensions prerequisites](https://rancher.github.io/dashboard/extensions/extensions-getting-started#prerequisites).
12 changes: 9 additions & 3 deletions pkg/app-launcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ Rancher App Launcher Extension is a powerful tool for improved accessibility and
## How to Run

1. Clone this repository to your machine.
2. Install the npm dependencies using the command "yarn install".
3. Run the extension with the command "API=<Rancher Backend URL> yarn dev".
2. Install the npm dependencies using the command `yarn install`.
3. Run the extension locally with the command `API=<Rancher Backend URL> yarn dev`.

## Usage

Once the Rancher App Launcher Extension is installed, you can access a unified resource page from the main dashboard. This page showcases cards for each discovered service, allowing you to conveniently open the service with a simple click. The extension is designed to improve discoverability, and offer a straightforward way to navigate the complexities of multi-cluster environments.
- Once the Rancher App Launcher Extension is installed, you can access a unified resource page from the main dashboard.
- This page showcases cards for each discovered service and ingress, allowing you to conveniently open the service with a simple click.
*The extension is designed to improve discoverability, and offer a straightforward way to navigate the complexities of multi-cluster environments.*
- Global apps will show at the top as a combination of global apps defined by cluster YAML files and user-selected favorites
*\*note: global apps can be set by modifying the service's `metadata.annotations['extensions.applauncher/global-app']` to 'true'*
- Select different clusters to view the services of. Global Apps perisist across all views.
- The view can be changed with the view buttons from grid to list views.

## Contribution

Expand Down
138 changes: 66 additions & 72 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,79 +55,88 @@ 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';
},
ingressPath() {
return ingressFullPath(this.ingress, this.ingress?.spec?.rules?.[0]);
}
},
name: 'AppLauncherCard',
layout: 'plain',
};
</script>
<template>
<Card :show-highlight-border="false" :sticky="true">
<Card class="app-launcher-card" :show-highlight-border="false" :sticky="true">
<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 class="icon-button" @click="toggleFavorite">
<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"
rel="noopener noreferrer nofollow"
:title="
endpoints?.length === 0
? t('appLauncher.noEndpointFoundForApp')
: t('appLauncher.launchEndpoint', {
endpoint: endpoints[0].label,
})
"
:title="endpoints?.length === 0 ? t('appLauncher.noEndpointFoundForApp') : t('appLauncher.launchEndpoint', {
endpoint: endpoints[0].label,
})"
class="btn role-primary"
>
{{ 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') }}
Expand All @@ -177,6 +155,12 @@ export default {
<style lang="scss" scoped>
@import "@shell/assets/styles/fonts/_icons.scss";
.app-launcher-card {
::v-deep .card-body {
overflow: hidden !important;
}
}
.icon-button {
background: none;
border: none;
Expand All @@ -186,4 +170,14 @@ export default {
font-size: 1.8rem;
margin-right: 1rem;
}
.icon-only {
background: none;
border: none;
padding: 0;
color: var(--primary);
font-size: 1.8rem;
margin-right: 1rem;
margin-top: 0.5rem;
}
</style>
Loading

0 comments on commit e1e7960

Please sign in to comment.