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

cluster-status -- part 2

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-07-20 13:17:39 +03:00
parent ef3962f8b5
commit 323a4c141e
18 changed files with 319 additions and 379 deletions

View File

@ -29,6 +29,10 @@ msgstr "(as a percentage of request)"
msgid "(empty) (Allowing the specific traffic to all pods in this namespace)" msgid "(empty) (Allowing the specific traffic to all pods in this namespace)"
msgstr "(empty) (Allowing the specific traffic to all pods in this namespace)" msgstr "(empty) (Allowing the specific traffic to all pods in this namespace)"
#: src/renderer/components/+add-cluster/add-cluster.tsx:104
msgid "(new)"
msgstr "(new)"
#: src/renderer/components/item-object-list/item-list-layout.tsx:224 #: src/renderer/components/item-object-list/item-list-layout.tsx:224
msgid "<0>Filtered</0>: {itemsCount} / {allItemsCount}" msgid "<0>Filtered</0>: {itemsCount} / {allItemsCount}"
msgstr "<0>Filtered</0>: {itemsCount} / {allItemsCount}" msgstr "<0>Filtered</0>: {itemsCount} / {allItemsCount}"
@ -41,7 +45,7 @@ msgstr "<0>Your browser does not support all Lens features. </0> Please consider
msgid "<0>{0}</0> successfully created" msgid "<0>{0}</0> successfully created"
msgstr "<0>{0}</0> successfully created" msgstr "<0>{0}</0> successfully created"
#: src/renderer/components/+add-cluster/add-cluster.tsx:52 #: src/renderer/components/+add-cluster/add-cluster.tsx:126
msgid "A HTTP proxy server URL (format: http://<address>:<port>)" msgid "A HTTP proxy server URL (format: http://<address>:<port>)"
msgstr "A HTTP proxy server URL (format: http://<address>:<port>)" msgstr "A HTTP proxy server URL (format: http://<address>:<port>)"
@ -67,7 +71,8 @@ msgstr "Account Name"
msgid "Active" msgid "Active"
msgstr "Active" msgstr "Active"
#: src/renderer/components/+add-cluster/add-cluster.tsx:44 #: src/renderer/components/+add-cluster/add-cluster.tsx:118
#: src/renderer/components/cluster-manager/clusters-menu.tsx:97
msgid "Add Cluster" msgid "Add Cluster"
msgstr "Add Cluster" msgstr "Add Cluster"
@ -83,7 +88,7 @@ msgstr "Add RoleBinding"
msgid "Add bindings to {name}" msgid "Add bindings to {name}"
msgstr "Add bindings to {name}" msgstr "Add bindings to {name}"
#: src/renderer/components/+add-cluster/add-cluster.tsx:62 #: src/renderer/components/+add-cluster/add-cluster.tsx:137
msgid "Add cluster" msgid "Add cluster"
msgstr "Add cluster" msgstr "Add cluster"
@ -223,7 +228,7 @@ msgstr "Are you sure you want to drain <0>{nodeName}</0>?"
msgid "Arguments" msgid "Arguments"
msgstr "Arguments" msgstr "Arguments"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 #: src/renderer/components/cluster-manager/clusters-menu.tsx:84
msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
msgstr "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgstr "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
@ -634,7 +639,7 @@ msgstr "Currently applied filters:"
msgid "Custom Resources" msgid "Custom Resources"
msgstr "Custom Resources" msgstr "Custom Resources"
#: src/renderer/components/+add-cluster/add-cluster.tsx:36 #: src/renderer/components/+add-cluster/add-cluster.tsx:110
msgid "Custom.." msgid "Custom.."
msgstr "Custom.." msgstr "Custom.."
@ -698,7 +703,7 @@ msgstr "Description"
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "Desired number of replicas" msgstr "Desired number of replicas"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:49 #: src/renderer/components/cluster-manager/clusters-menu.tsx:51
msgid "Disconnect" msgid "Disconnect"
msgstr "Disconnect" msgstr "Disconnect"
@ -882,7 +887,7 @@ msgstr "Groups"
msgid "HPA" msgid "HPA"
msgstr "HPA" msgstr "HPA"
#: src/renderer/components/+add-cluster/add-cluster.tsx:54 #: src/renderer/components/+add-cluster/add-cluster.tsx:128
msgid "HTTP Proxy server. Used for communicating with Kubernetes API." msgid "HTTP Proxy server. Used for communicating with Kubernetes API."
msgstr "HTTP Proxy server. Used for communicating with Kubernetes API." msgstr "HTTP Proxy server. Used for communicating with Kubernetes API."
@ -1570,6 +1575,10 @@ msgstr "Persistent Volume Claims"
msgid "Persistent Volumes" msgid "Persistent Volumes"
msgstr "Persistent Volumes" msgstr "Persistent Volumes"
#: src/renderer/components/+add-cluster/add-cluster.tsx:51
msgid "Please select kubeconfig"
msgstr "Please select kubeconfig"
#: src/renderer/components/+workloads-pods/pod-menu.tsx:50 #: src/renderer/components/+workloads-pods/pod-menu.tsx:50
msgid "Pod" msgid "Pod"
msgstr "Pod" msgstr "Pod"
@ -1651,7 +1660,7 @@ msgstr "Privileged"
msgid "Provisioner" msgid "Provisioner"
msgstr "Provisioner" msgstr "Provisioner"
#: src/renderer/components/+add-cluster/add-cluster.tsx:48 #: src/renderer/components/+add-cluster/add-cluster.tsx:122
msgid "Proxy settings" msgid "Proxy settings"
msgstr "Proxy settings" msgstr "Proxy settings"
@ -1701,6 +1710,10 @@ msgstr "Receive"
msgid "Reclaim Policy" msgid "Reclaim Policy"
msgstr "Reclaim Policy" msgstr "Reclaim Policy"
#: src/renderer/components/cluster-manager/cluster-status.tsx:52
msgid "Reconnect"
msgstr "Reconnect"
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:70 #: src/renderer/components/+config-autoscalers/hpa-details.tsx:70
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:75 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:75
msgid "Reference" msgid "Reference"
@ -1728,6 +1741,8 @@ msgid "Releases"
msgstr "Releases" msgstr "Releases"
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:58
#: src/renderer/components/cluster-manager/clusters-menu.tsx:62
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2015,7 +2030,7 @@ msgstr "Secrets"
msgid "Select a quota.." msgid "Select a quota.."
msgstr "Select a quota.." msgstr "Select a quota.."
#: src/renderer/components/+add-cluster/add-cluster.tsx:45 #: src/renderer/components/+add-cluster/add-cluster.tsx:119
msgid "Select kubeconfig" msgid "Select kubeconfig"
msgstr "Select kubeconfig" msgstr "Select kubeconfig"
@ -2070,7 +2085,7 @@ msgstr "Set"
msgid "Set quota" msgid "Set quota"
msgstr "Set quota" msgstr "Set quota"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:43 #: src/renderer/components/cluster-manager/clusters-menu.tsx:46
msgid "Settings" msgid "Settings"
msgstr "Settings" msgstr "Settings"
@ -2244,7 +2259,7 @@ msgstr "This field is required"
msgid "This field must contain only lowercase latin characters, numbers and dash." msgid "This field must contain only lowercase latin characters, numbers and dash."
msgstr "This field must contain only lowercase latin characters, numbers and dash." msgstr "This field must contain only lowercase latin characters, numbers and dash."
#: src/renderer/components/cluster-manager/clusters-menu.tsx:72 #: src/renderer/components/cluster-manager/clusters-menu.tsx:82
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "This is the quick launch menu." msgstr "This is the quick launch menu."

