mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
loading k8s resources into stores per selected namespaces -- part 2
- fix: generating helm chart id Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
47bea52160
commit
f71829ff14
@ -86,7 +86,7 @@ export class HelmChart {
|
|||||||
tillerVersion?: string;
|
tillerVersion?: string;
|
||||||
|
|
||||||
getId() {
|
getId() {
|
||||||
return this.digest;
|
return `${this.apiVersion}/${this.name}@${this.getAppVersion()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
|||||||
@ -62,27 +62,37 @@ export class KubeWatchApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getQuery(): Partial<IKubeWatchRouteQuery> {
|
// FIXME: use POST to send apis for subscribing (list could be huge)
|
||||||
const { isAdmin, allowedNamespaces } = getHostedCluster();
|
// TODO: try to use normal fetch res.body stream to consume watch-api updates
|
||||||
|
// https://github.com/lensapp/lens/issues/1898
|
||||||
|
protected async getQuery() {
|
||||||
|
const { namespaceStore } = await import("../components/+namespaces/namespace.store");
|
||||||
|
await namespaceStore.whenReady;
|
||||||
|
const { isAdmin } = getHostedCluster();
|
||||||
return {
|
return {
|
||||||
api: this.activeApis.map(api => {
|
api: this.activeApis.map(api => {
|
||||||
if (isAdmin) return api.getWatchUrl();
|
if (isAdmin && !api.isNamespaced) {
|
||||||
|
return api.getWatchUrl();
|
||||||
return allowedNamespaces.map(namespace => api.getWatchUrl(namespace));
|
}
|
||||||
|
if (api.isNamespaced) {
|
||||||
|
return namespaceStore.getContextNamespaces().map(namespace => api.getWatchUrl(namespace));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}).flat()
|
}).flat()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: maybe switch to websocket to avoid often reconnects
|
// todo: maybe switch to websocket to avoid often reconnects
|
||||||
@autobind()
|
@autobind()
|
||||||
protected connect() {
|
protected async connect() {
|
||||||
if (this.evtSource) this.disconnect(); // close previous connection
|
if (this.evtSource) this.disconnect(); // close previous connection
|
||||||
|
|
||||||
if (!this.activeApis.length) {
|
const query = await this.getQuery();
|
||||||
|
|
||||||
|
if (!this.activeApis.length || !query.api.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const query = this.getQuery();
|
|
||||||
const apiUrl = `${apiPrefix}/watch?${stringify(query)}`;
|
const apiUrl = `${apiPrefix}/watch?${stringify(query)}`;
|
||||||
|
|
||||||
this.evtSource = new EventSource(apiUrl);
|
this.evtSource = new EventSource(apiUrl);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { HelmRelease, helmReleasesApi, IReleaseCreatePayload, IReleaseUpdatePayl
|
|||||||
import { ItemStore } from "../../item.store";
|
import { ItemStore } from "../../item.store";
|
||||||
import { Secret } from "../../api/endpoints";
|
import { Secret } from "../../api/endpoints";
|
||||||
import { secretsStore } from "../+config-secrets/secrets.store";
|
import { secretsStore } from "../+config-secrets/secrets.store";
|
||||||
import { getHostedCluster } from "../../../common/cluster-store";
|
import { namespaceStore } from "../+namespaces/namespace.store";
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
export class ReleaseStore extends ItemStore<HelmRelease> {
|
export class ReleaseStore extends ItemStore<HelmRelease> {
|
||||||
@ -60,30 +60,24 @@ export class ReleaseStore extends ItemStore<HelmRelease> {
|
|||||||
@action
|
@action
|
||||||
async loadAll() {
|
async loadAll() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
let items;
|
let items: HelmRelease[];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { isAdmin, allowedNamespaces } = getHostedCluster();
|
items = await this.loadItems(namespaceStore.getContextNamespaces());
|
||||||
|
items = this.sortItems(items);
|
||||||
items = await this.loadItems(!isAdmin ? allowedNamespaces : null);
|
this.items.replace(items);
|
||||||
} finally {
|
|
||||||
if (items) {
|
|
||||||
items = this.sortItems(items);
|
|
||||||
this.items.replace(items);
|
|
||||||
}
|
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Loading Helm Chart releases has failed: ${error}`);
|
||||||
|
} finally {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadItems(namespaces?: string[]) {
|
async loadItems(namespaces: string[]) {
|
||||||
if (!namespaces) {
|
return Promise
|
||||||
return helmReleasesApi.list();
|
.all(namespaces.map(namespace => helmReleasesApi.list(namespace)))
|
||||||
} else {
|
.then(items => items.flat());
|
||||||
return Promise
|
|
||||||
.all(namespaces.map(namespace => helmReleasesApi.list(namespace)))
|
|
||||||
.then(items => items.flat());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(payload: IReleaseCreatePayload) {
|
async create(payload: IReleaseCreatePayload) {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { action, comparer, observable, reaction } from "mobx";
|
import { action, comparer, observable, reaction, when } from "mobx";
|
||||||
import { autobind, createStorage } from "../../utils";
|
import { autobind, createStorage } from "../../utils";
|
||||||
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store";
|
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store";
|
||||||
import { Namespace, namespacesApi } from "../../api/endpoints/namespaces.api";
|
import { Namespace, namespacesApi } from "../../api/endpoints/namespaces.api";
|
||||||
import { createPageParam } from "../../navigation";
|
import { createPageParam } from "../../navigation";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { apiManager } from "../../api/api-manager";
|
||||||
import { isAllowedResource } from "../../../common/rbac";
|
import { isAllowedResource } from "../../../common/rbac";
|
||||||
import { getHostedCluster } from "../../../common/cluster-store";
|
import { clusterStore, getHostedCluster } from "../../../common/cluster-store";
|
||||||
|
|
||||||
const storage = createStorage<string[]>("context_namespaces", []);
|
const storage = createStorage<string[]>("context_namespaces", []);
|
||||||
|
|
||||||
@ -21,14 +21,25 @@ export const namespaceUrlParam = createPageParam<string[]>({
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||||
api = namespacesApi;
|
api = namespacesApi;
|
||||||
contextNs = observable.array<string>();
|
|
||||||
|
@observable contextNs = observable.array<string>();
|
||||||
|
@observable isReady = false;
|
||||||
|
|
||||||
|
whenReady = when(() => this.isReady);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
private async init() {
|
||||||
|
await clusterStore.whenLoaded;
|
||||||
|
if (!getHostedCluster()) return;
|
||||||
|
|
||||||
|
await getHostedCluster().whenReady; // wait for cluster-state from main
|
||||||
|
await this.loadAll(); // auto-load allowed namespaces
|
||||||
|
this.isReady = true;
|
||||||
|
|
||||||
this.setContext(this.initNamespaces);
|
this.setContext(this.initNamespaces);
|
||||||
|
|
||||||
return reaction(() => this.contextNs.toJS(), namespaces => {
|
return reaction(() => this.contextNs.toJS(), namespaces => {
|
||||||
@ -40,13 +51,52 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get initNamespaces() {
|
get allowedNamespaces(): string[] {
|
||||||
return namespaceUrlParam.get();
|
return getHostedCluster().allowedNamespaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: page/app reload doesn't restore previously selected namespaces
|
||||||
|
get initNamespaces() {
|
||||||
|
const allowedNamespaces = new Set(this.allowedNamespaces);
|
||||||
|
const lastUsedNamespaces = new Set(storage.get());
|
||||||
|
|
||||||
|
// remove previously saved, but currently disallowed namespaces
|
||||||
|
lastUsedNamespaces.forEach(namespace => {
|
||||||
|
if (!allowedNamespaces.has(namespace)) {
|
||||||
|
lastUsedNamespaces.delete(namespace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// return previously saved and currently allowed namespaces
|
||||||
|
if (lastUsedNamespaces.size) {
|
||||||
|
return Array.from(lastUsedNamespaces);
|
||||||
|
}
|
||||||
|
// otherwise select "default" or first allowed namespace
|
||||||
|
else {
|
||||||
|
if (allowedNamespaces.has("default")) {
|
||||||
|
return ["default"];
|
||||||
|
} else if (allowedNamespaces.size) {
|
||||||
|
return [Array.from(allowedNamespaces)[0]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getContextNamespaces(): string[] {
|
||||||
|
let namespaces = this.contextNs.toJS();
|
||||||
|
if (!namespaces.length) {
|
||||||
|
return [...this.allowedNamespaces]; // show all namespaces when nothing selected
|
||||||
|
}
|
||||||
|
return namespaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
getContextParams() {
|
getContextParams() {
|
||||||
return {
|
return {
|
||||||
namespaces: this.contextNs.toJS(),
|
namespaces: this.getContextNamespaces(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +111,12 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
return super.subscribe(apis);
|
return super.subscribe(apis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadAll() {
|
||||||
|
return super.loadAll({
|
||||||
|
namespaces: this.allowedNamespaces,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams) {
|
protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams) {
|
||||||
if (!isAllowedResource("namespaces")) {
|
if (!isAllowedResource("namespaces")) {
|
||||||
return namespaces.map(this.getDummyNamespace);
|
return namespaces.map(this.getDummyNamespace);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export class OverviewStatuses extends React.Component {
|
|||||||
@autobind()
|
@autobind()
|
||||||
renderWorkload(resource: KubeResource): React.ReactElement {
|
renderWorkload(resource: KubeResource): React.ReactElement {
|
||||||
const store = workloadStores[resource];
|
const store = workloadStores[resource];
|
||||||
const items = store.getAllByNs(namespaceStore.contextNs);
|
const items = store.getAllByNs(namespaceStore.getContextNamespaces());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="workload" key={resource}>
|
<div className="workload" key={resource}>
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: reload and re-subscribe stores when context namespaces changed
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const { store, dependentStores, isClusterScoped } = this.props;
|
const { store, dependentStores, isClusterScoped } = this.props;
|
||||||
const stores = [store, ...dependentStores];
|
const stores = [store, ...dependentStores];
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export interface ItemObject {
|
|||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
export abstract class ItemStore<T extends ItemObject = ItemObject> {
|
export abstract class ItemStore<T extends ItemObject = ItemObject> {
|
||||||
|
abstract loadAll(...args: any[]): any;
|
||||||
abstract loadAll(): Promise<void>;
|
abstract loadAll(): Promise<void>;
|
||||||
|
|
||||||
protected defaultSorting = (item: T) => item.getName();
|
protected defaultSorting = (item: T) => item.getName();
|
||||||
@ -40,8 +41,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
|
|||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
return item;
|
return item;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
const items = this.sortItems([...this.items, newItem]);
|
const items = this.sortItems([...this.items, newItem]);
|
||||||
|
|
||||||
this.items.replace(items);
|
this.items.replace(items);
|
||||||
@ -83,8 +83,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
|
|||||||
const index = this.items.findIndex(item => item === existingItem);
|
const index = this.items.findIndex(item => item === existingItem);
|
||||||
|
|
||||||
this.items.splice(index, 1, item);
|
this.items.splice(index, 1, item);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
let items = [...this.items, item];
|
let items = [...this.items, item];
|
||||||
|
|
||||||
if (sortItems) items = this.sortItems(items);
|
if (sortItems) items = this.sortItems(items);
|
||||||
@ -130,8 +129,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
|
|||||||
toggleSelection(item: T) {
|
toggleSelection(item: T) {
|
||||||
if (this.isSelected(item)) {
|
if (this.isSelected(item)) {
|
||||||
this.unselect(item);
|
this.unselect(item);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.select(item);
|
this.select(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,8 +140,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
|
|||||||
|
|
||||||
if (allSelected) {
|
if (allSelected) {
|
||||||
visibleItems.forEach(this.unselect);
|
visibleItems.forEach(this.unselect);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
visibleItems.forEach(this.select);
|
visibleItems.forEach(this.select);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,13 +93,20 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async loadAll() {
|
async loadAll(params: { namespaces?: string[] } = {}) {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
let items: T[];
|
let items: T[];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { allowedNamespaces: namespaces, isAdmin } = getHostedCluster();
|
const { namespaceStore } = await import("./components/+namespaces/namespace.store");
|
||||||
items = await this.loadItems({ isAdmin, namespaces, api: this.api });
|
const contextNamespaces = params.namespaces || namespaceStore.getContextNamespaces();
|
||||||
|
|
||||||
|
items = await this.loadItems({
|
||||||
|
isAdmin: getHostedCluster().isAdmin,
|
||||||
|
namespaces: contextNamespaces,
|
||||||
|
api: this.api,
|
||||||
|
});
|
||||||
|
|
||||||
items = this.filterItemsOnLoad(items);
|
items = this.filterItemsOnLoad(items);
|
||||||
items = this.sortItems(items);
|
items = this.sortItems(items);
|
||||||
this.items.replace(items);
|
this.items.replace(items);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user