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

tracker / user-store refactoring, part 1

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-07-05 17:16:50 +03:00
parent c9fb015bcf
commit 4270ec4240
23 changed files with 247 additions and 225 deletions

View File

@ -170,9 +170,9 @@
"@types/node": "^12.12.45", "@types/node": "^12.12.45",
"@types/proper-lockfile": "^4.1.1", "@types/proper-lockfile": "^4.1.1",
"@types/tar": "^4.0.3", "@types/tar": "^4.0.3",
"conf": "^7.0.1",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"electron-promise-ipc": "^2.1.0", "electron-promise-ipc": "^2.1.0",
"electron-store": "^5.2.0",
"electron-updater": "^4.3.1", "electron-updater": "^4.3.1",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"filenamify": "^4.1.0", "filenamify": "^4.1.0",
@ -193,6 +193,7 @@
"request": "^2.88.2", "request": "^2.88.2",
"request-promise-native": "^1.0.8", "request-promise-native": "^1.0.8",
"semver": "^7.3.2", "semver": "^7.3.2",
"serializr": "^2.0.3",
"shell-env": "^3.0.0", "shell-env": "^3.0.0",
"tar": "^6.0.2", "tar": "^6.0.2",
"tcp-port-used": "^1.0.1", "tcp-port-used": "^1.0.1",

View File