View File

@ -29,6 +29,10 @@ msgstr ""
msgid "(empty) (Allowing the specific traffic to all pods in this namespace)" msgid "(empty) (Allowing the specific traffic to all pods in this namespace)"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:104
msgid "(new)"
msgstr ""
#: src/renderer/components/item-object-list/item-list-layout.tsx:224 #: src/renderer/components/item-object-list/item-list-layout.tsx:224
msgid "<0>Filtered</0>: {itemsCount} / {allItemsCount}" msgid "<0>Filtered</0>: {itemsCount} / {allItemsCount}"
msgstr "" msgstr ""
@ -41,7 +45,7 @@ msgstr ""
msgid "<0>{0}</0> successfully created" msgid "<0>{0}</0> successfully created"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:52 #: src/renderer/components/+add-cluster/add-cluster.tsx:126
msgid "A HTTP proxy server URL (format: http://<address>:<port>)" msgid "A HTTP proxy server URL (format: http://<address>:<port>)"
msgstr "" msgstr ""
@ -67,7 +71,8 @@ msgstr ""
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:44 #: src/renderer/components/+add-cluster/add-cluster.tsx:118
#: src/renderer/components/cluster-manager/clusters-menu.tsx:97
msgid "Add Cluster" msgid "Add Cluster"
msgstr "" msgstr ""
@ -83,7 +88,7 @@ msgstr ""
msgid "Add bindings to {name}" msgid "Add bindings to {name}"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:62 #: src/renderer/components/+add-cluster/add-cluster.tsx:137
msgid "Add cluster" msgid "Add cluster"
msgstr "" msgstr ""
@ -223,7 +228,7 @@ msgstr ""
msgid "Arguments" msgid "Arguments"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 #: src/renderer/components/cluster-manager/clusters-menu.tsx:84
msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
msgstr "" msgstr ""
@ -630,7 +635,7 @@ msgstr ""
msgid "Custom Resources" msgid "Custom Resources"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:36 #: src/renderer/components/+add-cluster/add-cluster.tsx:110
msgid "Custom.." msgid "Custom.."
msgstr "" msgstr ""
@ -694,7 +699,7 @@ msgstr ""
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:49 #: src/renderer/components/cluster-manager/clusters-menu.tsx:51
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
@ -873,7 +878,7 @@ msgstr ""
msgid "HPA" msgid "HPA"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:54 #: src/renderer/components/+add-cluster/add-cluster.tsx:128
msgid "HTTP Proxy server. Used for communicating with Kubernetes API." msgid "HTTP Proxy server. Used for communicating with Kubernetes API."
msgstr "" msgstr ""
@ -1553,6 +1558,10 @@ msgstr ""
msgid "Persistent Volumes" msgid "Persistent Volumes"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:51
msgid "Please select kubeconfig"
msgstr ""
#: src/renderer/components/+workloads-pods/pod-menu.tsx:50 #: src/renderer/components/+workloads-pods/pod-menu.tsx:50
msgid "Pod" msgid "Pod"
msgstr "" msgstr ""
@ -1634,7 +1643,7 @@ msgstr ""
msgid "Provisioner" msgid "Provisioner"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:48 #: src/renderer/components/+add-cluster/add-cluster.tsx:122
msgid "Proxy settings" msgid "Proxy settings"
msgstr "" msgstr ""
@ -1684,6 +1693,10 @@ msgstr ""
msgid "Reclaim Policy" msgid "Reclaim Policy"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/cluster-status.tsx:52
msgid "Reconnect"
msgstr ""
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:70 #: src/renderer/components/+config-autoscalers/hpa-details.tsx:70
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:75 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:75
msgid "Reference" msgid "Reference"
@ -1711,6 +1724,8 @@ msgid "Releases"
msgstr "" msgstr ""
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:58
#: src/renderer/components/cluster-manager/clusters-menu.tsx:62
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -1998,7 +2013,7 @@ msgstr ""
msgid "Select a quota.." msgid "Select a quota.."
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:45 #: src/renderer/components/+add-cluster/add-cluster.tsx:119
msgid "Select kubeconfig" msgid "Select kubeconfig"
msgstr "" msgstr ""
@ -2053,7 +2068,7 @@ msgstr ""
msgid "Set quota" msgid "Set quota"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:43 #: src/renderer/components/cluster-manager/clusters-menu.tsx:46
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
@ -2227,7 +2242,7 @@ msgstr ""
msgid "This field must contain only lowercase latin characters, numbers and dash." msgid "This field must contain only lowercase latin characters, numbers and dash."
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:72 #: src/renderer/components/cluster-manager/clusters-menu.tsx:82
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "" msgstr ""

View File

