1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

clean up reused vue-parts

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-07-16 18:28:54 +03:00
parent 10e68510c4
commit 2a818601f9
25 changed files with 3 additions and 1819 deletions

View File

@ -4,7 +4,6 @@
export enum ClusterIpcMessage {
ADD = "cluster-add",
STOP = "cluster-stop",
REFRESH = "cluster-refresh",
REMOVE = "cluster-remove",
REMOVE_WORKSPACE = "cluster-remove-all-from-workspace",
FEATURE_INSTALL = "cluster-feature-install",

View File

@ -143,20 +143,12 @@ export class ClusterManager {
return this.getCluster(clusterId)?.uninstallFeature(name);
}
protected async refreshCluster(clusterId: ClusterId) {
const cluster = this.getCluster(clusterId);
if (cluster) {
await cluster.refreshStatus();
}
}
static ipcListen(clusterManager: ClusterManager) {
const handlers = {
[ClusterIpcMessage.ADD]: clusterManager.addCluster,
[ClusterIpcMessage.STOP]: clusterManager.stopCluster,
[ClusterIpcMessage.REMOVE]: clusterManager.removeCluster,
[ClusterIpcMessage.REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace,
[ClusterIpcMessage.REFRESH]: clusterManager.refreshCluster,
[ClusterIpcMessage.FEATURE_INSTALL]: clusterManager.installFeature,
[ClusterIpcMessage.FEATURE_UPGRADE]: clusterManager.upgradeFeature,
[ClusterIpcMessage.FEATURE_REMOVE]: clusterManager.uninstallFeature,

View File

@ -160,18 +160,15 @@ export class Cluster implements ClusterModel {
}
async installFeature(name: string, config: any) {
await installFeature(name, this, config)
await this.refreshStatus()
return await installFeature(name, this, config)
}
async upgradeFeature(name: string, config: any) {
await upgradeFeature(name, this, config)
await this.refreshStatus()
return await upgradeFeature(name, this, config)
}
async uninstallFeature(name: string) {
await uninstallFeature(name, this)
await this.refreshStatus()
return await uninstallFeature(name, this)
}
getPrometheusApiPrefix() {

View File

@ -1,38 +0,0 @@
<template>
<div id="app">
<div id="lens-container" />
<div class="draggable-top" />
<div class="main-view" :class="{ 'menu-visible': isMenuVisible }">
<main-menu v-if="isMenuVisible" />
<router-view />
</div>
<bottom-bar v-if="isMenuVisible" />
</div>
</template>
<script>
import MainMenu from "@/_vue/components/MainMenu/MainMenu";
import BottomBar from "@/_vue/components/BottomBar/BottomBar";
export default {
name: 'Lens',
components: {
BottomBar,
MainMenu
},
computed: {
isMenuVisible: function () { return this.$store.getters.isMenuVisible }
}
}
</script>
<style scoped>
.draggable-top {
-webkit-app-region: drag;
left: 0;
top: 0;
position: absolute;
height: 20px;
width: 100%;
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -1,73 +0,0 @@
// from Lens Dashboard
$lens-main-bg: #1e2124 !default; // dark bg
$lens-pane-bg: #262b2f !default; // all panels main bg
$lens-dock-bg: #2E3136 !default; // terminal and top menu bar
$lens-menu-bg: #36393E !default; // sidemenu on left
$lens-menu-hl: #414448 !default; // sidemenu on left, top left corner
$lens-text-color: #87909c !default;
$lens-text-color-light: #a0a0a0 !default;
$lens-primary: #3d90ce !default;
// export as css variables
:root {
--lens-main-bg: #{$lens-main-bg}; // dark bg
--lens-pane-bg: #{$lens-pane-bg}; // all panels main bg
--lens-dock-bg: #{$lens-dock-bg}; // terminal and top menu bar
--lens-menu-bg: #{$lens-menu-bg}; // sidemenu on left
--lens-menu-hl: #{$lens-menu-hl}; // sidemenu on left, top left corner
--lens-text-color: #{$lens-text-color};
--lens-text-color-light: #{$lens-text-color-light};
--lens-primary: #{$lens-primary};
--lens-bottom-bar-height: 20px;
}
// Base grayscale colors definitions
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #6c757d !default;
$gray-700: #495057 !default;
$gray-800: #343a40 !default;
$gray-900: #1e2124 !default;
$black: #000 !default;
// Base colors definitions
$blue: #3d90ce !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #CE3933 !default;
$orange: #fd7e14 !default;
$yellow: #ffc107 !default;
$green: #4caf50 !default;
$teal: #20c997 !default;
$cyan: #6ca5b7 !default;
// Theme color default definitions
$primary: $lens-primary !default;
$secondary: $gray-600 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-100 !default;
$dark: $gray-800 !default;
// This table defines the theme colors (variant names)
$theme-colors: () !default;
$theme-colors: map-merge(
(
'primary': $primary,
'secondary': $secondary,
'success': $success,
'info': $info,
'warning': $warning,
'danger': $danger,
'light': $light,
'dark': $dark
),
$theme-colors
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

View File

@ -1,88 +0,0 @@
<template>
<div class="bottom-bar">
<div id="workspace-area">
<i class="material-icons">layers</i> {{ currentWorkspace.name }}
</div>
<b-popover target="workspace-area" triggers="click" placement="top" :show.sync="show">
<template v-slot:title>
<a href="#" @click.prevent="goWorkspaces"><i class="material-icons">layers</i> Workspaces</a>
</template>
<ul
v-for="workspace in workspaces"
:key="workspace.id"
:workspace="workspace"
class="list-group list-group-flush"
>
<li class="list-group-item">
<a href="#" @click.prevent="switchWorkspace(workspace)">{{ workspace.name }}</a>
</li>
</ul>
</b-popover>
</div>
</template>
<script>
export default {
name: "BottomBar",
data() {
return {
show: false
}
},
computed: {
currentWorkspace: function() {
return this.$store.getters.currentWorkspace;
},
workspaces: function() {
return this.$store.getters.workspaces;
}
},
methods: {
switchWorkspace: function(workspace) {
this.show = false;
this.$store.commit("setCurrentWorkspace", workspace);
this.$store.dispatch("clearClusters");
this.$store.dispatch("refreshClusters", workspace);
this.$router.push({
name: "landing-page"
})
},
goWorkspaces: function() {
this.$router.push({
name: "workspaces-page"
})
}
}
}
</script>
<style scoped lang="scss">
.bottom-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: var(--lens-bottom-bar-height);
background-color: var(--lens-primary);
z-index: 2000;
}
#workspace-area {
position: absolute;
bottom: 2px;
right: 10px;
display: block;
color: #fff;
opacity: 0.9;
font-size: 11px;
cursor: pointer;
&.active{
opacity: 1.0;
}
i {
position: relative;
top: 4px;
font-size: 14px;
}
}
</style>

View File

@ -1,116 +0,0 @@
<template>
<div class="loading">
<div class="CubeSpinner">
<p>{{ text }}</p>
<div class="sk-cube-grid">
<div class="sk-cube sk-cube1" />
<div class="sk-cube sk-cube2" />
<div class="sk-cube sk-cube3" />
<div class="sk-cube sk-cube4" />
<div class="sk-cube sk-cube5" />
<div class="sk-cube sk-cube6" />
<div class="sk-cube sk-cube7" />
<div class="sk-cube sk-cube8" />
<div class="sk-cube sk-cube9" />
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CubeSpinner',
components: {},
methods: {},
props: {
text: {
type: String,
required: false,
default: "Connecting ..."
}
},
}
</script>
<style scoped lang="scss">
.loading {
position: absolute;
left: 50%;
top: 50%;
margin: -44px;
margin-top: -70px;
z-index: 1000;
}
/* http://tobiasahlin.com/spinkit/ */
.CubeSpinner {
--size: 70px;
.sk-cube-grid {
width: 70px;
height: 70px;
transform: rotate(45deg);
margin: 24px auto;
border-bottom-left-radius: 3px;
border-top-right-radius: 3px;
overflow: hidden;
}
.sk-cube-grid .sk-cube {
width: 33%;
height: 33%;
float: left;
-webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
}
.sk-cube-grid .sk-cube1 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube2 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube3 {
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.sk-cube-grid .sk-cube4 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube5 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube6 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube7 {
-webkit-animation-delay: 0s;
animation-delay: 0s;
}
.sk-cube-grid .sk-cube8 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube9 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
@-webkit-keyframes sk-cubeGridScaleDelay {
0%, 35% {
background-color: #36393e;
}
0%, 70%, 100% {
-webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1);
}
35% {
-webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1);
}
100% {
background-color: #3d90ce;
}
}
}
</style>

View File

@ -1,65 +0,0 @@
<template>
<div class="content">
<div class="landing-page">
<b-container fluid class="h-100">
<!-- wrapper for center/center align -->
<b-row class="h-100">
<b-col sm="12" class="my-auto">
<div>
<b-row align-h="center" class="banner">
<b-col sm="8">
<h1 v-if="clusters.length === 0" class="display-4 text-center">
Welcome!
</h1>
<p v-if="clusters.length === 0" class="text-center">
Get started by associating one or more clusters to Lens.
</p>
</b-col>
</b-row>
</div>
</b-col>
</b-row>
</b-container>
</div>
</div>
</template>
<script>
export default {
name: 'LandingPage',
components: {},
methods: {},
computed: {
workspaces: function() {
return this.$store.getters.workspaces;
},
clusters: function() {
return this.$store.getters.clusters;
}
}
}
</script>
<style scoped lang="scss">
.landing-page{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: #282b2f url(../../components/icon/crane.svg) no-repeat;
background-position: 0px 35%;
background-size: 85%;
background-clip: content-box;
.banner {
h1 {
text-align: center;
font-size: 28px;
font-weight: normal;
}
p {
font-size: 14px;
}
}
}
</style>

View File

@ -1,70 +0,0 @@
<template>
<span>
<a
id="add-cluster"
class="add-cluster menu-item"
:class="[{
'active':isActive(),
}]"
@click.prevent="addCluster"
v-b-tooltip.hover.right
title="Add clusters"
>
<i class="material-icons">add_box</i>
<span v-if="newContexts.length > 0" class="badge badge-success">{{ newContexts.length }}</span>
</a>
</span>
</template>
<script>
import ClustersMixin from "@/_vue/mixins/ClustersMixin";
export default {
name: "AddClusterMenuItem",
mixins: [ClustersMixin],
data(){
return {
}
},
computed: {
},
methods: {
isActive: function( id ){
return "new" === this.$route.params.id;
},
addCluster: async function(){
this.$router.push({
name: "add-cluster-page",
params: {
id: "new"
},
}).catch(err => {})
}
},
mounted() {
this.$store.dispatch("reloadAvailableKubeContexts");
}
}
</script>
<style scoped lang="scss">
.add-cluster{
position: relative;
display: block;
color: #fff;
padding: 6px 8px 6px 9px;
opacity: 0.4;
cursor: pointer;
&.active{
opacity: 0.75;
}
i {
font-size: 50px;
}
.badge {
position: absolute;
bottom: 10px;
right: 10px;
}
}
</style>

View File

@ -1,166 +0,0 @@
<template>
<a
class="menu-item"
:class="[{
'active':isActive,
'online':(activated && !isActive),
}]"
@click.prevent="openClusterPage()"
@contextmenu.prevent="openContextMenu()"
:title="preferences.clusterName"
v-b-tooltip.hover.right
>
<div
class="icon-frame"
:class="[{
'active':isActive
}]"
>
<img v-if="preferences.icon" :src="preferences.icon">
<hashicon v-else :name="preferences.clusterName" size="38" />
<span v-if="isAdmin && eventCount > 0" class="badge badge-danger">{{ eventCount >= 1000 ? "1000+" : eventCount }}</span>
</div>
</a>
</template>
<script>
import hashicon from "../hashicon/hashicon";
import { setInterval, clearInterval } from "timers";
const { remote } = require('electron')
const { Menu, MenuItem } = remote
export default {
name: "ClusterMenuItem",
components: {
hashicon
},
data(){
return {
eventPoller: null,
eventCount: 0,
activated: false
}
},
props: {
cluster: {
type: Object,
required: true,
default: null
},
},
computed: {
preferences: function() {
return this.cluster.preferences;
},
clusterAccessible: function() {
return this.$store.getters.clusterById(this.cluster.id).accessible;
},
isActive: function() {
return this.cluster.id === this.$route.params.id;
},
isAdmin: function() {
return this.cluster.isAdmin;
}
},
methods: {
openClusterPage() {
this.activated = true;
this.$router.push({
name: "cluster-page",
params: {
id: this.cluster.id
},
}).catch(err => {})
},
openContextMenu(){
const self = this;
const menu = new Menu()
menu.append(new MenuItem({ label: 'Settings', click() {
self.$router.push({
name: "cluster-settings-page",
params: {
id: self.cluster.id
},
}).catch(err => {})
} }))
if (this.activated) {
menu.append(new MenuItem({ label: 'Disconnect', click() {
self.activated = false;
self.stopPolling();
self.$store.dispatch("stopCluster", self.cluster.id);
if (self.isActive) {
self.$router.push({
name: "landing-page"
}).catch(err => {})
}
} }))
}
menu.popup({ window: remote.getCurrentWindow() })
},
toggleEventPolling: function() {
if(this.clusterAccessible && !this.eventPoller && this.activated && !this.isActive) {
this.fetchEvents()
this.eventPoller = setInterval(async () => {
await this.fetchEvents()
}, 30 * 1000);
} else {
this.eventCount = 0;
this.stopPolling()
}
},
fetchEvents: async function() {
try {
this.eventCount = await this.$promiseIpc.send("getClusterEvents", this.cluster.id);
} catch (error) {
console.error("Failed to get event count for cluster:", error)
}
},
stopPolling: function() {
if (this.eventPoller) {
clearInterval(this.eventPoller)
this.eventPoller = null
}
}
},
watch: {
clusterAccessible: "toggleEventPolling",
isActive: "toggleEventPolling"
},
beforeDestroy() {
this.stopPolling();
}
}
</script>
<style scoped lang="scss">
.badge {
position: absolute;
bottom: 10px;
right: 10px;
padding: 4px;
i {
font-size: 8px;
}
}
img {
width: 38px;
max-height: 42px;
}
span.hashicon {
margin-top: 6px;
}
div.icon-frame {
width: 44px;
height: 44px;
display: flex;
align-items: center;
&.active, &:hover {
margin-left: -3px;
padding: 0px 0px 0px 3px;
background-color: #f8f9fa;
border-radius: 3px;
}
}
</style>

