mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix build and tests
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
3254f49f01
commit
9fa75199f3
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kube-object-event-status",
|
"name": "kube-object-event-status",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kube-object-event-status",
|
"name": "kube-object-event-status",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"description": "Adds kube object status from events",
|
"description": "Adds kube object status from events",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-metrics-cluster-feature",
|
"name": "lens-metrics-cluster-feature",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-metrics-cluster-feature",
|
"name": "lens-metrics-cluster-feature",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"description": "Lens metrics cluster feature",
|
"description": "Lens metrics cluster feature",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
2
extensions/node-menu/package-lock.json
generated
2
extensions/node-menu/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-node-menu",
|
"name": "lens-node-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-node-menu",
|
"name": "lens-node-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"description": "Lens node menu",
|
"description": "Lens node menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
2
extensions/pod-menu/package-lock.json
generated
2
extensions/pod-menu/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-pod-menu",
|
"name": "lens-pod-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-pod-menu",
|
"name": "lens-pod-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"description": "Lens pod menu",
|
"description": "Lens pod menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "5.3.0",
|
"version": "5.3.0-latest.1643044317859.1643044464147",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021 OpenLens Authors",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -12,5 +12,4 @@ export * from "./update-available.ipc";
|
|||||||
export * from "./cluster.ipc";
|
export * from "./cluster.ipc";
|
||||||
export * from "./type-enforced-ipc";
|
export * from "./type-enforced-ipc";
|
||||||
export * from "./hotbar";
|
export * from "./hotbar";
|
||||||
export * from "./extension-loader.ipc";
|
|
||||||
export * from "./window";
|
export * from "./window";
|
||||||
|
|||||||
@ -8,8 +8,7 @@ import { Console } from "console";
|
|||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
||||||
import { runInAction } from "mobx";
|
import { runInAction } from "mobx";
|
||||||
import updateExtensionsStateInjectable
|
import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
|
||||||
from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
|
|
||||||
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
|
|
||||||
@ -24,7 +23,7 @@ jest.mock(
|
|||||||
() => ({
|
() => ({
|
||||||
ipcRenderer: {
|
ipcRenderer: {
|
||||||
invoke: jest.fn(async (channel: string) => {
|
invoke: jest.fn(async (channel: string) => {
|
||||||
if (channel === "extensions:main") {
|
if (channel === "extension-loader:main:state") {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
manifestPath,
|
manifestPath,
|
||||||
@ -61,7 +60,7 @@ jest.mock(
|
|||||||
}),
|
}),
|
||||||
on: jest.fn(
|
on: jest.fn(
|
||||||
(channel: string, listener: (event: any, ...args: any[]) => void) => {
|
(channel: string, listener: (event: any, ...args: any[]) => void) => {
|
||||||
if (channel === "extensions:main") {
|
if (channel === "extension-loader:main:state") {
|
||||||
// First initialize with extensions 1 and 2
|
// First initialize with extensions 1 and 2
|
||||||
// and then broadcast event to remove extension 2 and add extension number 3
|
// and then broadcast event to remove extension 2 and add extension number 3
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -51,8 +51,7 @@ describe("create clusters", () => {
|
|||||||
|
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
mockFs({
|
||||||
const mockOpts = {
|
|
||||||
"minikube-config.yml": JSON.stringify({
|
"minikube-config.yml": JSON.stringify({
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
clusters: [{
|
clusters: [{
|
||||||
@ -74,9 +73,7 @@ describe("create clusters", () => {
|
|||||||
kind: "Config",
|
kind: "Config",
|
||||||
preferences: {},
|
preferences: {},
|
||||||
}),
|
}),
|
||||||
};
|
});
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
|
|||||||
@ -27,12 +27,11 @@ import enableExtensionInjectable from "./enable-extension/enable-extension.injec
|
|||||||
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
||||||
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension/confirm-uninstall-extension.injectable";
|
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension/confirm-uninstall-extension.injectable";
|
||||||
import installFromInputInjectable from "./install-from-input/install-from-input.injectable";
|
import installFromInputInjectable from "./install-from-input/install-from-input.injectable";
|
||||||
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog/install-from-select-file-dialog.injectable";
|
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
import installOnDropInjectable from "./install-on-drop/install-on-drop.injectable";
|
import installOnDropInjectable from "./install-on-drop/install-on-drop.injectable";
|
||||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||||
import extensionInstallationStateStoreInjectable
|
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
@ -107,22 +106,15 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Extensions = withInjectables<Dependencies>(
|
export const Extensions = withInjectables<Dependencies>(NonInjectedExtensions, {
|
||||||
NonInjectedExtensions,
|
getProps: (di) => ({
|
||||||
{
|
userExtensions: di.inject(userExtensionsInjectable),
|
||||||
getProps: (di) => ({
|
enableExtension: di.inject(enableExtensionInjectable),
|
||||||
userExtensions: di.inject(userExtensionsInjectable),
|
disableExtension: di.inject(disableExtensionInjectable),
|
||||||
enableExtension: di.inject(enableExtensionInjectable),
|
confirmUninstallExtension: di.inject(confirmUninstallExtensionInjectable),
|
||||||
disableExtension: di.inject(disableExtensionInjectable),
|
installFromInput: di.inject(installFromInputInjectable),
|
||||||
confirmUninstallExtension: di.inject(confirmUninstallExtensionInjectable),
|
installOnDrop: di.inject(installOnDropInjectable),
|
||||||
installFromInput: di.inject(installFromInputInjectable),
|
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
|
||||||
installOnDrop: di.inject(installOnDropInjectable),
|
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||||
|
}),
|
||||||
installFromSelectFileDialog: di.inject(
|
});
|
||||||
installFromSelectFileDialogInjectable,
|
|
||||||
),
|
|
||||||
|
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { requestOpenFilePickingDialog } from "../../ipc";
|
||||||
|
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||||
|
import attemptInstallsInjectable from "./attempt-installs/attempt-installs.injectable";
|
||||||
|
import directoryForDownloadsInjectable from "../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
||||||
|
import { bind } from "../../utils";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
attemptInstalls: (filePaths: string[]) => Promise<void>
|
||||||
|
directoryForDownloads: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installFromSelectFileDialog({ attemptInstalls, directoryForDownloads }: Dependencies) {
|
||||||
|
const { canceled, filePaths } = await requestOpenFilePickingDialog({
|
||||||
|
defaultPath: directoryForDownloads,
|
||||||
|
properties: ["openFile", "multiSelections"],
|
||||||
|
message: `Select extensions to install (formats: ${supportedExtensionFormats.join(", ")}), `,
|
||||||
|
buttonLabel: "Use configuration",
|
||||||
|
filters: [{ name: "tarball", extensions: supportedExtensionFormats }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!canceled) {
|
||||||
|
await attemptInstalls(filePaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const installFromSelectFileDialogInjectable = getInjectable({
|
||||||
|
instantiate: (di) => bind(installFromSelectFileDialog, null, {
|
||||||
|
attemptInstalls: di.inject(attemptInstallsInjectable),
|
||||||
|
directoryForDownloads: di.inject(directoryForDownloadsInjectable),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installFromSelectFileDialogInjectable;
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import { installFromSelectFileDialog } from "./install-from-select-file-dialog";
|
|
||||||
import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable";
|
|
||||||
import directoryForDownloadsInjectable from "../../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
|
||||||
|
|
||||||
const installFromSelectFileDialogInjectable = getInjectable({
|
|
||||||
instantiate: (di) =>
|
|
||||||
installFromSelectFileDialog({
|
|
||||||
attemptInstalls: di.inject(attemptInstallsInjectable),
|
|
||||||
directoryForDownloads: di.inject(directoryForDownloadsInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default installFromSelectFileDialogInjectable;
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { dialog } from "../../../remote-helpers";
|
|
||||||
import { supportedExtensionFormats } from "../supported-extension-formats";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
attemptInstalls: (filePaths: string[]) => Promise<void>
|
|
||||||
directoryForDownloads: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const installFromSelectFileDialog =
|
|
||||||
({ attemptInstalls, directoryForDownloads }: Dependencies) =>
|
|
||||||
async () => {
|
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
|
||||||
defaultPath: directoryForDownloads,
|
|
||||||
properties: ["openFile", "multiSelections"],
|
|
||||||
message: `Select extensions to install (formats: ${supportedExtensionFormats.join(
|
|
||||||
", ",
|
|
||||||
)}), `,
|
|
||||||
buttonLabel: "Use configuration",
|
|
||||||
filters: [{ name: "tarball", extensions: supportedExtensionFormats }],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!canceled) {
|
|
||||||
await attemptInstalls(filePaths);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -19,7 +19,7 @@ import { SubTitle } from "../layout/sub-title";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { HelmRepo, HelmRepoManager } from "../../../main/helm/helm-repo-manager";
|
import { HelmRepo, HelmRepoManager } from "../../../main/helm/helm-repo-manager";
|
||||||
import { dialog } from "../../remote-helpers";
|
import { requestOpenFilePickingDialog } from "../../ipc";
|
||||||
|
|
||||||
interface Props extends Partial<DialogProps> {
|
interface Props extends Partial<DialogProps> {
|
||||||
onAddRepo: Function
|
onAddRepo: Function
|
||||||
@ -73,7 +73,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async selectFileDialog(type: FileType, fileFilter: FileFilter) {
|
async selectFileDialog(type: FileType, fileFilter: FileFilter) {
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
const { canceled, filePaths } = await requestOpenFilePickingDialog({
|
||||||
defaultPath: this.getFilePath(type),
|
defaultPath: this.getFilePath(type),
|
||||||
properties: ["openFile", "showHiddenFiles"],
|
properties: ["openFile", "showHiddenFiles"],
|
||||||
message: `Select file`,
|
message: `Select file`,
|
||||||
|
|||||||
@ -10,8 +10,7 @@ import type { IToleration } from "../../../../common/k8s-api/workload-kube-objec
|
|||||||
import { PodTolerations } from "../pod-tolerations";
|
import { PodTolerations } from "../pod-tolerations";
|
||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
||||||
import directoryForLensLocalStorageInjectable
|
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||||
from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
|
|||||||
@ -353,6 +353,7 @@ export class Input extends React.Component<InputProps, State> {
|
|||||||
dirty: _dirty, // excluded from passing to input-element
|
dirty: _dirty, // excluded from passing to input-element
|
||||||
defaultValue,
|
defaultValue,
|
||||||
trim,
|
trim,
|
||||||
|
blurOnEnter,
|
||||||
...inputProps
|
...inputProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { focused, dirty, valid, validating, errors } = this.state;
|
const { focused, dirty, valid, validating, errors } = this.state;
|
||||||
|
|||||||
@ -7,17 +7,18 @@ import React from "react";
|
|||||||
import { fireEvent } from "@testing-library/react";
|
import { fireEvent } from "@testing-library/react";
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { TopBar } from "./top-bar";
|
import { TopBar } from "./top-bar";
|
||||||
import { IpcMainWindowEvents, broadcastMessage, requestMain } from "../../../../common/ipc";
|
import { IpcMainWindowEvents, broadcastMessage } from "../../../../common/ipc";
|
||||||
import * as vars from "../../../../common/vars";
|
import * as vars from "../../../../common/vars";
|
||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
import { DiRender, renderFor } from "../../test-utils/renderFor";
|
||||||
import directoryForUserDataInjectable
|
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
|
import { requestWindowAction } from "../../../ipc";
|
||||||
|
|
||||||
const mockConfig = vars as { isWindows: boolean; isLinux: boolean };
|
const mockConfig = vars as { isWindows: boolean; isLinux: boolean };
|
||||||
|
|
||||||
jest.mock("../../../../common/ipc");
|
jest.mock("../../../../common/ipc");
|
||||||
|
jest.mock("../../../ipc");
|
||||||
|
|
||||||
jest.mock("../../../../common/vars", () => {
|
jest.mock("../../../../common/vars", () => {
|
||||||
const SemVer = require("semver").SemVer;
|
const SemVer = require("semver").SemVer;
|
||||||
@ -89,12 +90,12 @@ describe("<TopBar/> in Windows and Linux", () => {
|
|||||||
expect(broadcastMessage).toHaveBeenCalledWith(IpcMainWindowEvents.OPEN_CONTEXT_MENU);
|
expect(broadcastMessage).toHaveBeenCalledWith(IpcMainWindowEvents.OPEN_CONTEXT_MENU);
|
||||||
|
|
||||||
fireEvent.click(minimize);
|
fireEvent.click(minimize);
|
||||||
expect(requestMain).toHaveBeenCalledWith(IpcMainWindowEvents.WINDOW_ACTION, "minimize");
|
expect(requestWindowAction).toHaveBeenCalledWith("minimize");
|
||||||
|
|
||||||
fireEvent.click(maximize);
|
fireEvent.click(maximize);
|
||||||
expect(requestMain).toHaveBeenCalledWith(IpcMainWindowEvents.WINDOW_ACTION, "toggleMaximize");
|
expect(requestWindowAction).toHaveBeenCalledWith("toggle-maximize");
|
||||||
|
|
||||||
fireEvent.click(close);
|
fireEvent.click(close);
|
||||||
expect(requestMain).toHaveBeenCalledWith(IpcMainWindowEvents.WINDOW_ACTION, "close");
|
expect(requestWindowAction).toHaveBeenCalledWith("close");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user