@ -30,6 +30,10 @@ msgstr ""
msgid "(empty) (Allowing the specific traffic to all pods in this namespace)" msgid "(empty) (Allowing the specific traffic to all pods in this namespace)"
msgstr "(Пусто) (Допускается трафик ко всем подам в данной области имен)" msgstr "(Пусто) (Допускается трафик ко всем подам в данной области имен)"
#: src/renderer/components/+add-cluster/add-cluster.tsx:104
msgid "(new)"
msgstr ""
#: src/renderer/components/item-object-list/item-list-layout.tsx:224 #: src/renderer/components/item-object-list/item-list-layout.tsx:224
msgid "<0>Filtered</0>: {itemsCount} / {allItemsCount}" msgid "<0>Filtered</0>: {itemsCount} / {allItemsCount}"
msgstr "<0>Отфильтровано</0>: {itemsCount} / {allItemsCount}" msgstr "<0>Отфильтровано</0>: {itemsCount} / {allItemsCount}"
@ -42,7 +46,7 @@ msgstr "<0>Ваш браузер не поддерживает все возмо
msgid "<0>{0}</0> successfully created" msgid "<0>{0}</0> successfully created"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:52 #: src/renderer/components/+add-cluster/add-cluster.tsx:126
msgid "A HTTP proxy server URL (format: http://<address>:<port>)" msgid "A HTTP proxy server URL (format: http://<address>:<port>)"
msgstr "" msgstr ""
@ -68,7 +72,8 @@ msgstr "Название аккаунта"
msgid "Active" msgid "Active"
msgstr "Активный" msgstr "Активный"
#: src/renderer/components/+add-cluster/add-cluster.tsx:44 #: src/renderer/components/+add-cluster/add-cluster.tsx:118
#: src/renderer/components/cluster-manager/clusters-menu.tsx:97
msgid "Add Cluster" msgid "Add Cluster"
msgstr "" msgstr ""
@ -84,7 +89,7 @@ msgstr "Добавить привязку ролей"
msgid "Add bindings to {name}" msgid "Add bindings to {name}"
msgstr "Добавить привязки к {name}" msgstr "Добавить привязки к {name}"
#: src/renderer/components/+add-cluster/add-cluster.tsx:62 #: src/renderer/components/+add-cluster/add-cluster.tsx:137
msgid "Add cluster" msgid "Add cluster"
msgstr "" msgstr ""
@ -224,7 +229,7 @@ msgstr "Выполнить команду drain для ноды <0>{nodeName}</0
msgid "Arguments" msgid "Arguments"
msgstr "Аргументы" msgstr "Аргументы"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 #: src/renderer/components/cluster-manager/clusters-menu.tsx:84
msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
msgstr "" msgstr ""
@ -635,7 +640,7 @@ msgstr "Текущие фильтры:"
msgid "Custom Resources" msgid "Custom Resources"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:36 #: src/renderer/components/+add-cluster/add-cluster.tsx:110
msgid "Custom.." msgid "Custom.."
msgstr "" msgstr ""
@ -699,7 +704,7 @@ msgstr "Описание"
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "Нужный уровень реплик" msgstr "Нужный уровень реплик"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:49 #: src/renderer/components/cluster-manager/clusters-menu.tsx:51
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
@ -883,7 +888,7 @@ msgstr "Группы"
msgid "HPA" msgid "HPA"
msgstr "HPA" msgstr "HPA"
#: src/renderer/components/+add-cluster/add-cluster.tsx:54 #: src/renderer/components/+add-cluster/add-cluster.tsx:128
msgid "HTTP Proxy server. Used for communicating with Kubernetes API." msgid "HTTP Proxy server. Used for communicating with Kubernetes API."
msgstr "" msgstr ""
@ -1571,6 +1576,10 @@ msgstr "Persistent Volume Claims"
msgid "Persistent Volumes" msgid "Persistent Volumes"
msgstr "Persistent Volumes" msgstr "Persistent Volumes"
#: src/renderer/components/+add-cluster/add-cluster.tsx:51
msgid "Please select kubeconfig"
msgstr ""
#: src/renderer/components/+workloads-pods/pod-menu.tsx:50 #: src/renderer/components/+workloads-pods/pod-menu.tsx:50
msgid "Pod" msgid "Pod"
msgstr "" msgstr ""
@ -1652,7 +1661,7 @@ msgstr ""
msgid "Provisioner" msgid "Provisioner"
msgstr "Комиссия" msgstr "Комиссия"
#: src/renderer/components/+add-cluster/add-cluster.tsx:48 #: src/renderer/components/+add-cluster/add-cluster.tsx:122
msgid "Proxy settings" msgid "Proxy settings"
msgstr "" msgstr ""
@ -1702,6 +1711,10 @@ msgstr "Получение"
msgid "Reclaim Policy" msgid "Reclaim Policy"
msgstr "Политика отката" msgstr "Политика отката"
#: src/renderer/components/cluster-manager/cluster-status.tsx:52
msgid "Reconnect"
msgstr ""
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:70 #: src/renderer/components/+config-autoscalers/hpa-details.tsx:70
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:75 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:75
msgid "Reference" msgid "Reference"
@ -1729,6 +1742,8 @@ msgid "Releases"
msgstr "Релизы" msgstr "Релизы"
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:58
#: src/renderer/components/cluster-manager/clusters-menu.tsx:62
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2016,7 +2031,7 @@ msgstr "Secrets"
msgid "Select a quota.." msgid "Select a quota.."
msgstr "Выберите квоту..." msgstr "Выберите квоту..."
#: src/renderer/components/+add-cluster/add-cluster.tsx:45 #: src/renderer/components/+add-cluster/add-cluster.tsx:119
msgid "Select kubeconfig" msgid "Select kubeconfig"
msgstr "" msgstr ""
@ -2071,7 +2086,7 @@ msgstr "Установлено"
msgid "Set quota" msgid "Set quota"
msgstr "Установить квоту" msgstr "Установить квоту"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:43 #: src/renderer/components/cluster-manager/clusters-menu.tsx:46
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
@ -2245,7 +2260,7 @@ msgstr "Это обязательное поле"
msgid "This field must contain only lowercase latin characters, numbers and dash." msgid "This field must contain only lowercase latin characters, numbers and dash."
msgstr "Это поле может содержать только латинские буквы в нижнем регистре, номера и дефис." msgstr "Это поле может содержать только латинские буквы в нижнем регистре, номера и дефис."
#: src/renderer/components/cluster-manager/clusters-menu.tsx:72 #: src/renderer/components/cluster-manager/clusters-menu.tsx:82
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "" msgstr ""

View File