View File

@ -1,107 +0,0 @@
<template>
<div :style="{ paddingTop: computedPaddingTop }" class="main-menu">
<draggable v-model="clusters">
<ClusterMenuItem
v-for="cluster in clusters" :key="cluster.id"
:cluster="cluster"
/>
</draggable>
<AddClusterMenuItem />
<b-tooltip v-if="clusters.length === 0" show target="add-cluster" placement="rightbottom">
<p class="text-left">
This is the quick launch menu.
</p>
<p class="text-left">
Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button.
</p>
</b-tooltip>
</div>
</template>
<script>
import ClusterMenuItem from "@/_vue/components/MainMenu/ClusterMenuItem";
import AddClusterMenuItem from "@/_vue/components/MainMenu/AddClusterMenuItem";
import draggable from 'vuedraggable'
import { isMac } from "../../../../common/vars"
const {remote} = require('electron')
const {Menu, MenuItem} = remote
export default {
name: "MainMenu",
components: {
ClusterMenuItem,
AddClusterMenuItem,
draggable
},
data() {
return {
computedPaddingTop: "15px"
}
},
computed: {
clusters: {
get: function () {
return this.$store.getters.clusters;
},
set: function (clusters) {
this.$store.commit("updateClusters", clusters);
// clusterStore.storeClusters(clusters);
}
}
},
methods: {
isActive: function (id) {
return id === this.$route.params.id;
},
addCluster: async function () {
this.$router.push({
name: "add-cluster-page",
params: {
id: "new"
},
}).catch(err => {})
}
},
mounted: function () {
if (isMac) {
this.computedPaddingTop = "25px";
}
}
}
</script>
<style scoped lang="scss">
.main-menu {
position: absolute;
left: 0;
top: 0;
bottom: 20px;
width: 70px;
padding-top: 15px;
background: #252729;
overflow: hidden;
z-index: 1000;
}
.menu-item {
position: relative;
cursor: pointer;
display: block;
padding: 6px 15px 6px 15px;
color: #87909c;
opacity: 0.3;
&.active {
opacity: 1;
}
&.online {
opacity: 1;
}
&:hover {
opacity: 1;
}
}
</style>