@ -1,32 +1,32 @@
import ElectronStore from "electron-store" import Config from "conf"
import { Singleton } from "./utils/singleton"; import Singleton from "./utils/singleton";
import migrations from "../migrations/cluster-store" import migrations from "../migrations/cluster-store"
import { Cluster, ClusterBaseInfo } from "../main/cluster"; import { Cluster, ClusterBaseInfo } from "../main/cluster";
export class ClusterStore extends Singleton { export class ClusterStore extends Singleton {
private store = new ElectronStore({ private storeConfig = new Config({
name: "lens-cluster-store", configName: "lens-cluster-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
migrations: migrations, migrations: migrations,
}) })
public getAllClusterObjects(): Cluster[] { public getAllClusterObjects(): Cluster[] {
return this.store.get("clusters", []).map((clusterInfo: ClusterBaseInfo) => { return this.storeConfig.get("clusters", []).map((clusterInfo: ClusterBaseInfo) => {
return new Cluster(clusterInfo) return new Cluster(clusterInfo)
}) })
} }
public getAllClusters(): ClusterBaseInfo[] { public getAllClusters(): ClusterBaseInfo[] {
return this.store.get("clusters", []) return this.storeConfig.get("clusters", [])
} }
public removeCluster(id: string): void { public removeCluster(id: string): void {
this.store.delete(id); this.storeConfig.delete(id);
const clusterBaseInfos = this.getAllClusters() const clusterBaseInfos = this.getAllClusters()
const index = clusterBaseInfos.findIndex((cbi) => cbi.id === id) const index = clusterBaseInfos.findIndex((cbi) => cbi.id === id)
if (index !== -1) { if (index !== -1) {
clusterBaseInfos.splice(index, 1) clusterBaseInfos.splice(index, 1)
this.store.set("clusters", clusterBaseInfos) this.storeConfig.set("clusters", clusterBaseInfos)
} }
} }
@ -62,7 +62,7 @@ export class ClusterStore extends Singleton {
} else { } else {
clusters[index] = storable clusters[index] = storable
} }
this.store.set("clusters", clusters) this.storeConfig.set("clusters", clusters)
} }
public storeClusters(clusters: ClusterBaseInfo[]) { public storeClusters(clusters: ClusterBaseInfo[]) {

View File

@ -1,12 +1,11 @@
import request from "request" import request from "request"
import { userStore } from "./user-store" import { userStore } from "./user-store"
export function globalRequestOpts(requestOpts: request.Options ) { export function globalRequestOpts(requestOpts: request.Options) {
const userPrefs = userStore.getPreferences() const { httpsProxy, allowUntrustedCAs } = userStore.preferences
if (userPrefs.httpsProxy) { if (httpsProxy) {
requestOpts.proxy = userPrefs.httpsProxy requestOpts.proxy = httpsProxy
} }
requestOpts.rejectUnauthorized = !userPrefs.allowUntrustedCAs; requestOpts.rejectUnauthorized = !allowUntrustedCAs;
return requestOpts return requestOpts
} }

View File

@ -1,10 +1,12 @@
import { app, App, remote } from "electron"
import ua from "universal-analytics" import ua from "universal-analytics"
import { machineIdSync } from "node-machine-id" import { machineIdSync } from "node-machine-id"
import Singleton from "./utils/singleton";
import { userStore } from "./user-store" import { userStore } from "./user-store"
const GA_ID = "UA-159377374-1" export class Tracker extends Singleton {
static readonly GA_ID = "UA-159377374-1"
export class Tracker {
protected visitor: ua.Visitor protected visitor: ua.Visitor
protected machineId: string = null; protected machineId: string = null;
protected ip: string = null; protected ip: string = null;
@ -12,31 +14,35 @@ export class Tracker {
protected locale: string; protected locale: string;
protected electronUA: string; protected electronUA: string;
constructor(app: Electron.App) { private constructor(app: App) {
super();
try { try {
this.visitor = ua(GA_ID, machineIdSync(), {strictCidFormat: false}) this.visitor = ua(Tracker.GA_ID, machineIdSync(), { strictCidFormat: false })
} catch (error) { } catch (error) {
this.visitor = ua(GA_ID) this.visitor = ua(Tracker.GA_ID)
} }
this.visitor.set("dl", "https://lensapptelemetry.lakendlabs.com") this.visitor.set("dl", "https://lensapptelemetry.lakendlabs.com")
} }
public async event(eventCategory: string, eventAction: string) { protected async isTelemetryAllowed(): Promise<boolean> {
return new Promise(async (resolve, reject) => { return userStore.preferences.allowTelemetry;
if (!this.telemetryAllowed()) { }
resolve()
return async event(eventCategory: string, eventAction: string, otherParams = {}) {
try {
const allowed = await this.isTelemetryAllowed();
if (!allowed) {
return;
} }
this.visitor.event({ this.visitor.event({
ec: eventCategory, ec: eventCategory,
ea: eventAction ea: eventAction,
...otherParams,
}).send() }).send()
resolve() } catch (err) {
}) console.error(`Failed to track "${eventCategory}:${eventAction}"`, err)
} }
protected telemetryAllowed() {
const userPrefs = userStore.getPreferences()
return !!userPrefs.allowTelemetry
} }
} }
export const tracker: Tracker = Tracker.getInstance(app || remote.app);

View File

@ -1,59 +1,105 @@
import ElectronStore from "electron-store" import { app, remote } from "electron"
import { computed, observable, reaction, toJS } from "mobx";
import Config from "conf"
import semver from "semver"
import migrations from "../migrations/user-store" import migrations from "../migrations/user-store"
import { Singleton } from "./utils/singleton"; import Singleton from "./utils/singleton";
import { getAppVersion } from "./utils/app-version";
import { tracker } from "./tracker";
export interface UserStoreModel {
lastSeenAppVersion: string;
seenContexts: string[];
preferences: UserPreferences;
}
export interface UserPreferences { export interface UserPreferences {
httpsProxy?: string; httpsProxy?: string;
colorTheme?: string; colorTheme?: string | "dark";
allowUntrustedCAs?: boolean; allowUntrustedCAs?: boolean;
allowTelemetry?: boolean; allowTelemetry?: boolean;
downloadMirror?: string; downloadMirror?: string | "default";
} }
export class UserStore extends Singleton { export class UserStore extends Singleton {
protected store = new ElectronStore({ private storeConfig: Config<UserStoreModel>;
name: "lens-user-store",
migrations: migrations,
});
public lastSeenAppVersion() { @observable isReady = false;
return this.store.get('lastSeenAppVersion', "0.0.0") @observable lastSeenAppVersion = "0.0.0"
@observable seenContexts = observable.set();
@observable preferences: UserPreferences = {
allowTelemetry: true,
colorTheme: "dark",
downloadMirror: "default",
};
@computed get hasNewAppVersion() {
return semver.gt(getAppVersion(), this.lastSeenAppVersion);
} }
public setLastSeenAppVersion(version: string) { private constructor() {
this.store.set('lastSeenAppVersion', version) super();
this.init();
} }
public getSeenContexts(): Array<string> { async init() {
return this.store.get("seenContexts", []) /*await*/ this.load();
this.bindEvents();
this.isReady = true;
} }
public storeSeenContext(newContexts: string[]) { saveLastSeenAppVersion() {
const seenContexts = this.getSeenContexts().concat(newContexts) this.lastSeenAppVersion = getAppVersion();
// store unique contexts by casting array to set first
const newContextSet = new Set(seenContexts)
const allContexts = [...newContextSet]
this.store.set("seenContexts", allContexts)
return allContexts
} }
public setPreferences(preferences: UserPreferences) { // todo: use "conf" as pseudo-async for more future-proof usages
this.store.set('preferences', preferences) protected async load() {
this.storeConfig = new Config<UserStoreModel>({
configName: "lens-user-store",
migrations: migrations,
cwd: (app || remote.app).getPath("userData"),
});
this.fromStore(this.storeConfig.store);
} }
public getPreferences(): UserPreferences { protected bindEvents() {
const prefs = this.store.get("preferences", {}) // refresh from file-system updates
if (!prefs.colorTheme) { this.storeConfig.onDidAnyChange((data, oldValue) => {
prefs.colorTheme = "dark" this.fromStore(data);
});
// refresh config file from runtime
reaction(() => this.toJSON(), model => {
this.storeConfig.store = model;
});
// track telemetry availability
reaction(() => this.preferences.allowTelemetry, allowed => {
tracker.event("telemetry", allowed ? "enabled" : "disabled");
});
}
// todo: use "serializr" ?
protected fromStore(data: Partial<UserStoreModel> = {}) {
const { lastSeenAppVersion, seenContexts, preferences } = data
if (lastSeenAppVersion) {
this.lastSeenAppVersion = lastSeenAppVersion;
} }
if (!prefs.downloadMirror) { if (seenContexts) {
prefs.downloadMirror = "default" this.seenContexts = observable.set(seenContexts)
} }
if (prefs.allowTelemetry === undefined) { if (preferences) {
prefs.allowTelemetry = true Object.assign(this.preferences, preferences);
} }
}
return prefs protected toJSON(): UserStoreModel {
return toJS({
lastSeenAppVersion: this.lastSeenAppVersion,
seenContexts: Array.from(this.seenContexts),
preferences: this.preferences,
})
} }
} }

View File

@ -6,7 +6,8 @@
* const usersStore: UsersStore = UsersStore.getInstance(); * const usersStore: UsersStore = UsersStore.getInstance();
*/ */
export class Singleton { // todo: maybe convert to @decorator
class Singleton {
private static instances = new WeakMap<object, Singleton>(); private static instances = new WeakMap<object, Singleton>();
// todo: figure out how to infer child class + arguments types // todo: figure out how to infer child class + arguments types
@ -21,3 +22,6 @@ export class Singleton {
Singleton.instances.delete(this); Singleton.instances.delete(this);
} }
} }
export { Singleton }
export default Singleton;

View File

@ -1,30 +1,26 @@
import ElectronStore from "electron-store" import Config from "conf"
import { Singleton } from "./utils/singleton"; import Singleton from "./utils/singleton";
import { clusterStore } from "./cluster-store" import { clusterStore } from "./cluster-store"
import { getAppVersion } from "./utils/app-version";
export type WorkspaceId = string; export type WorkspaceId = string;
export interface WorkspaceData { export interface WorkspaceStoreModel {
workspaces: Workspace[]
}
export interface Workspace {
id: WorkspaceId; id: WorkspaceId;
name: string; name: string;
description?: string; description?: string;
} }
export class Workspace implements WorkspaceData {
public id: string
public name: string
public description?: string
public constructor(data: WorkspaceData) {
Object.assign(this, data)
}
}
export class WorkspaceStore extends Singleton { export class WorkspaceStore extends Singleton {
static defaultId = "default" static readonly defaultId = "default"
private store = new ElectronStore({ private storeConfig = new Config<WorkspaceStoreModel>({
name: "lens-workspace-store" configName: "lens-workspace-store",
projectVersion: getAppVersion(),
}); });
private constructor() { private constructor() {
@ -46,11 +42,10 @@ export class WorkspaceStore extends Singleton {
} }
public getAllWorkspaces(): Workspace[] { public getAllWorkspaces(): Workspace[] {
const workspacesData: WorkspaceData[] = this.store.get("workspaces", []) return this.storeConfig.get("workspaces", [])
return workspacesData.map((wsd) => new Workspace(wsd))
} }
public saveWorkspace(workspace: WorkspaceData) { public saveWorkspace(workspace: Workspace) {
const workspaces = this.getAllWorkspaces() const workspaces = this.getAllWorkspaces()
const index = workspaces.findIndex((w) => w.id === workspace.id) const index = workspaces.findIndex((w) => w.id === workspace.id)
if (index !== -1) { if (index !== -1) {
@ -58,7 +53,7 @@ export class WorkspaceStore extends Singleton {
} else { } else {
workspaces.push(workspace) workspaces.push(workspace)
} }
this.store.set("workspaces", workspaces) this.storeConfig.set("workspaces", workspaces)
} }
public removeWorkspace(workspace: Workspace) { public removeWorkspace(workspace: Workspace) {
@ -70,7 +65,7 @@ export class WorkspaceStore extends Singleton {
if (index !== -1) { if (index !== -1) {
clusterStore.removeClustersByWorkspace(workspace.id) clusterStore.removeClustersByWorkspace(workspace.id)
workspaces.splice(index, 1) workspaces.splice(index, 1)
this.store.set("workspaces", workspaces) this.storeConfig.set("workspaces", workspaces)
} }
} }
} }

View File

@ -18,7 +18,6 @@ import initMenu from "./menu"
import * as proxy from "./proxy" import * as proxy from "./proxy"
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { clusterStore } from "../common/cluster-store" import { clusterStore } from "../common/cluster-store"
import { tracker } from "./tracker"
import { ClusterManager } from "./cluster-manager"; import { ClusterManager } from "./cluster-manager";
import AppUpdater from "./app-updater" import AppUpdater from "./app-updater"
import { shellSync } from "./shell-sync" import { shellSync } from "./shell-sync"
@ -26,6 +25,7 @@ import { getFreePort } from "./port"
import { mangleProxyEnv } from "./proxy-env" import { mangleProxyEnv } from "./proxy-env"
import { findMainWebContents } from "./webcontents" import { findMainWebContents } from "./webcontents"
import { registerStaticProtocol } from "../common/register-static"; import { registerStaticProtocol } from "../common/register-static";
import { tracker } from "../common/tracker";
mangleProxyEnv() mangleProxyEnv()
if (app.commandLine.getSwitchValue("proxy-server") !== "") { if (app.commandLine.getSwitchValue("proxy-server") !== "") {

View File

@ -285,7 +285,7 @@ export class Kubectl {
} }
protected getDownloadMirror() { protected getDownloadMirror() {
const mirror = packageMirrors.get(userStore.getPreferences().downloadMirror) const mirror = packageMirrors.get(userStore.preferences.downloadMirror)
if (mirror) { if (mirror) {
return mirror return mirror
} }

View File

@ -1,19 +1,7 @@
import packageInfo from "../../package.json" import packageInfo from "../../package.json"
import { bundledKubectl, Kubectl } from "../../src/main/kubectl"; import { bundledKubectl, Kubectl } from "../../src/main/kubectl";
import { UserStore } from "../common/user-store";
jest.mock("../common/user-store", () => { jest.mock("../common/user-store");
const userStoreMock: Partial<UserStore> = {
getPreferences() {
return {
downloadMirror: "default"
}
}
}
return {
userStore: userStoreMock,
}
})
describe("kubectlVersion", () => { describe("kubectlVersion", () => {
it("returns bundled version if exactly same version used", async () => { it("returns bundled version if exactly same version used", async () => {

View File

@ -3,10 +3,10 @@ import * as pty from "node-pty"
import { ShellSession } from "./shell-session"; import { ShellSession } from "./shell-session";
import { v4 as uuid } from "uuid" import { v4 as uuid } from "uuid"
import * as k8s from "@kubernetes/client-node" import * as k8s from "@kubernetes/client-node"
import { KubeConfig } from "@kubernetes/client-node"
import { Cluster } from "./cluster"
import logger from "./logger"; import logger from "./logger";
import { KubeConfig, V1Pod } from "@kubernetes/client-node"; import { tracker } from "../common/tracker";
import { tracker } from "./tracker"
import { Cluster, ClusterPreferences } from "./cluster"
export class NodeShellSession extends ShellSession { export class NodeShellSession extends ShellSession {
protected nodeName: string; protected nodeName: string;
@ -107,7 +107,7 @@ export class NodeShellSession extends ShellSession {
const watch = new k8s.Watch(kc); const watch = new k8s.Watch(kc);
const req = await watch.watch(`/api/v1/namespaces/kube-system/pods`, {}, const req = await watch.watch(`/api/v1/namespaces/kube-system/pods`, {},
// callback is called for each received object. // callback is called for each received object.
(_type, obj) => { (_type, obj) => {
if (obj.metadata.name == podId && obj.status.phase === "Running") { if (obj.metadata.name == podId && obj.status.phase === "Running") {
resolve(true) resolve(true)
@ -119,9 +119,13 @@ export class NodeShellSession extends ShellSession {
reject(false) reject(false)
} }
); );
setTimeout(() => { req.abort(); reject(false); }, 120 * 1000); setTimeout(() => {
req.abort();
reject(false);
}, 120 * 1000);
}) })
} }
protected deleteNodeShellPod() { protected deleteNodeShellPod() {
const kc = this.getKubeConfig(); const kc = this.getKubeConfig();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api); const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
@ -130,12 +134,11 @@ export class NodeShellSession extends ShellSession {
} }
export async function open(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster, nodeName?: string): Promise<ShellSession> { export async function open(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster, nodeName?: string): Promise<ShellSession> {
return new Promise(async(resolve, reject) => { return new Promise(async (resolve, reject) => {
let shell = null let shell = null
if (nodeName) { if (nodeName) {
shell = new NodeShellSession(socket, pathToKubeconfig, cluster, nodeName) shell = new NodeShellSession(socket, pathToKubeconfig, cluster, nodeName)
} } else {
else {
shell = new ShellSession(socket, pathToKubeconfig, cluster) shell = new ShellSession(socket, pathToKubeconfig, cluster)
} }
shell.open() shell.open()

View File

@ -5,7 +5,7 @@ import path from "path";
import * as tempy from "tempy"; import * as tempy from "tempy";
import logger from "./logger" import logger from "./logger"
import { Cluster } from "./cluster"; import { Cluster } from "./cluster";
import { tracker } from "./tracker"; import { tracker } from "../common/tracker";
type KubeObject = { type KubeObject = {
status: {}; status: {};

View File

@ -94,7 +94,7 @@ class ConfigRoute extends LensApi {
const data: IConfigRoutePayload = { const data: IConfigRoutePayload = {
clusterName: cluster.contextName, clusterName: cluster.contextName,
lensVersion: app.getVersion(), lensVersion: app.getVersion(),
lensTheme: `kontena-${userStore.getPreferences().colorTheme}`, lensTheme: `kontena-${userStore.preferences.colorTheme}`,
kubeVersion: cluster.version, kubeVersion: cluster.version,
chartsEnabled: true, chartsEnabled: true,
isClusterAdmin: cluster.isAdmin, isClusterAdmin: cluster.isAdmin,

View File

@ -5,10 +5,10 @@ import path from "path"
import shellEnv from "shell-env" import shellEnv from "shell-env"
import { app } from "electron" import { app } from "electron"
import { Kubectl } from "./kubectl" import { Kubectl } from "./kubectl"
import { tracker } from "./tracker"
import { Cluster, ClusterPreferences } from "./cluster" import { Cluster, ClusterPreferences } from "./cluster"
import { helmCli } from "./helm-cli" import { helmCli } from "./helm-cli"
import { isWindows } from "../common/vars"; import { isWindows } from "../common/vars";
import { tracker } from "../common/tracker";
export class ShellSession extends EventEmitter { export class ShellSession extends EventEmitter {
static shellEnvs: Map<string, any> = new Map() static shellEnvs: Map<string, any> = new Map()

View File

@ -1,4 +0,0 @@
import { Tracker } from "../common/tracker"
import { app, remote } from "electron"
export const tracker = new Tracker(app || remote.app);

View File

@ -1,8 +1,8 @@
import { BrowserWindow, shell } from "electron" import { BrowserWindow, shell } from "electron"
import { PromiseIpc } from "electron-promise-ipc" import { PromiseIpc } from "electron-promise-ipc"
import windowStateKeeper from "electron-window-state" import windowStateKeeper from "electron-window-state"
import { tracker } from "./tracker";
import { getStaticUrl } from "../common/register-static"; import { getStaticUrl } from "../common/register-static";
import { tracker } from "../common/tracker";
export class WindowManager { export class WindowManager {
public mainWindow: BrowserWindow = null; public mainWindow: BrowserWindow = null;

View File

@ -1,10 +1,10 @@
import path from "path"; import path from "path";
import ElectronStore from "electron-store"; import Config from "conf";
import { isTestEnv } from "../common/vars"; import { isTestEnv } from "../common/vars";
export interface MigrationOpts { export interface MigrationOpts {
version: string; version: string;
run(store: ElectronStore, log: (...args: any[]) => void): void; run(storeConfig: Config<any>, log: (...args: any[]) => void): void;
} }
function infoLog(...args: any[]) { function infoLog(...args: any[]) {
@ -12,12 +12,12 @@ function infoLog(...args: any[]) {
console.log(...args); console.log(...args);
} }
export function migration({ version, run }: MigrationOpts) { export function migration<S = any>({ version, run }: MigrationOpts) {
return { return {
[version]: (store: ElectronStore) => { [version]: (storeConfig: Config<S>) => {
const storeName = path.dirname(store.path); const storeName = path.dirname(storeConfig.path);
infoLog(`STORE MIGRATION (${storeName}): ${version}`, ); infoLog(`STORE MIGRATION (${storeName}): ${version}`,);
run(store, infoLog); run(storeConfig, infoLog);
} }
}; };
} }

View File

@ -27,6 +27,8 @@
<script> <script>
import CubeSpinner from "@/_vue/components/CubeSpinner"; import CubeSpinner from "@/_vue/components/CubeSpinner";
import { tracker } from "../../../common/tracker"
export default { export default {
name: "ClusterPage", name: "ClusterPage",
components: { components: {
@ -112,7 +114,7 @@ export default {
this.lens.webview = webview; this.lens.webview = webview;
} }
this.$store.dispatch("attachWebview", this.lens); this.$store.dispatch("attachWebview", this.lens);
this.$tracker.event("cluster", "open"); tracker.event("cluster", "open");
}, },
hideLens: function() { hideLens: function() {
this.$store.dispatch("hideWebviews"); this.$store.dispatch("hideWebviews");
@ -131,7 +133,7 @@ export default {
"accessible": function(newStatus, oldStatus) { "accessible": function(newStatus, oldStatus) {
console.log("accessible watch, vals:", newStatus, oldStatus); console.log("accessible watch, vals:", newStatus, oldStatus);
if(newStatus === false) { // accessble == false if(newStatus === false) { // accessble == false
this.$tracker.event("cluster", "open-failed"); tracker.event("cluster", "open-failed");
} }
}, },
} }

View File

@ -2,17 +2,14 @@ import "../../common/system-ca"
import "./assets/css/app.scss" import "./assets/css/app.scss"
import "prismjs"; import "prismjs";
import "prismjs/components/prism-yaml" import "prismjs/components/prism-yaml"
import { remote } from "electron" import { PromiseIpc } from 'electron-promise-ipc'
import Vue from 'vue' import Vue from 'vue'
import VueElectron from 'vue-electron' import VueElectron from 'vue-electron'
import BootstrapVue from 'bootstrap-vue' import BootstrapVue from 'bootstrap-vue'
import { PromiseIpc } from 'electron-promise-ipc'
import { Tracker } from "../../common/tracker"
import App from './App' import App from './App'
import router from './router' import router from './router'
import store from './store' import store from './store'
const tracker = new Tracker(remote.app);
const promiseIpc = new PromiseIpc({maxTimeoutMs: 6000}); const promiseIpc = new PromiseIpc({maxTimeoutMs: 6000});
promiseIpc.on('navigate', async (view) => { promiseIpc.on('navigate', async (view) => {
@ -26,7 +23,6 @@ Vue.use(BootstrapVue)
Vue.mixin({ Vue.mixin({
created: function () { created: function () {
this.$promiseIpc = promiseIpc; this.$promiseIpc = promiseIpc;
this.$tracker = tracker;
} }
}) })

View File

@ -7,18 +7,13 @@ import KubeContexts from './modules/kube-contexts'
import Clusters from './modules/clusters' import Clusters from './modules/clusters'
import HelmRepos from './modules/helm-repos' import HelmRepos from './modules/helm-repos'
import Workspaces from './modules/workspaces' import Workspaces from './modules/workspaces'
import { tracker } from "../../../common/tracker"
// promise ipc
import { PromiseIpc } from 'electron-promise-ipc' import { PromiseIpc } from 'electron-promise-ipc'
const promiseIpc = new PromiseIpc( { maxTimeoutMs: 120000 } );
// tracker
import { Tracker } from "../../../common/tracker"
import { remote } from "electron"
const tracker = new Tracker(remote.app);
Vue.use(Vuex); Vue.use(Vuex);
const promiseIpc = new PromiseIpc( { maxTimeoutMs: 120000 } );
export default new Vuex.Store({ export default new Vuex.Store({
modules: { modules: {
Clusters, Clusters,
@ -31,29 +26,24 @@ export default new Vuex.Store({
hud: { hud: {
isMenuVisible: true, isMenuVisible: true,
}, },
seenContexts: userStore.getSeenContexts(), seenContexts: userStore.seenContexts,
lastSeenAppVersion: userStore.lastSeenAppVersion(), lastSeenAppVersion: userStore.lastSeenAppVersion,
}, },
mutations: { mutations: {
storeSeenContexts(state, context) { storeSeenContexts(state, context) {
const seenContexts = userStore.storeSeenContext(context); userStore.seenContexts.add(context);
state.seenContexts = seenContexts state.seenContexts = Array.from(userStore.seenContexts);
}, },
updateLastSeenAppVersion(state, appVersion) { updateLastSeenAppVersion(state, appVersion) {
state.lastSeenAppVersion = appVersion; state.lastSeenAppVersion = appVersion;
userStore.setLastSeenAppVersion(appVersion) userStore.lastSeenAppVersion = appVersion
}, },
loadPreferences(state) { loadPreferences(state) {
this.commit("savePreferences", userStore.getPreferences()); this.commit("savePreferences", userStore.preferences);
}, },
savePreferences(state, prefs) { savePreferences(state, prefs) {
if (prefs.allowTelemetry) {
tracker.event("telemetry", "enabled")
} else {
tracker.event("telemetry", "disabled")
}
state.preferences = prefs; state.preferences = prefs;
userStore.setPreferences(prefs); userStore.preferences = prefs;
this.dispatch("destroyWebviews") this.dispatch("destroyWebviews")
promiseIpc.send("preferencesSaved") promiseIpc.send("preferencesSaved")
}, },
@ -67,9 +57,7 @@ export default new Vuex.Store({
actions: { actions: {
async init({commit, getters}) { async init({commit, getters}) {
commit("loadPreferences"); commit("loadPreferences");
await this.dispatch('refreshClusters', getters.currentWorkspace); await this.dispatch('refreshClusters', getters.currentWorkspace);
return true; return true;
}, },
async addSeenContexts({commit}, data){ async addSeenContexts({commit}, data){

View File

@ -1,14 +1,12 @@
import Vue from "vue" import Vue from "vue"
import { ClusterInfo } from "../../../../main/cluster" import { ClusterInfo } from "../../../../main/cluster"
import { MutationTree, ActionTree, GetterTree } from "vuex" import { ActionTree, GetterTree, MutationTree } from "vuex"
import { PromiseIpc } from 'electron-promise-ipc' import { PromiseIpc } from 'electron-promise-ipc'
import { Tracker } from "../../../../common/tracker"
import { remote } from "electron"
import { clusterStore } from "../../../../common/cluster-store" import { clusterStore } from "../../../../common/cluster-store"
import { Workspace } from "../../../../common/workspace-store" import { Workspace } from "../../../../common/workspace-store"
import { tracker } from "../../../../common/tracker";
const promiseIpc = new PromiseIpc( { maxTimeoutMs: 120000 } ); const promiseIpc = new PromiseIpc({ maxTimeoutMs: 120000 });
const tracker = new Tracker(remote.app);
export interface LensWebview { export interface LensWebview {
id: string; id: string;
@ -26,12 +24,12 @@ const state: ClusterState = {
clusters: [] clusters: []
} }
const actions: ActionTree<ClusterState, any> = { const actions: ActionTree<ClusterState, any> = {
async refreshClusters({commit}, currentWorkspace: Workspace) { async refreshClusters({ commit }, currentWorkspace: Workspace) {
const clusters: ClusterInfo[] = await promiseIpc.send('getClusters', currentWorkspace.id).catch((error: Error) => { const clusters: ClusterInfo[] = await promiseIpc.send('getClusters', currentWorkspace.id).catch((error: Error) => {
return false; return false;
}) })
if(!clusters) return false; if (!clusters) return false;
commit('updateClusters', clusters); commit('updateClusters', clusters);
clusters.forEach((cluster: ClusterInfo) => { clusters.forEach((cluster: ClusterInfo) => {
const lens: LensWebview = { const lens: LensWebview = {
@ -43,29 +41,29 @@ const actions: ActionTree<ClusterState, any> = {
}) })
return true; return true;
}, },
async getCluster({commit, getters}, id: string) { async getCluster({ commit, getters }, id: string) {
const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id) const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id)
if(!cluster) return null; if (!cluster) return null;
const remoteCluster = await promiseIpc.send("getCluster", cluster.id) const remoteCluster = await promiseIpc.send("getCluster", cluster.id)
if(!remoteCluster) return null; if (!remoteCluster) return null;
Object.assign(cluster, remoteCluster) Object.assign(cluster, remoteCluster)
commit('updateCluster', cluster); commit('updateCluster', cluster);
return cluster; return cluster;
}, },
async refineCluster({commit}, id: string) { async refineCluster({ commit }, id: string) {
console.log("VUEX: ACTION: REFINE CLUSTER", id); console.log("VUEX: ACTION: REFINE CLUSTER", id);
const remoteCluster = await promiseIpc.send("getCluster", id) const remoteCluster = await promiseIpc.send("getCluster", id)
if(!remoteCluster) return null; if (!remoteCluster) return null;
commit('updateCluster', remoteCluster); commit('updateCluster', remoteCluster);
return remoteCluster; return remoteCluster;
}, },
async stopCluster({dispatch, getters}, id: string) { async stopCluster({ dispatch, getters }, id: string) {
const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id) const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id)
if (!cluster) return; if (!cluster) return;
@ -76,7 +74,7 @@ const actions: ActionTree<ClusterState, any> = {
tracker.event("cluster", "stop") tracker.event("cluster", "stop")
} }
}, },
async removeCluster({getters, dispatch}, id: string) { async removeCluster({ getters, dispatch }, id: string) {
const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id) const cluster: ClusterInfo = getters.clusters.find((c: ClusterInfo) => c.id === id)
if (!cluster) { if (!cluster) {
return return
@ -92,16 +90,16 @@ const actions: ActionTree<ClusterState, any> = {
await dispatch("refreshClusters", getters.currentWorkspace) await dispatch("refreshClusters", getters.currentWorkspace)
return true; return true;
}, },
async addCluster({commit, getters, dispatch}, data) { async addCluster({ commit, getters, dispatch }, data) {
const res = await promiseIpc.send("addCluster", data) const res = await promiseIpc.send("addCluster", data)
if(!res) return null; if (!res) return null;
tracker.event("cluster", "add"); tracker.event("cluster", "add");
commit('updateClusters', res.allClusters); commit('updateClusters', res.allClusters);
await dispatch("refreshClusters", getters.currentWorkspace); await dispatch("refreshClusters", getters.currentWorkspace);
return res.addedCluster; return res.addedCluster;
}, },
async clearClusters({commit, getters, dispatch}){ async clearClusters({ commit, getters, dispatch }) {
// todo: clean from main process as well? // todo: clean from main process as well?
getters.lenses.forEach((lens: LensWebview) => { getters.lenses.forEach((lens: LensWebview) => {
if (lens.webview) { if (lens.webview) {
@ -113,14 +111,14 @@ const actions: ActionTree<ClusterState, any> = {
return true; return true;
}, },
async uploadClusterIcon({commit}, data) { async uploadClusterIcon({ commit }, data) {
const res = await promiseIpc.send("saveClusterIcon", data) const res = await promiseIpc.send("saveClusterIcon", data)
tracker.event("cluster", "upload-icon") tracker.event("cluster", "upload-icon")
if (res.cluster) commit("updateCluster", res.cluster) if (res.cluster) commit("updateCluster", res.cluster)
return res return res
}, },
async resetClusterIcon({commit}, data) { async resetClusterIcon({ commit }, data) {
const res = await promiseIpc.send("resetClusterIcon", data.clusterId) const res = await promiseIpc.send("resetClusterIcon", data.clusterId)
tracker.event("cluster", "reset-icon") tracker.event("cluster", "reset-icon")
if (res.cluster) commit("updateCluster", res.cluster) if (res.cluster) commit("updateCluster", res.cluster)
@ -128,7 +126,7 @@ const actions: ActionTree<ClusterState, any> = {
}, },
// For data structure see: cluster-manager.ts / FeatureInstallRequest // For data structure see: cluster-manager.ts / FeatureInstallRequest
async installClusterFeature({commit}, data) { async installClusterFeature({ commit }, data) {
// Custom no timeout IPC as install can take very variable time // Custom no timeout IPC as install can take very variable time
const ipc = new PromiseIpc(); const ipc = new PromiseIpc();
const response = await ipc.send('installFeature', data) const response = await ipc.send('installFeature', data)
@ -140,20 +138,19 @@ const actions: ActionTree<ClusterState, any> = {
return response return response
}, },
// For data structure see: cluster-manager.ts / FeatureInstallRequest // For data structure see: cluster-manager.ts / FeatureInstallRequest
async upgradeClusterFeature({commit}, data) { async upgradeClusterFeature({ commit }, data) {
// Custom no timeout IPC as install can take very variable time // Custom no timeout IPC as install can take very variable time
const ipc = new PromiseIpc(); const ipc = new PromiseIpc();
const response = await ipc.send('upgradeFeature', data) const response = await ipc.send('upgradeFeature', data)
console.log("upgrade result:", response); console.log("upgrade result:", response);
const cluster = await ipc.send('refreshCluster', data.clusterId) const cluster = await ipc.send('refreshCluster', data.clusterId)
tracker.event("cluster", "upgrade-feature") tracker.event("cluster", "upgrade-feature")
commit("updateCluster", cluster) commit("updateCluster", cluster)
return response return response
}, },
// For data structure see: cluster-manager.ts / FeatureInstallRequest // For data structure see: cluster-manager.ts / FeatureInstallRequest
async uninstallClusterFeature({commit}, data) { async uninstallClusterFeature({ commit }, data) {
// Custom no timeout IPC as uninstall can take very variable time // Custom no timeout IPC as uninstall can take very variable time
const ipc = new PromiseIpc(); const ipc = new PromiseIpc();
const response = await ipc.send('uninstallFeature', data) const response = await ipc.send('uninstallFeature', data)
@ -165,7 +162,7 @@ const actions: ActionTree<ClusterState, any> = {
return response return response
}, },
attachWebview({commit}, lens: LensWebview) { attachWebview({ commit }, lens: LensWebview) {
const container: any = document.getElementById("lens-container"); const container: any = document.getElementById("lens-container");
if (!container || !lens.webview) { if (!container || !lens.webview) {
return return
@ -189,9 +186,11 @@ const actions: ActionTree<ClusterState, any> = {
}) })
promiseIpc.send("enableClusterSettingsMenuItem", lens.id) promiseIpc.send("enableClusterSettingsMenuItem", lens.id)
}, },
detachWebview({commit}, lens: LensWebview) { detachWebview({ commit }, lens: LensWebview) {
const container: any = document.getElementById("lens-container"); const container: any = document.getElementById("lens-container");
if (!container) { return } if (!container) {
return
}
container.childNodes.forEach((child: any) => { container.childNodes.forEach((child: any) => {
if (child === lens.webview) { if (child === lens.webview) {
container.removeChild(lens.webview) container.removeChild(lens.webview)
@ -202,21 +201,23 @@ const actions: ActionTree<ClusterState, any> = {
}) })
promiseIpc.send("disableClusterSettingsMenuItem") promiseIpc.send("disableClusterSettingsMenuItem")
}, },
hideWebviews({commit}) { hideWebviews({ commit }) {
const container: any = document.getElementById("lens-container"); const container: any = document.getElementById("lens-container");
if (!container) { return } if (!container) {
return
}
container.style = "display: none;" container.style = "display: none;"
container.childNodes.forEach((child: any) => { container.childNodes.forEach((child: any) => {
child.style = "display: none;" child.style = "display: none;"
}) })
promiseIpc.send("disableClusterSettingsMenuItem") promiseIpc.send("disableClusterSettingsMenuItem")
}, },
destroyWebviews({commit}) { destroyWebviews({ commit }) {
state.lenses.forEach((lens) => { state.lenses.forEach((lens) => {
this.dispatch("detachWebview", lens) this.dispatch("detachWebview", lens)
}) })
}, },
storeCluster({commit}, cluster: ClusterInfo) { storeCluster({ commit }, cluster: ClusterInfo) {
clusterStore.saveCluster(cluster); clusterStore.saveCluster(cluster);
commit("updateCluster", cluster) commit("updateCluster", cluster)
promiseIpc.send("clusterStored", cluster.id) promiseIpc.send("clusterStored", cluster.id)
@ -250,7 +251,7 @@ const mutations: MutationTree<ClusterState> = {
}, },
updateCluster(state, cluster) { updateCluster(state, cluster) {
state.clusters.forEach((c, index) => { state.clusters.forEach((c, index) => {
if(c.id === cluster.id) { if (c.id === cluster.id) {
Vue.set(state.clusters, index, cluster) Vue.set(state.clusters, index, cluster)
} }
}) })

View File

@ -1,5 +1,5 @@
import { MutationTree, ActionTree, GetterTree } from "vuex" import { MutationTree, ActionTree, GetterTree } from "vuex"
import { workspaceStore, Workspace, WorkspaceData } from "../../../../common/workspace-store" import { workspaceStore, Workspace } from "../../../../common/workspace-store"
export interface WorkspaceState { export interface WorkspaceState {
workspaces: Array<Workspace>; workspaces: Array<Workspace>;
@ -26,11 +26,11 @@ const mutations: MutationTree<WorkspaceState> = {
setCurrentWorkspace(state, workspace: Workspace) { setCurrentWorkspace(state, workspace: Workspace) {
state.currentWorkspace = workspace state.currentWorkspace = workspace
}, },
addWorkspace(state, workspace: WorkspaceData) { addWorkspace(state, workspace: Workspace) {
workspaceStore.saveWorkspace(workspace) workspaceStore.saveWorkspace(workspace)
state.workspaces = workspaceStore.getAllWorkspaces() state.workspaces = workspaceStore.getAllWorkspaces()
}, },
updateWorkspace(state, workspace: WorkspaceData) { updateWorkspace(state, workspace: Workspace) {
workspaceStore.saveWorkspace(workspace) workspaceStore.saveWorkspace(workspace)
state.workspaces = workspaceStore.getAllWorkspaces() state.workspaces = workspaceStore.getAllWorkspaces()
}, },

View File

@ -3877,21 +3877,21 @@ concurrently@^5.2.0:
tree-kill "^1.2.2" tree-kill "^1.2.2"
yargs "^13.3.0" yargs "^13.3.0"
conf@^6.2.1: conf@^7.0.1:
version "6.2.4" version "7.0.1"
resolved "https://registry.yarnpkg.com/conf/-/conf-6.2.4.tgz#49d23c4e21ef2ac2860f7b5ed25b7b7e484f769f" resolved "https://registry.yarnpkg.com/conf/-/conf-7.0.1.tgz#a3ddd860db0357219b9ccdf92fd618e161fe6848"
integrity sha512-GjgyPRLo1qK1LR9RWAdUagqo+DP18f5HWCFk4va7GS+wpxQTOzfuKTwKOvGW2c01/YXNicAyyoyuSddmdkBzZQ== integrity sha512-UFA9EPFKK+tedCjz4JKQSxAN0/0r0rjURdq9N805JXWOhFzmr7fTLmG1cnsHCvyYYd65wRjf0KB8zg+WPelkvA==
dependencies: dependencies:
ajv "^6.10.2" ajv "^6.12.2"
debounce-fn "^3.0.1" debounce-fn "^4.0.0"
dot-prop "^5.0.0" dot-prop "^5.2.0"
env-paths "^2.2.0" env-paths "^2.2.0"
json-schema-typed "^7.0.1" json-schema-typed "^7.0.3"
make-dir "^3.0.0" make-dir "^3.1.0"
onetime "^5.1.0" onetime "^5.1.0"
pkg-up "^3.0.1" pkg-up "^3.1.0"
semver "^6.2.0" semver "^7.3.2"
write-file-atomic "^3.0.0" write-file-atomic "^3.0.3"
config-chain@^1.1.11: config-chain@^1.1.11:
version "1.1.12" version "1.1.12"
@ -4233,12 +4233,12 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
debounce-fn@^3.0.1: debounce-fn@^4.0.0:
version "3.0.1" version "4.0.0"
resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-3.0.1.tgz#034afe8b904d985d1ec1aa589cd15f388741d680" resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7"
integrity sha512-aBoJh5AhpqlRoHZjHmOzZlRx+wz2xVwGL9rjs+Kj0EWUrL4/h4K7OD176thl2Tdoqui/AaA4xhHrNArGLAaI3Q== integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==
dependencies: dependencies:
mimic-fn "^2.1.0" mimic-fn "^3.0.0"
debug@4.1.0: debug@4.1.0:
version "4.1.0" version "4.1.0"
@ -4547,7 +4547,7 @@ dot-case@^3.0.3:
no-case "^3.0.3" no-case "^3.0.3"
tslib "^1.10.0" tslib "^1.10.0"
dot-prop@^5.0.0, dot-prop@^5.2.0: dot-prop@^5.2.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb"
integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==
@ -4674,14 +4674,6 @@ electron-publish@22.7.0:
lazy-val "^1.0.4" lazy-val "^1.0.4"
mime "^2.4.5" mime "^2.4.5"
electron-store@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-5.2.0.tgz#a15718fc1fa21acfd07af55f9b94f9fa6a536665"
integrity sha512-iU3WDqEDAYNYR9XV7p0tJajq/zs9z7Nrn0sAoR5nDyn8h/9dr9kusKbTxD8NtVEBD1TB1pkGMqcbIt/y6knDwQ==
dependencies:
conf "^6.2.1"
type-fest "^0.7.1"
electron-to-chromium@^1.3.413: electron-to-chromium@^1.3.413:
version "1.3.464" version "1.3.464"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.464.tgz#fe13feaa08f6f865d3c89d5d72e54c194f463aa5" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.464.tgz#fe13feaa08f6f865d3c89d5d72e54c194f463aa5"
@ -7260,7 +7252,7 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-typed@^7.0.1: json-schema-typed@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9" resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9"
integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A== integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==
@ -7703,7 +7695,7 @@ make-dir@^2.0.0:
pify "^4.0.1" pify "^4.0.1"
semver "^5.6.0" semver "^5.6.0"
make-dir@^3.0.0, make-dir@^3.0.2: make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@ -7914,6 +7906,11 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-fn@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.0.0.tgz#76044cfa8818bbf6999c5c9acadf2d3649b14b4b"
integrity sha512-PiVO95TKvhiwgSwg1IdLYlCTdul38yZxZMIcnDSFIBUm4BNZha2qpQ4GpJ++15bHoKDtrW2D69lMfFwdFYtNZQ==
mimic-response@^1.0.0, mimic-response@^1.0.1: mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
@ -9071,7 +9068,7 @@ pkg-up@^2.0.0:
dependencies: dependencies:
find-up "^2.1.0" find-up "^2.1.0"
pkg-up@^3.0.1: pkg-up@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
@ -10197,6 +10194,11 @@ serialize-javascript@^3.1.0:
dependencies: dependencies:
randombytes "^2.1.0" randombytes "^2.1.0"
serializr@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/serializr/-/serializr-2.0.3.tgz#f163b2b39938e1313beaf8457133713df2a936bb"
integrity sha512-YHDTg+QE//5vESifXc7vvncxIAiUiX04FAy1RmSgACkwhvJ9DivljDEjqs5WJzg5KLokI3x3jh30KEfXq5nDJA==
set-blocking@^2.0.0, set-blocking@~2.0.0: set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@ -11292,11 +11294,6 @@ type-fest@^0.6.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
type-fest@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
type-fest@^0.8.0, type-fest@^0.8.1: type-fest@^0.8.0, type-fest@^0.8.1:
version "0.8.1" version "0.8.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
@ -11984,7 +11981,7 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
write-file-atomic@^3.0.0: write-file-atomic@^3.0.0, write-file-atomic@^3.0.3:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==