@ -6,7 +6,7 @@ import { action, observable, reaction, runInAction, toJS, when } from "mobx";
import Singleton from "./utils/singleton"; import Singleton from "./utils/singleton";
import { getAppVersion } from "./utils/app-version"; import { getAppVersion } from "./utils/app-version";
import logger from "../main/logger"; import logger from "../main/logger";
import { sendMessage } from "./ipc"; import { broadcastIpc } from "./ipc";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
export interface BaseStoreParams<T = any> extends ConfOptions<T> { export interface BaseStoreParams<T = any> extends ConfOptions<T> {
@ -118,7 +118,7 @@ export class BaseStore<T = any> extends Singleton {
protected async onModelChange(model: T) { protected async onModelChange(model: T) {
if (ipcMain) { if (ipcMain) {
this.save(model); // save config file this.save(model); // save config file
sendMessage({ channel: this.syncChannel, args: [model] }); // broadcast to renderer views broadcastIpc({ channel: this.syncChannel, args: [model] }); // broadcast to renderer views
} }
// send "update-request" to main-process // send "update-request" to main-process
if (ipcRenderer) { if (ipcRenderer) {

View File

@ -19,11 +19,11 @@ export interface IpcMessageOpts<A extends any[] = any> {
channel: IpcChannel channel: IpcChannel
webContentId?: number; // sends to single webContents view webContentId?: number; // sends to single webContents view
filter?: (webContent: WebContents) => boolean filter?: (webContent: WebContents) => boolean
timeout?: number; // fixme: support timeout?: number; // fixme: add support
args?: A; args?: A;
} }
export function sendMessage({ channel, webContentId, filter, args = [] }: IpcMessageOpts) { export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcMessageOpts) {
const singleView = webContentId ? webContents.fromId(webContentId) : null; const singleView = webContentId ? webContents.fromId(webContentId) : null;
let views = singleView ? [singleView] : webContents.getAllWebContents(); let views = singleView ? [singleView] : webContents.getAllWebContents();
if (filter) { if (filter) {
@ -37,16 +37,16 @@ export function sendMessage({ channel, webContentId, filter, args = [] }: IpcMes
} }
// todo: support timeout + merge with sendMessage? // todo: support timeout + merge with sendMessage?
export async function invokeMessage<T extends any[], R = any>(channel: IpcChannel, ...args: T): Promise<R> { export async function invokeIpc<R = any>(channel: IpcChannel, ...args: any[]): Promise<R> {
logger.debug(`[IPC]: invoke channel "${channel}"`, { args }); logger.info(`[IPC]: invoke channel "${channel}"`, { args });
return ipcRenderer.invoke(channel, ...args); return ipcRenderer.invoke(channel, ...args);
} }
// todo: make isomorphic api // todo: make isomorphic api
export function handleMessage<T extends any[]>(channel: IpcChannel, handler: IpcMessageHandler<T>, options: IpcHandleOpts = {}) { export function handleIpc<T extends any[]>(channel: IpcChannel, handler: IpcMessageHandler<T>, options: IpcHandleOpts = {}) {
const { timeout = 0 } = options; const { timeout = 0 } = options;
ipcMain.handle(channel, async (event, ...args: T) => { ipcMain.handle(channel, async (event, ...args: T) => {
logger.debug(`[IPC]: handle "${channel}"`, { args }); logger.info(`[IPC]: handle "${channel}"`, { args });
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
let timerId; let timerId;
if (timeout) { if (timeout) {
@ -57,17 +57,11 @@ export function handleMessage<T extends any[]>(channel: IpcChannel, handler: Ipc
} }
try { try {
const result = await handler(...args); // todo: maybe exec in separate thread/worker const result = await handler(...args); // todo: maybe exec in separate thread/worker
resolve(result);
clearTimeout(timerId); clearTimeout(timerId);
return result;
} catch (err) { } catch (err) {
logger.debug(`[IPC]: handling "${channel}" error`, { err }); reject(err);
} }
}) })
}) })
} }
export function handleMessages(messages: Record<string, IpcMessageHandler>, options?: IpcHandleOpts) {
Object.entries(messages).forEach(([channel, handler]) => {
handleMessage(channel, handler, options);
})
}

View File

@ -1,61 +1,92 @@
import type http from "http" import type http from "http"
import { autorun } from "mobx"; import { autorun, reaction } from "mobx";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { ClusterId, clusterStore } from "../common/cluster-store" import { ClusterId, clusterStore } from "../common/cluster-store"
import { handleMessage } from "../common/ipc"; import { handleIpc } from "../common/ipc";
import { tracker } from "../common/tracker"; import { Cluster, ClusterIpcChannel } from "./cluster"
import { Cluster, ClusterIpcEvent } from "./cluster"
import logger from "./logger"; import logger from "./logger";
import { tracker } from "../common/tracker";
export class ClusterManager { export class ClusterManager {
protected activeClusterId: ClusterId;
constructor(public readonly port: number) { constructor(public readonly port: number) {
this.activeClusterId = clusterStore.activeClusterId;
// auto-init clusters // auto-init clusters
autorun(() => { autorun(() => {
clusterStore.clusters.forEach(cluster => { clusterStore.clusters.forEach(cluster => {
if (cluster.initialized) return; if (!cluster.initialized) {
cluster.init(port); logger.info(`[CLUSTER-MANAGER]: initializing cluster`, cluster.getMeta());
logger.info(`[CLUSTER-MANAGER]: initializing cluster`, cluster.getMeta()); cluster.init(port);
}
}); });
}); });
// auto-bind events for active cluster
reaction(() => clusterStore.activeCluster, activeCluster => {
const prevCluster = clusterStore.getById(this.activeClusterId);
if (prevCluster) {
prevCluster.unbindEvents();
}
if (activeCluster) {
this.activeClusterId = activeCluster.id;
activeCluster.bindEvents();
activeCluster.refreshStatus();
}
}, {
fireImmediately: true
});
// auto-stop removed clusters // auto-stop removed clusters
autorun(() => { autorun(() => {
const { removedClusters } = clusterStore; const { removedClusters } = clusterStore;
const meta = Array.from(removedClusters.values()).map(cluster => cluster.getMeta()); if (removedClusters.size > 0) {
logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta); const meta = Array.from(removedClusters.values()).map(cluster => cluster.getMeta());
removedClusters.forEach(cluster => cluster.destroy()); logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta);
removedClusters.clear(); removedClusters.forEach(cluster => cluster.disconnect());
removedClusters.clear();
}
}, { }, {
delay: 250 delay: 250
}); });
// listen for ipc-events that must be handled *only* in main-process (nodeIntegration=true) // listen for ipc-events that must/can be handled *only* in main-process (nodeIntegration=true)
handleMessage(ClusterIpcEvent.STOP, this.stopCluster.bind(this)); handleIpc(ClusterIpcChannel.INIT, this.onClusterInit);
handleIpc(ClusterIpcChannel.DISCONNECT, this.onClusterDisconnect);
handleIpc(ClusterIpcChannel.RECONNECT, this.onClusterReconnect);
} }
stop() { stop() {
clusterStore.clusters.forEach((cluster: Cluster) => { clusterStore.clusters.forEach((cluster: Cluster) => {
cluster.stop(); cluster.disconnect();
}) })
} }
protected onClusterInit = async (id = clusterStore.activeClusterId) => {
const cluster = this.getCluster(id);
if (cluster) {
logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta());
tracker.event("cluster", "activate");
await cluster.refreshStatus();
cluster.pushState();
}
}
protected onClusterDisconnect = (id: ClusterId) => {
tracker.event("cluster", "stop");
this.getCluster(id)?.disconnect();
}
protected onClusterReconnect = (id: ClusterId) => {
tracker.event("cluster", "reconnect");
this.getCluster(id)?.reconnect();
}
protected getCluster(id: ClusterId) { protected getCluster(id: ClusterId) {
return clusterStore.getById(id); return clusterStore.getById(id);
} }
protected stopCluster(clusterId: ClusterId) {
tracker.event("cluster", "stop");
this.getCluster(clusterId)?.destroy();
}
// todo
protected reconnectCluster(clusterId: ClusterId) {
tracker.event("cluster", "reconnect");
logger.info(`[CLUSTER-MANAGER]: reconnect cluster`, {
meta: this.getCluster(clusterId)?.getMeta()
});
}
getClusterForRequest(req: http.IncomingMessage): Cluster { getClusterForRequest(req: http.IncomingMessage): Cluster {
let cluster: Cluster = null let cluster: Cluster = null

View File

@ -1,9 +1,9 @@
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store" import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
import type { FeatureStatusMap } from "./feature" import type { FeatureStatusMap } from "./feature"
import type { WorkspaceId } from "../common/workspace-store"; import type { WorkspaceId } from "../common/workspace-store";
import { action, observable, reaction, toJS, when } from "mobx"; import { action, computed, observable, reaction, toJS, when } from "mobx";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { sendMessage } from "../common/ipc"; import { broadcastIpc } from "../common/ipc";
import { ContextHandler } from "./context-handler" import { ContextHandler } from "./context-handler"
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node" import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"
import { Kubectl } from "./kubectl"; import { Kubectl } from "./kubectl";
@ -13,8 +13,9 @@ import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from ".
import request, { RequestPromiseOptions } from "request-promise-native" import request, { RequestPromiseOptions } from "request-promise-native"
import logger from "./logger" import logger from "./logger"
export enum ClusterIpcEvent { export enum ClusterIpcChannel {
STOP = "cluster:stop", INIT = "cluster:init",
DISCONNECT = "cluster:disconnect",
RECONNECT = "cluster:reconnect", RECONNECT = "cluster:reconnect",
} }
@ -43,8 +44,6 @@ export class Cluster implements ClusterModel {
public kubeCtl: Kubectl public kubeCtl: Kubectl
public contextHandler: ContextHandler; public contextHandler: ContextHandler;
protected kubeconfigManager: KubeconfigManager; protected kubeconfigManager: KubeconfigManager;
public whenReady = when(() => this.initialized);
protected disposers: Function[] = []; protected disposers: Function[] = [];
@observable initialized = false; @observable initialized = false;
@ -69,6 +68,10 @@ export class Cluster implements ClusterModel {
this.updateModel(model); this.updateModel(model);
} }
@computed get isReady() {
return this.initialized && this.accessible === true;
}
@action @action
updateModel(model: ClusterModel) { updateModel(model: ClusterModel) {
Object.assign(this, model); Object.assign(this, model);
@ -98,8 +101,7 @@ export class Cluster implements ClusterModel {
} }
} }
bindEvents(viewId: number) { bindEvents() {
if (!this.initialized) return;
logger.info(`[CLUSTER]: bind events`, this.getMeta()); logger.info(`[CLUSTER]: bind events`, this.getMeta());
const refreshStatusTimer = setInterval(() => this.refreshStatus(), 30000); // every 30s const refreshStatusTimer = setInterval(() => this.refreshStatus(), 30000); // every 30s
const refreshEventsTimer = setInterval(() => this.refreshEvents(), 3000); // every 3s const refreshEventsTimer = setInterval(() => this.refreshEvents(), 3000); // every 3s
@ -107,43 +109,35 @@ export class Cluster implements ClusterModel {
this.disposers.push( this.disposers.push(
() => clearInterval(refreshStatusTimer), () => clearInterval(refreshStatusTimer),
() => clearInterval(refreshEventsTimer), () => clearInterval(refreshEventsTimer),
reaction(() => this.getState(), this.pushState, {
reaction(() => this.getState(), clusterState => {
sendMessage({
channel: "cluster:state",
webContentId: viewId,
args: [clusterState],
})
}, {
fireImmediately: true fireImmediately: true
}) })
); );
} }
unbindEvents() { unbindEvents() {
if (!this.initialized) return;
logger.info(`[CLUSTER]: unbind events`, this.getMeta()); logger.info(`[CLUSTER]: unbind events`, this.getMeta());
this.disposers.forEach(dispose => dispose()); this.disposers.forEach(dispose => dispose());
this.disposers.length = 0; this.disposers.length = 0;
} }
stop() { // fixme: possibly doesn't work as expected
this.contextHandler.stopServer(); async reconnect() {
logger.info(`[CLUSTER]: reconnect`, this.getMeta());
await this.contextHandler.stopServer();
await this.contextHandler.ensureServer();
} }
destroy() { disconnect() {
try { logger.info(`[CLUSTER]: disconnect`, this.getMeta());
this.stop(); this.contextHandler.stopServer();
this.unbindEvents(); this.unbindEvents();
this.kubeconfigManager.unlink();
} catch (err) {
logger.error(`[CLUSTER]: destroy() throws: ${err}`, this.getMeta());
}
} }
@action @action
async refreshStatus() { async refreshStatus() {
await this.whenReady; await when(() => this.initialized);
logger.info(`[CLUSTER]: refreshing status`, this.getMeta());
const connectionStatus = await this.getConnectionStatus(); const connectionStatus = await this.getConnectionStatus();
this.online = connectionStatus > ClusterStatus.Offline; this.online = connectionStatus > ClusterStatus.Offline;
this.accessible = connectionStatus == ClusterStatus.AccessGranted; this.accessible = connectionStatus == ClusterStatus.AccessGranted;
@ -191,9 +185,8 @@ export class Cluster implements ClusterModel {
return this.preferences.prometheus?.prefix || "" return this.preferences.prometheus?.prefix || ""
} }
protected k8sRequest(path: string, options: RequestPromiseOptions = {}) { protected async k8sRequest(path: string, options: RequestPromiseOptions = {}) {
const apiUrl = this.kubeProxyUrl + path; const apiUrl = this.kubeProxyUrl + path;
logger.debug(`[CLUSTER]: getting request to: ${apiUrl}`);
return request(apiUrl, { return request(apiUrl, {
json: true, json: true,
timeout: 10000, timeout: 10000,
@ -211,7 +204,7 @@ export class Cluster implements ClusterModel {
this.failureReason = null this.failureReason = null
return ClusterStatus.AccessGranted; return ClusterStatus.AccessGranted;
} catch (error) { } catch (error) {
logger.error(`Failed to connect cluster "${this.contextName}": ${error.stack}`) logger.error(`Failed to connect cluster "${this.contextName}": ${error}`)
if (error.statusCode) { if (error.statusCode) {
if (error.statusCode >= 400 && error.statusCode < 500) { if (error.statusCode >= 400 && error.statusCode < 500) {
this.failureReason = "Invalid credentials"; this.failureReason = "Invalid credentials";
@ -347,6 +340,15 @@ export class Cluster implements ClusterModel {
}) })
} }
pushState = (clusterState = this.getState()) => {
logger.info(`[CLUSTER]: push-state`, clusterState);
broadcastIpc({
// webContentId: viewId, // todo: send to cluster-view only
channel: "cluster:state",
args: [clusterState],
})
}
// get cluster system meta, e.g. use in "logger" // get cluster system meta, e.g. use in "logger"
getMeta() { getMeta() {
return { return {

View File

@ -12,7 +12,7 @@ import { KubeAuthProxy } from "./kube-auth-proxy"
export class ContextHandler { export class ContextHandler {
public proxyPort: number; public proxyPort: number;
public clusterUrl: UrlWithStringQuery; public clusterUrl: UrlWithStringQuery;
protected proxyServer: KubeAuthProxy protected kubeAuthProxy: KubeAuthProxy
protected apiTarget: httpProxy.ServerOptions protected apiTarget: httpProxy.ServerOptions
protected prometheusProvider: string protected prometheusProvider: string
protected prometheusPath: string protected prometheusPath: string
@ -36,7 +36,7 @@ export class ContextHandler {
return `${namespace}/services/${service}:${port}` return `${namespace}/services/${service}:${port}`
} }
public async getPrometheusProvider() { async getPrometheusProvider() {
if (!this.prometheusProvider) { if (!this.prometheusProvider) {
const service = await this.getPrometheusService() const service = await this.getPrometheusService()
logger.info(`using ${service.id} as prometheus provider`) logger.info(`using ${service.id} as prometheus provider`)
@ -45,7 +45,7 @@ export class ContextHandler {
return prometheusProviders.find(p => p.id === this.prometheusProvider) return prometheusProviders.find(p => p.id === this.prometheusProvider)
} }
public async getPrometheusService(): Promise<PrometheusService> { async getPrometheusService(): Promise<PrometheusService> {
const providers = this.prometheusProvider ? prometheusProviders.filter(provider => provider.id == this.prometheusProvider) : prometheusProviders; const providers = this.prometheusProvider ? prometheusProviders.filter(provider => provider.id == this.prometheusProvider) : prometheusProviders;
const prometheusPromises: Promise<PrometheusService>[] = providers.map(async (provider: PrometheusProvider): Promise<PrometheusService> => { const prometheusPromises: Promise<PrometheusService>[] = providers.map(async (provider: PrometheusProvider): Promise<PrometheusService> => {
const apiClient = this.cluster.getProxyKubeconfig().makeApiClient(CoreV1Api) const apiClient = this.cluster.getProxyKubeconfig().makeApiClient(CoreV1Api)
@ -61,19 +61,19 @@ export class ContextHandler {
} }
} }
public async getPrometheusPath(): Promise<string> { async getPrometheusPath(): Promise<string> {
if (!this.prometheusPath) { if (!this.prometheusPath) {
this.prometheusPath = await this.resolvePrometheusPath() this.prometheusPath = await this.resolvePrometheusPath()
} }
return this.prometheusPath; return this.prometheusPath;
} }
public async resolveAuthProxyUrl() { async resolveAuthProxyUrl() {
const proxyPort = await this.ensurePort(); const proxyPort = await this.ensurePort();
return `http://127.0.0.1:${proxyPort}`; return `http://127.0.0.1:${proxyPort}`;
} }
public async getApiTarget(isWatchRequest = false): Promise<httpProxy.ServerOptions> { async getApiTarget(isWatchRequest = false): Promise<httpProxy.ServerOptions> {
if (this.apiTarget && !isWatchRequest) { if (this.apiTarget && !isWatchRequest) {
return this.apiTarget return this.apiTarget
} }
@ -104,26 +104,26 @@ export class ContextHandler {
return this.proxyPort return this.proxyPort
} }
public async ensureServer() { async ensureServer() {
if (!this.proxyServer) { if (!this.kubeAuthProxy) {
await this.ensurePort(); await this.ensurePort();
const proxyEnv = Object.assign({}, process.env) const proxyEnv = Object.assign({}, process.env)
if (this.cluster.preferences.httpsProxy) { if (this.cluster.preferences.httpsProxy) {
proxyEnv.HTTPS_PROXY = this.cluster.preferences.httpsProxy proxyEnv.HTTPS_PROXY = this.cluster.preferences.httpsProxy
} }
this.proxyServer = new KubeAuthProxy(this.cluster, this.proxyPort, proxyEnv) this.kubeAuthProxy = new KubeAuthProxy(this.cluster, this.proxyPort, proxyEnv)
await this.proxyServer.run() await this.kubeAuthProxy.run()
} }
} }
public stopServer() { stopServer() {
if (this.proxyServer) { if (this.kubeAuthProxy) {
this.proxyServer.exit() this.kubeAuthProxy.exit()
this.proxyServer = null this.kubeAuthProxy = null
} }
} }
public proxyServerError(): string { get proxyLastError(): string {
return this.proxyServer?.lastError || "" return this.kubeAuthProxy?.lastError || ""
} }
} }

View File

@ -1,6 +1,6 @@
import { ChildProcess, spawn } from "child_process" import { ChildProcess, spawn } from "child_process"
import { waitUntilUsed } from "tcp-port-used"; import { waitUntilUsed } from "tcp-port-used";
import { sendMessage } from "../common/ipc"; import { broadcastIpc } from "../common/ipc";
import type { Cluster } from "./cluster" import type { Cluster } from "./cluster"
import { bundledKubectl, Kubectl } from "./kubectl" import { bundledKubectl, Kubectl } from "./kubectl"
import logger from "./logger" import logger from "./logger"
@ -85,8 +85,8 @@ export class KubeAuthProxy {
protected async sendIpcLogMessage(res: KubeAuthProxyResponse) { protected async sendIpcLogMessage(res: KubeAuthProxyResponse) {
const channel = `kube-auth:${this.cluster.id}` const channel = `kube-auth:${this.cluster.id}`
logger.debug(`[KUBE-AUTH]: output for ${channel}`, { ...res, meta: this.cluster.getMeta() }); logger.info(`[KUBE-AUTH]: out-channel "${channel}"`, { ...res, meta: this.cluster.getMeta() });
sendMessage({ broadcastIpc({
// webContentId: null, // todo: send a message only to single cluster's window // webContentId: null, // todo: send a message only to single cluster's window
channel: channel, channel: channel,
args: [res], args: [res],
@ -95,7 +95,7 @@ export class KubeAuthProxy {
public exit() { public exit() {
if (this.proxyProcess) { if (this.proxyProcess) {
logger.debug(`Stopping local proxy: ${this.cluster.contextName}`) logger.debug("[KUBE-AUTH]: stopping local proxy", this.cluster.getMeta())
this.proxyProcess.kill() this.proxyProcess.kill()
} }
} }

View File

@ -52,21 +52,17 @@ export class LensProxy {
protected createProxy(): httpProxy { protected createProxy(): httpProxy {
const proxy = httpProxy.createProxyServer(); const proxy = httpProxy.createProxyServer();
proxy.on("proxyRes", (proxyRes, req, res) => { proxy.on("proxyRes", (proxyRes, req, res) => {
if (req.method !== "GET") {
return;
}
if (proxyRes.statusCode === 502) { if (proxyRes.statusCode === 502) {
const cluster = this.clusterManager.getClusterForRequest(req) const cluster = this.clusterManager.getClusterForRequest(req)
if (cluster && cluster.contextHandler.proxyServerError()) { const proxyError = cluster?.contextHandler.proxyLastError;
res.writeHead(proxyRes.statusCode, { if (proxyError) {
"Content-Type": "text/plain" return res.writeHead(502).end(proxyError);
})
res.end(cluster.contextHandler.proxyServerError())
return
} }
} }
if (req.method !== "GET") {
return
}
const reqId = this.getRequestId(req); const reqId = this.getRequestId(req);
if (this.retryCounters.has(reqId)) { if (this.retryCounters.has(reqId)) {
logger.debug(`Resetting proxy retry cache for url: ${reqId}`); logger.debug(`Resetting proxy retry cache for url: ${reqId}`);
@ -92,10 +88,7 @@ export class LensProxy {
} }
} }
} }
res.writeHead(500, { res.writeHead(500).end("Oops, something went wrong.")
'Content-Type': 'text/plain'
})
res.end('Oops, something went wrong.')
}) })
return proxy; return proxy;

View File

@ -1,4 +1,4 @@
import { autorun, reaction } from "mobx"; import { autorun, reaction, when } from "mobx";
import { BrowserWindow, shell } from "electron" import { BrowserWindow, shell } from "electron"
import windowStateKeeper from "electron-window-state" import windowStateKeeper from "electron-window-state"
import type { ClusterId } from "../common/cluster-store"; import type { ClusterId } from "../common/cluster-store";
@ -9,7 +9,6 @@ import logger from "./logger";
// fixme: remove switching view delay on first load // fixme: remove switching view delay on first load
export class WindowManager { export class WindowManager {
protected activeClusterId: ClusterId;
protected activeView: BrowserWindow; protected activeView: BrowserWindow;
protected views = new Map<ClusterId, BrowserWindow>(); protected views = new Map<ClusterId, BrowserWindow>();
protected disposers: CallableFunction[] = []; protected disposers: CallableFunction[] = [];
@ -36,21 +35,7 @@ export class WindowManager {
// Manage reactive state // Manage reactive state
this.disposers.push( this.disposers.push(
// auto-show active cluster window and subscribe for push-events // auto-show active cluster window and subscribe for push-events
reaction(() => clusterStore.activeCluster, async activeCluster => { reaction(() => clusterStore.activeClusterId, clusterId => this.activateView(clusterId), {
if (this.activeClusterId) {
const prevCluster = clusterStore.getById(this.activeClusterId);
if (prevCluster) prevCluster.unbindEvents();
this.activeClusterId = null;
}
if (activeCluster) {
this.activeClusterId = activeCluster.id;
const viewId = await this.activateView(activeCluster.id);
if (viewId) {
await activeCluster.refreshStatus();
activeCluster.bindEvents(viewId);
}
}
}, {
fireImmediately: true, fireImmediately: true,
}), }),
@ -108,7 +93,7 @@ export class WindowManager {
if (activeView !== view) { if (activeView !== view) {
this.activeView = view; this.activeView = view;
if (!isLoadedBefore) { if (!isLoadedBefore) {
await cluster.whenReady; await when(() => cluster.initialized);
await view.loadURL(cluster.webContentUrl); await view.loadURL(cluster.webContentUrl);
this.hideSplash(); this.hideSplash();
} }

View File

@ -1,159 +0,0 @@
<template>
<div class="content">
<div class="h-100">
<div class="wrapper" v-if="status === 'LOADING'">
<cube-spinner text="" />
<div class="auth-output">
<!-- eslint-disable-next-line vue/no-v-html -->
<pre class="auth-output" v-html="authOutput" />
</div>
</div>
<div class="wrapper" v-if="status === 'ERROR'">
<div class="error">
<i class="material-icons">{{ error_icon }}</i>
<div class="text-center">
<h2>{{ cluster.preferences.clusterName }}</h2>
<!-- eslint-disable-next-line vue/no-v-html -->
<pre v-html="authOutput" />
<b-button variant="link" @click="tryAgain">
Reconnect
</b-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import CubeSpinner from "@/_vue/components/CubeSpinner";
import { tracker } from "../../../common/tracker"
export default {
name: "ClusterPage",
components: {
CubeSpinner
},
data(){
return {
authOutput: ""
}
},
computed: {
cluster: function() {
return this.$store.getters.clusterById(this.$route.params.id);
},
online: function() {
if (!this.cluster) { return false }
return this.cluster.online;
},
accessible: function() {
if (!this.cluster) { return false }
return this.cluster.accessible;
},
lens: function() {
return this.$store.getters.lensById(this.cluster.id);
},
status: function() {
if (this.cluster) {
if (this.cluster.accessible && this.lens.loaded === true) {
return "SUCCESS";
} else if (this.cluster.accessible === false) {
return "ERROR";
}
return "LOADING";
}
return "ERROR";
},
error_icon: function() {
if (!this.cluster.online) {
return "cloud_off"
} else {
return "https"
}
}
},
methods: {
tryAgain: function() {
this.authOutput = ""
this.cluster.accessible = null
setTimeout(() => {
this.loadLens()
}, 1000)
},
loadLens: function() {
this.authOutput = "Connecting ...\n";
this.$promiseIpc.on(`kube-auth:${this.cluster.id}`, (output) => {
this.authOutput += output.data;
})
this.toggleLens();
return this.$store.dispatch("refineCluster", this.$route.params.id);
},
lensLoaded: function() {
console.log("lens loaded")
this.lens.loaded = true;
this.$store.commit("updateLens", this.lens);
},
// Called only when online state changes
toggleLens: function() {
if (!this.lens) { return }
if (this.accessible) {
setTimeout(this.activateLens, 0); // see: https://github.com/electron/electron/issues/10016
} else {
this.hideLens();
}
},
activateLens: async function() {
console.log("activate lens")
if (!this.lens.webview) {
console.log("creating an iframe")
const webview = document.createElement('iframe');
webview.addEventListener('load', this.lensLoaded);
webview.src = this.cluster.url;
this.lens.webview = webview;
}
this.$store.dispatch("attachWebview", this.lens);
tracker.event("cluster", "open");
},
hideLens: function() {
this.$store.dispatch("hideWebviews");
}
},
created() {
this.loadLens();
},
destroyed() {
this.hideLens();
},
watch: {
"$route": "loadLens",
"online": "toggleLens",
"cluster": "toggleLens",
"accessible": function(newStatus, oldStatus) {
console.log("accessible watch, vals:", newStatus, oldStatus);
if(newStatus === false) { // accessble == false
tracker.event("cluster", "open-failed");
}
},
}
};
</script>
<style scoped lang="scss">
div.auth-output {
padding-top: 250px;
width: 70%;
pre {
height: 100px;
text-align: center;
}
}
.error {
width: 90%;
}
pre {
font-size: 80%;
overflow: auto;
max-height: 150px;
}
</style>

View File

@ -1,5 +1,5 @@
import "./app.scss"; import "./app.scss";
import React, { Fragment } from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { i18nStore } from "../i18n"; import { i18nStore } from "../i18n";
import { configStore } from "../config.store"; import { configStore } from "../config.store";
@ -34,11 +34,10 @@ import { LandingPage, landingRoute, landingURL } from "./+landing-page";
import { clusterStore } from "../../common/cluster-store"; import { clusterStore } from "../../common/cluster-store";
import { ClusterSettings, clusterSettingsRoute } from "./+cluster-settings"; import { ClusterSettings, clusterSettingsRoute } from "./+cluster-settings";
import { Workspaces, workspacesRoute } from "./+workspaces"; import { Workspaces, workspacesRoute } from "./+workspaces";
import { ErrorBoundary } from "./error-boundary";
@observer @observer
export class App extends React.Component { export class App extends React.Component {
static rootElem = document.getElementById('app');
static async init() { static async init() {
await i18nStore.init(); await i18nStore.init();
await configStore.init(); await configStore.init();
@ -57,27 +56,25 @@ export class App extends React.Component {
render() { render() {
return ( return (
<Fragment> <ErrorBoundary>
<Switch> <Switch>
<Switch> <Route component={LandingPage} {...landingRoute}/>
<Route component={LandingPage} {...landingRoute}/> <Route component={AddCluster} {...addClusterRoute}/>
<Route component={AddCluster} {...addClusterRoute}/> <Route component={Workspaces} {...workspacesRoute}/>
<Route component={Workspaces} {...workspacesRoute}/> <Route component={ClusterSettings} {...clusterSettingsRoute}/>
<Route component={ClusterSettings} {...clusterSettingsRoute}/> <Route component={Cluster} {...clusterRoute}/>
<Route component={Cluster} {...clusterRoute}/> <Route component={Nodes} {...nodesRoute}/>
<Route component={Nodes} {...nodesRoute}/> <Route component={Workloads} {...workloadsRoute}/>
<Route component={Workloads} {...workloadsRoute}/> <Route component={Config} {...configRoute}/>
<Route component={Config} {...configRoute}/> <Route component={Network} {...networkRoute}/>
<Route component={Network} {...networkRoute}/> <Route component={Storage} {...storageRoute}/>
<Route component={Storage} {...storageRoute}/> <Route component={Namespaces} {...namespacesRoute}/>
<Route component={Namespaces} {...namespacesRoute}/> <Route component={Events} {...eventRoute}/>
<Route component={Events} {...eventRoute}/> <Route component={CustomResources} {...crdRoute}/>
<Route component={CustomResources} {...crdRoute}/> <Route component={UserManagement} {...usersManagementRoute}/>
<Route component={UserManagement} {...usersManagementRoute}/> <Route component={Apps} {...appsRoute}/>
<Route component={Apps} {...appsRoute}/> <Redirect exact from="/" to={this.startURL}/>
<Redirect exact from="/" to={this.startURL}/> <Route component={NotFound}/>
<Route component={NotFound}/>
</Switch>
</Switch> </Switch>
<KubeObjectDetails/> <KubeObjectDetails/>
<Notifications/> <Notifications/>
@ -86,7 +83,7 @@ export class App extends React.Component {
<AddRoleBindingDialog/> <AddRoleBindingDialog/>
<PodLogsDialog/> <PodLogsDialog/>
<DeploymentScaleDialog/> <DeploymentScaleDialog/>
</Fragment> </ErrorBoundary>
) )
} }
} }

View File

@ -1,16 +1,40 @@
import "./cluster-manager.scss" import "./cluster-manager.scss"
import React from "react"; import React from "react";
import { observer } from "mobx-react";
import { computed } from "mobx";
import { App } from "../app";
import { ClusterStatus } from "./cluster-status";
import { ClustersMenu } from "./clusters-menu"; import { ClustersMenu } from "./clusters-menu";
import { BottomBar } from "./bottom-bar"; import { BottomBar } from "./bottom-bar";
import { App } from "../app"; import { cssNames, IClassName } from "../../utils";
import { invokeIpc } from "../../../common/ipc";
import { ClusterIpcChannel } from "../../../main/cluster";
import { clusterStore } from "../../../common/cluster-store";
interface Props {
className?: IClassName;
contentClass?: IClassName;
}
@observer
export class ClusterManager extends React.Component<Props> {
@computed get isReady() {
return clusterStore.activeCluster?.isReady
}
async componentDidMount() {
await invokeIpc(ClusterIpcChannel.INIT)
await App.init();
}
export class ClusterManager extends React.Component {
render() { render() {
const { className, contentClass } = this.props;
return ( return (
<div className="ClusterManager"> <div className={cssNames("ClusterManager", className)}>
<div id="draggable-top"/> <div id="draggable-top"/>
<div id="lens-view"> <div id="lens-view" className={cssNames("flex", contentClass)}>
<App/> {this.isReady && <App/>}
{!this.isReady && <ClusterStatus/>}
</div> </div>
<ClustersMenu/> <ClustersMenu/>
<BottomBar/> <BottomBar/>

View File

@ -1,3 +1,19 @@
.ClusterStatus { .ClusterStatus {
--flex-gap: #{$padding * 2};
min-width: 350px;
margin: auto;
text-align: center;
pre {
@include custom-scrollbar;
max-width: 70vw;
max-height: 40vh;
//text-align: left;
}
.Icon {
--size: 70px;
margin: auto;
}
} }

View File

@ -1,29 +1,32 @@
import "./cluster-manager.scss" import "./cluster-status.scss"
import type { KubeAuthProxyResponse } from "../../../main/kube-auth-proxy";
import { Cluster, ClusterIpcEvent } from "../../../main/cluster";
import React from "react"; import React from "react";
import type { KubeAuthProxyResponse } from "../../../main/kube-auth-proxy";
import { ClusterIpcChannel } from "../../../main/cluster";
import { invokeIpc } from "../../../common/ipc";
import { clusterStore } from "../../../common/cluster-store";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { computed, observable } from "mobx"; import { observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Button } from "../button"; import { Button } from "../button";
import { Trans } from "@lingui/macro"; import { cssNames } from "../../utils";
interface Props {
cluster: Cluster;
}
@observer @observer
export class ClusterStatus extends React.Component<Props> { export class ClusterStatus extends React.Component {
@observable authProxyOutput = "Connecting ...\n" @observable authOutput: string[] = [];
@computed get clusterId() { get cluster() {
return this.props.cluster.id; return clusterStore.activeCluster;
}
get clusterId() {
return clusterStore.activeClusterId;
} }
componentDidMount() { componentDidMount() {
ipcRenderer.on(`kube-auth:${this.clusterId}`, (evt, authResponse: KubeAuthProxyResponse) => { this.authOutput = ["Connecting ...\n"];
this.authProxyOutput += authResponse.data; ipcRenderer.on(`kube-auth:${this.clusterId}`, (evt, { data, stream }: KubeAuthProxyResponse) => {
this.authOutput.push(`[${stream}]: ${data}`);
}) })
} }
@ -31,22 +34,32 @@ export class ClusterStatus extends React.Component<Props> {
ipcRenderer.removeAllListeners(`kube-auth:${this.clusterId}`); ipcRenderer.removeAllListeners(`kube-auth:${this.clusterId}`);
} }
reconnectCluster = () => { reconnect = () => {
ipcRenderer.send(ClusterIpcEvent.RECONNECT, this.clusterId); this.authOutput = ["Reconnecting ...\n"];
invokeIpc(ClusterIpcChannel.RECONNECT, this.clusterId);
} }
render() { render() {
const { authProxyOutput } = this; const { authOutput, cluster } = this;
const { contextName, online } = this.props.cluster; const isError = cluster?.accessible === false;
return ( return (
<div className="ClusterStatus flex column"> <div className="ClusterStatus flex column gaps">
<Icon sticker className="status-icon" material={online ? "https" : "cloud_off"}/> {!isError && <Icon material="cloud_queue"/>}
<h2>{contextName}</h2> {isError && <Icon material="cloud_off" className="error"/>}
<pre className="kube-auth-stdout">{authProxyOutput}</pre> <h2>{cluster?.contextName}</h2>
<Button <pre className="kube-auth-out">
primary label={<Trans>Reconnect</Trans>} {authOutput.map((data, index) => {
onClick={this.reconnectCluster} const error = data.startsWith("[stderr]");
/> return <p key={index} className={cssNames({ error })}>{data}</p>
})}
</pre>
{isError && (
<Button
primary className="box center"
label="Reconnect"
onClick={this.reconnect}
/>
)}
</div> </div>
) )
} }

View File

@ -51,7 +51,7 @@ export class ClustersMenu extends React.Component<Props> {
label: _i18n._(t`Settings`), label: _i18n._(t`Settings`),
click: () => navigate(clusterSettingsURL()) click: () => navigate(clusterSettingsURL())
})); }));
if (cluster.initialized) { if (cluster.online) {
menu.append(new MenuItem({ menu.append(new MenuItem({
label: _i18n._(t`Disconnect`), label: _i18n._(t`Disconnect`),
click: () => { click: () => {

View File

@ -10,7 +10,6 @@ import { I18nProvider } from "@lingui/react";
import { browserHistory } from "./navigation"; import { browserHistory } from "./navigation";
import { isMac } from "../common/vars"; import { isMac } from "../common/vars";
import { _i18n } from "./i18n"; import { _i18n } from "./i18n";
import { App } from "./components/app";
import { ClusterManager } from "./components/cluster-manager"; import { ClusterManager } from "./components/cluster-manager";
import { ErrorBoundary } from "./components/error-boundary"; import { ErrorBoundary } from "./components/error-boundary";
import { WhatsNew, whatsNewRoute } from "./components/+whats-new"; import { WhatsNew, whatsNewRoute } from "./components/+whats-new";
@ -19,14 +18,14 @@ import { Preferences, preferencesRoute } from "./components/+preferences";
@observer @observer
class LensApp extends React.Component { class LensApp extends React.Component {
static async init() { static async init() {
App.rootElem.classList.toggle("is-mac", isMac);
await Promise.all([ await Promise.all([
userStore.load(), userStore.load(),
workspaceStore.load(), workspaceStore.load(),
clusterStore.load(), clusterStore.load(),
]); ]);
await App.init(); const elem = document.getElementById("app");
render(<LensApp/>, App.rootElem); elem.classList.toggle("is-mac", isMac);
render(<LensApp/>, elem);
} }
render() { render() {