View File

@ -1,114 +0,0 @@
<template>
<div class="content">
<div class="container-fluid whats-new">
<div class="row">
<div class="col-md whats-new-text-wrapper">
<div class="logo">
<img src="../../components/icon/lens-logo.svg">
</div>
<!-- Safe to use v-html as we self generate the html locally only -->
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="whats-new-text" v-html="content">
<!-- What's new content -->
</div>
</div>
</div>
<div class="row">
<div class="col-md whats-new-actions-wrapper">
<div class="whats-new-actions">
<button type="button" class="btn btn-primary" @click="toLanding">
Ok, got it!
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import marked from 'marked'
import {readFileSync} from 'fs'
import { userStore } from "../../../common/user-store"
export default {
name: 'WhatsNewPage',
data() {
// todo: check if "fs" can work with custom protocols
let fileContents = readFileSync("static://RELEASE_NOTES.md", 'utf8');
let content = marked(fileContents);
return {
error: "",
content: content,
}
},
components: {},
computed: {
},
methods: {
toLanding: async function() {
userStore.saveLastSeenAppVersion();
this.$router.push({
name: "landing-page",
}).catch(err => {})
},
},
}
</script>
<style lang="scss" scoped>
.content {
background: #282b2f url(../../components/icon/crane.svg) no-repeat;
background-position: 0px 35%;
background-size: 85%;
background-clip: content-box;
}
.content > .whats-new{
width: 100%;
height: 100%;
background-color:rgba(0, 0, 0, 0.3);
.whats-new-text-wrapper{
position: absolute;
top: 20px;
bottom: 77px;
left: 0;
right: 0;
overflow-y: scroll;
.logo {
padding: 30px 30px;
max-width: 1000px;
img {
width: 200px;
}
}
.whats-new-text{
padding: 10px 30px;
max-width: 1000px;
h1{
font-size: 24px;
}
h2{
padding: 0;
}
}
}
.whats-new-actions-wrapper{
position: absolute;
height: 77px;
left: 0;
right: 0;
bottom: 0;
border-top: 1px solid #333;
background: var(--lens-pane-bg);
.whats-new-actions{
padding: 20px;
text-align: center;
}
}
}
</style>

