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:
parent
ef3962f8b5
commit
323a4c141e
@ -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."
|
||||||
|
|
||||||
|
|||||||
@ -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 ""
|
||||||
|
|
||||||
|
|||||||
@ -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 ""
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 || ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
|
||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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/>
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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: () => {
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user