Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: for APPLAUNCHER-6 added k8 annotations to global #10

Merged
merged 5 commits into from
Mar 13, 2024
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
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
Loading