View File

@ -1,40 +0,0 @@
<template>
<div class="close-page-button">
<a @click.prevent="navigateBack" href="#">
<i class="material-icons">close</i>
</a>
</div>
</template>
<script>
export default {
name: "ClosePageButton",
data() {
return {}
},
methods: {
navigateBack: () => {
window.history.back();
}
}
}
</script>
<style scoped lang="scss">
div.close-page-button {
position: absolute;
top: 20px;
right: 25px;
z-index: 1100;
a {
position: relative;
display: block;
color: #fff;
opacity: 0.4;
}
i {
font-size: 30px;
}
}
</style>

View File

@ -1,28 +0,0 @@
<template>
<span class="hashicon" ref="hashicon" />
</template>
<script>
import hashicon from 'hashicon'
export default {
name:'Hashicon',
props:{
name: {
default: null,
type: String
},
size: {
default: null,
type: String
},
},
mounted() {
const params = {
size: this.size || 48,
lightness: { min: 45, max: 65 },
saturation: { min: 30, max: 60 }
}
this.$refs.hashicon.appendChild(hashicon(this.name, params))
}
}
</script>

View File

@ -1,35 +0,0 @@
// todo: remove, currently not used in runtime
import "../../common/system-ca"
import "./assets/css/app.scss"
import { PromiseIpc } from 'electron-promise-ipc'
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import App from './App'
import router from './router'
import store from './store'
const promiseIpc = new PromiseIpc({maxTimeoutMs: 6000});
promiseIpc.on('navigate', async (view) => {
router.push(view).catch(err => {})
});
Vue.config.productionTip = false
Vue.use(BootstrapVue)
Vue.mixin({
created: function () {
this.$promiseIpc = promiseIpc;
}
})
setTimeout(async () => {
await store.dispatch('init')
new Vue({
components: {App},
store,
router,
template: '<App/>'
}).$mount('#app')
})

