mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into no-updates-available-popup
This commit is contained in:
commit
dafa84dc20
11
.github/actions/add-card-to-project/Dockerfile
vendored
11
.github/actions/add-card-to-project/Dockerfile
vendored
@ -1,11 +0,0 @@
|
|||||||
# Container image that runs your code
|
|
||||||
FROM alpine:3.10
|
|
||||||
|
|
||||||
RUN apk add --no-cache --no-progress curl jq
|
|
||||||
|
|
||||||
# Copies your code file from your action repository to the filesystem path `/` of the container
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
# Code file to execute when the docker container starts up (`entrypoint.sh`)
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
23
.github/actions/add-card-to-project/action.yml
vendored
23
.github/actions/add-card-to-project/action.yml
vendored
@ -1,23 +0,0 @@
|
|||||||
name: 'add_card_to_project'
|
|
||||||
description: 'A GitHub Action to add a card to a project and set the card position'
|
|
||||||
author: 'Steve Richards'
|
|
||||||
branding:
|
|
||||||
icon: 'command'
|
|
||||||
color: 'blue'
|
|
||||||
inputs:
|
|
||||||
project:
|
|
||||||
description: 'The url of the project to be assigned to.'
|
|
||||||
required: true
|
|
||||||
column_name:
|
|
||||||
description: 'The column name of the project, defaults to "To do" for issues and "In progress" for pull requests.'
|
|
||||||
required: false
|
|
||||||
card_position:
|
|
||||||
description: 'The card position of the card in the column, defaults to "bottom". Valid values are "top", "bottom" and "after:<card id>".'
|
|
||||||
required: false
|
|
||||||
runs:
|
|
||||||
using: 'docker'
|
|
||||||
image: 'Dockerfile'
|
|
||||||
args:
|
|
||||||
- ${{ inputs.project }}
|
|
||||||
- ${{ inputs.column_name }}
|
|
||||||
- ${{ inputs.card_position }}
|
|
||||||
157
.github/actions/add-card-to-project/entrypoint.sh
vendored
157
.github/actions/add-card-to-project/entrypoint.sh
vendored
@ -1,157 +0,0 @@
|
|||||||
#!/bin/sh -l
|
|
||||||
|
|
||||||
PROJECT_URL="$INPUT_PROJECT"
|
|
||||||
if [ -z "$PROJECT_URL" ]; then
|
|
||||||
echo "PROJECT_URL is not defined." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_project_type() {
|
|
||||||
_PROJECT_URL="$1"
|
|
||||||
|
|
||||||
case "$_PROJECT_URL" in
|
|
||||||
https://github.com/orgs/*)
|
|
||||||
echo "org"
|
|
||||||
;;
|
|
||||||
https://github.com/users/*)
|
|
||||||
echo "user"
|
|
||||||
;;
|
|
||||||
https://github.com/*/projects/*)
|
|
||||||
echo "repo"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Invalid PROJECT_URL: $_PROJECT_URL" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
unset _PROJECT_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
find_project_id() {
|
|
||||||
_PROJECT_TYPE="$1"
|
|
||||||
_PROJECT_URL="$2"
|
|
||||||
|
|
||||||
case "$_PROJECT_TYPE" in
|
|
||||||
org)
|
|
||||||
_ORG_NAME=$(echo "$_PROJECT_URL" | sed -e 's@https://github.com/orgs/\([^/]\+\)/projects/[0-9]\+@\1@')
|
|
||||||
_ENDPOINT="https://api.github.com/orgs/$_ORG_NAME/projects"
|
|
||||||
;;
|
|
||||||
user)
|
|
||||||
_USER_NAME=$(echo "$_PROJECT_URL" | sed -e 's@https://github.com/users/\([^/]\+\)/projects/[0-9]\+@\1@')
|
|
||||||
_ENDPOINT="https://api.github.com/users/$_USER_NAME/projects"
|
|
||||||
;;
|
|
||||||
repo)
|
|
||||||
_ENDPOINT="https://api.github.com/repos/$GITHUB_REPOSITORY/projects"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
_PROJECTS=$(curl -s -X GET -u "$GITHUB_ACTOR:$TOKEN" --retry 3 \
|
|
||||||
-H 'Accept: application/vnd.github.inertia-preview+json' \
|
|
||||||
"$_ENDPOINT")
|
|
||||||
|
|
||||||
_PROJECTID=$(echo "$_PROJECTS" | jq -r ".[] | select(.html_url == \"$_PROJECT_URL\").id")
|
|
||||||
|
|
||||||
if [ "$_PROJECTID" != "" ]; then
|
|
||||||
echo "$_PROJECTID"
|
|
||||||
else
|
|
||||||
echo "No project was found." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset _PROJECT_TYPE _PROJECT_URL _ORG_NAME _USER_NAME _ENDPOINT _PROJECTS _PROJECTID
|
|
||||||
}
|
|
||||||
|
|
||||||
find_column_id() {
|
|
||||||
_PROJECT_ID="$1"
|
|
||||||
_INITIAL_COLUMN_NAME="$2"
|
|
||||||
|
|
||||||
_COLUMNS=$(curl -s -X GET -u "$GITHUB_ACTOR:$TOKEN" --retry 3 \
|
|
||||||
-H 'Accept: application/vnd.github.inertia-preview+json' \
|
|
||||||
"https://api.github.com/projects/$_PROJECT_ID/columns")
|
|
||||||
|
|
||||||
|
|
||||||
echo "$_COLUMNS" | jq -r ".[] | select(.name == \"$_INITIAL_COLUMN_NAME\").id"
|
|
||||||
unset _PROJECT_ID _INITIAL_COLUMN_NAME _COLUMNS
|
|
||||||
}
|
|
||||||
|
|
||||||
PROJECT_TYPE=$(get_project_type "${PROJECT_URL:?<Error> required this environment variable}")
|
|
||||||
|
|
||||||
if [ "$PROJECT_TYPE" = org ] || [ "$PROJECT_TYPE" = user ]; then
|
|
||||||
if [ -z "$MY_GITHUB_TOKEN" ]; then
|
|
||||||
echo "MY_GITHUB_TOKEN not defined" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
TOKEN="$MY_GITHUB_TOKEN" # It's User's personal access token. It should be secret.
|
|
||||||
else
|
|
||||||
if [ -z "$GITHUB_TOKEN" ]; then
|
|
||||||
echo "GITHUB_TOKEN not defined" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
TOKEN="$GITHUB_TOKEN" # GitHub sets. The scope in only the repository containing the workflow file.
|
|
||||||
fi
|
|
||||||
|
|
||||||
INITIAL_COLUMN_NAME="$INPUT_COLUMN_NAME"
|
|
||||||
if [ -z "$INITIAL_COLUMN_NAME" ]; then
|
|
||||||
# assing the column name by default
|
|
||||||
INITIAL_COLUMN_NAME='To do'
|
|
||||||
if [ "$GITHUB_EVENT_NAME" == "pull_request" ] || [ "$GITHUB_EVENT_NAME" == "pull_request_target" ]; then
|
|
||||||
echo "changing col name for PR event"
|
|
||||||
INITIAL_COLUMN_NAME='In progress'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
PROJECT_ID=$(find_project_id "$PROJECT_TYPE" "$PROJECT_URL")
|
|
||||||
INITIAL_COLUMN_ID=$(find_column_id "$PROJECT_ID" "${INITIAL_COLUMN_NAME:?<Error> required this environment variable}")
|
|
||||||
|
|
||||||
if [ -z "$INITIAL_COLUMN_ID" ]; then
|
|
||||||
echo "INITIAL_COLUMN_ID is not found." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$GITHUB_EVENT_NAME" in
|
|
||||||
issues)
|
|
||||||
ISSUE_ID=$(jq -r '.issue.id' < "$GITHUB_EVENT_PATH")
|
|
||||||
|
|
||||||
# Add this issue to the project column
|
|
||||||
_CARDS=$(curl -s -X POST -u "$GITHUB_ACTOR:$TOKEN" --retry 3 \
|
|
||||||
-H 'Accept: application/vnd.github.inertia-preview+json' \
|
|
||||||
-d "{\"content_type\": \"Issue\", \"content_id\": $ISSUE_ID}" \
|
|
||||||
"https://api.github.com/projects/columns/$INITIAL_COLUMN_ID/cards")
|
|
||||||
;;
|
|
||||||
pull_request|pull_request_target)
|
|
||||||
PULL_REQUEST_ID=$(jq -r '.pull_request.id' < "$GITHUB_EVENT_PATH")
|
|
||||||
|
|
||||||
# Add this pull_request to the project column
|
|
||||||
_CARDS=$(curl -s -X POST -u "$GITHUB_ACTOR:$TOKEN" --retry 3 \
|
|
||||||
-H 'Accept: application/vnd.github.inertia-preview+json' \
|
|
||||||
-d "{\"content_type\": \"PullRequest\", \"content_id\": $PULL_REQUEST_ID}" \
|
|
||||||
"https://api.github.com/projects/columns/$INITIAL_COLUMN_ID/cards")
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Nothing to be done on this action: $GITHUB_EVENT_NAME" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
CARDS_ID=$(echo "$_CARDS" | jq -r .id)
|
|
||||||
unset _CARDS
|
|
||||||
|
|
||||||
if [ -z "$CARDS_ID" ]; then
|
|
||||||
echo "CARDS_ID is not found." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
INITIAL_CARD_POSITION="$INPUT_CARD_POSITION"
|
|
||||||
if [ -z "$INITIAL_CARD_POSITION" ]; then
|
|
||||||
# assign the card position by default
|
|
||||||
INITIAL_CARD_POSITION='bottom'
|
|
||||||
fi
|
|
||||||
|
|
||||||
_CARDS=$(curl -s -X POST -u "$GITHUB_ACTOR:$TOKEN" --retry 3 \
|
|
||||||
-H 'Accept: application/vnd.github.inertia-preview+json' \
|
|
||||||
-d "{\"position\": \"$INITIAL_CARD_POSITION\"}" \
|
|
||||||
"https://api.github.com/projects/columns/cards/$CARDS_ID/moves")
|
|
||||||
33
.github/workflows/add-to-project-board.yaml
vendored
33
.github/workflows/add-to-project-board.yaml
vendored
@ -1,33 +0,0 @@
|
|||||||
name: Add Card to Project(s)
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened]
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened]
|
|
||||||
env:
|
|
||||||
MY_GITHUB_TOKEN: ${{ secrets.PROJECT_GITHUB_TOKEN }}
|
|
||||||
EVENT_TYPE: $GITHUB_EVENT_NAME
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
add_card_to_project:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Add Card to Project(s)
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Get Event Type
|
|
||||||
run: echo $GITHUB_EVENT_NAME
|
|
||||||
- name: Assign NEW issues to project 1
|
|
||||||
uses: ./.github/actions/add-card-to-project
|
|
||||||
if: github.event_name == 'issues' && github.event.action == 'opened'
|
|
||||||
with:
|
|
||||||
project: "https://github.com/orgs/lensapp/projects/1"
|
|
||||||
column_name: "Backlog"
|
|
||||||
card_position: "bottom"
|
|
||||||
- name: Assign NEW pull requests to project 1
|
|
||||||
uses: ./.github/actions/add-card-to-project
|
|
||||||
if: github.event_name == 'pull_request_target' && github.event.action == 'opened'
|
|
||||||
with:
|
|
||||||
project: "https://github.com/orgs/lensapp/projects/1"
|
|
||||||
column_name: "PRs"
|
|
||||||
card_position: "bottom"
|
|
||||||
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -57,7 +57,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
pip install mkdocs-material
|
||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
|
|||||||
2
.github/workflows/mkdocs-delete-version.yml
vendored
2
.github/workflows/mkdocs-delete-version.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
pip install mkdocs-material
|
||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
|
|||||||
2
.github/workflows/mkdocs-manual.yml
vendored
2
.github/workflows/mkdocs-manual.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
pip install mkdocs-material
|
||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Version from lens
|
- name: Checkout Version from lens
|
||||||
|
|||||||
@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
pip install mkdocs-material
|
||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
|
|||||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -121,5 +121,7 @@ jobs:
|
|||||||
retry_on: error
|
retry_on: error
|
||||||
command: npm ci
|
command: npm ci
|
||||||
|
|
||||||
- run: npm run test:unit
|
- run: |
|
||||||
|
npm run build -- --ignore open-lens
|
||||||
|
npm run test:unit
|
||||||
name: Run tests
|
name: Run tests
|
||||||
|
|||||||
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@ -2,5 +2,6 @@
|
|||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="ES6PreferShortImport" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
<inspection_tool class="ES6PreferShortImport" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
6
.idea/jsLinters/eslint.xml
generated
Normal file
6
.idea/jsLinters/eslint.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EslintConfiguration">
|
||||||
|
<option name="fix-on-save" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/lens.iml
generated
7
.idea/lens.iml
generated
@ -6,11 +6,12 @@
|
|||||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/node_modules" />
|
<excludeFolder url="file://$MODULE_DIR$/node_modules" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/static/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/extensions/npm/extensions/dist" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/binaries" />
|
<excludePattern pattern="dist" />
|
||||||
|
<excludePattern pattern="static" />
|
||||||
|
<excludePattern pattern="coverage" />
|
||||||
|
<excludePattern pattern="binaries" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
4
.nxignore
Normal file
4
.nxignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
packages/**/dist
|
||||||
|
packages/**/static/build
|
||||||
|
packages/**/build/webpack
|
||||||
|
packages/**/binaries
|
||||||
@ -53,6 +53,7 @@ theme:
|
|||||||
- toc.autohide
|
- toc.autohide
|
||||||
- search.suggest
|
- search.suggest
|
||||||
- search.highlight
|
- search.highlight
|
||||||
|
- content.code.copy
|
||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- stylesheets/extra.css
|
- stylesheets/extra.css
|
||||||
|
|||||||
8755
package-lock.json
generated
8755
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,10 @@
|
|||||||
"build:docs": "lerna run --stream build:docs",
|
"build:docs": "lerna run --stream build:docs",
|
||||||
"clean": "lerna run clean --stream",
|
"clean": "lerna run clean --stream",
|
||||||
"clean:node_modules": "lerna clean -y && rimraf node_modules",
|
"clean:node_modules": "lerna clean -y && rimraf node_modules",
|
||||||
"dev": "lerna run dev --stream --skip-nx-cache",
|
"dev": "cross-env NODE_ENV=development lerna run build --stream --skip-nx-cache",
|
||||||
|
"postdev": "lerna watch -- lerna run build --stream --scope \\$LERNA_PACKAGE_NAME",
|
||||||
|
"prestart-dev": "cd packages/open-lens && rimraf static/build/ && npm run build:tray-icons && npm run download:binaries",
|
||||||
|
"start-dev": "lerna run start",
|
||||||
"lint": "lerna run lint --stream",
|
"lint": "lerna run lint --stream",
|
||||||
"lint:fix": "lerna run lint:fix --stream",
|
"lint:fix": "lerna run lint:fix --stream",
|
||||||
"mkdocs:serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
"mkdocs:serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
||||||
@ -37,7 +40,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"adr": "^1.4.3",
|
"adr": "^1.4.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"lerna": "^6.5.1",
|
"lerna": "^6.6.1",
|
||||||
"rimraf": "^4.4.0"
|
"rimraf": "^4.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,10 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf dist/",
|
"clean": "rimraf dist/",
|
||||||
"build": "swc ./src/index.ts -d ./dist"
|
"build": "swc ./src/index.ts -d ./dist",
|
||||||
|
"postbuild": "run-script-os",
|
||||||
|
"postbuild:windows": "",
|
||||||
|
"postbuild:nix": "chmod u+x ./dist/index.js"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": "./dist/index.js",
|
"bin": "./dist/index.js",
|
||||||
@ -23,9 +26,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/cli": "^0.1.61",
|
"@swc/cli": "^0.1.61",
|
||||||
"@swc/core": "^1.3.37",
|
"@swc/core": "^1.3.44",
|
||||||
"@types/node": "^16.18.11",
|
"@types/node": "^16.18.11",
|
||||||
"@types/semver": "^7.3.13",
|
"@types/semver": "^7.3.13",
|
||||||
"rimraf": "^4.1.2"
|
"rimraf": "^4.4.1",
|
||||||
|
"run-script-os": "^1.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "@k8slens/eslint-config/eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
"@k8slens/eslint-config/prettier"
|
||||||
21
packages/business-features/keyboard-shortcuts/README.md
Normal file
21
packages/business-features/keyboard-shortcuts/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# @k8slens/keyboard-shortcuts
|
||||||
|
|
||||||
|
This Feature enables keyboard shortcuts in Lens
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install @k8slens/keyboard-shortcuts
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { keyboardShortcutsFeature } from "@k8slens/keyboard-shortcuts";
|
||||||
|
import { registerFeature } from "@k8slens/feature-core";
|
||||||
|
import { createContainer } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
const di = createContainer("some-container");
|
||||||
|
|
||||||
|
registerFeature(di, keyboardShortcutsFeature);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extendability
|
||||||
7
packages/business-features/keyboard-shortcuts/index.ts
Normal file
7
packages/business-features/keyboard-shortcuts/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export { KeyboardShortcutScope } from "./src/keyboard-shortcut-scope";
|
||||||
|
export type { KeyboardShortcutScopeProps } from "./src/keyboard-shortcut-scope";
|
||||||
|
|
||||||
|
export { keyboardShortcutInjectionToken } from "./src/keyboard-shortcut-injection-token";
|
||||||
|
export type { Binding, KeyboardShortcut } from "./src/keyboard-shortcut-injection-token";
|
||||||
|
|
||||||
|
export { keyboardShortcutsFeature } from "./src/feature";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
|
||||||
46
packages/business-features/keyboard-shortcuts/package.json
Normal file
46
packages/business-features/keyboard-shortcuts/package.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "@k8slens/keyboard-shortcuts",
|
||||||
|
"private": false,
|
||||||
|
"version": "1.0.0-alpha.0",
|
||||||
|
"description": "Keyboard shortcuts for Lens",
|
||||||
|
"type": "commonjs",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lensapp/lens.git"
|
||||||
|
},
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"author": {
|
||||||
|
"name": "OpenLens Authors",
|
||||||
|
"email": "info@k8slens.dev"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack",
|
||||||
|
"clean": "rimraf dist/",
|
||||||
|
"test:unit": "jest --coverage --runInBand",
|
||||||
|
"lint": "lens-lint",
|
||||||
|
"lint:fix": "lens-lint --fix"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@k8slens/feature-core": "^6.5.0-alpha.0",
|
||||||
|
"@k8slens/react-application": "^1.0.0-alpha.0",
|
||||||
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
|
"@ogre-tools/fp": "^15.1.2",
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@async-fn/jest": "^1.6.4",
|
||||||
|
"@k8slens/eslint-config": "6.5.0-alpha.1",
|
||||||
|
"@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`keyboard-shortcuts when application is started renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-keyboard-shortcut-scope="some-scope"
|
||||||
|
data-keyboard-shortcut-scope-test="some-scope"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
17
packages/business-features/keyboard-shortcuts/src/feature.ts
Normal file
17
packages/business-features/keyboard-shortcuts/src/feature.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { getFeature } from "@k8slens/feature-core";
|
||||||
|
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
|
||||||
|
import { reactApplicationFeature } from "@k8slens/react-application";
|
||||||
|
|
||||||
|
export const keyboardShortcutsFeature = getFeature({
|
||||||
|
id: "keyboard-shortcuts",
|
||||||
|
|
||||||
|
register: (di) => {
|
||||||
|
autoRegister({
|
||||||
|
di,
|
||||||
|
targetModule: module,
|
||||||
|
getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
dependencies: [reactApplicationFeature],
|
||||||
|
});
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import { filter, isString } from "lodash/fp";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import {
|
||||||
|
Binding,
|
||||||
|
KeyboardShortcut,
|
||||||
|
keyboardShortcutInjectionToken,
|
||||||
|
} from "./keyboard-shortcut-injection-token";
|
||||||
|
import platformInjectable from "./platform.injectable";
|
||||||
|
|
||||||
|
export type InvokeShortcut = (event: KeyboardEvent) => void;
|
||||||
|
|
||||||
|
const toShortcutsWithMatchingScope = (shortcut: KeyboardShortcut) => {
|
||||||
|
const activeScopeElement = document.activeElement?.closest("[data-keyboard-shortcut-scope]");
|
||||||
|
|
||||||
|
if (!activeScopeElement) {
|
||||||
|
const shortcutIsRootLevel = !shortcut.scope;
|
||||||
|
|
||||||
|
return shortcutIsRootLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const castedActiveScopeElementHtml = activeScopeElement as HTMLDivElement;
|
||||||
|
|
||||||
|
// eslint-disable-next-line xss/no-mixed-html
|
||||||
|
const activeScope = castedActiveScopeElementHtml.dataset.keyboardShortcutScope;
|
||||||
|
|
||||||
|
return shortcut.scope === activeScope;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toBindingWithDefaults = (binding: Binding) =>
|
||||||
|
isString(binding)
|
||||||
|
? {
|
||||||
|
code: binding,
|
||||||
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
altOrOption: false,
|
||||||
|
meta: false,
|
||||||
|
ctrlOrCommand: false,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
ctrl: false,
|
||||||
|
shift: false,
|
||||||
|
altOrOption: false,
|
||||||
|
meta: false,
|
||||||
|
ctrlOrCommand: false,
|
||||||
|
...binding,
|
||||||
|
};
|
||||||
|
|
||||||
|
const toShortcutsWithMatchingBinding =
|
||||||
|
(event: KeyboardEvent, platform: string) => (shortcut: KeyboardShortcut) => {
|
||||||
|
const binding = toBindingWithDefaults(shortcut.binding);
|
||||||
|
|
||||||
|
const shiftModifierMatches = binding.shift === event.shiftKey;
|
||||||
|
const altModifierMatches = binding.altOrOption === event.altKey;
|
||||||
|
|
||||||
|
const isMac = platform === "darwin";
|
||||||
|
|
||||||
|
const ctrlModifierMatches =
|
||||||
|
binding.ctrl === event.ctrlKey || (!isMac && binding.ctrlOrCommand === event.ctrlKey);
|
||||||
|
|
||||||
|
const metaModifierMatches =
|
||||||
|
binding.meta === event.metaKey || (isMac && binding.ctrlOrCommand === event.metaKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
event.code === binding.code &&
|
||||||
|
shiftModifierMatches &&
|
||||||
|
ctrlModifierMatches &&
|
||||||
|
altModifierMatches &&
|
||||||
|
metaModifierMatches
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const invokeShortcutInjectable = getInjectable({
|
||||||
|
id: "invoke-shortcut",
|
||||||
|
|
||||||
|
instantiate: (di): InvokeShortcut => {
|
||||||
|
const getShortcuts = () => di.injectMany(keyboardShortcutInjectionToken);
|
||||||
|
const platform = di.inject(platformInjectable);
|
||||||
|
|
||||||
|
return (event) => {
|
||||||
|
const shortcutsToInvoke = pipeline(
|
||||||
|
getShortcuts(),
|
||||||
|
filter(toShortcutsWithMatchingBinding(event, platform)),
|
||||||
|
filter(toShortcutsWithMatchingScope),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shortcutsToInvoke.length) {
|
||||||
|
shortcutsToInvoke.forEach((shortcut) => shortcut.invoke());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default invokeShortcutInjectable;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export type Binding =
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
code: string;
|
||||||
|
shift?: boolean;
|
||||||
|
ctrl?: boolean;
|
||||||
|
altOrOption?: boolean;
|
||||||
|
meta?: boolean;
|
||||||
|
ctrlOrCommand?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KeyboardShortcut = {
|
||||||
|
binding: Binding;
|
||||||
|
invoke: () => void;
|
||||||
|
scope?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const keyboardShortcutInjectionToken = getInjectionToken<KeyboardShortcut>({
|
||||||
|
id: "keyboard-shortcut-injection-token",
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { KeyboardShortcutListener } from "./keyboard-shortcut-listener";
|
||||||
|
import { reactApplicationHigherOrderComponentInjectionToken } from "@k8slens/react-application";
|
||||||
|
|
||||||
|
export const keyboardShortcutListenerReactApplicationHocInjectable = getInjectable({
|
||||||
|
id: "keyboard-shortcut-listener-react-application-hoc",
|
||||||
|
instantiate: () => KeyboardShortcutListener,
|
||||||
|
|
||||||
|
injectionToken: reactApplicationHigherOrderComponentInjectionToken,
|
||||||
|
});
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
import invokeShortcutInjectable, { InvokeShortcut } from "./invoke-shortcut.injectable";
|
||||||
|
|
||||||
|
export interface KeyboardShortcutListenerProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
invokeShortcut: InvokeShortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedKeyboardShortcutListener = ({
|
||||||
|
children,
|
||||||
|
invokeShortcut,
|
||||||
|
}: KeyboardShortcutListenerProps & Dependencies) => {
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("keydown", invokeShortcut);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", invokeShortcut);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KeyboardShortcutListener = withInjectables<
|
||||||
|
Dependencies,
|
||||||
|
KeyboardShortcutListenerProps
|
||||||
|
>(
|
||||||
|
NonInjectedKeyboardShortcutListener,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
invokeShortcut: di.inject(invokeShortcutInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface KeyboardShortcutScopeProps {
|
||||||
|
id: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KeyboardShortcutScope = ({ id, children }: KeyboardShortcutScopeProps) => (
|
||||||
|
<div data-keyboard-shortcut-scope={id} data-keyboard-shortcut-scope-test={id} tabIndex={-1}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@ -0,0 +1,353 @@
|
|||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import { createContainer, DiContainer, getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { registerInjectableReact } from "@ogre-tools/injectable-react";
|
||||||
|
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||||
|
import { keyboardShortcutInjectionToken } from "./keyboard-shortcut-injection-token";
|
||||||
|
import { registerFeature } from "@k8slens/feature-core";
|
||||||
|
import { keyboardShortcutsFeature } from "./feature";
|
||||||
|
import React from "react";
|
||||||
|
import { computed, runInAction } from "mobx";
|
||||||
|
import { KeyboardShortcutScope } from "./keyboard-shortcut-scope";
|
||||||
|
import { Discover, discoverFor } from "@k8slens/react-testing-library-discovery";
|
||||||
|
import { startApplicationInjectionToken } from "@k8slens/application";
|
||||||
|
import { renderInjectionToken } from "@k8slens/react-application";
|
||||||
|
import { reactApplicationChildrenInjectionToken } from "@k8slens/react-application";
|
||||||
|
import platformInjectable from "./platform.injectable";
|
||||||
|
|
||||||
|
describe("keyboard-shortcuts", () => {
|
||||||
|
let di: DiContainer;
|
||||||
|
let invokeMock: jest.Mock;
|
||||||
|
let rendered: RenderResult;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
di = createContainer("irrelevant");
|
||||||
|
|
||||||
|
registerInjectableReact(di);
|
||||||
|
registerMobX(di);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
registerFeature(di, keyboardShortcutsFeature);
|
||||||
|
});
|
||||||
|
|
||||||
|
invokeMock = jest.fn();
|
||||||
|
|
||||||
|
const someKeyboardShortcutInjectable = getInjectable({
|
||||||
|
id: "some-keyboard-shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding: "Escape",
|
||||||
|
invoke: () => invokeMock("esc-in-root"),
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const someScopedKeyboardShortcutInjectable = getInjectable({
|
||||||
|
id: "some-scoped-keyboard-shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding: "Escape",
|
||||||
|
invoke: () => invokeMock("esc-in-scope"),
|
||||||
|
scope: "some-scope",
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const someOtherKeyboardShortcutInjectable = getInjectable({
|
||||||
|
id: "some-other-keyboard-shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding: "something-else-than-esc",
|
||||||
|
invoke: () => invokeMock("something-else-than-esc"),
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const childComponentForScopeInjectable = getInjectable({
|
||||||
|
id: "some-child-component-for-scope",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "some-child-component-for-scope",
|
||||||
|
|
||||||
|
enabled: computed(() => true),
|
||||||
|
|
||||||
|
Component: () => (
|
||||||
|
<KeyboardShortcutScope id="some-scope">
|
||||||
|
<div />
|
||||||
|
</KeyboardShortcutScope>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: reactApplicationChildrenInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(
|
||||||
|
someKeyboardShortcutInjectable,
|
||||||
|
someScopedKeyboardShortcutInjectable,
|
||||||
|
someOtherKeyboardShortcutInjectable,
|
||||||
|
childComponentForScopeInjectable,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
di.override(renderInjectionToken, () => (application) => {
|
||||||
|
rendered = render(application);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when application is started", () => {
|
||||||
|
let discover: Discover;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const startApplication = di.inject(startApplicationInjectionToken);
|
||||||
|
|
||||||
|
await startApplication();
|
||||||
|
|
||||||
|
discover = discoverFor(() => rendered);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given focus is in the body, when pressing the shortcut, calls shortcut in global scope", () => {
|
||||||
|
userEvent.keyboard("{Escape}");
|
||||||
|
|
||||||
|
expect(invokeMock.mock.calls).toEqual([["esc-in-root"]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given focus inside a nested scope, when pressing the shortcut, calls only the callback for the scope", () => {
|
||||||
|
const result = discover.getSingleElement("keyboard-shortcut-scope", "some-scope");
|
||||||
|
|
||||||
|
const discoveredHtml = result.discovered as HTMLDivElement;
|
||||||
|
|
||||||
|
discoveredHtml.focus();
|
||||||
|
|
||||||
|
userEvent.keyboard("{Escape}");
|
||||||
|
|
||||||
|
expect(invokeMock.mock.calls).toEqual([["esc-in-scope"]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given conflicting shortcut, when pressing the shortcut, calls both callbacks", () => {
|
||||||
|
const conflictingShortcutInjectable = getInjectable({
|
||||||
|
id: "some-conflicting-keyboard-shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding: "Escape",
|
||||||
|
invoke: () => invokeMock("conflicting-esc-in-root"),
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(conflictingShortcutInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
userEvent.keyboard("{Escape}");
|
||||||
|
|
||||||
|
expect(invokeMock.mock.calls).toEqual([["esc-in-root"], ["conflicting-esc-in-root"]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
scenario: "given shortcut without modifiers, when shortcut is pressed, calls the callback",
|
||||||
|
binding: { code: "Escape" },
|
||||||
|
keyboard: "{Escape}",
|
||||||
|
shouldCallCallback: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario:
|
||||||
|
"given shortcut without modifiers, when shortcut is pressed but with modifier, does not call the callback",
|
||||||
|
binding: { code: "F1" },
|
||||||
|
keyboard: "{Meta>}[F1]",
|
||||||
|
shouldCallCallback: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "given shortcut with meta modifier, when shortcut is pressed, calls the callback",
|
||||||
|
|
||||||
|
binding: { meta: true, code: "F1" },
|
||||||
|
keyboard: "{Meta>}[F1]",
|
||||||
|
shouldCallCallback: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario:
|
||||||
|
"given shortcut with shift modifier, when shortcut is pressed, calls the callback",
|
||||||
|
|
||||||
|
binding: { shift: true, code: "F1" },
|
||||||
|
keyboard: "{Shift>}[F1]",
|
||||||
|
shouldCallCallback: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "given shortcut with alt modifier, when shortcut is pressed, calls the callback",
|
||||||
|
binding: { altOrOption: true, code: "F1" },
|
||||||
|
keyboard: "{Alt>}[F1]",
|
||||||
|
shouldCallCallback: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "given shortcut with ctrl modifier, when shortcut is pressed, calls the callback",
|
||||||
|
binding: { ctrl: true, code: "F1" },
|
||||||
|
keyboard: "{Control>}[F1]",
|
||||||
|
shouldCallCallback: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "given shortcut with all modifiers, when shortcut is pressed, calls the callback",
|
||||||
|
|
||||||
|
binding: { ctrl: true, altOrOption: true, shift: true, meta: true, code: "F1" },
|
||||||
|
keyboard: "{Meta>}{Shift>}{Alt>}{Control>}[F1]",
|
||||||
|
shouldCallCallback: true,
|
||||||
|
},
|
||||||
|
].forEach(({ binding, keyboard, scenario, shouldCallCallback }) => {
|
||||||
|
// eslint-disable-next-line jest/valid-title
|
||||||
|
it(scenario, () => {
|
||||||
|
const invokeMock = jest.fn();
|
||||||
|
|
||||||
|
const shortcutInjectable = getInjectable({
|
||||||
|
id: "shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding,
|
||||||
|
invoke: invokeMock,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(shortcutInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
userEvent.keyboard(keyboard);
|
||||||
|
|
||||||
|
if (shouldCallCallback) {
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
expect(invokeMock).toHaveBeenCalled();
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
expect(invokeMock).not.toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given in mac and keyboard shortcut with modifier for ctrl or command", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
di.override(platformInjectable, () => "darwin");
|
||||||
|
|
||||||
|
invokeMock = jest.fn();
|
||||||
|
|
||||||
|
const shortcutInjectable = getInjectable({
|
||||||
|
id: "shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding: { code: "KeyK", ctrlOrCommand: true },
|
||||||
|
invoke: invokeMock,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(shortcutInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
const startApplication = di.inject(startApplicationInjectionToken);
|
||||||
|
|
||||||
|
await startApplication();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when pressing the keyboard shortcut with command, calls the callback", () => {
|
||||||
|
userEvent.keyboard("{Meta>}[KeyK]");
|
||||||
|
|
||||||
|
expect(invokeMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when pressing the keyboard shortcut with ctrl, does not call the callback", () => {
|
||||||
|
userEvent.keyboard("{Control>}[KeyK]");
|
||||||
|
|
||||||
|
expect(invokeMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given in windows and keyboard shortcut with modifier for ctrl or command", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
di.override(platformInjectable, () => "win32");
|
||||||
|
|
||||||
|
invokeMock = jest.fn();
|
||||||
|
|
||||||
|
const shortcutInjectable = getInjectable({
|
||||||
|
id: "shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding: { code: "KeyK", ctrlOrCommand: true },
|
||||||
|
invoke: invokeMock,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(shortcutInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
const startApplication = di.inject(startApplicationInjectionToken);
|
||||||
|
|
||||||
|
await startApplication();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when pressing the keyboard shortcut with windows, does not call the callback", () => {
|
||||||
|
userEvent.keyboard("{Meta>}[KeyK]");
|
||||||
|
|
||||||
|
expect(invokeMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when pressing the keyboard shortcut with ctrl, calls the callback", () => {
|
||||||
|
userEvent.keyboard("{Control>}[KeyK]");
|
||||||
|
|
||||||
|
expect(invokeMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given in any other platform and keyboard shortcut with modifier for ctrl or command", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
di.override(platformInjectable, () => "some-other-platform");
|
||||||
|
|
||||||
|
invokeMock = jest.fn();
|
||||||
|
|
||||||
|
const shortcutInjectable = getInjectable({
|
||||||
|
id: "shortcut",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
binding: { code: "KeyK", ctrlOrCommand: true },
|
||||||
|
invoke: invokeMock,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: keyboardShortcutInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(shortcutInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
const startApplication = di.inject(startApplicationInjectionToken);
|
||||||
|
|
||||||
|
await startApplication();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when pressing the keyboard shortcut with meta, does not call the callback", () => {
|
||||||
|
userEvent.keyboard("{Meta>}[KeyK]");
|
||||||
|
|
||||||
|
expect(invokeMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when pressing the keyboard shortcut with ctrl, calls the callback", () => {
|
||||||
|
userEvent.keyboard("{Control>}[KeyK]");
|
||||||
|
|
||||||
|
expect(invokeMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export const allPlatforms = ["win32", "darwin", "linux"] as const;
|
||||||
|
|
||||||
|
const platformInjectable = getInjectable({
|
||||||
|
id: "platform",
|
||||||
|
instantiate: () => process.platform as (typeof allPlatforms)[number],
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default platformInjectable;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "@k8slens/typescript/config/base.json",
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
module.exports = require("@k8slens/webpack").configForReact;
|
||||||
@ -18,14 +18,16 @@
|
|||||||
"clean": "rimraf dist/",
|
"clean": "rimraf dist/",
|
||||||
"generate-types": "tsc --d --declarationDir ./dist --declarationMap --emitDeclarationOnly",
|
"generate-types": "tsc --d --declarationDir ./dist --declarationMap --emitDeclarationOnly",
|
||||||
"build": "npm run generate-types && swc ./src/index.ts -d ./dist",
|
"build": "npm run generate-types && swc ./src/index.ts -d ./dist",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"prepare:dev": "npm run build",
|
||||||
"prepare:test": "npm run build"
|
"prepare:test": "npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@swc/cli": "^0.1.61",
|
"@swc/cli": "^0.1.61",
|
||||||
"@swc/core": "^1.3.37",
|
"@swc/core": "^1.3.44",
|
||||||
"@types/node": "^16.18.11",
|
"@types/node": "^16.18.11",
|
||||||
"@types/semver": "^7.3.13",
|
"@types/semver": "^7.3.13",
|
||||||
"rimraf": "^4.1.2"
|
"rimraf": "^4.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,8 +48,6 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cross-env NODE_ENV=production webpack --config webpack/library-bundle.ts --progress",
|
"build": "cross-env NODE_ENV=production webpack --config webpack/library-bundle.ts --progress",
|
||||||
"clean": "rimraf dist static/build",
|
"clean": "rimraf dist static/build",
|
||||||
"prepare:dev": "cross-env NODE_ENV=development webpack --config webpack/library-bundle.ts --progress",
|
|
||||||
"dev": "cross-env NODE_ENV=development webpack --config webpack/library-bundle.ts --watch",
|
|
||||||
"test:unit": "jest --testPathIgnorePatterns integration",
|
"test:unit": "jest --testPathIgnorePatterns integration",
|
||||||
"test:watch": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
|
"test:watch": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
|
||||||
"lint": "PROD=true eslint --ext js,ts,tsx --max-warnings=0 .",
|
"lint": "PROD=true eslint --ext js,ts,tsx --max-warnings=0 .",
|
||||||
@ -98,7 +96,6 @@
|
|||||||
],
|
],
|
||||||
"runtime": "@side/jest-runtime"
|
"runtime": "@side/jest-runtime"
|
||||||
},
|
},
|
||||||
"build": {},
|
|
||||||
"nx": {
|
"nx": {
|
||||||
"targets": {
|
"targets": {
|
||||||
"build": {
|
"build": {
|
||||||
@ -108,11 +105,6 @@
|
|||||||
"outputs": [
|
"outputs": [
|
||||||
"{workspaceRoot}/static/build/"
|
"{workspaceRoot}/static/build/"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"dev": {
|
|
||||||
"outputs": [
|
|
||||||
"{workspaceRoot}/static/build/"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -120,8 +112,8 @@
|
|||||||
"@astronautlabs/jsonpath": "^1.1.0",
|
"@astronautlabs/jsonpath": "^1.1.0",
|
||||||
"@hapi/call": "^9.0.1",
|
"@hapi/call": "^9.0.1",
|
||||||
"@hapi/subtext": "^7.1.0",
|
"@hapi/subtext": "^7.1.0",
|
||||||
"@k8slens/cluster-settings": "^6.5.0-alpha.1",
|
|
||||||
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
||||||
|
"@k8slens/react-application": "^1.0.0-alpha.0",
|
||||||
"@kubernetes/client-node": "^0.18.1",
|
"@kubernetes/client-node": "^0.18.1",
|
||||||
"@material-ui/styles": "^4.11.5",
|
"@material-ui/styles": "^4.11.5",
|
||||||
"@ogre-tools/fp": "^15.1.2",
|
"@ogre-tools/fp": "^15.1.2",
|
||||||
@ -136,7 +128,7 @@
|
|||||||
"await-lock": "^2.2.2",
|
"await-lock": "^2.2.2",
|
||||||
"byline": "^5.0.0",
|
"byline": "^5.0.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"conf": "^7.1.2",
|
"conf": "^10.2.0",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-updater": "^4.6.5",
|
"electron-updater": "^4.6.5",
|
||||||
@ -144,7 +136,6 @@
|
|||||||
"filehound": "^1.17.6",
|
"filehound": "^1.17.6",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"got": "^11.8.6",
|
|
||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"history": "^4.10.1",
|
"history": "^4.10.1",
|
||||||
@ -191,12 +182,13 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@async-fn/jest": "1.6.4",
|
"@async-fn/jest": "1.6.4",
|
||||||
"@k8slens/messaging-fake-bridge": "^1.0.0-alpha.1",
|
"@k8slens/messaging-fake-bridge": "^1.0.0-alpha.1",
|
||||||
|
"@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0",
|
||||||
"@material-ui/core": "^4.12.3",
|
"@material-ui/core": "^4.12.3",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
"@sentry/types": "^6.19.7",
|
"@sentry/types": "^6.19.7",
|
||||||
"@swc/cli": "^0.1.61",
|
"@swc/cli": "^0.1.61",
|
||||||
"@swc/core": "^1.3.37",
|
"@swc/core": "^1.3.44",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/jest": "^0.2.24",
|
||||||
"@testing-library/dom": "^7.31.2",
|
"@testing-library/dom": "^7.31.2",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
@ -215,7 +207,7 @@
|
|||||||
"@types/hapi__call": "^9.0.0",
|
"@types/hapi__call": "^9.0.0",
|
||||||
"@types/hapi__subtext": "^7.0.0",
|
"@types/hapi__subtext": "^7.0.0",
|
||||||
"@types/http-proxy": "^1.17.9",
|
"@types/http-proxy": "^1.17.9",
|
||||||
"@types/jest": "^28.1.6",
|
"@types/jest": "^29.5.0",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/marked": "^4.0.8",
|
"@types/marked": "^4.0.8",
|
||||||
@ -226,7 +218,7 @@
|
|||||||
"@types/proper-lockfile": "^4.1.2",
|
"@types/proper-lockfile": "^4.1.2",
|
||||||
"@types/randomcolor": "^0.5.7",
|
"@types/randomcolor": "^0.5.7",
|
||||||
"@types/react": "^17.0.45",
|
"@types/react": "^17.0.45",
|
||||||
"@types/react-beautiful-dnd": "^13.1.3",
|
"@types/react-beautiful-dnd": "^13.1.4",
|
||||||
"@types/react-dom": "^17.0.16",
|
"@types/react-dom": "^17.0.16",
|
||||||
"@types/react-router": "^5.1.19",
|
"@types/react-router": "^5.1.19",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
@ -240,7 +232,7 @@
|
|||||||
"@types/triple-beam": "^1.3.2",
|
"@types/triple-beam": "^1.3.2",
|
||||||
"@types/url-parse": "^1.4.8",
|
"@types/url-parse": "^1.4.8",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.1",
|
||||||
"@types/webpack-dev-server": "^4.7.2",
|
"@types/webpack-dev-server": "^4.7.2",
|
||||||
"@types/webpack-env": "^1.18.0",
|
"@types/webpack-env": "^1.18.0",
|
||||||
"@types/webpack-node-externals": "^2.5.3",
|
"@types/webpack-node-externals": "^2.5.3",
|
||||||
@ -273,7 +265,7 @@
|
|||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"ignore-loader": "^0.1.2",
|
"ignore-loader": "^0.1.2",
|
||||||
"include-media": "^1.4.9",
|
"include-media": "^1.4.9",
|
||||||
"jest": "^28.1.3",
|
"jest": "^29.5.0",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
"jest-environment-jsdom": "^28.1.3",
|
"jest-environment-jsdom": "^28.1.3",
|
||||||
"jest-mock-extended": "^2.0.9",
|
"jest-mock-extended": "^2.0.9",
|
||||||
@ -287,7 +279,7 @@
|
|||||||
"node-gyp": "^8.3.0",
|
"node-gyp": "^8.3.0",
|
||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
"playwright": "^1.30.0",
|
"playwright": "^1.32.1",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"postcss-loader": "^6.2.1",
|
"postcss-loader": "^6.2.1",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
@ -300,7 +292,7 @@
|
|||||||
"react-select-event": "^5.5.1",
|
"react-select-event": "^5.5.1",
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"react-window": "^1.8.8",
|
"react-window": "^1.8.8",
|
||||||
"rimraf": "^4.1.2",
|
"rimraf": "^4.4.1",
|
||||||
"sass": "^1.58.2",
|
"sass": "^1.58.2",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
@ -313,9 +305,9 @@
|
|||||||
"typedoc-plugin-markdown": "^3.13.6",
|
"typedoc-plugin-markdown": "^3.13.6",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"typescript-plugin-css-modules": "^3.4.0",
|
"typescript-plugin-css-modules": "^3.4.0",
|
||||||
"webpack": "^5.75.0",
|
"webpack": "^5.77.0",
|
||||||
"webpack-cli": "^4.9.2",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack-dev-server": "^4.11.1",
|
"webpack-dev-server": "^4.13.2",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"xterm": "^4.19.0",
|
"xterm": "^4.19.0",
|
||||||
"xterm-addon-fit": "^0.5.0"
|
"xterm-addon-fit": "^0.5.0"
|
||||||
@ -323,6 +315,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@k8slens/application": "^6.5.0-alpha.0",
|
"@k8slens/application": "^6.5.0-alpha.0",
|
||||||
"@k8slens/application-for-electron-main": "^6.5.0-alpha.0",
|
"@k8slens/application-for-electron-main": "^6.5.0-alpha.0",
|
||||||
|
"@k8slens/cluster-settings": "^6.5.0-alpha.1",
|
||||||
"@k8slens/legacy-extensions": "^1.0.0-alpha.0",
|
"@k8slens/legacy-extensions": "^1.0.0-alpha.0",
|
||||||
"@k8slens/messaging": "^1.0.0-alpha.1",
|
"@k8slens/messaging": "^1.0.0-alpha.1",
|
||||||
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
|
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
|
||||||
|
|||||||
@ -1,356 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { anyObject } from "jest-mock-extended";
|
|
||||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
|
||||||
import hotbarStoreInjectable from "../hotbars/store.injectable";
|
|
||||||
import type { HotbarStore } from "../hotbars/store";
|
|
||||||
import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import hasCategoryForEntityInjectable from "../catalog/has-category-for-entity.injectable";
|
|
||||||
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import type { Logger } from "../logger";
|
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
|
||||||
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
|
|
||||||
|
|
||||||
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
|
||||||
return {
|
|
||||||
getName: jest.fn(() => data.metadata?.name),
|
|
||||||
getId: jest.fn(() => data.metadata?.uid),
|
|
||||||
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
|
|
||||||
isEnabled: jest.fn(() => data.status?.enabled ?? true),
|
|
||||||
onContextMenuOpen: jest.fn(),
|
|
||||||
onSettingsOpen: jest.fn(),
|
|
||||||
metadata: {},
|
|
||||||
spec: {},
|
|
||||||
status: {},
|
|
||||||
...data,
|
|
||||||
} as CatalogEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("HotbarStore", () => {
|
|
||||||
let di: DiContainer;
|
|
||||||
let hotbarStore: HotbarStore;
|
|
||||||
let testCluster: CatalogEntity;
|
|
||||||
let minikubeCluster: CatalogEntity;
|
|
||||||
let awsCluster: CatalogEntity;
|
|
||||||
let loggerMock: jest.Mocked<Logger>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
di = getDiForUnitTesting();
|
|
||||||
|
|
||||||
testCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "some-test-id",
|
|
||||||
name: "my-test-cluster",
|
|
||||||
source: "local",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
minikubeCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "some-minikube-id",
|
|
||||||
name: "my-minikube-cluster",
|
|
||||||
source: "local",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
awsCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "some-aws-id",
|
|
||||||
name: "my-aws-cluster",
|
|
||||||
source: "local",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
di.override(hasCategoryForEntityInjectable, () => () => true);
|
|
||||||
|
|
||||||
loggerMock = {
|
|
||||||
warn: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
silly: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
di.override(loggerInjectable, () => loggerMock);
|
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
|
||||||
|
|
||||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
|
||||||
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
|
|
||||||
|
|
||||||
catalogEntityRegistry.addComputedSource("some-id", computed(() => [
|
|
||||||
testCluster,
|
|
||||||
minikubeCluster,
|
|
||||||
awsCluster,
|
|
||||||
catalogCatalogEntity,
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("given no previous data in store, running all migrations", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
hotbarStore = di.inject(hotbarStoreInjectable);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("load", () => {
|
|
||||||
it("loads one hotbar by default", () => {
|
|
||||||
expect(hotbarStore.hotbars.length).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("add", () => {
|
|
||||||
it("adds a hotbar", () => {
|
|
||||||
hotbarStore.add({ name: "hottest" });
|
|
||||||
expect(hotbarStore.hotbars.length).toEqual(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hotbar items", () => {
|
|
||||||
it("initially creates 12 empty cells", () => {
|
|
||||||
expect(hotbarStore.getActive().items.length).toEqual(12);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initially adds catalog entity as first item", () => {
|
|
||||||
expect(hotbarStore.getActive().items[0]?.entity.name).toEqual("Catalog");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds items", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes items", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.removeFromHotbar("some-test-id");
|
|
||||||
hotbarStore.removeFromHotbar("catalog-entity");
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items).toStrictEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does nothing if removing with invalid uid", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.removeFromHotbar("invalid uid");
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves item to empty cell", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[6]).toBeNull();
|
|
||||||
|
|
||||||
hotbarStore.restackItems(1, 5);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[5]).toBeTruthy();
|
|
||||||
expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("some-test-id");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves items down", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
// aws -> catalog
|
|
||||||
hotbarStore.restackItems(3, 0);
|
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves items up", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
// test -> aws
|
|
||||||
hotbarStore.restackItems(1, 3);
|
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("logs an error if cellIndex is out of bounds", () => {
|
|
||||||
hotbarStore.add({ name: "hottest", id: "hottest" });
|
|
||||||
hotbarStore.setActiveHotbar("hottest");
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, -1);
|
|
||||||
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, 12);
|
|
||||||
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, 13);
|
|
||||||
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws an error if getId is invalid or returns not a string", () => {
|
|
||||||
expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(TypeError);
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => true } as any)).toThrowError(TypeError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws an error if getName is invalid or returns not a string", () => {
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError);
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does nothing when item moved to same cell", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.restackItems(1, 1);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("some-test-id");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("new items takes first empty cell", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
hotbarStore.restackItems(0, 3);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("some-minikube-id");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws if invalid arguments provided", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
|
|
||||||
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(14, 1)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(11, 112)).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("checks if entity already pinned to hotbar", () => {
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
|
|
||||||
expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const writeJsonSync = di.inject(writeJsonSyncInjectable);
|
|
||||||
|
|
||||||
writeJsonSync("/some-directory-for-user-data/lens-hotbar-store.json", {
|
|
||||||
__internal__: {
|
|
||||||
migrations: {
|
|
||||||
version: "5.0.0-beta.3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hotbars: [
|
|
||||||
{
|
|
||||||
id: "3caac17f-aec2-4723-9694-ad204465d935",
|
|
||||||
name: "myhotbar",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "some-aws-id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "55b42c3c7ba3b04193416cda405269a5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "176fd331968660832f62283219d7eb6e",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "61c4fb45528840ebad1badc25da41d14",
|
|
||||||
name: "user1-context",
|
|
||||||
source: "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "27d6f99fe9e7548a6e306760bfe19969",
|
|
||||||
name: "foo2",
|
|
||||||
source: "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
entity: {
|
|
||||||
uid: "c0b20040646849bb4dcf773e43a0bf27",
|
|
||||||
name: "multinode-demo",
|
|
||||||
source: "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
|
|
||||||
|
|
||||||
hotbarStore = di.inject(hotbarStoreInjectable);
|
|
||||||
|
|
||||||
hotbarStore.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows to retrieve a hotbar", () => {
|
|
||||||
const hotbar = hotbarStore.findById("3caac17f-aec2-4723-9694-ad204465d935");
|
|
||||||
|
|
||||||
expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clears cells without entity", () => {
|
|
||||||
const items = hotbarStore.hotbars[0].items;
|
|
||||||
|
|
||||||
expect(items[2]).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds extra data to cells with according entity", () => {
|
|
||||||
const items = hotbarStore.hotbars[0].items;
|
|
||||||
|
|
||||||
expect(items[0]).toEqual({
|
|
||||||
entity: {
|
|
||||||
name: "my-aws-cluster",
|
|
||||||
source: "local",
|
|
||||||
uid: "some-aws-id",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -2,11 +2,8 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { UserStore } from "../user-store";
|
|
||||||
import userStoreInjectable from "../user-store/user-store.injectable";
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import type { ClusterStoreModel } from "../cluster-store/cluster-store";
|
|
||||||
import { defaultThemeId } from "../vars";
|
import { defaultThemeId } from "../vars";
|
||||||
import writeFileInjectable from "../fs/write-file.injectable";
|
import writeFileInjectable from "../fs/write-file.injectable";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
@ -15,9 +12,16 @@ import releaseChannelInjectable from "../vars/release-channel.injectable";
|
|||||||
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
||||||
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
|
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
|
||||||
import writeFileSyncInjectable from "../fs/write-file-sync.injectable";
|
import writeFileSyncInjectable from "../fs/write-file-sync.injectable";
|
||||||
|
import type { UserPreferencesState } from "../../features/user-preferences/common/state.injectable";
|
||||||
|
import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable";
|
||||||
|
import userPreferencesPersistentStorageInjectable from "../../features/user-preferences/common/storage.injectable";
|
||||||
|
import type { ResetTheme } from "../../features/user-preferences/common/reset-theme.injectable";
|
||||||
|
import resetThemeInjectable from "../../features/user-preferences/common/reset-theme.injectable";
|
||||||
|
import type { ClusterStoreModel } from "../../features/cluster/storage/common/storage.injectable";
|
||||||
|
|
||||||
describe("user store tests", () => {
|
describe("user store tests", () => {
|
||||||
let userStore: UserStore;
|
let state: UserPreferencesState;
|
||||||
|
let resetTheme: ResetTheme;
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -33,6 +37,8 @@ describe("user store tests", () => {
|
|||||||
|
|
||||||
await di.inject(defaultUpdateChannelInjectable).init();
|
await di.inject(defaultUpdateChannelInjectable).init();
|
||||||
|
|
||||||
|
state = di.inject(userPreferencesStateInjectable);
|
||||||
|
resetTheme = di.inject(resetThemeInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("for an empty config", () => {
|
describe("for an empty config", () => {
|
||||||
@ -42,25 +48,23 @@ describe("user store tests", () => {
|
|||||||
writeJsonSync("/some-directory-for-user-data/lens-user-store.json", {});
|
writeJsonSync("/some-directory-for-user-data/lens-user-store.json", {});
|
||||||
writeJsonSync("/some-directory-for-user-data/kube_config", {});
|
writeJsonSync("/some-directory-for-user-data/kube_config", {});
|
||||||
|
|
||||||
userStore = di.inject(userStoreInjectable);
|
di.inject(userPreferencesPersistentStorageInjectable).loadAndStartSyncing();
|
||||||
|
|
||||||
userStore.load();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and getting preferences", () => {
|
it("allows setting and getting preferences", () => {
|
||||||
userStore.httpsProxy = "abcd://defg";
|
state.httpsProxy = "abcd://defg";
|
||||||
|
|
||||||
expect(userStore.httpsProxy).toBe("abcd://defg");
|
expect(state.httpsProxy).toBe("abcd://defg");
|
||||||
expect(userStore.colorTheme).toBe(defaultThemeId);
|
expect(state.colorTheme).toBe(defaultThemeId);
|
||||||
|
|
||||||
userStore.colorTheme = "light";
|
state.colorTheme = "light";
|
||||||
expect(userStore.colorTheme).toBe("light");
|
expect(state.colorTheme).toBe("light");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("correctly resets theme to default value", async () => {
|
it("correctly resets theme to default value", async () => {
|
||||||
userStore.colorTheme = "some other theme";
|
state.colorTheme = "some other theme";
|
||||||
userStore.resetTheme();
|
resetTheme();
|
||||||
expect(userStore.colorTheme).toBe(defaultThemeId);
|
expect(state.colorTheme).toBe(defaultThemeId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,18 +96,16 @@ describe("user store tests", () => {
|
|||||||
|
|
||||||
di.override(storeMigrationVersionInjectable, () => "10.0.0");
|
di.override(storeMigrationVersionInjectable, () => "10.0.0");
|
||||||
|
|
||||||
userStore = di.inject(userStoreInjectable);
|
di.inject(userPreferencesPersistentStorageInjectable).loadAndStartSyncing();
|
||||||
|
|
||||||
userStore.load();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips clusters for adding to kube-sync with files under extension_data/", () => {
|
it("skips clusters for adding to kube-sync with files under extension_data/", () => {
|
||||||
expect(userStore.syncKubeconfigEntries.has("/some-directory-for-user-data/extension_data/foo/bar")).toBe(false);
|
expect(state.syncKubeconfigEntries.has("/some-directory-for-user-data/extension_data/foo/bar")).toBe(false);
|
||||||
expect(userStore.syncKubeconfigEntries.has("/some/other/path")).toBe(true);
|
expect(state.syncKubeconfigEntries.has("/some/other/path")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows access to the colorTheme preference", () => {
|
it("allows access to the colorTheme preference", () => {
|
||||||
expect(userStore.colorTheme).toBe("light");
|
expect(state.colorTheme).toBe("light");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,156 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type Config from "conf";
|
|
||||||
import type { Migrations, Options as ConfOptions } from "conf/dist/source/types";
|
|
||||||
import type { IEqualsComparer } from "mobx";
|
|
||||||
import { makeObservable, reaction } from "mobx";
|
|
||||||
import { disposer, isPromiseLike } from "@k8slens/utilities";
|
|
||||||
import { broadcastMessage } from "../ipc";
|
|
||||||
import isEqual from "lodash/isEqual";
|
|
||||||
import { kebabCase } from "lodash";
|
|
||||||
import type { GetConfigurationFileModel } from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
|
||||||
import type { Logger } from "../logger";
|
|
||||||
import type { PersistStateToConfig } from "./save-to-file";
|
|
||||||
import type { GetBasenameOfPath } from "../path/get-basename.injectable";
|
|
||||||
import type { EnlistMessageChannelListener } from "@k8slens/messaging";
|
|
||||||
import { toJS } from "../utils";
|
|
||||||
|
|
||||||
export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> {
|
|
||||||
syncOptions?: {
|
|
||||||
fireImmediately?: boolean;
|
|
||||||
equals?: IEqualsComparer<T>;
|
|
||||||
};
|
|
||||||
configName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IpcChannelPrefixes {
|
|
||||||
local: string;
|
|
||||||
remote: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BaseStoreDependencies {
|
|
||||||
readonly logger: Logger;
|
|
||||||
readonly storeMigrationVersion: string;
|
|
||||||
readonly directoryForUserData: string;
|
|
||||||
readonly migrations: Migrations<Record<string, unknown>>;
|
|
||||||
readonly ipcChannelPrefixes: IpcChannelPrefixes;
|
|
||||||
readonly shouldDisableSyncInListener: boolean;
|
|
||||||
getConfigurationFileModel: GetConfigurationFileModel;
|
|
||||||
persistStateToConfig: PersistStateToConfig;
|
|
||||||
getBasenameOfPath: GetBasenameOfPath;
|
|
||||||
enlistMessageChannelListener: EnlistMessageChannelListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: T should only contain base JSON serializable types.
|
|
||||||
*/
|
|
||||||
export abstract class BaseStore<T extends object> {
|
|
||||||
private readonly syncDisposers = disposer();
|
|
||||||
|
|
||||||
readonly displayName = kebabCase(this.params.configName).toUpperCase();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
protected readonly dependencies: BaseStoreDependencies;
|
|
||||||
|
|
||||||
protected constructor(
|
|
||||||
dependencies: BaseStoreDependencies,
|
|
||||||
protected readonly params: BaseStoreParams<T>,
|
|
||||||
) {
|
|
||||||
this.dependencies = dependencies;
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This must be called after the last child's constructor is finished (or just before it finishes)
|
|
||||||
*/
|
|
||||||
load() {
|
|
||||||
this.dependencies.logger.info(`[${this.displayName}]: LOADING ...`);
|
|
||||||
|
|
||||||
const config = this.dependencies.getConfigurationFileModel({
|
|
||||||
projectName: "lens",
|
|
||||||
projectVersion: this.dependencies.storeMigrationVersion,
|
|
||||||
cwd: this.cwd(),
|
|
||||||
...this.params,
|
|
||||||
migrations: this.dependencies.migrations as Migrations<T>,
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = this.fromStore(config.store);
|
|
||||||
|
|
||||||
if (isPromiseLike(res)) {
|
|
||||||
this.dependencies.logger.error(`${this.displayName} extends BaseStore<T>'s fromStore method returns a Promise or promise-like object. This is an error and must be fixed.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startSyncing(config);
|
|
||||||
this.dependencies.logger.info(`[${this.displayName}]: LOADED from ${config.path}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected cwd() {
|
|
||||||
return this.dependencies.directoryForUserData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private startSyncing(config: Config<T>) {
|
|
||||||
const name = this.dependencies.getBasenameOfPath(config.path);
|
|
||||||
|
|
||||||
const disableSync = () => this.syncDisposers();
|
|
||||||
const enableSync = () => {
|
|
||||||
this.syncDisposers.push(
|
|
||||||
reaction(
|
|
||||||
() => toJS(this.toJSON()), // unwrap possible observables and react to everything
|
|
||||||
model => {
|
|
||||||
this.dependencies.persistStateToConfig(config, model);
|
|
||||||
broadcastMessage(`${this.dependencies.ipcChannelPrefixes.remote}:${config.path}`, model);
|
|
||||||
},
|
|
||||||
this.params.syncOptions,
|
|
||||||
),
|
|
||||||
this.dependencies.enlistMessageChannelListener({
|
|
||||||
id: this.displayName,
|
|
||||||
channel: {
|
|
||||||
id: `${this.dependencies.ipcChannelPrefixes.local}:${config.path}`,
|
|
||||||
},
|
|
||||||
handler: (model) => {
|
|
||||||
this.dependencies.logger.silly(`[${this.displayName}]: syncing ${name}`, { model });
|
|
||||||
|
|
||||||
if (this.dependencies.shouldDisableSyncInListener) {
|
|
||||||
disableSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: use "resourceVersion" if merge required (to avoid equality checks => better performance)
|
|
||||||
if (!isEqual(this.toJSON(), model)) {
|
|
||||||
this.fromStore(model as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dependencies.shouldDisableSyncInListener) {
|
|
||||||
enableSync();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
enableSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fromStore is called internally when a child class syncs with the file
|
|
||||||
* system.
|
|
||||||
*
|
|
||||||
* Note: This function **must** be synchronous.
|
|
||||||
*
|
|
||||||
* @param data the parsed information read from the stored JSON file
|
|
||||||
*/
|
|
||||||
protected abstract fromStore(data: T): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* toJSON is called when syncing the store to the filesystem. It should
|
|
||||||
* produce a JSON serializable object representation of the current state.
|
|
||||||
*
|
|
||||||
* It is recommended that a round trip is valid. Namely, calling
|
|
||||||
* `this.fromStore(this.toJSON())` shouldn't change the state.
|
|
||||||
*/
|
|
||||||
abstract toJSON(): T;
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
|
||||||
import type { IpcChannelPrefixes } from "./base-store";
|
|
||||||
|
|
||||||
export const baseStoreIpcChannelPrefixesInjectionToken = getInjectionToken<IpcChannelPrefixes>({
|
|
||||||
id: "base-store-ipc-channel-prefix-token",
|
|
||||||
});
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { InjectionToken } from "@ogre-tools/injectable";
|
|
||||||
import { lifecycleEnum, getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type Conf from "conf/dist/source";
|
|
||||||
import type { Migrations } from "conf/dist/source/types";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import { getOrInsert, iter } from "@k8slens/utilities";
|
|
||||||
|
|
||||||
export interface MigrationDeclaration {
|
|
||||||
version: string;
|
|
||||||
run(store: Conf<Partial<Record<string, unknown>>>): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storeMigrationsInjectable = getInjectable({
|
|
||||||
id: "store-migrations",
|
|
||||||
instantiate: (di, token): Migrations<Record<string, unknown>> => {
|
|
||||||
const logger = di.inject(loggerInjectable);
|
|
||||||
const declarations = di.injectMany(token);
|
|
||||||
const migrations = new Map<string, MigrationDeclaration["run"][]>();
|
|
||||||
|
|
||||||
for (const decl of declarations) {
|
|
||||||
getOrInsert(migrations, decl.version, []).push(decl.run);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
iter.map(
|
|
||||||
migrations,
|
|
||||||
([v, fns]) => [v, (store) => {
|
|
||||||
logger.info(`Running ${v} migration for ${store.path}`);
|
|
||||||
|
|
||||||
for (const fn of fns) {
|
|
||||||
fn(store);
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
lifecycle: lifecycleEnum.keyedSingleton({
|
|
||||||
getInstanceKey: (di, token: InjectionToken<MigrationDeclaration, void>) => token.id,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default storeMigrationsInjectable;
|
|
||||||
@ -4,10 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
import { getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
|
import removeWeblinkInjectable from "../../features/weblinks/common/remove.injectable";
|
||||||
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||||
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
|
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
|
||||||
import productNameInjectable from "../vars/product-name.injectable";
|
import productNameInjectable from "../vars/product-name.injectable";
|
||||||
import weblinkStoreInjectable from "../weblinks-store/weblink-store.injectable";
|
|
||||||
|
|
||||||
export type WebLinkStatusPhase = "available" | "unavailable";
|
export type WebLinkStatusPhase = "available" | "unavailable";
|
||||||
|
|
||||||
@ -34,13 +34,13 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
|
|||||||
// NOTE: this is safe because `onContextMenuOpen` is only supposed to be called in the renderer
|
// NOTE: this is safe because `onContextMenuOpen` is only supposed to be called in the renderer
|
||||||
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi("renderer");
|
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi("renderer");
|
||||||
const productName = di.inject(productNameInjectable);
|
const productName = di.inject(productNameInjectable);
|
||||||
const weblinkStore = di.inject(weblinkStoreInjectable);
|
const removeWeblink = di.inject(removeWeblinkInjectable);
|
||||||
|
|
||||||
if (this.metadata.source === "local") {
|
if (this.metadata.source === "local") {
|
||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
icon: "delete",
|
icon: "delete",
|
||||||
onClick: async () => weblinkStore.removeById(this.getId()),
|
onClick: () => removeWeblink(this.getId()),
|
||||||
confirm: {
|
confirm: {
|
||||||
message: `Remove Web Link "${this.getName()}" from ${productName}?`,
|
message: `Remove Web Link "${this.getName()}" from ${productName}?`,
|
||||||
},
|
},
|
||||||
|
|||||||
83
packages/core/src/common/catalog/helpers.ts
Normal file
83
packages/core/src/common/catalog/helpers.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { CatalogEntity } from "./catalog-entity";
|
||||||
|
import GraphemeSplitter from "grapheme-splitter";
|
||||||
|
import { hasOwnProperty, hasTypedProperty, isObject, isString, iter } from "@k8slens/utilities";
|
||||||
|
|
||||||
|
function getNameParts(name: string): string[] {
|
||||||
|
const byWhitespace = name.split(/\s+/);
|
||||||
|
|
||||||
|
if (byWhitespace.length > 1) {
|
||||||
|
return byWhitespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
const byDashes = name.split(/[-_]+/);
|
||||||
|
|
||||||
|
if (byDashes.length > 1) {
|
||||||
|
return byDashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name.split(/@+/);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function limitGraphemeLengthOf(src: string, count: number): string {
|
||||||
|
const splitter = new GraphemeSplitter();
|
||||||
|
|
||||||
|
return iter
|
||||||
|
.chain(splitter.iterateGraphemes(src))
|
||||||
|
.take(count)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeDefaultShortName(name: string) {
|
||||||
|
if (!name || typeof name !== "string") {
|
||||||
|
return "??";
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rawFirst, rawSecond, rawThird] = getNameParts(name);
|
||||||
|
const splitter = new GraphemeSplitter();
|
||||||
|
const first = splitter.iterateGraphemes(rawFirst);
|
||||||
|
const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first;
|
||||||
|
const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty<string>();
|
||||||
|
|
||||||
|
return iter.chain(iter.take(first, 1))
|
||||||
|
.concat(iter.take(second, 1))
|
||||||
|
.concat(iter.take(third, 1))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getShortName(entity: CatalogEntity): string {
|
||||||
|
return entity.metadata.shortName || computeDefaultShortName(entity.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIconColourHash(entity: CatalogEntity): string {
|
||||||
|
return `${entity.metadata.name}-${entity.metadata.source}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIconBackground(entity: CatalogEntity): string | undefined {
|
||||||
|
if (isObject(entity.spec.icon)) {
|
||||||
|
if (hasTypedProperty(entity.spec.icon, "background", isString)) {
|
||||||
|
return entity.spec.icon.background;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasOwnProperty(entity.spec.icon, "src")
|
||||||
|
? "transparent"
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIconMaterial(entity: CatalogEntity): string | undefined {
|
||||||
|
if (
|
||||||
|
isObject(entity.spec.icon)
|
||||||
|
&& hasTypedProperty(entity.spec.icon, "material", isString)
|
||||||
|
) {
|
||||||
|
return entity.spec.icon.material;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { ClusterStore } from "./cluster-store";
|
|
||||||
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
|
||||||
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
|
||||||
import storeMigrationsInjectable from "../base-store/migrations.injectable";
|
|
||||||
import { clusterStoreMigrationInjectionToken } from "./migration-token";
|
|
||||||
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
|
||||||
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
|
||||||
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
|
||||||
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
|
||||||
|
|
||||||
const clusterStoreInjectable = getInjectable({
|
|
||||||
id: "cluster-store",
|
|
||||||
|
|
||||||
instantiate: (di) => new ClusterStore({
|
|
||||||
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
|
||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
|
||||||
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
|
||||||
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
|
||||||
migrations: di.inject(storeMigrationsInjectable, clusterStoreMigrationInjectionToken),
|
|
||||||
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
|
||||||
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
|
||||||
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
|
||||||
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
|
||||||
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default clusterStoreInjectable;
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import { action, comparer, computed, makeObservable, observable } from "mobx";
|
|
||||||
import type { BaseStoreDependencies } from "../base-store/base-store";
|
|
||||||
import { BaseStore } from "../base-store/base-store";
|
|
||||||
import { Cluster } from "../cluster/cluster";
|
|
||||||
import { toJS } from "../utils";
|
|
||||||
import type { ClusterModel, ClusterId } from "../cluster-types";
|
|
||||||
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
|
|
||||||
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
|
|
||||||
|
|
||||||
export interface ClusterStoreModel {
|
|
||||||
clusters?: ClusterModel[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies extends BaseStoreDependencies {
|
|
||||||
readClusterConfigSync: ReadClusterConfigSync;
|
|
||||||
emitAppEvent: EmitAppEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|
||||||
readonly clusters = observable.map<ClusterId, Cluster>();
|
|
||||||
|
|
||||||
constructor(protected readonly dependencies: Dependencies) {
|
|
||||||
super(dependencies, {
|
|
||||||
configName: "lens-cluster-store",
|
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
|
||||||
syncOptions: {
|
|
||||||
equals: comparer.structural,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get clustersList(): Cluster[] {
|
|
||||||
return Array.from(this.clusters.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get connectedClustersList(): Cluster[] {
|
|
||||||
return this.clustersList.filter((c) => !c.disconnected);
|
|
||||||
}
|
|
||||||
|
|
||||||
hasClusters() {
|
|
||||||
return this.clusters.size > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
getById(id: ClusterId | undefined): Cluster | undefined {
|
|
||||||
if (id) {
|
|
||||||
return this.clusters.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
|
|
||||||
this.dependencies.emitAppEvent({ name: "cluster", action: "add" });
|
|
||||||
|
|
||||||
const cluster = clusterOrModel instanceof Cluster
|
|
||||||
? clusterOrModel
|
|
||||||
: new Cluster(
|
|
||||||
clusterOrModel,
|
|
||||||
this.dependencies.readClusterConfigSync(clusterOrModel),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.clusters.set(cluster.id, cluster);
|
|
||||||
|
|
||||||
return cluster;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
protected fromStore({ clusters = [] }: ClusterStoreModel = {}) {
|
|
||||||
const currentClusters = new Map(this.clusters);
|
|
||||||
const newClusters = new Map<ClusterId, Cluster>();
|
|
||||||
|
|
||||||
// update new clusters
|
|
||||||
for (const clusterModel of clusters) {
|
|
||||||
try {
|
|
||||||
let cluster = currentClusters.get(clusterModel.id);
|
|
||||||
|
|
||||||
if (cluster) {
|
|
||||||
cluster.updateModel(clusterModel);
|
|
||||||
} else {
|
|
||||||
cluster = new Cluster(
|
|
||||||
clusterModel,
|
|
||||||
this.dependencies.readClusterConfigSync(clusterModel),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
newClusters.set(clusterModel.id, cluster);
|
|
||||||
} catch (error) {
|
|
||||||
this.dependencies.logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.clusters.replace(newClusters);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): ClusterStoreModel {
|
|
||||||
return toJS({
|
|
||||||
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { ClusterConfigData, ClusterModel } from "../cluster-types";
|
|
||||||
import readFileSyncInjectable from "../fs/read-file-sync.injectable";
|
|
||||||
import { loadConfigFromString, validateKubeConfig } from "../kube-helpers";
|
|
||||||
|
|
||||||
export type ReadClusterConfigSync = (model: ClusterModel) => ClusterConfigData;
|
|
||||||
|
|
||||||
const readClusterConfigSyncInjectable = getInjectable({
|
|
||||||
id: "read-cluster-config-sync",
|
|
||||||
instantiate: (di): ReadClusterConfigSync => {
|
|
||||||
const readFileSync = di.inject(readFileSyncInjectable);
|
|
||||||
|
|
||||||
return ({ kubeConfigPath, contextName }) => {
|
|
||||||
const kubeConfigData = readFileSync(kubeConfigPath);
|
|
||||||
const { config } = loadConfigFromString(kubeConfigData);
|
|
||||||
const result = validateKubeConfig(config, contextName);
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
throw result.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { clusterServerUrl: result.cluster.server };
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default readClusterConfigSyncInjectable;
|
|
||||||
@ -167,7 +167,7 @@ export enum ClusterMetricsResourceType {
|
|||||||
StatefulSet = "StatefulSet",
|
StatefulSet = "StatefulSet",
|
||||||
Container = "Container",
|
Container = "Container",
|
||||||
Ingress = "Ingress",
|
Ingress = "Ingress",
|
||||||
VolumeClaim = "VolumeClaim",
|
VolumeClaim = "PersistentVolumeClaim",
|
||||||
ReplicaSet = "ReplicaSet",
|
ReplicaSet = "ReplicaSet",
|
||||||
DaemonSet = "DaemonSet",
|
DaemonSet = "DaemonSet",
|
||||||
Job = "Job",
|
Job = "Job",
|
||||||
@ -183,7 +183,6 @@ export const initialNodeShellImage = "docker.io/alpine:3.13";
|
|||||||
* The data representing a cluster's state, for passing between main and renderer
|
* The data representing a cluster's state, for passing between main and renderer
|
||||||
*/
|
*/
|
||||||
export interface ClusterState {
|
export interface ClusterState {
|
||||||
apiUrl: string;
|
|
||||||
online: boolean;
|
online: boolean;
|
||||||
disconnected: boolean;
|
disconnected: boolean;
|
||||||
accessible: boolean;
|
accessible: boolean;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { computed, observable, toJS, runInAction } from "mobx";
|
import { computed, observable, toJS, runInAction } from "mobx";
|
||||||
import type { KubeApiResource } from "../rbac";
|
import type { KubeApiResource } from "../rbac";
|
||||||
import type { ClusterState, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, ClusterConfigData } from "../cluster-types";
|
import type { ClusterState, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../cluster-types";
|
||||||
import { ClusterMetadataKey, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
|
import { ClusterMetadataKey, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
|
||||||
import type { IObservableValue } from "mobx";
|
import type { IObservableValue } from "mobx";
|
||||||
import { replaceObservableObject } from "../utils/replace-observable-object";
|
import { replaceObservableObject } from "../utils/replace-observable-object";
|
||||||
@ -27,11 +27,6 @@ export class Cluster {
|
|||||||
*/
|
*/
|
||||||
readonly kubeConfigPath = observable.box() as IObservableValue<string>;
|
readonly kubeConfigPath = observable.box() as IObservableValue<string>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Kubernetes API server URL
|
|
||||||
*/
|
|
||||||
readonly apiUrl: IObservableValue<string>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes if we can detect that cluster is online
|
* Describes if we can detect that cluster is online
|
||||||
*/
|
*/
|
||||||
@ -122,7 +117,7 @@ export class Cluster {
|
|||||||
*/
|
*/
|
||||||
readonly prometheusPreferences = computed(() => pick(toJS(this.preferences), "prometheus", "prometheusProvider") as ClusterPrometheusPreferences);
|
readonly prometheusPreferences = computed(() => pick(toJS(this.preferences), "prometheus", "prometheusProvider") as ClusterPrometheusPreferences);
|
||||||
|
|
||||||
constructor({ id, ...model }: ClusterModel, configData: ClusterConfigData) {
|
constructor({ id, ...model }: ClusterModel) {
|
||||||
const { error } = clusterModelIdChecker.validate({ id });
|
const { error } = clusterModelIdChecker.validate({ id });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -131,7 +126,6 @@ export class Cluster {
|
|||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
this.apiUrl = observable.box(configData.clusterServerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,7 +181,6 @@ export class Cluster {
|
|||||||
*/
|
*/
|
||||||
getState(): ClusterState {
|
getState(): ClusterState {
|
||||||
return {
|
return {
|
||||||
apiUrl: this.apiUrl.get(),
|
|
||||||
online: this.online.get(),
|
online: this.online.get(),
|
||||||
ready: this.ready.get(),
|
ready: this.ready.get(),
|
||||||
disconnected: this.disconnected.get(),
|
disconnected: this.disconnected.get(),
|
||||||
@ -207,7 +200,6 @@ export class Cluster {
|
|||||||
this.accessible.set(state.accessible);
|
this.accessible.set(state.accessible);
|
||||||
this.allowedNamespaces.replace(state.allowedNamespaces);
|
this.allowedNamespaces.replace(state.allowedNamespaces);
|
||||||
this.resourcesToShow.replace(state.resourcesToShow);
|
this.resourcesToShow.replace(state.resourcesToShow);
|
||||||
this.apiUrl.set(state.apiUrl);
|
|
||||||
this.disconnected.set(state.disconnected);
|
this.disconnected.set(state.disconnected);
|
||||||
this.isAdmin.set(state.isAdmin);
|
this.isAdmin.set(state.isAdmin);
|
||||||
this.isGlobalWatchEnabled.set(state.isGlobalWatchEnabled);
|
this.isGlobalWatchEnabled.set(state.isGlobalWatchEnabled);
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import isProductionInjectable from "../vars/is-production.injectable";
|
|||||||
import sentryDataSourceNameInjectable from "../vars/sentry-dsn-url.injectable";
|
import sentryDataSourceNameInjectable from "../vars/sentry-dsn-url.injectable";
|
||||||
import { Dedupe, Offline } from "@sentry/integrations";
|
import { Dedupe, Offline } from "@sentry/integrations";
|
||||||
import { inspect } from "util";
|
import { inspect } from "util";
|
||||||
import userStoreInjectable from "../user-store/user-store.injectable";
|
import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable";
|
||||||
|
|
||||||
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const initializeSentryReportingWithInjectable = getInjectable({
|
|||||||
instantiate: (di): InitializeSentryReportingWith => {
|
instantiate: (di): InitializeSentryReportingWith => {
|
||||||
const sentryDataSourceName = di.inject(sentryDataSourceNameInjectable);
|
const sentryDataSourceName = di.inject(sentryDataSourceNameInjectable);
|
||||||
const isProduction = di.inject(isProductionInjectable);
|
const isProduction = di.inject(isProductionInjectable);
|
||||||
const userStore = di.inject(userStoreInjectable);
|
const state = di.inject(userPreferencesStateInjectable);
|
||||||
|
|
||||||
if (!sentryDataSourceName) {
|
if (!sentryDataSourceName) {
|
||||||
return () => {};
|
return () => {};
|
||||||
@ -28,7 +28,7 @@ const initializeSentryReportingWithInjectable = getInjectable({
|
|||||||
|
|
||||||
return (initSentry) => initSentry({
|
return (initSentry) => initSentry({
|
||||||
beforeSend: (event) => {
|
beforeSend: (event) => {
|
||||||
if (userStore.allowErrorReporting) {
|
if (state.allowErrorReporting) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { HttpsProxyAgent } from "hpagent";
|
import { HttpsProxyAgent } from "hpagent";
|
||||||
import userStoreInjectable from "../user-store/user-store.injectable";
|
import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable";
|
||||||
import type { Fetch } from "./fetch.injectable";
|
import type { Fetch } from "./fetch.injectable";
|
||||||
import fetchInjectable from "./fetch.injectable";
|
import fetchInjectable from "./fetch.injectable";
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ const proxyFetchInjectable = getInjectable({
|
|||||||
id: "proxy-fetch",
|
id: "proxy-fetch",
|
||||||
instantiate: (di): Fetch => {
|
instantiate: (di): Fetch => {
|
||||||
const fetch = di.inject(fetchInjectable);
|
const fetch = di.inject(fetchInjectable);
|
||||||
const { httpsProxy, allowUntrustedCAs } = di.inject(userStoreInjectable);
|
const { httpsProxy, allowUntrustedCAs } = di.inject(userPreferencesStateInjectable);
|
||||||
const agent = httpsProxy
|
const agent = httpsProxy
|
||||||
? new HttpsProxyAgent({
|
? new HttpsProxyAgent({
|
||||||
proxy: httpsProxy,
|
proxy: httpsProxy,
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const configMapsRouteInjectable = getInjectable({
|
const configMapsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const horizontalPodAutoscalersRouteInjectable = getInjectable({
|
const horizontalPodAutoscalersRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const leasesRouteInjectable = getInjectable({
|
const leasesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const limitRangesRouteInjectable = getInjectable({
|
const limitRangesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const podDisruptionBudgetsRouteInjectable = getInjectable({
|
const podDisruptionBudgetsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const priorityClassesRouteInjectable = getInjectable({
|
const priorityClassesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const resourceQuotasRouteInjectable = getInjectable({
|
const resourceQuotasRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const runtimeClassesRouteInjectable = getInjectable({
|
const runtimeClassesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const secretsRouteInjectable = getInjectable({
|
const secretsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const verticalPodAutoscalersRouteInjectable = getInjectable({
|
const verticalPodAutoscalersRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const eventsRouteInjectable = getInjectable({
|
const eventsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const namespacesRouteInjectable = getInjectable({
|
const namespacesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const endpointsRouteInjectable = getInjectable({
|
const endpointsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
import {
|
import {
|
||||||
shouldShowResourceInjectionToken,
|
shouldShowResourceInjectionToken,
|
||||||
} from "../../../../../cluster-store/allowed-resources-injection-token";
|
} from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
|
|
||||||
const ingressClassesesRouteInjectable = getInjectable({
|
const ingressClassesesRouteInjectable = getInjectable({
|
||||||
id: "ingress-classes-route",
|
id: "ingress-classes-route",
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { computedOr } from "@k8slens/utilities";
|
import { computedOr } from "@k8slens/utilities";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const networkPoliciesRouteInjectable = getInjectable({
|
const networkPoliciesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const servicesRouteInjectable = getInjectable({
|
const servicesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const nodesRouteInjectable = getInjectable({
|
const nodesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const clusterOverviewRouteInjectable = getInjectable({
|
const clusterOverviewRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const persistentVolumeClaimsRouteInjectable = getInjectable({
|
const persistentVolumeClaimsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const persistentVolumesRouteInjectable = getInjectable({
|
const persistentVolumesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const storageClassesRouteInjectable = getInjectable({
|
const storageClassesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const clusterRoleBindingsRouteInjectable = getInjectable({
|
const clusterRoleBindingsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const clusterRolesRouteInjectable = getInjectable({
|
const clusterRolesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const podSecurityPoliciesRouteInjectable = getInjectable({
|
const podSecurityPoliciesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const roleBindingsRouteInjectable = getInjectable({
|
const roleBindingsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const rolesRouteInjectable = getInjectable({
|
const rolesRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const serviceAccountsRouteInjectable = getInjectable({
|
const serviceAccountsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const cronJobsRouteInjectable = getInjectable({
|
const cronJobsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const daemonsetsRouteInjectable = getInjectable({
|
const daemonsetsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const deploymentsRouteInjectable = getInjectable({
|
const deploymentsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const jobsRouteInjectable = getInjectable({
|
const jobsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const podsRouteInjectable = getInjectable({
|
const podsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const replicasetsRouteInjectable = getInjectable({
|
const replicasetsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const replicationControllersRouteInjectable = getInjectable({
|
const replicationControllersRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
|
import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
const statefulsetsRouteInjectable = getInjectable({
|
const statefulsetsRouteInjectable = getInjectable({
|
||||||
|
|||||||
@ -6,22 +6,14 @@ import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
|
|||||||
import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token";
|
import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token";
|
||||||
import { frontEndRouteInjectionToken } from "./front-end-route-injection-token";
|
import { frontEndRouteInjectionToken } from "./front-end-route-injection-token";
|
||||||
import { filter, map } from "lodash/fp";
|
import { filter, map } from "lodash/fp";
|
||||||
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
|
|
||||||
import type { ClusterStore } from "../cluster-store/cluster-store";
|
|
||||||
import { pipeline } from "@ogre-tools/fp";
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
|
||||||
describe("verify-that-all-routes-have-component", () => {
|
describe("verify-that-all-routes-have-component", () => {
|
||||||
it("verify that routes have route component", () => {
|
it("verify that routes have route component", () => {
|
||||||
const rendererDi = getDiForUnitTesting();
|
const rendererDi = getDiForUnitTesting();
|
||||||
|
|
||||||
rendererDi.override(clusterStoreInjectable, () => ({
|
|
||||||
getById: () => null,
|
|
||||||
} as unknown as ClusterStore));
|
|
||||||
|
|
||||||
const routes = rendererDi.injectMany(frontEndRouteInjectionToken);
|
const routes = rendererDi.injectMany(frontEndRouteInjectionToken);
|
||||||
const routeComponents = rendererDi.injectMany(
|
const routeComponents = rendererDi.injectMany(routeSpecificComponentInjectionToken);
|
||||||
routeSpecificComponentInjectionToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
const routesMissingComponent = pipeline(
|
const routesMissingComponent = pipeline(
|
||||||
routes,
|
routes,
|
||||||
|
|||||||
@ -22,6 +22,7 @@ const fsInjectable = getInjectable({
|
|||||||
access,
|
access,
|
||||||
stat,
|
stat,
|
||||||
unlink,
|
unlink,
|
||||||
|
rename,
|
||||||
},
|
},
|
||||||
ensureDir,
|
ensureDir,
|
||||||
ensureDirSync,
|
ensureDirSync,
|
||||||
@ -58,6 +59,7 @@ const fsInjectable = getInjectable({
|
|||||||
createReadStream,
|
createReadStream,
|
||||||
stat,
|
stat,
|
||||||
unlink,
|
unlink,
|
||||||
|
rename,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import getConfigurationFileModelInjectable from "./get-configuration-file-model.
|
|||||||
import type Config from "conf";
|
import type Config from "conf";
|
||||||
import readJsonSyncInjectable from "../fs/read-json-sync.injectable";
|
import readJsonSyncInjectable from "../fs/read-json-sync.injectable";
|
||||||
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
|
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
|
||||||
import { get, set } from "lodash";
|
import { get, has, set } from "lodash";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
|
|
||||||
const MIGRATION_KEY = `__internal__.migrations.version`;
|
const MIGRATION_KEY = `__internal__.migrations.version`;
|
||||||
@ -64,7 +64,7 @@ export default getGlobalOverride(getConfigurationFileModelInjectable, (di) => {
|
|||||||
path: configFilePath,
|
path: configFilePath,
|
||||||
get: (key: string) => get(store, key),
|
get: (key: string) => get(store, key),
|
||||||
set: (key: string, value: unknown) => {
|
set: (key: string, value: unknown) => {
|
||||||
let currentState: object;
|
let currentState: Partial<Record<string, unknown>>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
currentState = readJsonSync(configFilePath);
|
currentState = readJsonSync(configFilePath);
|
||||||
@ -78,6 +78,25 @@ export default getGlobalOverride(getConfigurationFileModelInjectable, (di) => {
|
|||||||
});
|
});
|
||||||
store = readJsonSync(configFilePath);
|
store = readJsonSync(configFilePath);
|
||||||
},
|
},
|
||||||
|
delete: (key: string) => {
|
||||||
|
let currentState: Partial<Record<string, unknown>>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentState = readJsonSync(configFilePath);
|
||||||
|
} catch {
|
||||||
|
currentState = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
delete currentState[key];
|
||||||
|
|
||||||
|
writeJsonSync(configFilePath, currentState);
|
||||||
|
store = readJsonSync(configFilePath);
|
||||||
|
},
|
||||||
|
has: (key: string) => has(store, key),
|
||||||
|
clear: () => {
|
||||||
|
writeJsonSync(configFilePath, {});
|
||||||
|
store = readJsonSync(configFilePath);
|
||||||
|
},
|
||||||
} as Partial<Config> as Config<any>;
|
} as Partial<Config> as Config<any>;
|
||||||
|
|
||||||
// Migrate
|
// Migrate
|
||||||
|
|||||||
@ -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 } from "@ogre-tools/injectable";
|
|
||||||
import hotbarStoreInjectable from "./store.injectable";
|
|
||||||
import type { CreateHotbarData, CreateHotbarOptions } from "./types";
|
|
||||||
|
|
||||||
export type AddHotbar = (data: CreateHotbarData, opts?: CreateHotbarOptions) => void;
|
|
||||||
|
|
||||||
const addHotbarInjectable = getInjectable({
|
|
||||||
id: "add-hotbar",
|
|
||||||
instantiate: (di): AddHotbar => {
|
|
||||||
const store = di.inject(hotbarStoreInjectable);
|
|
||||||
|
|
||||||
return (data, opts) => store.add(data, opts);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default addHotbarInjectable;
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
|
||||||
import { HotbarStore } from "./store";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
|
||||||
import storeMigrationsInjectable from "../base-store/migrations.injectable";
|
|
||||||
import { hotbarStoreMigrationInjectionToken } from "./migrations-token";
|
|
||||||
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
|
||||||
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
|
||||||
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
|
||||||
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
|
||||||
|
|
||||||
const hotbarStoreInjectable = getInjectable({
|
|
||||||
id: "hotbar-store",
|
|
||||||
|
|
||||||
instantiate: (di) => new HotbarStore({
|
|
||||||
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
|
||||||
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
|
||||||
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
|
||||||
migrations: di.inject(storeMigrationsInjectable, hotbarStoreMigrationInjectionToken),
|
|
||||||
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
|
||||||
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
|
||||||
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
|
||||||
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
|
||||||
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default hotbarStoreInjectable;
|
|
||||||
@ -1,351 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
|
||||||
import type { BaseStoreDependencies } from "../base-store/base-store";
|
|
||||||
import { BaseStore } from "../base-store/base-store";
|
|
||||||
import { toJS } from "../utils";
|
|
||||||
import type { CatalogEntity } from "../catalog";
|
|
||||||
import { broadcastMessage } from "../ipc";
|
|
||||||
import type { Hotbar, CreateHotbarData, CreateHotbarOptions } from "./types";
|
|
||||||
import { defaultHotbarCells, getEmptyHotbar } from "./types";
|
|
||||||
import { hotbarTooManyItemsChannel } from "../ipc/hotbar";
|
|
||||||
import type { GeneralEntity } from "../catalog-entities";
|
|
||||||
import type { Logger } from "../logger";
|
|
||||||
import assert from "assert";
|
|
||||||
|
|
||||||
export interface HotbarStoreModel {
|
|
||||||
hotbars: Hotbar[];
|
|
||||||
activeHotbarId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies extends BaseStoreDependencies {
|
|
||||||
readonly catalogCatalogEntity: GeneralEntity;
|
|
||||||
readonly logger: Logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|
||||||
@observable hotbars: Hotbar[] = [];
|
|
||||||
@observable private _activeHotbarId!: string;
|
|
||||||
|
|
||||||
constructor(protected readonly dependencies: Dependencies) {
|
|
||||||
super(dependencies, {
|
|
||||||
configName: "lens-hotbar-store",
|
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
|
||||||
syncOptions: {
|
|
||||||
equals: comparer.structural,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get activeHotbarId() {
|
|
||||||
return this._activeHotbarId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If `hotbar` points to a known hotbar, make it active. Otherwise, ignore
|
|
||||||
* @param hotbar The hotbar instance, or the index, or its ID
|
|
||||||
*/
|
|
||||||
setActiveHotbar(hotbar: Hotbar | number | string) {
|
|
||||||
if (typeof hotbar === "number") {
|
|
||||||
if (hotbar >= 0 && hotbar < this.hotbars.length) {
|
|
||||||
this._activeHotbarId = this.hotbars[hotbar].id;
|
|
||||||
}
|
|
||||||
} else if (typeof hotbar === "string") {
|
|
||||||
if (this.findById(hotbar)) {
|
|
||||||
this._activeHotbarId = hotbar;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.hotbars.indexOf(hotbar) >= 0) {
|
|
||||||
this._activeHotbarId = hotbar.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private hotbarIndexById(id: string) {
|
|
||||||
return this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private hotbarIndex(hotbar: Hotbar) {
|
|
||||||
return this.hotbars.indexOf(hotbar);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get activeHotbarIndex() {
|
|
||||||
return this.hotbarIndexById(this.activeHotbarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
protected fromStore(data: Partial<HotbarStoreModel> = {}) {
|
|
||||||
if (!data.hotbars || !data.hotbars.length) {
|
|
||||||
const hotbar = getEmptyHotbar("Default");
|
|
||||||
const {
|
|
||||||
metadata: { uid, name, source },
|
|
||||||
} = this.dependencies.catalogCatalogEntity;
|
|
||||||
const initialItem = { entity: { uid, name, source }};
|
|
||||||
|
|
||||||
hotbar.items[0] = initialItem;
|
|
||||||
|
|
||||||
this.hotbars = [hotbar];
|
|
||||||
} else {
|
|
||||||
this.hotbars = data.hotbars;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hotbars.forEach(ensureExactHotbarItemLength);
|
|
||||||
|
|
||||||
if (data.activeHotbarId) {
|
|
||||||
this._activeHotbarId = data.activeHotbarId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._activeHotbarId) {
|
|
||||||
this._activeHotbarId = this.hotbars[0].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): HotbarStoreModel {
|
|
||||||
return toJS({
|
|
||||||
hotbars: this.hotbars,
|
|
||||||
activeHotbarId: this.activeHotbarId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getActive(): Hotbar {
|
|
||||||
const hotbar = this.findById(this.activeHotbarId);
|
|
||||||
|
|
||||||
assert(hotbar, "There MUST always be an active hotbar");
|
|
||||||
|
|
||||||
return hotbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
findByName(name: string) {
|
|
||||||
return this.hotbars.find((hotbar) => hotbar.name === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
findById(id: string) {
|
|
||||||
return this.hotbars.find((hotbar) => hotbar.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
add(data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) {
|
|
||||||
const hotbar = getEmptyHotbar(data.name, data.id);
|
|
||||||
|
|
||||||
this.hotbars.push(hotbar);
|
|
||||||
|
|
||||||
if (setActive) {
|
|
||||||
this._activeHotbarId = hotbar.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
setHotbarName(id: string, name: string): void {
|
|
||||||
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
return this.dependencies.logger.warn(
|
|
||||||
`[HOTBAR-STORE]: cannot setHotbarName: unknown id`,
|
|
||||||
{ id },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hotbars[index].name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
remove(hotbar: Hotbar) {
|
|
||||||
assert(this.hotbars.length >= 2, "Cannot remove the last hotbar");
|
|
||||||
|
|
||||||
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
|
|
||||||
|
|
||||||
if (this.activeHotbarId === hotbar.id) {
|
|
||||||
this.setActiveHotbar(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
addToHotbar(item: CatalogEntity, cellIndex?: number) {
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
const uid = item.getId();
|
|
||||||
const name = item.getName();
|
|
||||||
|
|
||||||
if (typeof uid !== "string") {
|
|
||||||
throw new TypeError("CatalogEntity's ID must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof name !== "string") {
|
|
||||||
throw new TypeError("CatalogEntity's NAME must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isAddedToActive(item)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = {
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
source: item.metadata.source,
|
|
||||||
};
|
|
||||||
const newItem = { entity };
|
|
||||||
|
|
||||||
if (cellIndex === undefined) {
|
|
||||||
// Add item to empty cell
|
|
||||||
const emptyCellIndex = hotbar.items.indexOf(null);
|
|
||||||
|
|
||||||
if (emptyCellIndex != -1) {
|
|
||||||
hotbar.items[emptyCellIndex] = newItem;
|
|
||||||
} else {
|
|
||||||
broadcastMessage(hotbarTooManyItemsChannel);
|
|
||||||
}
|
|
||||||
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
|
|
||||||
hotbar.items[cellIndex] = newItem;
|
|
||||||
} else {
|
|
||||||
this.dependencies.logger.error(
|
|
||||||
`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`,
|
|
||||||
{ entityId: uid, hotbarId: hotbar.id, cellIndex },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
removeFromHotbar(uid: string): void {
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
const index = hotbar.items.findIndex((item) => item?.entity.uid === uid);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotbar.items[index] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all hotbar items that reference the `uid`.
|
|
||||||
* @param uid The `EntityId` that each hotbar item refers to
|
|
||||||
* @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed.
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
removeAllHotbarItems(uid: string) {
|
|
||||||
for (const hotbar of this.hotbars) {
|
|
||||||
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
hotbar.items[index] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findClosestEmptyIndex(from: number, direction = 1) {
|
|
||||||
let index = from;
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
|
|
||||||
while (hotbar.items[index] != null) {
|
|
||||||
index += direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
restackItems(from: number, to: number): void {
|
|
||||||
const { items } = this.getActive();
|
|
||||||
const source = items[from];
|
|
||||||
const moveDown = from < to;
|
|
||||||
|
|
||||||
if (
|
|
||||||
from < 0 ||
|
|
||||||
to < 0 ||
|
|
||||||
from >= items.length ||
|
|
||||||
to >= items.length ||
|
|
||||||
isNaN(from) ||
|
|
||||||
isNaN(to)
|
|
||||||
) {
|
|
||||||
throw new Error("Invalid 'from' or 'to' arguments");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from == to) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
items.splice(from, 1, null);
|
|
||||||
|
|
||||||
if (items[to] == null) {
|
|
||||||
items.splice(to, 1, source);
|
|
||||||
} else {
|
|
||||||
// Move cells up or down to closes empty cell
|
|
||||||
items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
|
|
||||||
items.splice(to, 0, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToPrevious() {
|
|
||||||
let index = this.activeHotbarIndex - 1;
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
index = this.hotbars.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setActiveHotbar(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToNext() {
|
|
||||||
let index = this.activeHotbarIndex + 1;
|
|
||||||
|
|
||||||
if (index >= this.hotbars.length) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setActiveHotbar(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if entity already pinned to the active hotbar
|
|
||||||
*/
|
|
||||||
isAddedToActive(entity: CatalogEntity | null | undefined): boolean {
|
|
||||||
if (!entity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexInActiveHotbar = this.getActive().items.findIndex(item => item?.entity.uid === entity.getId());
|
|
||||||
|
|
||||||
return indexInActiveHotbar >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDisplayLabel(hotbar: Hotbar): string {
|
|
||||||
return `${this.getDisplayIndex(hotbar)}: ${hotbar.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDisplayIndex(hotbar: Hotbar): string {
|
|
||||||
const index = this.hotbarIndex(hotbar);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
return "??";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${index + 1}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function ensures that there are always exactly `defaultHotbarCells`
|
|
||||||
* worth of items in the hotbar.
|
|
||||||
* @param hotbar The hotbar to modify
|
|
||||||
*/
|
|
||||||
function ensureExactHotbarItemLength(hotbar: Hotbar) {
|
|
||||||
// if there are not enough items
|
|
||||||
while (hotbar.items.length < defaultHotbarCells) {
|
|
||||||
hotbar.items.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if for some reason the hotbar was overfilled before, remove as many entries
|
|
||||||
// as needed, but prefer empty slots and items at the end first.
|
|
||||||
while (hotbar.items.length > defaultHotbarCells) {
|
|
||||||
const lastNull = hotbar.items.lastIndexOf(null);
|
|
||||||
|
|
||||||
if (lastNull >= 0) {
|
|
||||||
hotbar.items.splice(lastNull, 1);
|
|
||||||
} else {
|
|
||||||
hotbar.items.length = defaultHotbarCells;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -51,8 +51,6 @@ describe("ApiManager", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
apiManager = di.inject(apiManagerInjectable);
|
apiManager = di.inject(apiManagerInjectable);
|
||||||
|
|||||||
@ -43,8 +43,6 @@ describe("KubeApi", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
apiManager = di.inject(apiManagerInjectable);
|
apiManager = di.inject(apiManagerInjectable);
|
||||||
|
|||||||
@ -62,8 +62,6 @@ describe("createKubeApiForRemoteCluster", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fetchMock = asyncFn();
|
fetchMock = asyncFn();
|
||||||
@ -168,8 +166,6 @@ describe("KubeApi", () => {
|
|||||||
contextName: "some-context-name",
|
contextName: "some-context-name",
|
||||||
id: "some-cluster-id",
|
id: "some-cluster-id",
|
||||||
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://localhost:8080",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
di.override(apiKubeInjectable, () => createKubeJsonApi({
|
di.override(apiKubeInjectable, () => createKubeJsonApi({
|
||||||
|
|||||||
@ -130,6 +130,16 @@ export function loadConfigFromString(content: string): ConfigResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loadValidatedConfig(content: string, contextName: string): ValidateKubeConfigResult {
|
||||||
|
const { options, error } = loadToOptions(content);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateKubeConfig(loadFromOptions(options), contextName);
|
||||||
|
}
|
||||||
|
|
||||||
export interface SplitConfigEntry {
|
export interface SplitConfigEntry {
|
||||||
config: KubeConfig;
|
config: KubeConfig;
|
||||||
validationResult: ValidateKubeConfigResult;
|
validationResult: ValidateKubeConfigResult;
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Cluster } from "../cluster/cluster";
|
||||||
|
import readFileInjectable from "../fs/read-file.injectable";
|
||||||
|
import type { ValidateKubeConfigResult } from "../kube-helpers";
|
||||||
|
import { loadValidatedConfig } from "../kube-helpers";
|
||||||
|
import resolveTildeInjectable from "../path/resolve-tilde.injectable";
|
||||||
|
|
||||||
|
export type LoadValidatedClusterConfig = (cluster: Cluster) => Promise<ValidateKubeConfigResult>;
|
||||||
|
|
||||||
|
const loadValidatedClusterConfigInjectable = getInjectable({
|
||||||
|
id: "load-validated-cluster-config",
|
||||||
|
instantiate: (di): LoadValidatedClusterConfig => {
|
||||||
|
const readFile = di.inject(readFileInjectable);
|
||||||
|
const resolveTilde = di.inject(resolveTildeInjectable);
|
||||||
|
|
||||||
|
return async (cluster) => {
|
||||||
|
const data = await readFile(resolveTilde(cluster.kubeConfigPath.get()));
|
||||||
|
|
||||||
|
return loadValidatedConfig(data, cluster.contextName.get());
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default loadValidatedClusterConfigInjectable;
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import userInfoInjectable from "../user-store/user-info.injectable";
|
import userInfoInjectable from "../vars/user-info.injectable";
|
||||||
|
|
||||||
const homeDirectoryPathInjectable = getInjectable({
|
const homeDirectoryPathInjectable = getInjectable({
|
||||||
id: "home-directory-path",
|
id: "home-directory-path",
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export interface IpcChannelPrefixes {
|
||||||
|
readonly local: string;
|
||||||
|
readonly remote: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const persistentStorageIpcChannelPrefixesInjectionToken = getInjectionToken<IpcChannelPrefixes>({
|
||||||
|
id: "persistent-storage-ipc-channel-prefix-token",
|
||||||
|
});
|
||||||
158
packages/core/src/common/persistent-storage/create.injectable.ts
Normal file
158
packages/core/src/common/persistent-storage/create.injectable.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { disposer, isPromiseLike } from "@k8slens/utilities";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Options } from "conf/dist/source";
|
||||||
|
import { isEqual, kebabCase } from "lodash";
|
||||||
|
import type { IEqualsComparer } from "mobx";
|
||||||
|
import { reaction } from "mobx";
|
||||||
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken, sendMessageToChannelInjectionToken } from "@k8slens/messaging";
|
||||||
|
import type { MessageChannel } from "@k8slens/messaging";
|
||||||
|
import { persistentStorageIpcChannelPrefixesInjectionToken } from "./channel-prefix";
|
||||||
|
import { shouldPersistentStorageDisableSyncInIpcListenerInjectionToken } from "./disable-sync";
|
||||||
|
import { persistStateToConfigInjectionToken } from "./save-to-file";
|
||||||
|
import type { Migrations } from "./migrations.injectable";
|
||||||
|
import { nextTick } from "process";
|
||||||
|
|
||||||
|
export interface PersistentStorage {
|
||||||
|
/**
|
||||||
|
* This method does the initial synchronous load from disk and then starts writing the state
|
||||||
|
* back to disk whenever it changes.
|
||||||
|
*/
|
||||||
|
loadAndStartSyncing: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersistentStorageParams<T extends object> extends Omit<Options<T>, "migrations"> {
|
||||||
|
readonly syncOptions?: {
|
||||||
|
readonly fireImmediately?: boolean;
|
||||||
|
equals?: IEqualsComparer<T>;
|
||||||
|
};
|
||||||
|
readonly configName: string;
|
||||||
|
readonly migrations?: Migrations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fromStore is called internally when a child class syncs with the file
|
||||||
|
* system.
|
||||||
|
*
|
||||||
|
* Note: This function **must** be synchronous.
|
||||||
|
*
|
||||||
|
* @param data the parsed information read from the stored JSON file
|
||||||
|
*/
|
||||||
|
fromStore(data: Partial<T>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toJSON is called when syncing the store to the filesystem. It should
|
||||||
|
* produce a JSON serializable object representation of the current state.
|
||||||
|
*
|
||||||
|
* It is recommended that a round trip is valid. Namely, calling
|
||||||
|
* `this.fromStore(this.toJSON())` shouldn't change the state.
|
||||||
|
*/
|
||||||
|
toJSON(): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreatePersistentStorage = <T extends object>(params: PersistentStorageParams<T>) => PersistentStorage;
|
||||||
|
|
||||||
|
const createPersistentStorageInjectable = getInjectable({
|
||||||
|
id: "create-persistent-storage",
|
||||||
|
instantiate: (di): CreatePersistentStorage => {
|
||||||
|
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||||
|
const getConfigurationFileModel = di.inject(getConfigurationFileModelInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||||
|
const ipcChannelPrefixes = di.inject(persistentStorageIpcChannelPrefixesInjectionToken);
|
||||||
|
const persistStateToConfig = di.inject(persistStateToConfigInjectionToken);
|
||||||
|
const enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken);
|
||||||
|
const shouldDisableSyncInListener = di.inject(shouldPersistentStorageDisableSyncInIpcListenerInjectionToken);
|
||||||
|
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
|
|
||||||
|
return <T extends object>(rawParams: PersistentStorageParams<T>) => {
|
||||||
|
const {
|
||||||
|
fromStore,
|
||||||
|
toJSON,
|
||||||
|
syncOptions,
|
||||||
|
migrations,
|
||||||
|
cwd = directoryForUserData,
|
||||||
|
...params
|
||||||
|
} = rawParams;
|
||||||
|
const displayName = kebabCase(params.configName).toUpperCase();
|
||||||
|
|
||||||
|
const loadAndStartSyncing = () => {
|
||||||
|
logger.info(`[${displayName}]: LOADING ...`);
|
||||||
|
|
||||||
|
const config = getConfigurationFileModel({
|
||||||
|
projectName: "lens",
|
||||||
|
cwd,
|
||||||
|
migrations: migrations as Options<T>["migrations"],
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = fromStore(config.store);
|
||||||
|
|
||||||
|
if (isPromiseLike(res)) {
|
||||||
|
logger.error(`${displayName} extends BaseStore<T>'s fromStore method returns a Promise or promise-like object. This is an error and must be fixed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`[${displayName}]: LOADED from ${config.path}`);
|
||||||
|
|
||||||
|
const syncDisposers = disposer();
|
||||||
|
const sendChannel: MessageChannel<T> = {
|
||||||
|
id: `${ipcChannelPrefixes.remote}:${config.path}`,
|
||||||
|
};
|
||||||
|
const receiveChannel: MessageChannel<T> = {
|
||||||
|
id: `${ipcChannelPrefixes.local}:${config.path}`,
|
||||||
|
};
|
||||||
|
const name = getBasenameOfPath(config.path);
|
||||||
|
|
||||||
|
const disableSync = () => syncDisposers();
|
||||||
|
const enableSync = () => {
|
||||||
|
syncDisposers.push(
|
||||||
|
reaction(
|
||||||
|
() => toJSON(),
|
||||||
|
model => {
|
||||||
|
persistStateToConfig(config, model);
|
||||||
|
sendMessageToChannel(sendChannel, model);
|
||||||
|
},
|
||||||
|
syncOptions,
|
||||||
|
),
|
||||||
|
enlistMessageChannelListener({
|
||||||
|
id: "persistent-storage-sync",
|
||||||
|
channel: receiveChannel,
|
||||||
|
handler: (model) => {
|
||||||
|
logger.silly(`[${displayName}]: syncing ${name}`, { model });
|
||||||
|
|
||||||
|
if (shouldDisableSyncInListener) {
|
||||||
|
disableSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: use "resourceVersion" if merge required (to avoid equality checks => better performance)
|
||||||
|
if (!isEqual(toJSON(), model)) {
|
||||||
|
fromStore(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDisableSyncInListener) {
|
||||||
|
nextTick(() => {
|
||||||
|
enableSync();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
enableSync();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
loadAndStartSyncing,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createPersistentStorageInjectable;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user