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

Merge branch 'master' into file-menu-reorder

This commit is contained in:
Lauri Nevala 2020-11-18 09:52:01 +02:00
commit 660d4016bf
26 changed files with 304 additions and 35924 deletions

View File

@ -36,11 +36,11 @@ jobs:
yarn yarn
path: $(YARN_CACHE_FOLDER) path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages displayName: Cache Yarn packages
- script: make install-deps - script: make node_modules
displayName: Install dependencies displayName: Install dependencies
- script: make build-npm - script: make build-npm
displayName: Generate npm package displayName: Generate npm package
- script: make build-extensions - script: make -j2 build-extensions
displayName: Build bundled extensions displayName: Build bundled extensions
- script: make integration-win - script: make integration-win
displayName: Run integration tests displayName: Run integration tests
@ -76,11 +76,11 @@ jobs:
yarn yarn
path: $(YARN_CACHE_FOLDER) path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages displayName: Cache Yarn packages
- script: make install-deps - script: make node_modules
displayName: Install dependencies displayName: Install dependencies
- script: make build-npm - script: make build-npm
displayName: Generate npm package displayName: Generate npm package
- script: make build-extensions - script: make -j2 build-extensions
displayName: Build bundled extensions displayName: Build bundled extensions
- script: make test - script: make test
displayName: Run tests displayName: Run tests
@ -122,13 +122,13 @@ jobs:
yarn yarn
path: $(YARN_CACHE_FOLDER) path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages displayName: Cache Yarn packages
- script: make install-deps - script: make node_modules
displayName: Install dependencies displayName: Install dependencies
- script: make lint - script: make lint
displayName: Lint displayName: Lint
- script: make build-npm - script: make build-npm
displayName: Generate npm package displayName: Generate npm package
- script: make build-extensions - script: make -j2 build-extensions
displayName: Build bundled extensions displayName: Build bundled extensions
- script: make test - script: make test
displayName: Run tests displayName: Run tests
@ -164,4 +164,4 @@ jobs:
displayName: Publish npm package displayName: Publish npm package
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
env: env:
NPM_TOKEN: $(NPM_TOKEN) NPM_TOKEN: $(NPM_TOKEN)

View File

@ -3,7 +3,9 @@ on:
push: push:
branches: branches:
- master - master
release:
types:
- published
jobs: jobs:
build: build:
name: Deploy docs name: Deploy docs
@ -27,13 +29,12 @@ jobs:
- name: Checkout Release from lens - name: Checkout Release from lens
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: lensapp/lens fetch-depth: 0
- name: git config - name: git config
run: | run: |
git config --local user.email "action@github.com" git config --local user.email "action@github.com"
git config --local user.name "GitHub Action" git config --local user.name "GitHub Action"
git pull
- name: Using Node.js ${{ matrix.node-version }} - name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v1
@ -45,17 +46,19 @@ jobs:
yarn install yarn install
yarn typedocs-extensions-api yarn typedocs-extensions-api
- name: mkdocs deploy latest - name: mkdocs deploy master
if: contains(github.ref, 'refs/heads/master')
run: | run: |
mike deploy --push latest mike deploy --push master
- name: Get the release version
- name: mkdocs deploy new release / tag if: contains(github.ref, 'refs/tags/v') # && !github.event.release.prerelease (generate pre-release docs until Lens 4.0.0 is GA, see #1408)
if: contains(github.ref, 'refs/tags/v') id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: mkdocs deploy new release
if: contains(github.ref, 'refs/tags/v') # && !github.event.release.prerelease (generate pre-release docs until Lens 4.0.0 is GA, see #1408)
run: | run: |
mike deploy --push--update-aliases ${{ github.ref }} latest mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest
mike set-default --push ${{ github.ref }} mike set-default --push ${{ steps.get_version.outputs.VERSION }}

View File

@ -0,0 +1,38 @@
name: Delete Documentation Version
on:
workflow_dispatch:
inputs:
version:
description: 'Version string to be deleted (e.g."v0.0.1")'
required: true
jobs:
build:
name: Delete docs Version
runs-on: ubuntu-latest
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: git config
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: mkdocs delete version
run: |
mike delete --push ${{ github.event.inputs.version }}

View File

@ -0,0 +1,38 @@
name: Update Default Documentation Version
on:
workflow_dispatch:
inputs:
version:
description: 'Version string to be default (e.g."v0.0.1")'
required: true
jobs:
build:
name: Update default docs Version
runs-on: ubuntu-latest
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: git config
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: mkdocs update default version
run: |
mike set-default --push ${{ github.event.inputs.version }}