View File

@ -1,16 +0,0 @@
export default {
computed: {
clusters: function () {
return this.$store.getters.clusters
},
newContexts: function () {
const seenContexts = this.seenContexts ? Array.from(this.seenContexts) : this.$store.getters.seenContexts
const contextNamesFromKubeconfig = this.availableContexts.map(item => item.currentContext)
return contextNamesFromKubeconfig.filter((item) => seenContexts.indexOf(item) < 0)
},
availableContexts: function () {
// read available kubeconfigs from store on filter out configs already found in added clusters
return this.$store.getters.availableKubeContexts.filter(item => !this.clusters.find((cluster) => cluster.contextName == item.currentContext));
},
}
}

View File

@ -1,97 +0,0 @@
import Vue from 'vue'
import Router from 'vue-router'
import store from "../store";
import { whatsNew } from './routeguard'
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/',
name: 'landing-page',
component: require('@/_vue/components/LandingPage').default,
meta: {
routeguard: [
// guards in priority order; the first one to catch will trigger something
whatsNew,
],
}
},
{
path: '/preferences',
name: 'preferences-page',
component: require('@/_vue/components/PreferencesPage').default,
},
{
path: '/workspaces',
name: 'workspaces-page',
component: require('@/_vue/components/WorkspacesPage').default,
props: true,
},
{
path: '/add-workspace',
name: 'add-workspace-page',
component: require('@/_vue/components/AddWorkspacePage').default,
props: true,
},
{
path: '/edit-workspace',
name: 'edit-workspace-page',
component: require('@/_vue/components/EditWorkspacePage').default,
props: true,
},
{
path: '/clusters/:id',
name: 'cluster-page',
component: require('@/_vue/components/ClusterPage').default,
props: true,
},
{
path: '/clusters/:id/settings',
name: 'cluster-settings-page',
component: require('@/_vue/components/ClusterSettings').default,
props: true,
},
{
path: "/add-cluster",
name: "add-cluster-page",
component: require('@/_vue/components/AddClusterPage').default,
props: true,
},
{
path: "/whats-new",
name: "whats-new-page",
component: require('@/_vue/components/WhatsNewPage').default,
props: true,
},
{
path: '*',
redirect: '/'
}
]
})
router.beforeEach((to, from, next) => {
// guard routes
if(to.meta && to.meta.routeguard && to.meta.routeguard.length > 0){
let guardNext;
to.meta.routeguard.forEach(guard => {
if(!guardNext) guardNext = guard(to, from, store);
});
if(guardNext) {
next(guardNext);
} else {
next();
}
}
next();
});
export default router;

View File

@ -1,10 +0,0 @@
import { userStore } from "../../../../common/user-store"
export function whatsNew() {
if(userStore.hasNewAppVersion){
console.log("router: guard: whatsNew: activated");
return {
path: '/whats-new'
}
}
}

View File

@ -1,82 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { userStore } from "../../../common/user-store"
import { getAppVersion } from "../../../common/utils/app-version"
import KubeContexts from './modules/kube-contexts'
import Clusters from './modules/clusters'
import HelmRepos from './modules/helm-repos'
import Workspaces from './modules/workspaces'
import { tracker } from "../../../common/tracker"
import { PromiseIpc } from 'electron-promise-ipc'
Vue.use(Vuex);
const promiseIpc = new PromiseIpc({maxTimeoutMs: 120000});
export default new Vuex.Store({
modules: {
Clusters,
HelmRepos,
KubeContexts,
Workspaces
},
state: {
preferences: {},
hud: {
isMenuVisible: true,
},
seenContexts: userStore.seenContexts,
lastSeenAppVersion: userStore.lastSeenAppVersion,
},
mutations: {
storeSeenContexts(state, contexts) {
contexts.forEach(ctx => userStore.seenContexts.add(ctx));
state.seenContexts = contexts;
},
updateLastSeenAppVersion(state, appVersion) {
state.lastSeenAppVersion = appVersion;
userStore.lastSeenAppVersion = appVersion
},
loadPreferences(state) {
this.commit("savePreferences", userStore.preferences);
},
savePreferences(state, prefs) {
state.preferences = prefs;
userStore.preferences = prefs;
this.dispatch("destroyWebviews")
promiseIpc.send("preferencesSaved")
},
hideMenu(state) {
state.hud.isMenuVisible = false;
},
showMenu(state) {
state.hud.isMenuVisible = true;
}
},
actions: {
async init({commit, getters}) {
commit("loadPreferences");
await this.dispatch('refreshClusters', getters.currentWorkspace);
return true;
},
async addSeenContexts({commit}, data) {
commit('storeSeenContexts', data);
},
async updateLastSeenAppVersion({commit, state}) {
tracker.event("app", "whats-new-seen")
commit("updateLastSeenAppVersion", getAppVersion())
}
},
getters: {
seenContexts: state => state.seenContexts,
hud: state => state.hud,
isMenuVisible: function (state, getters) {
if (userStore.hasNewAppVersion) return false;
return state.hud.isMenuVisible;
},
showWhatsNew: function (state) {
return userStore.hasNewAppVersion;
},
preferences: state => state.preferences,
}
});

View File