View File

@ -1,4 +1,7 @@
EXTENSIONS_DIR = ./extensions EXTENSIONS_DIR = ./extensions
extensions = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir})
extension_node_modules = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir}/node_modules)
extension_dists = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir}/dist)
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
DETECTED_OS := Windows DETECTED_OS := Windows
@ -6,29 +9,23 @@ else
DETECTED_OS := $(shell uname) DETECTED_OS := $(shell uname)
endif endif
.PHONY: init binaries/client:
init: install-deps download-bins compile-dev
echo "Init done"
.PHONY: download-bins
download-bins:
yarn download-bins yarn download-bins
.PHONY: install-deps node_modules:
install-deps:
yarn install --frozen-lockfile --verbose yarn install --frozen-lockfile --verbose
yarn check --verify-tree --integrity yarn check --verify-tree --integrity
static/build/LensDev.html:
yarn compile:renderer
.PHONY: compile-dev .PHONY: compile-dev
compile-dev: compile-dev:
yarn compile:main --cache yarn compile:main --cache
yarn compile:renderer --cache yarn compile:renderer --cache
.PHONY: dev .PHONY: dev
dev: dev: node_modules binaries/client build-extensions static/build/LensDev.html
ifeq ("$(wildcard static/build/main.js)","")
make init
endif
yarn dev yarn dev
.PHONY: lint .PHONY: lint
@ -36,7 +33,7 @@ lint:
yarn lint yarn lint
.PHONY: test .PHONY: test
test: download-bins test: binaries/client
yarn test yarn test
.PHONY: integration-linux .PHONY: integration-linux
@ -59,20 +56,25 @@ test-app:
yarn test yarn test
.PHONY: build .PHONY: build
build: install-deps download-bins build-extensions build: node_modules binaries/client build-extensions
ifeq "$(DETECTED_OS)" "Windows" ifeq "$(DETECTED_OS)" "Windows"
yarn dist:win yarn dist:win
else else
yarn dist yarn dist
endif endif
$(extension_node_modules):
cd $(@:/node_modules=) && npm install --no-audit --no-fund
$(extension_dists): src/extensions/npm/extensions/dist
cd $(@:/dist=) && npm run build
.PHONY: build-extensions .PHONY: build-extensions
build-extensions: build-extensions: $(extension_node_modules) $(extension_dists)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), (cd $(dir) && npm install && npm run build || exit $?);)
.PHONY: test-extensions .PHONY: test-extensions
test-extensions: test-extensions: $(extension_node_modules)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), (cd $(dir) && npm install --dev && npm run test || exit $?);) $(foreach dir, $(extensions), (cd $(dir) && npm run test || exit $?);)
.PHONY: copy-extension-themes .PHONY: copy-extension-themes
copy-extension-themes: copy-extension-themes:
@ -97,6 +99,16 @@ publish-npm: build-npm
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
cd src/extensions/npm/extensions && npm publish --access=public cd src/extensions/npm/extensions && npm publish --access=public
.PHONY: clean-extensions
clean-extensions:
ifeq "$(DETECTED_OS)" "Windows"
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), if exist $(dir)\dist del /s /q $(dir)\dist)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), if exist $(dir)\node_modules del /s /q $(dir)\node_modules)
else
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules)
endif
.PHONY: clean-npm .PHONY: clean-npm
clean-npm: clean-npm:
ifeq "$(DETECTED_OS)" "Windows" ifeq "$(DETECTED_OS)" "Windows"
@ -110,13 +122,13 @@ else
endif endif
.PHONY: clean .PHONY: clean
clean: clean-npm clean: clean-npm clean-extensions
ifeq "$(DETECTED_OS)" "Windows" ifeq "$(DETECTED_OS)" "Windows"
if exist binaries\client del /s /q binaries\client\*.* if exist binaries\client del /s /q binaries\client
if exist dist del /s /q dist\*.* if exist dist del /s /q dist\*.*
if exist static\build del /s /q static\build\*.* if exist static\build del /s /q static\build\*.*
else else
rm -rf binaries/client/* rm -rf binaries/client
rm -rf dist/* rm -rf dist/*
rm -rf static/build/* rm -rf static/build/*
endif endif

View File

@ -36,7 +36,6 @@ brew cask install lens
> Prerequisites: Nodejs v12, make, yarn > Prerequisites: Nodejs v12, make, yarn
* `make init` - initial compilation, installing deps, etc.
* `make dev` - builds and starts the app * `make dev` - builds and starts the app
* `make test` - run tests * `make test` - run tests

View File

@ -0,0 +1,28 @@
# Extension Guides
The basics of the Lens Extension API are covered in [Your First Extension](../get-started/your-first-extension.md). In this section detailed code guides and samples are used to explain how to use specific Lens Extension APIs.
Each guide or sample will include:
- Clearly commented source code.
- Instructions for running the sample extension.
- Image of the sample extension's appearance and usage.
- Listing of Extension API being used.
- Explanation of Extension API concepts.
## Guides
| Guide | APIs |
| ----- | ----- |
| [Main process extension](main-extension.md) | LensMainExtension |
| [Renderer process extension](renderer-extension.md) | LensRendererExtension |
| [Stores](stores.md) | |
| [Components](components.md) | |
| [KubeObjectListLayout](kube-object-list-layout.md) | |
## Samples
| Sample | APIs |
| ----- | ----- |
[helloworld](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension <br> Store.clusterStore <br> Store.workspaceStore |

View File

@ -0,0 +1,76 @@
# Main Extension
The main extension api is the interface to Lens' main process (Lens runs in main and renderer processes). It allows you to access, configure, and customize Lens data, add custom application menu items, and generally run custom code in Lens' main process.
## `LensMainExtension` Class
To create a main extension simply extend the `LensMainExtension` class:
``` typescript
import { LensMainExtension } from "@k8slens/extensions";
export default class ExampleExtensionMain extends LensMainExtension {
onActivate() {
console.log('custom main process extension code started');
}
onDeactivate() {
console.log('custom main process extension de-activated');
}
}
```
There are two methods that you can implement to facilitate running your custom code. `onActivate()` is called when your extension has been successfully enabled. By overriding `onActivate()` you can initiate your custom code. `onDeactivate()` is called when the extension is disabled (typically from the [Lens Extensions Page]()) and when implemented gives you a chance to clean up after your extension, if necessary. The example above simply logs messages when the extension is enabled and disabled. Note that to see standard output from the main process there must be a console connected to it. This is typically achieved by starting Lens from the command prompt.
The following example is a little more interesting in that it accesses some Lens state data and periodically logs the name of the currently active cluster in Lens.
``` typescript
import { LensMainExtension, Store } from "@k8slens/extensions";
const clusterStore = Store.clusterStore
export default class ActiveClusterExtensionMain extends LensMainExtension {
timer: NodeJS.Timeout
onActivate() {
console.log("Cluster logger activated");
this.timer = setInterval(() => {
if (!clusterStore.active) {
console.log("No active cluster");
return;
}
console.log("active cluster is", clusterStore.active.contextName)
}, 5000)
}
onDeactivate() {
clearInterval(this.timer)
console.log("Cluster logger deactivated");
}
}
```
See the [Stores](../stores) guide for more details on accessing Lens state data.
### `appMenus`
The only UI feature customizable in the main extension api is the application menu. Custom menu items can be inserted and linked to custom functionality, such as navigating to a specific page. The following example demonstrates adding a menu item to the Help menu.
``` typescript
import { LensMainExtension } from "@k8slens/extensions";
export default class SamplePageMainExtension extends LensMainExtension {
appMenus = [
{
parentId: "help",
label: "Sample",
click() {
console.log("Sample clicked");
}
}
]
}
```
`appMenus` is an array of objects satisfying the `MenuRegistration` interface. `MenuRegistration` extends React's `MenuItemConstructorOptions` interface. `parentId` is the id of the menu to put this menu item under (todo: is this case sensitive and how do we know what the available ids are?), `label` is the text to show on the menu item, and `click()` is called when the menu item is selected. In this example we simply log a message, but typically you would navigate to a specific page or perform some operation. Pages are associated with the [`LensRendererExtension`](renderer-extension.md) class and can be defined when you extend it.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ nav:
- Color Reference: extensions/capabilities/color-reference.md - Color Reference: extensions/capabilities/color-reference.md
- Extension Guides: - Extension Guides:
- Overview: extensions/guides/README.md - Overview: extensions/guides/README.md
- Main Extension: extensions/guides/main-extension.md
- Renderer Extension: extensions/guides/renderer-extension.md - Renderer Extension: extensions/guides/renderer-extension.md
- Testing and Publishing: - Testing and Publishing:
- Testing Extensions: extensions/testing-and-publishing/testing.md - Testing Extensions: extensions/testing-and-publishing/testing.md

View File

@ -38,15 +38,18 @@ describe("workspace store tests", () => {
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove"); expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
}) })
it("can update default workspace name", () => { it("can update workspace description", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>(); const ws = WorkspaceStore.getInstance<WorkspaceStore>();
ws.addWorkspace(new Workspace({ const workspace = ws.addWorkspace(new Workspace({
id: WorkspaceStore.defaultId, id: "foobar",
name: "foobar", name: "foobar",
})); }));
expect(ws.currentWorkspace.name).toBe("foobar"); workspace.description = "Foobar description";
ws.updateWorkspace(workspace);
expect(ws.getById("foobar").description).toBe("Foobar description");
}) })
it("can add workspaces", () => { it("can add workspaces", () => {

View File

@ -153,20 +153,20 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
@action @action
addWorkspace(workspace: Workspace) { addWorkspace(workspace: Workspace) {
const { id, name } = workspace; const { id, name } = workspace;
const existingWorkspace = this.getById(id);
if (!name.trim() || this.getByName(name.trim())) { if (!name.trim() || this.getByName(name.trim())) {
return; return;
} }
if (existingWorkspace) {
Object.assign(existingWorkspace, workspace);
appEventBus.emit({name: "workspace", action: "update"})
} else {
appEventBus.emit({name: "workspace", action: "add"})
}
this.workspaces.set(id, workspace); this.workspaces.set(id, workspace);
appEventBus.emit({name: "workspace", action: "add"})
return workspace; return workspace;
} }
@action
updateWorkspace(workspace: Workspace) {
this.workspaces.set(workspace.id, workspace);
appEventBus.emit({name: "workspace", action: "update"});
}
@action @action
removeWorkspace(workspace: Workspace) { removeWorkspace(workspace: Workspace) {
this.removeWorkspaceById(workspace.id) this.removeWorkspaceById(workspace.id)

View File

@ -86,6 +86,10 @@ export class Cluster implements ClusterModel, ClusterState {
return this.accessible && !this.disconnected; return this.accessible && !this.disconnected;
} }
@computed get name() {
return this.preferences.clusterName || this.contextName
}
get version(): string { get version(): string {
return String(this.metadata?.version) || "" return String(this.metadata?.version) || ""
} }

View File

@ -9,8 +9,8 @@ export function exitApp() {
const windowManager = WindowManager.getInstance<WindowManager>() const windowManager = WindowManager.getInstance<WindowManager>()
const clusterManager = ClusterManager.getInstance<ClusterManager>() const clusterManager = ClusterManager.getInstance<ClusterManager>()
appEventBus.emit({ name: "service", action: "close" }) appEventBus.emit({ name: "service", action: "close" })
windowManager?.hide(); windowManager.hide();
clusterManager?.stop(); clusterManager.stop();
logger.info('SERVICE:QUIT'); logger.info('SERVICE:QUIT');
setTimeout(() => { setTimeout(() => {
app.exit() app.exit()

View File

@ -89,7 +89,7 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
label: workspace.name, label: workspace.name,
toolTip: workspace.description, toolTip: workspace.description,
submenu: clusters.map(cluster => { submenu: clusters.map(cluster => {
const { id: clusterId, preferences: { clusterName: label }, online, workspace } = cluster; const { id: clusterId, name: label, online, workspace } = cluster;
return { return {
label: `${online ? '✓' : '\x20'.repeat(3)/*offset*/}${label}`, label: `${online ? '✓' : '\x20'.repeat(3)/*offset*/}${label}`,
toolTip: clusterId, toolTip: clusterId,

View File

@ -20,7 +20,7 @@ export class Workspaces extends React.Component {
@computed get workspaces(): Workspace[] { @computed get workspaces(): Workspace[] {
const currentWorkspaces: Map<WorkspaceId, Workspace> = new Map() const currentWorkspaces: Map<WorkspaceId, Workspace> = new Map()
workspaceStore.enabledWorkspacesList.forEach((w) => { workspaceStore.workspacesList.forEach((w) => {
currentWorkspaces.set(w.id, w) currentWorkspaces.set(w.id, w)
}) })
const allWorkspaces = new Map([ const allWorkspaces = new Map([
@ -45,9 +45,13 @@ export class Workspaces extends React.Component {
} }
saveWorkspace = (id: WorkspaceId) => { saveWorkspace = (id: WorkspaceId) => {
const draft = toJS(this.editingWorkspaces.get(id)); const workspace = new Workspace(this.editingWorkspaces.get(id));
const workspace = workspaceStore.addWorkspace(draft); if (workspaceStore.getById(id)) {
if (workspace) { workspaceStore.updateWorkspace(workspace);
this.clearEditing(id);
return;
}
if (workspaceStore.addWorkspace(workspace)) {
this.clearEditing(id); this.clearEditing(id);
} }
} }
@ -127,7 +131,7 @@ export class Workspaces extends React.Component {
validate: value => !workspaceStore.getByName(value.trim()) validate: value => !workspaceStore.getByName(value.trim())
} }
return ( return (
<div key={workspaceId} className={className}> <div key={workspaceId} className={cssNames(className)}>
{!isEditing && ( {!isEditing && (
<Fragment> <Fragment>
<span className="name flex gaps align-center"> <span className="name flex gaps align-center">

View File

@ -34,8 +34,8 @@ export class ClusterIcon extends React.Component<Props> {
cluster, showErrors, showTooltip, errorClass, options, interactive, isActive, cluster, showErrors, showTooltip, errorClass, options, interactive, isActive,
children, ...elemProps children, ...elemProps
} = this.props; } = this.props;
const { isAdmin, eventCount, preferences, id: clusterId } = cluster; const { isAdmin, name, eventCount, preferences, id: clusterId } = cluster;
const { clusterName, icon } = preferences; const { icon } = preferences;
const clusterIconId = `cluster-icon-${clusterId}`; const clusterIconId = `cluster-icon-${clusterId}`;
const className = cssNames("ClusterIcon flex inline", this.props.className, { const className = cssNames("ClusterIcon flex inline", this.props.className, {
interactive: interactive !== undefined ? interactive : !!this.props.onClick, interactive: interactive !== undefined ? interactive : !!this.props.onClick,
@ -44,9 +44,9 @@ export class ClusterIcon extends React.Component<Props> {
return ( return (
<div {...elemProps} className={className} id={showTooltip ? clusterIconId : null}> <div {...elemProps} className={className} id={showTooltip ? clusterIconId : null}>
{showTooltip && ( {showTooltip && (
<Tooltip targetId={clusterIconId}>{clusterName}</Tooltip> <Tooltip targetId={clusterIconId}>{name}</Tooltip>
)} )}
{icon && <img src={icon} alt={clusterName}/>} {icon && <img src={icon} alt={name}/>}
{!icon && <Hashicon value={clusterId} options={options}/>} {!icon && <Hashicon value={clusterId} options={options}/>}
{showErrors && isAdmin && eventCount > 0 && ( {showErrors && isAdmin && eventCount > 0 && (
<Badge <Badge

View File

@ -1,4 +1,7 @@
.PodLogs { .PodLogs {
--overlay-bg: #8cc474b8;
--overlay-active-bg: orange;
.logs { .logs {
@include custom-scrollbar; @include custom-scrollbar;
@ -11,14 +14,6 @@
background: $logsBackground; background: $logsBackground;
flex-grow: 1; flex-grow: 1;
.find-overlay {
position: absolute;
border-radius: 2px;
background-color: #8cc474;
margin-top: 4px;
opacity: 0.5;
}
.VirtualList { .VirtualList {
height: 100%; height: 100%;
@ -29,19 +24,30 @@
font-family: $font-monospace; font-family: $font-monospace;
font-size: smaller; font-size: smaller;
white-space: pre; white-space: pre;
-webkit-font-smoothing: auto; // Better readability on non-retina screens
&:hover { &:hover {
background: $logRowHoverBackground; background: $logRowHoverBackground;
} }
span { span {
-webkit-font-smoothing: auto; // Better readability on non-retina screens
}
span.overlay {
border-radius: 2px; border-radius: 2px;
background-color: #8cc474b8;
-webkit-font-smoothing: auto; -webkit-font-smoothing: auto;
background-color: var(--overlay-bg);
span {
background-color: var(--overlay-bg)!important; // Rewriting inline styles from AnsiUp library
}
&.active { &.active {
background-color: orange; background-color: var(--overlay-active-bg);
span {
background-color: var(--overlay-active-bg)!important; // Rewriting inline styles from AnsiUp library
}
} }
} }
} }
@ -49,25 +55,6 @@
} }
} }
.new-logs-sep {
position: relative;
display: block;
height: 0;
border-top: 1px solid $primary;
margin: $margin * 2 0;
&:after {
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
content: 'new';
background: $primary;
color: white;
padding: $padding / 3;
border-radius: $radius;
}
}
.jump-to-bottom { .jump-to-bottom {
position: absolute; position: absolute;
right: 30px; right: 30px;

View File

@ -1,5 +1,7 @@
import "./pod-logs.scss"; import "./pod-logs.scss";
import React from "react"; import React from "react";
import AnsiUp from 'ansi_up';
import DOMPurify from "dompurify"
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { action, computed, observable, reaction } from "mobx"; import { action, computed, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
@ -33,6 +35,7 @@ export class PodLogs extends React.Component<Props> {
private logsElement = React.createRef<HTMLDivElement>(); // A reference for outer container in VirtualList private logsElement = React.createRef<HTMLDivElement>(); // A reference for outer container in VirtualList
private virtualListRef = React.createRef<VirtualList>(); // A reference for VirtualList component private virtualListRef = React.createRef<VirtualList>(); // A reference for VirtualList component
private lastLineIsShown = true; // used for proper auto-scroll content after refresh private lastLineIsShown = true; // used for proper auto-scroll content after refresh
private colorConverter = new AnsiUp();
componentDidMount() { componentDidMount() {
disposeOnUnmount(this, [ disposeOnUnmount(this, [
@ -185,6 +188,7 @@ export class PodLogs extends React.Component<Props> {
const { searchQuery, isActiveOverlay } = searchStore; const { searchQuery, isActiveOverlay } = searchStore;
const item = this.logs[rowIndex]; const item = this.logs[rowIndex];
const contents: React.ReactElement[] = []; const contents: React.ReactElement[] = [];
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(this.colorConverter.ansi_to_html(ansi));
if (searchQuery) { // If search is enabled, replace keyword with backgrounded <span> if (searchQuery) { // If search is enabled, replace keyword with backgrounded <span>
// Case-insensitive search (lowercasing query and keywords in line) // Case-insensitive search (lowercasing query and keywords in line)
const regex = new RegExp(searchStore.escapeRegex(searchQuery), "gi"); const regex = new RegExp(searchStore.escapeRegex(searchQuery), "gi");
@ -195,19 +199,26 @@ export class PodLogs extends React.Component<Props> {
pieces.forEach((piece, index) => { pieces.forEach((piece, index) => {
const active = isActiveOverlay(rowIndex, index); const active = isActiveOverlay(rowIndex, index);
const lastItem = index === pieces.length - 1; const lastItem = index === pieces.length - 1;
const overlayValue = matches.next().value;
const overlay = !lastItem ? const overlay = !lastItem ?
<span className={cssNames({ active })}>{matches.next().value}</span> : <span
className={cssNames("overlay", { active })}
dangerouslySetInnerHTML={{ __html: ansiToHtml(overlayValue) }}
/> :
null null
contents.push( contents.push(
<React.Fragment key={piece + index}> <React.Fragment key={piece + index}>
{piece}{overlay} <span dangerouslySetInnerHTML={{ __html: ansiToHtml(piece) }} />
{overlay}
</React.Fragment> </React.Fragment>
); );
}) })
} }
return ( return (
<div className={cssNames("LogRow")}> <div className={cssNames("LogRow")}>
{contents.length > 1 ? contents : item} {contents.length > 1 ? contents : (
<span dangerouslySetInnerHTML={{ __html: ansiToHtml(item) }} />
)}
</div> </div>
); );
} }

View File

@ -65,7 +65,7 @@ export class MainLayout extends React.Component<MainLayoutProps> {
return ( return (
<div className={cssNames("MainLayout", className)} style={this.getSidebarSize() as any}> <div className={cssNames("MainLayout", className)} style={this.getSidebarSize() as any}>
<header className={cssNames("flex gaps align-center", headerClass)}> <header className={cssNames("flex gaps align-center", headerClass)}>
<span className="cluster">{cluster.preferences.clusterName || cluster.contextName}</span> <span className="cluster">{cluster.name}</span>
</header> </header>
<aside className={cssNames("flex column", { pinned: this.isPinned, accessible: this.isAccessible })}> <aside className={cssNames("flex column", { pinned: this.isPinned, accessible: this.isAccessible })}>