@ -1,299 +0,0 @@
import Vue from "vue"
import { ActionTree, GetterTree, MutationTree } from "vuex"
import { PromiseIpc } from 'electron-promise-ipc'
import { ClusterModel } from "../../../../common/cluster-store"
import { Workspace } from "../../../../common/workspace-store"
import { tracker } from "../../../../common/tracker";
import { FeatureStatusMap } from "../../../../main/feature";
import { Kubectl } from "../../../../main/kubectl";
/**
* @deprecated
*/
export interface ClusterInfo extends ClusterModel {
url: string;
apiUrl: string;
online?: boolean;
accessible?: boolean;
failureReason?: string;
nodes?: number;
version?: string;
distribution?: string;
isAdmin?: boolean;
features?: FeatureStatusMap;
kubeCtl?: Kubectl;
contextName: string;
}
export interface LensWebview {
id: string;
loaded: boolean;
webview?: HTMLIFrameElement;
}
export interface ClusterState {
lenses: LensWebview[];
clusters: ClusterInfo[];
}
const promiseIpc = new PromiseIpc({ maxTimeoutMs: 120000 });
const state: ClusterState = {
lenses: [],
clusters: []
}
const actions: ActionTree<ClusterState, any> = {
async refreshClusters({ commit }, currentWorkspace: Workspace) {
const clusters: ClusterInfo[] = await promiseIpc.send('getClusters', currentWorkspace.id).catch((error: Error) => {
return false;
})
if (!clusters) return false;
commit('updateClusters', clusters);
clusters.forEach((cluster: ClusterInfo) => {
const lens: LensWebview = {
id: cluster.id,
webview: null,
loaded: false
};
commit("updateLens", lens)
})
return true;
},
async getCluster({ commit, getters }, id: string) {
const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id)
if (!cluster) return null;
const remoteCluster = await promiseIpc.send("getCluster", cluster.id)
if (!remoteCluster) return null;
Object.assign(cluster, remoteCluster)
commit('updateCluster', cluster);
return cluster;
},
async refineCluster({ commit }, id: string) {
console.log("VUEX: ACTION: REFINE CLUSTER", id);
const remoteCluster = await promiseIpc.send("getCluster", id)
if (!remoteCluster) return null;
commit('updateCluster', remoteCluster);
return remoteCluster;
},
async stopCluster({ dispatch, getters }, id: string) {
const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id)
if (!cluster) return;
const lens = getters.lensById(cluster.id)
if (lens) {
await dispatch("detachWebview", lens)
await promiseIpc.send("stopCluster", cluster.id)
tracker.event("cluster", "stop")
}
},
async removeCluster({ getters, dispatch }, id: string) {
const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id)
if (!cluster) {
return
}
const lens = this.getters.lensById(cluster.id)
if (lens) {
dispatch("detachWebview", lens)
}
await promiseIpc.send("removeCluster", cluster.id).catch((error: Error) => {
return false;
})
tracker.event("cluster", "remove");
await dispatch("refreshClusters", getters.currentWorkspace)
return true;
},
async addCluster({ commit, getters, dispatch }, data) {
const res = await promiseIpc.send("addCluster", data)
if (!res) return null;
tracker.event("cluster", "add");
commit('updateClusters', res.allClusters);
await dispatch("refreshClusters", getters.currentWorkspace);
return res.addedCluster;
},
async clearClusters({ commit, getters, dispatch }) {
// todo: clean from main process as well?
getters.lenses.forEach((lens: LensWebview) => {
if (lens.webview) {
dispatch("detachWebview", lens)
}
})
commit('updateLenses', []);
commit('updateClusters', []);
return true;
},
async uploadClusterIcon({ commit }, data) {
const res = await promiseIpc.send("saveClusterIcon", data)
tracker.event("cluster", "upload-icon")
if (res.cluster) commit("updateCluster", res.cluster)
return res
},
async resetClusterIcon({ commit }, data) {
const res = await promiseIpc.send("resetClusterIcon", data.clusterId)
tracker.event("cluster", "reset-icon")
if (res.cluster) commit("updateCluster", res.cluster)
return res
},
// For data structure see: cluster-manager.ts / FeatureInstallRequest
async installClusterFeature({ commit }, data) {
// Custom no timeout IPC as install can take very variable time
const ipc = new PromiseIpc();
const response = await ipc.send('installFeature', data)
console.log("installer result:", response);
const cluster = await ipc.send('refreshCluster', data.clusterId)
tracker.event("cluster", "install-feature")
commit("updateCluster", cluster)
return response
},
// For data structure see: cluster-manager.ts / FeatureInstallRequest
async upgradeClusterFeature({ commit }, data) {
// Custom no timeout IPC as install can take very variable time
const ipc = new PromiseIpc();
const response = await ipc.send('upgradeFeature', data)
console.log("upgrade result:", response);
const cluster = await ipc.send('refreshCluster', data.clusterId)
tracker.event("cluster", "upgrade-feature")
commit("updateCluster", cluster)
return response
},
// For data structure see: cluster-manager.ts / FeatureInstallRequest
async uninstallClusterFeature({ commit }, data) {
// Custom no timeout IPC as uninstall can take very variable time
const ipc = new PromiseIpc();
const response = await ipc.send('uninstallFeature', data)
console.log("uninstaller result:", response);
const cluster = await ipc.send('refreshCluster', data.clusterId)
tracker.event("cluster", "uninstall-feature")
commit("updateCluster", cluster)
return response
},
attachWebview({ commit }, lens: LensWebview) {
const container: any = document.getElementById("lens-container");
if (!container || !lens.webview) {
return
}
container.style = "display: block;"
let webview = null
container.childNodes.forEach((child: any) => {
if (child === lens.webview) {
webview = child
}
})
if (!webview) {
container.appendChild(lens.webview)
}
container.childNodes.forEach((child: any) => {
if (child !== lens.webview) {
child.style = "display: none;"
} else {
child.style = "top: 0; bottom: 20px; position: absolute; width: 100%;"
}
})
promiseIpc.send("enableClusterSettingsMenuItem", lens.id)
},
detachWebview({ commit }, lens: LensWebview) {
const container: any = document.getElementById("lens-container");
if (!container) {
return
}
container.childNodes.forEach((child: any) => {
if (child === lens.webview) {
container.removeChild(lens.webview)
lens.webview = null
lens.loaded = false
commit("updateLens", lens)
}
})
promiseIpc.send("disableClusterSettingsMenuItem")
},
hideWebviews({ commit }) {
const container: any = document.getElementById("lens-container");
if (!container) {
return
}
container.style = "display: none;"
container.childNodes.forEach((child: any) => {
child.style = "display: none;"
})
promiseIpc.send("disableClusterSettingsMenuItem")
},
destroyWebviews({ commit }) {
state.lenses.forEach((lens) => {
this.dispatch("detachWebview", lens)
})
},
storeCluster({ commit }, cluster: ClusterInfo) {
// clusterStore.saveCluster(cluster);
commit("updateCluster", cluster)
promiseIpc.send("clusterStored", cluster.id)
}
}
const getters: GetterTree<ClusterState, any> = {
clusters: state => state.clusters,
clusterById: state => (id: string) => {
const cluster = state.clusters.find(c => c.id === id);
if (cluster) {
return cluster;
} else {
return null;
}
},
lenses: state => state.lenses,
lensById: state => (id: string) => {
const lens = state.lenses.find(c => c.id === id);
if (lens) {
return lens;
} else {
return null;
}
},
}
const mutations: MutationTree<ClusterState> = {
updateClusters(state, clusters: ClusterInfo[]) {
Vue.set(state, 'clusters', [...clusters])
},
updateCluster(state, cluster) {
state.clusters.forEach((c, index) => {
if (c.id === cluster.id) {
Vue.set(state.clusters, index, cluster)
}
})
},
updateLenses(state, data) {
Vue.set(state, 'lenses', [...data])
},
updateLens(state, lens: LensWebview) {
const lensIndex = state.lenses.findIndex(l => l.id == lens.id);
if (lensIndex >= 0) {
state.lenses[lensIndex] = lens
Vue.set(state.lenses, lensIndex, lens)
} else {
console.log("update new lens")
state.lenses.push(lens)
}
}
}
export default {
namespaced: false,
state,
getters,
mutations,
actions
}

View File

@ -1,54 +0,0 @@
import Vue from "vue"
import { MutationTree, ActionTree, GetterTree } from "vuex"
import { HelmRepo, repoManager } from "../../../../main/helm/helm-repo-manager"
export interface HelmRepoState {
repos: HelmRepo[];
}
const state: HelmRepoState = {
repos: []
}
const actions: ActionTree<HelmRepoState, any> = {
async addHelmRepo({ commit }, data){
const res = await repoManager.addRepo(data).catch((error: Error) => {
return false;
})
if(!res) return false;
return await this.dispatch("refreshHelmRepos")
},
async removeHelmRepo({ commit }, data){
const res = await repoManager.removeRepo(data).catch((error: Error) => {
return false;
})
if(!res) return false;
return await this.dispatch("refreshHelmRepos")
},
async refreshHelmRepos({commit}){
const repos: HelmRepo[] = await repoManager.repositories().catch((error: Error) => {
return null;
})
if(!repos) return false;
commit('updateRepos', repos);
return true;
}
}
const getters: GetterTree<HelmRepoState, any> = {
repos: state => state.repos
}
const mutations: MutationTree<HelmRepoState> = {
updateRepos(state, repos: HelmRepo[]) {
Vue.set(state, 'repos', [...repos])
},
}
export default {
namespaced: false,
state,
getters,
mutations,
actions
}

View File

@ -1,47 +0,0 @@
import * as k8s from "@kubernetes/client-node"
import { splitConfig, dumpConfigYaml } from "../../../../main/k8s"
const state = {
availableKubeContexts: []
}
const actions = {
reloadAvailableKubeContexts({commit}, file) {
if(!file) return;
let kc = new k8s.KubeConfig();
try {
kc.loadFromFile(file);
} catch (error) {
console.error("Failed to read default kubeconfig: " + error.message);
}
// Remove the default setup the client makes if it does not find anything in the default config
// See: https://github.com/kubernetes-client/javascript/blob/2fc8fbc956ca89bf425ca3ea045d46ee7b75296b/src/config.ts#L253
// It defaults to loadFromClusterAndUser() when no config file can be found
if(kc.currentContext === "loaded-context") {
kc = new k8s.KubeConfig();
}
commit("saveAvailableKubeContexts", splitConfig(kc))
}
}
const getters = {
availableKubeContexts: function(state){
return state.availableKubeContexts
}
}
const mutations = {
saveAvailableKubeContexts(state, contexts) {
state.availableKubeContexts = contexts
}
}
export default {
namespaced: false,
state,
getters,
mutations,
actions
}

View File

@ -1,48 +0,0 @@
import { ActionTree, GetterTree, MutationTree } from "vuex"
import { Workspace, workspaceStore } from "../../../../common/workspace-store"
export interface WorkspaceState {
workspaces: Array<Workspace>;
currentWorkspace: Workspace;
}
const state: WorkspaceState = {
workspaces: workspaceStore.workspaces,
currentWorkspace: workspaceStore.workspaces.find((w) => w.id === "default")
}
const actions: ActionTree<WorkspaceState, any> = {}
const getters: GetterTree<WorkspaceState, any> = {
workspaces: state => state.workspaces,
currentWorkspace: state => state.currentWorkspace,
workspaceById: state => (id: string) => {
return state.workspaces.find((ws) => ws.id == id)
}
}
const mutations: MutationTree<WorkspaceState> = {
setCurrentWorkspace(state, workspace: Workspace) {
state.currentWorkspace = workspace
},
addWorkspace(state, workspace: Workspace) {
workspaceStore.saveWorkspace({ ...workspace })
state.workspaces = workspaceStore.workspaces
},
updateWorkspace(state, workspace: Workspace) {
workspaceStore.saveWorkspace({ ...workspace })
state.workspaces = workspaceStore.workspaces
},
removeWorkspace(state, workspace: Workspace) {
workspaceStore.removeWorkspace(workspace.id)
state.workspaces = workspaceStore.workspaces
}
}
export default {
namespaced: false,
state,
getters,
mutations,
actions
}