├── .yarnrc.yml
├── setup.cfg
├── .npmrc
├── style
├── index.js
├── tensorboard.svg
├── base.css
└── tab.scss
├── .husky
└── pre-commit
├── src
├── svg.d.ts
├── consts.ts
├── _typing.d.ts
├── commands.ts
├── utils
│ └── copy.ts
├── biz
│ ├── loading.tsx
│ ├── widget.tsx
│ └── tab.tsx
├── index.ts
├── manager.ts
└── tensorboard.ts
├── .eslintignore
├── .prettierignore
├── images
├── tensorboard.step1.png
├── tensorboard.step2.png
├── tensorboard.step3.png
├── tensorboard.step4.png
├── tensorboard.step5.png
└── tensorboard.step6.png
├── .prettierrc
├── tsconfig.eslint.json
├── pyproject.toml
├── jupyter-config
├── jupyter_lab_server_config.d
│ └── jupyterlab_tensorboard_pro.json
└── jupyter_notebook_server_config.d
│ └── jupyterlab_tensorboard_pro.json
├── install.json
├── .gitignore
├── jupyterlab_tensorboard_pro
├── __init__.py
├── api_handlers.py
├── handlers.py
└── tensorboard_manager.py
├── MANIFEST.in
├── tsconfig.json
├── webpack.config.js
├── LICENSE
├── .eslintrc.js
├── .github
└── workflows
│ └── build.yaml
├── setup.py
├── README.zh-cn.md
├── package.json
└── README.md
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 1
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmmirror.com
--------------------------------------------------------------------------------
/style/index.js:
--------------------------------------------------------------------------------
1 | import './base.css';
2 | import './tab.scss';
3 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/src/svg.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const value: string;
3 | export default value;
4 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | **/*.d.ts
5 | tests
6 |
7 | jupyterlab_tensorboard_pro
--------------------------------------------------------------------------------
/src/consts.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_REFRESH_INTERVAL = 120;
2 | export const DEFAULT_ENABLE_MULTI_LOG = false;
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | **/node_modules
3 | **/lib
4 | **/package.json
5 | jupyterlab_tensorboard_pro
6 |
--------------------------------------------------------------------------------
/images/tensorboard.step1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HFAiLab/jupyterlab_tensorboard_pro/HEAD/images/tensorboard.step1.png
--------------------------------------------------------------------------------
/images/tensorboard.step2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HFAiLab/jupyterlab_tensorboard_pro/HEAD/images/tensorboard.step2.png
--------------------------------------------------------------------------------
/images/tensorboard.step3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HFAiLab/jupyterlab_tensorboard_pro/HEAD/images/tensorboard.step3.png
--------------------------------------------------------------------------------
/images/tensorboard.step4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HFAiLab/jupyterlab_tensorboard_pro/HEAD/images/tensorboard.step4.png
--------------------------------------------------------------------------------
/images/tensorboard.step5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HFAiLab/jupyterlab_tensorboard_pro/HEAD/images/tensorboard.step5.png
--------------------------------------------------------------------------------
/images/tensorboard.step6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HFAiLab/jupyterlab_tensorboard_pro/HEAD/images/tensorboard.step6.png
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "none",
4 | "arrowParens": "avoid",
5 | "printWidth": 100
6 | }
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["src/**/*.ts", "src/**/*.tsx", ".eslintrc.js", "*.js", "style/*.js"]
4 | }
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["jupyter_packaging~=0.12.3", "jupyterlab>=4.0", "setuptools>=40.8.0", "wheel"]
3 | build-backend = "setuptools.build_meta"
4 |
--------------------------------------------------------------------------------
/jupyter-config/jupyter_lab_server_config.d/jupyterlab_tensorboard_pro.json:
--------------------------------------------------------------------------------
1 | {
2 | "ServerApp": {
3 | "jpserver_extensions": {
4 | "jupyterlab_tensorboard_pro": true
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/jupyter-config/jupyter_notebook_server_config.d/jupyterlab_tensorboard_pro.json:
--------------------------------------------------------------------------------
1 | {
2 | "NotebookApp": {
3 | "nbserver_extensions": {
4 | "jupyterlab_tensorboard_pro": true
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/install.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageManager": "python",
3 | "packageName": "jupyterlab_tensorboard_pro",
4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyterlab_tensorboard_pro"
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/*
2 | *.bundle.*
3 | lib/
4 | node_modules/
5 | *.egg-info/
6 | .ipynb_checkpoints
7 | *.tsbuildinfo
8 | .idea
9 | dist
10 | build
11 | debug.log
12 | .yarn
13 |
14 | jupyterlab_tensorboard_pro/labextension
15 | jupyterlab_tensorboard_pro/__pycache__
--------------------------------------------------------------------------------
/src/_typing.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.json' {
2 | const value: any;
3 | export default value;
4 | }
5 |
6 | declare module '*.png' {
7 | const value: any;
8 | export default value;
9 | }
10 |
11 | declare module '*jpg' {
12 | const value: any;
13 | export default value;
14 | }
15 |
--------------------------------------------------------------------------------
/src/commands.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The command IDs used by the tensorboard plugin.
3 | */
4 | export namespace CommandIDs {
5 | export const createNew = 'tensorboard:create-new';
6 |
7 | export const inputDirect = 'tensorboard:choose-direct';
8 |
9 | export const open = 'tensorboard:open';
10 |
11 | export const openDoc = 'tensorboard:openDoc';
12 |
13 | export const close = 'tensorboard:close';
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/copy.ts:
--------------------------------------------------------------------------------
1 | export function copyToClipboard(text: string): boolean {
2 | const input = document.createElement('textarea');
3 | input.value = text;
4 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5 | // @ts-ignore
6 | document.body.appendChild(input);
7 | input.select();
8 | const result = document.execCommand('copy');
9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
10 | // @ts-ignore
11 | document.body.removeChild(input);
12 | return result;
13 | }
14 |
--------------------------------------------------------------------------------
/jupyterlab_tensorboard_pro/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from .handlers import load_jupyter_server_extension # noqa
4 |
5 | __version__ = "4.0.0"
6 |
7 |
8 | def _jupyter_nbextension_paths():
9 | name = __name__
10 | section = "tree"
11 | src = "static"
12 | return [dict(
13 | section=section,
14 | src=src,
15 | dest=name,
16 | require="%s/%s" % (name, section))]
17 |
18 |
19 | def _jupyter_server_extension_paths():
20 | return [{
21 | "module": __name__
22 | }]
23 |
--------------------------------------------------------------------------------
/src/biz/loading.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export interface LoadingProps {
4 | title?: string;
5 | desc?: string;
6 | }
7 |
8 | export const Loading = (props: LoadingProps): JSX.Element => {
9 | return (
10 |
11 |
17 | {props.title &&
{props.title}
}
18 | {props.desc &&
{props.desc}
}
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.md
3 | include pyproject.toml
4 | include jupyter-config/jupyterlab_tensorboard_pro.json
5 |
6 | include package.json
7 | include install.json
8 | include ts*.json
9 | include yarn.lock
10 |
11 | graft jupyterlab_tensorboard_pro/labextension
12 |
13 | # Javascript files
14 | graft src
15 | graft style
16 | prune **/node_modules
17 | prune lib
18 |
19 | # Patterns to exclude from any directory
20 | global-exclude *~
21 | global-exclude *.pyc
22 | global-exclude *.pyo
23 | global-exclude .git
24 | global-exclude .ipynb_checkpoints
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "composite": true,
5 | "declaration": true,
6 | "esModuleInterop": true,
7 | "incremental": true,
8 | "jsx": "react",
9 | "module": "esnext",
10 | "moduleResolution": "node",
11 | "noEmitOnError": true,
12 | "noImplicitAny": true,
13 | "noUnusedLocals": false,
14 | "preserveWatchOutput": true,
15 | "resolveJsonModule": true,
16 | "outDir": "lib",
17 | "rootDir": "src",
18 | "strict": true,
19 | "strictNullChecks": true,
20 | "target": "es2018",
21 | "types": []
22 | },
23 | "include": ["src/**/*.ts", "src/**/*.tsx"],
24 | "exclude": ["node_modules/**/*.d.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const path = require('path');
3 |
4 | console.info('use custom webpack config...');
5 |
6 | module.exports = {
7 | resolve: {
8 | extensions: ['.tsx', '.ts', '.js', '.svg'],
9 | alias: {
10 | '@': path.resolve(__dirname, 'src')
11 | }
12 | },
13 | optimization: {
14 | usedExports: true
15 | },
16 | plugins: [],
17 | module: {
18 | rules: [
19 | {
20 | test: /\.tsx?$/,
21 | use: 'ts-loader',
22 | exclude: /node_modules/
23 | },
24 | {
25 | test: /\.s[ac]ss$/i,
26 | use: [
27 | // 将 JS 字符串生成为 style 节点
28 | 'style-loader',
29 | // 将 CSS 转化成 CommonJS 模块
30 | 'css-loader',
31 | // 将 Sass 编译成 CSS
32 | {
33 | loader: 'sass-loader'
34 | }
35 | ]
36 | }
37 | ]
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 HFAiLab
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | node: true
4 | },
5 | extends: [
6 | 'eslint:recommended',
7 | 'plugin:@typescript-eslint/eslint-recommended',
8 | 'plugin:@typescript-eslint/recommended',
9 | 'plugin:prettier/recommended'
10 | ],
11 | parser: '@typescript-eslint/parser',
12 | parserOptions: {
13 | project: ['tsconfig.eslint.json'],
14 | sourceType: 'module'
15 | },
16 | plugins: ['@typescript-eslint'],
17 | rules: {
18 | '@typescript-eslint/naming-convention': [
19 | 'error',
20 | {
21 | selector: 'interface',
22 | format: ['PascalCase']
23 | }
24 | ],
25 | '@typescript-eslint/no-non-null-assertion': 'off',
26 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
27 | '@typescript-eslint/no-explicit-any': 'off',
28 | '@typescript-eslint/no-namespace': 'off',
29 | '@typescript-eslint/no-use-before-define': 'off',
30 | '@typescript-eslint/quotes': [
31 | 'error',
32 | 'single',
33 | { avoidEscape: true, allowTemplateLiterals: false }
34 | ],
35 | curly: ['error', 'all'],
36 | eqeqeq: 'error',
37 | 'prefer-arrow-callback': 'error'
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: ['dev']
6 | pull_request:
7 | branches: ['dev']
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 | - name: Install node
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: '16.x'
19 | - name: Install Python
20 | uses: actions/setup-python@v2
21 | with:
22 | python-version: '3.8'
23 | architecture: 'x64'
24 |
25 | - name: Setup pip cache
26 | uses: actions/cache@v2
27 | with:
28 | path: ~/.cache/pip
29 | key: pip-3.8-${{ hashFiles('package.json') }}
30 | restore-keys: |
31 | pip-3.8-
32 | pip-
33 |
34 | - name: Get yarn cache directory path
35 | id: yarn-cache-dir-path
36 | run: echo "::set-output name=dir::$(yarn cache dir)"
37 | - name: Setup yarn cache
38 | uses: actions/cache@v2
39 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
40 | with:
41 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
42 | key: yarn-${{ hashFiles('**/yarn.lock') }}
43 | restore-keys: |
44 | yarn-
45 |
46 | - name: Install dependencies
47 | run: python -m pip install -U jupyterlab~=3.0 jupyter_packaging~=0.7.9
48 | - name: Build the extension
49 | run: |
50 | jlpm
51 | python -m pip install .
52 |
53 | jupyter labextension list 2>&1 | grep -ie "jupyterlab_tensorboard_pro.*OK"
54 |
--------------------------------------------------------------------------------
/style/tensorboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
15 |
17 |
18 |
20 | image/svg+xml
21 |
23 |
24 |
25 |
26 |
27 |
30 |
32 |
37 |
42 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/style/base.css:
--------------------------------------------------------------------------------
1 | /*-----------------------------------------------------------------------------
2 | | Variables
3 | |----------------------------------------------------------------------------*/
4 |
5 | @import "normalize.css";
6 | @import "@blueprintjs/core/lib/css/blueprint.css";
7 | @import "@blueprintjs/icons/lib/css/blueprint-icons.css";
8 |
9 | :root {
10 | --jp-Tensorboard-itemIcon: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAADECAMAAAD3eH5ZAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAD/UExURfFlKfaELvFmKfNyK/67NvWALf68Nv69NvNxK/20NfyyNP22NfN0K/JrKvqhMv2zNf25Nf24Nf23NfeOL/yzNPyvNPJoKviWMPmeMfN1K/WBLfePL/FnKfeML/qlMvR7LPmcMfeLL/aJLvR5LPFoKfJuKvR3LP66NvywNPeNL/V/LfaILv21Nf26NfNzK/NvK/R6LPmaMfyxNPqfMvV+LfurM/iSMPmbMfJvKvmdMfumM/qiMvmZMfytNPJqKvysNPN2K/iYMPNwK/upM/JtKvJsKviVMPaHLvaGLvJpKvR8LPaKLvqkMvuqM/aFLvR4LPuoM/iTMPWDLfiRMPmYMXS0ngkAAALoSURBVHja7drnctpAFIbhFUISSKJ3MKYa0+y4xTW9937/15JkJhlTjhrSrHRmvuf/as6L0YLFCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBJ6njenqspzgnPrsrGX9Zpi2tCrmnc6+dYNthVY5WpMmxQLWPdMsOuYVwzNj3ei2t3mQwaV43BJPDCS2NbJ5aEeuX/+9qcjQOtfFIkIkrvY2g4MVcmOBsFWbowKO/kNyj62gRpJcDaPBlxLr1B0zdG0C/8LzbJiJrshuvy1gzlA9+rD8mIkuyIJjFE3/dqnYwoSm7IUEPoD/wut8iIguSIDjlFxe/yfXL5vuSI21BTZLLhXoOILMO8Hxwa/L8bI0LfmUdhGowb2ZvT0e57pFNDgB06IlVyjmmIBl2T/nl9Rw6SD9GgSG/Q0uQkaW3XhmovKQ3eFQ4N2Uo9OQ1eFZsNerf7vP+rO4rhmY1Lg3vFVoP8+8BXg1sFnwbnCk4NThW8GuiKBDdkVVtTNFvNelVsNqTbyWnIOM2oeTRoyWvwmpJHg/ucXBrcJuXT4DwrpwZi2vy0VCx8YtXg/D2bU4OfiuQ3eFfE2KD4bfCqiLNB993gXsGlwa2CT4NzBacGIVQ6YsipQdh0xEdODUKjIxrSp88onZ8zbbFLg1DoiFO5BXvDGv2My9/JhUT8JUZTI0yDaNHLBzIbvqTDNYhUiVw/kdjQ1kM2CHFDPjKW+KzyRTF0g/ga9w9y+fANQpxvX8CU+Ny7FUWDeF3Y+g3lROIf4k0UDX9eCyvO531PyYhHga9zvPZJU5b73Y/eXj8Hv9D48n6HaF5LbcjRt8TZTtda5M1DfXnbkX1C0SHCFKzQB5Fe8op4GNGNHavvZESbVwT5r6W1xyuCPBY3Y9YgDqzknH/e3YfNzzuL30l0IebrZ5kKtuDIXt1n868ET6kf3/49tLvrCcZyF8Pu215dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcPIbNrBhOaBXucoAAAAASUVORK5CYII=");
11 | --jp-Tensorboard-icon: url('./tensorboard.svg');
12 | }
13 |
14 | .jp-Tensorboard {
15 | min-width: 240px;
16 | min-height: 120px;
17 | padding: 8px;
18 | margin: 0;
19 | }
20 |
21 | .jp-Tensorboard-icon {
22 | background-image: var(--jp-Tensorboard-icon);
23 | }
24 |
25 | .jp-Tensorboards-itemIcon {
26 | flex: 0 0 auto;
27 | margin-right: 4px;
28 | vertical-align: baseline;
29 | background-size: 16px;
30 | background-repeat: no-repeat;
31 | background-position: center;
32 | background-image: var(--jp-Tensorboard-itemIcon);
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | jupyterlab_tensorboard_pro setup
3 | """
4 | import os
5 | import json
6 | from pathlib import Path
7 | from setuptools.command.install import install
8 | from setuptools.command.develop import develop
9 |
10 | from jupyter_packaging import (
11 | create_cmdclass,
12 | install_npm,
13 | ensure_targets,
14 | combine_commands,
15 | skip_if_exists
16 | )
17 | import setuptools
18 |
19 | HERE = Path(__file__).parent.resolve()
20 |
21 | # The name of the project
22 | name = "jupyterlab_tensorboard_pro"
23 |
24 | lab_path = (HERE / name / "labextension")
25 |
26 | # Representative files that should exist after a successful build
27 | jstargets = [
28 | str(lab_path / "package.json"),
29 | ]
30 |
31 | package_data_spec = {
32 | name: ["*"],
33 | }
34 |
35 | labext_name = "jupyterlab_tensorboard_pro"
36 |
37 | data_files_spec = [
38 | ("share/jupyter/labextensions/%s" % labext_name, str(lab_path), "**"),
39 | ("share/jupyter/labextensions/%s" % labext_name, str(HERE), "install.json"),
40 | ]
41 |
42 | cmdclass = create_cmdclass("jsdeps",
43 | package_data_spec=package_data_spec,
44 | data_files_spec=data_files_spec
45 | )
46 |
47 | js_command = combine_commands(
48 | install_npm(HERE, build_cmd="build:prod", npm=["jlpm"]),
49 | ensure_targets(jstargets),
50 | )
51 |
52 | is_repo = (HERE / ".git").exists()
53 | if is_repo:
54 | cmdclass["jsdeps"] = js_command
55 | else:
56 | cmdclass["jsdeps"] = skip_if_exists(jstargets, js_command)
57 |
58 | long_description = (HERE / "README.md").read_text()
59 |
60 | # Get the package info from package.json
61 | pkg_json = json.loads((HERE / "package.json").read_bytes())
62 |
63 | setup_args = dict(
64 | name=name,
65 | version=pkg_json["version"],
66 | url=pkg_json["homepage"],
67 | author=pkg_json["author"]["name"],
68 | author_email=pkg_json["author"]["email"],
69 | description=pkg_json["description"],
70 | license=pkg_json["license"],
71 | long_description=long_description,
72 | long_description_content_type="text/markdown",
73 | cmdclass=cmdclass,
74 | data_files=[
75 | (
76 | "etc/jupyter/jupyter_server_config.d",
77 | ["jupyter-config/jupyter_lab_server_config.d/jupyterlab_tensorboard_pro.json"]
78 | ),
79 | (
80 | "etc/jupyter/jupyter_notebook_config.d",
81 | ["jupyter-config/jupyter_notebook_server_config.d/jupyterlab_tensorboard_pro.json"]
82 | ),
83 | ],
84 | packages=setuptools.find_packages(),
85 | install_requires=[
86 | "jupyterlab>=4.0",
87 | "tornado<=6.2",
88 | ],
89 | zip_safe=False,
90 | include_package_data=True,
91 | python_requires=">=3.6",
92 | platforms="Linux, Mac OS X, Windows",
93 | keywords=["Jupyter", "JupyterLab", "JupyterLab3", "Tensorboard", "Tensorflow" ],
94 | entry_points={
95 | 'console_scripts': [
96 | 'jupyterlab-tensorboard-pro = jupyterlab_tensorboard_pro.application:main',
97 | ],
98 | },
99 | classifiers=[
100 | 'Intended Audience :: Developers',
101 | 'Intended Audience :: Science/Research',
102 | "License :: OSI Approved :: BSD License",
103 | "Programming Language :: Python",
104 | "Programming Language :: Python :: 3",
105 | "Programming Language :: Python :: 3.6",
106 | "Programming Language :: Python :: 3.7",
107 | "Programming Language :: Python :: 3.8",
108 | "Programming Language :: Python :: 3.9",
109 | "Programming Language :: Python :: 3.10",
110 | "Framework :: Jupyter",
111 | ],
112 | )
113 |
114 |
115 | if __name__ == "__main__":
116 | setuptools.setup(**setup_args)
117 |
--------------------------------------------------------------------------------
/README.zh-cn.md:
--------------------------------------------------------------------------------
1 | # JupyterLab-TensorBoard-Pro
2 |
3 |  [](https://pypi.org/project/jupyterlab-tensorboard-pro/)
4 |
5 | 一个更加完善的 TensorBoard JupyterLab 插件
6 |
7 | 
8 |
9 | ## 依赖
10 |
11 | **python >= 3.6**
12 |
13 | 请在安装本项目之前安装以下依赖:
14 |
15 | - jupyterlab
16 | - tensorflow
17 | - tensorboard
18 |
19 | ## 安装
20 |
21 | ```
22 | pip install jupyterlab-tensorboard-pro
23 | ```
24 |
25 | > 这是一个 jupyterlab 插件,目前已经不在支持 jupyter notebook
26 |
27 | ## 开发背景
28 |
29 | 实际上,目前社区里面已经有了[jupyterlab_tensorboard](https://github.com/chaoleili/jupyterlab_tensorboard)(前端插件)和 [jupyter_tensorboard](https://github.com/lspvic/jupyter_tensorboard)(对应的后端插件),不过两个仓库都已经很久没有更新,对于一些新的修复 PR 也没有及时合入,基于此判断项目作者已经不在积极地维护对应仓库。
30 |
31 | 同时,现有社区的 TensorBoard 插件存在一定的体验问题,比如需要同时安装两个 python 包,以及点击之后无任何响应,无法设置 TensorBoard Reload 时间等问题,交互体验不够友好,也会影响用户的 JupyterLab 使用体验。
32 |
33 | 因此本项目 fork 了社区现有项目,对逻辑进行更改,并且参考了之前一些比较有帮助但是暂时没有合入的 PR,希望能够在接下来较长的一段时间持续维护。
34 |
35 | 这个项目对接口名也进行了更改,因此可以和上述插件保持完全的独立。
36 |
37 | 特别感谢之前相关仓库的开发者们。
38 |
39 | ## 使用说明
40 |
41 | ### 创建实例
42 |
43 | #### 从 launcher 面板创建
44 |
45 | 我们可以从 Launcher 面板点击 TensorBoard 图标,首次点击会进入到一个默认的初始化面板,我们可以从该面板创建 TensorBoard 实例。非首次进入则会直接进入到第一个活跃的 TensorBoard 实例。
46 |
47 | 
48 |
49 | #### 通过快捷命令创建
50 |
51 | 我们也可以在 JupyterLab 快捷指令面板(`ctrl + shift + c` 唤起)中输入 `Open TensorBoard`。
52 |
53 | 
54 |
55 | #### 创建参数
56 |
57 | 在初始化面板中,提供了两个参数设置项目:
58 |
59 | - **Log Dir**:默认是点击 TensorBoard 时当前侧边栏的目录,也可以手动填写对应目录,这里建议目录尽可能的细化,目录内容比较少的话会提高初始化速度。
60 | - **Reload Interval**:TensorBoard 多久对对应目录进行一次重新扫描,这个选项是默认是关闭的,日常使用选择手动 Reload 即可(设置 Reload Interval 之后,TensorBoard 后端持续扫描目录会对 Jupyter 的稳定性和文件系统都产生一定的影响)。
61 |
62 | 选择好参数点击 Create TensorBoard,会同步创建 TensorBoard 实例,这个时候 jupyter 后端是**阻塞**的,请等待实例创建好之后再进行其他操作。
63 |
64 | 
65 |
66 | ### 管理实例
67 |
68 | 创建实例后,我们可以对 TensorBoard 的实例进行管理,目前依次提供了以下几个功能:
69 |
70 | - **刷新和列表切换**:可以切换成其他的实例的 TensorBoard 后端,这个时候不会销毁实例。
71 | - **独立页面中打开**:可以在以独立网页 Tab 的形式打开 TensorBoard。
72 | - **Reload**:即重新初始化 TensorBoard 后端,当文件内容有更新时,可以通过此功能载入新的内容(注:TensorBoard 内部的刷新,不会造成 Reload)。
73 | - **Destroy**:销毁实例,会连同前端面板一起关掉。
74 | - **Duplicate**:重新打开一个完全一样的前端面板,此操作会复用 TensorBoard 后端。
75 | - **New**:额外新建一个 TensorBoard 后端,注意事项可以参考上文。
76 |
77 | 另外,对于我们创建的 TensorBoard 实例,可以在 Jupyter 的 Kernel 管理面板一同管理,提供跳转至对应实例和删除等功能。
78 |
79 | 
80 |
81 | ### 使用 AWS S3
82 |
83 | > 这里假设你已经对 aws s3 有了一定的使用经验
84 |
85 | TensorBoard 支持通过 `s3://path/to/dir` 的方式传入一个 s3 的路径,这个方式在本插件内也同样支持。
86 |
87 | 不过,因为 s3 的路径通常并不是直接可以访问的,需要先通过 `aws configure` 配置一些基本信息([下载](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) aws cli),通常情况下,JupyterLab 运行所在的系统应该有以下文件:
88 |
89 | ```shell
90 | # ~/.aws/config
91 | [default]
92 | region = ap-southeast-1
93 | output = json
94 |
95 | # ~/.aws/credentials
96 | [default]
97 | aws_access_key_id = ********
98 | aws_secret_access_key = ********
99 | ```
100 |
101 | 然后你需要额外安装一些依赖:
102 |
103 | ```
104 | pip install botocore boto3 tensorflow-io
105 | ```
106 |
107 | 之后你可以输入一个 s3 路径,然后点击 tensorboard 的刷新按钮,等待加载完成后即可展示:
108 |
109 | 
110 |
111 | > 实际上,现在 tensorboard 本身在这里的状态提示并不友好,后续我们会进一步调研有没有更好的体验的方式
112 |
113 | ## 调试
114 |
115 | 你可以通过 `jupyter-lab --debug` 开启 JupyterLab 和 TensorBoard 的调试日志。
116 |
117 | ## 本地开发
118 |
119 | ```shell
120 | jlpm install
121 | pip install jupyter_packaging
122 | jlpm run install:client
123 | jlpm run install:server
124 | # after above maybe you need create use a soft link to hot update
125 | ```
126 |
127 | 前端部分开发:
128 |
129 | ```
130 | jlpm run watch
131 | ```
132 |
133 | 后端部分可以在设置软链接之后,直接修改 python 文件,重启生效。
134 |
135 | 打包:
136 |
137 | ```
138 | python setup.py bdist_wheel --universal
139 | ```
140 |
141 | 一般情况下提交 MR 即可,本项目的开发者可以打包发布到 pypi。
142 |
--------------------------------------------------------------------------------
/jupyterlab_tensorboard_pro/api_handlers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 | import os
5 | import logging
6 |
7 | from tornado import web
8 | from jupyter_server.base.handlers import APIHandler
9 |
10 | from .handlers import notebook_dir
11 |
12 |
13 | def _trim_notebook_dir(dir, enable_multi_log):
14 | if dir.startswith("s3://"):
15 | return dir
16 | if enable_multi_log:
17 | return dir
18 | if ':' not in dir and not dir.startswith("/"):
19 | return os.path.join(
20 | "", os.path.relpath(dir, notebook_dir)
21 | )
22 | return dir
23 |
24 |
25 | class TbRootConfigHandler(APIHandler):
26 |
27 | @web.authenticated
28 | def get(self):
29 | terms = {
30 | 'notebook_dir': notebook_dir,
31 | }
32 | self.finish(json.dumps(terms))
33 |
34 |
35 | class TbRootHandler(APIHandler):
36 |
37 | @web.authenticated
38 | def get(self):
39 | terms = [
40 | {
41 | 'name': entry.name,
42 | 'reload_interval': entry.reload_interval,
43 | 'enable_multi_log': entry.enable_multi_log,
44 | 'logdir': _trim_notebook_dir(entry.logdir, entry.enable_multi_log),
45 | 'additional_args': entry.additional_args,
46 | } for entry in
47 | self.settings["tensorboard_manager"].values()
48 | ]
49 | self.finish(json.dumps(terms))
50 |
51 | @web.authenticated
52 | def post(self):
53 | try:
54 | data = self.get_json_body()
55 | reload_interval = data.get("reload_interval", None)
56 | enable_multi_log = data.get("enable_multi_log", False)
57 | additional_args = data.get("additional_args", '')
58 | entry = (
59 | self.settings["tensorboard_manager"]
60 | .new_instance(data["logdir"], reload_interval=reload_interval, enable_multi_log=enable_multi_log, additional_args=additional_args)
61 | )
62 | self.finish(json.dumps({
63 | 'name': entry.name,
64 | 'reload_interval': entry.reload_interval,
65 | 'enable_multi_log': entry.enable_multi_log,
66 | 'additional_args': entry.additional_args,
67 | 'logdir': _trim_notebook_dir(entry.logdir, entry.enable_multi_log),
68 | }))
69 | except SystemExit:
70 | logging.error("[Tensorboard Error] mostly parse args error")
71 | raise web.HTTPError(
72 | 500, "Tensorboard Error: mostly parse args error")
73 | except Exception as e:
74 | logging.error("[Tensorboard Error] catch exception: {e}")
75 | print('[Tensorboard Error]', e)
76 |
77 |
78 | class TbInstanceHandler(APIHandler):
79 |
80 | SUPPORTED_METHODS = ('GET', 'DELETE')
81 |
82 | @web.authenticated
83 | def get(self, name):
84 | manager = self.settings["tensorboard_manager"]
85 | if name in manager:
86 | entry = manager[name]
87 | self.finish(json.dumps({
88 | 'name': entry.name,
89 | 'reload_interval': entry.reload_interval,
90 | 'enable_multi_log': entry.enable_multi_log,
91 | 'logdir': _trim_notebook_dir(entry.logdir, entry.enable_multi_log),
92 | }))
93 | else:
94 | raise web.HTTPError(
95 | 404, "TensorBoard instance not found: %r" % name)
96 |
97 | @web.authenticated
98 | def delete(self, name):
99 | manager = self.settings["tensorboard_manager"]
100 | if name in manager:
101 | manager.terminate(name, force=True)
102 | self.set_status(204)
103 | self.finish()
104 | else:
105 | raise web.HTTPError(
106 | 404, "TensorBoard instance not found: %r" % name)
107 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jupyterlab_tensorboard_pro",
3 | "version": "4.0.0",
4 | "description": "A JupyterLab extension for tensorboard.",
5 | "keywords": [
6 | "jupyter",
7 | "jupyterlab",
8 | "jupyterlab-extension",
9 | "tensorboard"
10 | ],
11 | "homepage": "https://github.com/HFAiLab/jupyterlab_tensorboard_pro",
12 | "bugs": {
13 | "url": "https://github.com/HFAiLab/jupyterlab_tensorboard_pro/issues"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/HFAiLab/jupyterlab_tensorboard_pro"
18 | },
19 | "license": "MIT",
20 | "author": {
21 | "name": "aircloud",
22 | "email": "onlythen@yeah.net"
23 | },
24 | "sideEffects": [
25 | "style/*.css",
26 | "style/*.scss",
27 | "style/index.js"
28 | ],
29 | "main": "lib/index.js",
30 | "types": "lib/index.d.ts",
31 | "style": "style/index.css",
32 | "files": [
33 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
34 | "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
35 | "style/index.js"
36 | ],
37 | "scripts": {
38 | "build": "jlpm run build:lib && jlpm run build:labextension:dev",
39 | "build:prod": "jlpm run build:lib && jlpm run build:labextension",
40 | "build:labextension": "jupyter labextension build .",
41 | "build:labextension:dev": "jupyter labextension build --development True .",
42 | "build:lib": "tsc",
43 | "clean": "jlpm run clean:lib",
44 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo",
45 | "clean:labextension": "rimraf jupyterlab_tensorboard_pro/labextension",
46 | "clean:all": "jlpm run clean:lib && jlpm run clean:labextension",
47 | "eslint": "eslint . --ext .ts,.tsx --fix",
48 | "eslint:check": "eslint . --ext .ts,.tsx",
49 | "install:client": "jupyter labextension develop --overwrite .",
50 | "install:server": "jupyter server extension enable jupyterlab_tensorboard_pro",
51 | "prepare": "husky install && jlpm run clean && jlpm run build:prod",
52 | "watch": "jlpm run build:lib && run-p watch:src watch:labextension",
53 | "watch:src": "tsc -w",
54 | "watch:labextension": "jupyter labextension watch ."
55 | },
56 | "lint-staged": {
57 | "*.{js,ts,tsx}": "eslint --fix",
58 | "package.json": "sort-package-json"
59 | },
60 | "dependencies": {
61 | "@blueprintjs/core": "^5.3.2",
62 | "@blueprintjs/select": "^5.0.12",
63 | "@jupyterlab/application": "^4.0.6",
64 | "@jupyterlab/apputils": "^4.1.6",
65 | "@jupyterlab/coreutils": "^6.0.6",
66 | "@jupyterlab/filebrowser": "^4.0.6",
67 | "@jupyterlab/launcher": "^4.0.6",
68 | "@jupyterlab/mainmenu": "^4.0.6",
69 | "@jupyterlab/running": "^4.0.6",
70 | "@jupyterlab/services": "^7.0.6",
71 | "@jupyterlab/ui-components": "^4.0.6",
72 | "classnames": "^2.3.1",
73 | "svg-url-loader": "~6.0.0"
74 | },
75 | "devDependencies": {
76 | "@jupyterlab/builder": "^4.0.6",
77 | "@typescript-eslint/eslint-plugin": "^6.7.3",
78 | "@typescript-eslint/parser": "^6.7.3",
79 | "css-loader": "^5.0.1",
80 | "eslint": "^7.14.0",
81 | "eslint-config-prettier": "^6.15.0",
82 | "eslint-plugin-prettier": "^3.1.4",
83 | "husky": "^8.0.1",
84 | "lint-staged": "^13.0.3",
85 | "npm-run-all": "^4.1.5",
86 | "prettier": "^2.1.1",
87 | "raw-loader": "4.0.0",
88 | "rimraf": "^3.0.2",
89 | "sass": "^1.43.2",
90 | "sass-loader": "^12.2.0",
91 | "sort-package-json": "^1.57.0",
92 | "style-loader": "~2.0.0",
93 | "ts-loader": "^9.2.6",
94 | "typescript": "^5.2.2",
95 | "webpack": "*"
96 | },
97 | "jupyterlab": {
98 | "discovery": {
99 | "server": {
100 | "managers": [
101 | "pip"
102 | ],
103 | "base": {
104 | "name": "jupyterlab_tensorboard_pro"
105 | }
106 | }
107 | },
108 | "extension": true,
109 | "outputDir": "jupyterlab_tensorboard_pro/labextension",
110 | "webpackConfig": "./webpack.config.js"
111 | },
112 | "styleModule": "style/index.js"
113 | }
114 |
--------------------------------------------------------------------------------
/src/biz/widget.tsx:
--------------------------------------------------------------------------------
1 | import { JupyterFrontEnd } from '@jupyterlab/application';
2 | import { ReactWidget } from '@jupyterlab/apputils';
3 | import { FileBrowser } from '@jupyterlab/filebrowser';
4 | import React from 'react';
5 | import { Tensorboard } from '../tensorboard';
6 | import { Message } from '@lumino/messaging';
7 | import { TensorboardManager } from '../manager';
8 | import { CommandIDs } from '../commands';
9 |
10 | const TENSORBOARD_CLASS = 'jp-Tensorboard';
11 | const TENSORBOARD_ICON_CLASS = 'jp-Tensorboards-itemIcon';
12 |
13 | import { TensorboardTabReact } from './tab';
14 |
15 | export interface TensorboardInvokeOptions {
16 | fileBrowser: FileBrowser;
17 | tensorboardManager: TensorboardManager;
18 | createdModelName?: string;
19 | app: JupyterFrontEnd;
20 | }
21 |
22 | /**
23 | * A Counter Lumino Widget that wraps a CounterComponent.
24 | */
25 | export class TensorboardTabReactWidget extends ReactWidget {
26 | fileBrowser: FileBrowser;
27 | tensorboardManager: TensorboardManager;
28 | app: JupyterFrontEnd;
29 |
30 | currentTensorBoardModel: Tensorboard.IModel | null = null;
31 | createdModelName?: string;
32 | currentLogDir?: string;
33 |
34 | /**
35 | * Constructs a new CounterWidget.
36 | */
37 | constructor(options: TensorboardInvokeOptions) {
38 | super();
39 | this.fileBrowser = options.fileBrowser;
40 | this.tensorboardManager = options.tensorboardManager;
41 | this.createdModelName = options.createdModelName;
42 | this.app = options.app;
43 | if (!this.createdModelName) {
44 | // hint: if createdModelName exists,update later
45 | this.currentLogDir = this.getCWD();
46 | }
47 |
48 | this.addClass('tensorboard-ng-widget');
49 | this.addClass(TENSORBOARD_CLASS);
50 | this.title.iconClass = TENSORBOARD_ICON_CLASS;
51 | this.title.closable = true;
52 | this.title.label = 'Tensorboard';
53 | this.title.caption = `Name: ${this.title.label}`;
54 | }
55 |
56 | /**
57 | * Dispose of the resources held by the tensorboard widget.
58 | */
59 | dispose(): void {
60 | super.dispose();
61 | }
62 |
63 | closeCurrent = (): void => {
64 | this.dispose();
65 | this.close();
66 | };
67 |
68 | protected updateCurrentModel = (model: Tensorboard.IModel | null): void => {
69 | this.currentTensorBoardModel = model;
70 | this.currentLogDir = model?.logdir || '';
71 | };
72 |
73 | getCWD = (): string => {
74 | return this.fileBrowser.model.path;
75 | };
76 |
77 | protected onCloseRequest(msg: Message): void {
78 | super.onCloseRequest(msg);
79 | this.dispose();
80 | }
81 |
82 | protected openTensorBoard = (modelName: string, copy: boolean): void => {
83 | this.app.commands.execute(CommandIDs.open, {
84 | modelName,
85 | copy
86 | });
87 | };
88 |
89 | protected openDoc = (): void => {
90 | this.app.commands.execute(CommandIDs.openDoc);
91 | };
92 |
93 | startNew = (
94 | logdir: string,
95 | refreshInterval: number,
96 | enableMultiLog: boolean,
97 | additionalArgs: string,
98 | options?: Tensorboard.IOptions
99 | ): Promise => {
100 | this.currentLogDir = logdir;
101 | return this.tensorboardManager.startNew(
102 | logdir,
103 | refreshInterval,
104 | enableMultiLog,
105 | additionalArgs,
106 | options
107 | );
108 | };
109 |
110 | setWidgetName = (name: string): void => {
111 | this.title.label = name || 'Tensorboard';
112 | this.title.caption = `Name: ${this.title.label}`;
113 | };
114 |
115 | render(): JSX.Element {
116 | return (
117 |
129 | );
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/style/tab.scss:
--------------------------------------------------------------------------------
1 | .tensorboard-ng-creator {
2 | display: flex;
3 | width: 100%;
4 | flex-direction: row;
5 | align-items: center;
6 |
7 | .additional-config-input {
8 | width: 120px;
9 | margin-left: 5px;
10 | &.with-content {
11 | flex: 3;
12 | }
13 | }
14 | }
15 |
16 | .tensorboard-ng-control-layout {
17 | width: 100%;
18 | height: 70px;
19 | background-color: var(--jp-cell-editor-background);
20 | display: flex;
21 | flex-direction: column;
22 | overflow: hidden;
23 |
24 | * {
25 | box-sizing: border-box;
26 | }
27 |
28 | .tensorboard-ng-control-row {
29 | padding: 5px;
30 | width: 100%;
31 | height: 35px;
32 | flex-direction: row;
33 | display: flex;
34 | overflow: hidden;
35 | align-items: center;
36 | min-width: 760px;
37 | &.hide {
38 | height: 0px;
39 | padding-top: 0;
40 | padding-bottom: 0;
41 | }
42 | &.creator {
43 | background-color: var(--jp-border-color2);
44 | }
45 | }
46 |
47 | &.hide-one {
48 | height: 40px;
49 | }
50 | }
51 |
52 | .tensorboard-ng-config {
53 | display: flex;
54 | flex-direction: row;
55 | padding-left: 5px;
56 | padding-right: 5px;
57 | .input-container {
58 | display: flex;
59 | flex-direction: row;
60 | align-items: center;
61 | label {
62 | margin-right: 5px;
63 | &.interval-switch {
64 | margin-bottom: 0;
65 | }
66 | &.multi-log-switch {
67 | margin-bottom: 0;
68 | margin-right: 15px;
69 | }
70 | }
71 | &:not(:last-child) {
72 | margin-right: 10px;
73 | }
74 | }
75 | }
76 |
77 | .tensorboard-ng-logdir {
78 | .refresh-dir-btn {
79 | margin-right: 5px;
80 | margin-left: 5px;
81 | }
82 | .multi-log-tip {
83 | margin-right: 5px;
84 | }
85 | .reload-tip,
86 | .multi-log-tip {
87 | margin-bottom: 0;
88 | margin-left: 5px;
89 | font-size: 14px;
90 | }
91 | .custom-args-tip {
92 | margin-left: 10px;
93 | color: var(--jp-ui-font-color3);
94 | max-width: 200px;
95 | display: inline-block;
96 | overflow: hidden;
97 | text-overflow: ellipsis;
98 | white-space: nowrap;
99 | }
100 | }
101 |
102 | .tensorboard-ng-widget {
103 | padding: 0;
104 | .tensorboard-ng-op-btn {
105 | &:not(:last-child) {
106 | margin-right: 10px;
107 | }
108 | }
109 | }
110 |
111 | .tensorboard-ng-expand {
112 | flex: 1;
113 | }
114 |
115 | .tensorboard-ng-iframe-container {
116 | width: 100%;
117 | height: 100%;
118 | position: relative;
119 | background: var(--jp-layout-color1);
120 | .tensorboard-ng-iframe {
121 | width: 100%;
122 | height: 100%;
123 | }
124 | .tensorboard-ng-iframe-tip {
125 | position: absolute;
126 | top: 0;
127 | bottom: 0;
128 | left: 0;
129 | right: 0;
130 | display: flex;
131 | align-items: center;
132 | flex-direction: column;
133 | background: var(--jp-layout-color1);
134 | .common-tip {
135 | width: 100%;
136 | display: flex;
137 | flex-direction: column;
138 | align-items: center;
139 | margin: auto;
140 | }
141 |
142 | p {
143 | max-width: 75%;
144 | margin: auto;
145 | font-size: var(--jp-ui-font-size3);
146 | color: var(--jp-ui-font-color2);
147 |
148 | &.error {
149 | color: var(--jp-error-color1);
150 | }
151 |
152 | &.title {
153 | margin: auto;
154 | font-size: var(--jp-ui-font-size3);
155 | color: var(--jp-ui-font-color1);
156 | margin-bottom: 10px;
157 | font-weight: bold; // 700
158 | }
159 |
160 | &.desc {
161 | margin: auto;
162 | color: var(--jp-ui-font-color2);
163 | }
164 | }
165 | }
166 | }
167 |
168 | .tensorboard-ng-main {
169 | height: 100%;
170 | }
171 |
172 | .tb-ng-model-selector {
173 | width: 200px;
174 | height: 24px;
175 |
176 | .selector-active-btn {
177 | width: 200px;
178 | .active-btn-text {
179 | width: 170px;
180 | display: inline-block;
181 | overflow: hidden;
182 | text-overflow: ellipsis;
183 | white-space: nowrap;
184 | }
185 | }
186 | }
187 |
188 | .tensorboard-ng-ops {
189 | &.create {
190 | margin-left: 10px;
191 | }
192 | }
193 |
194 | // loading
195 | .tensorboard-loading-container {
196 | margin: auto;
197 | display: flex;
198 | height: max-content;
199 | flex-direction: column;
200 | align-items: center;
201 | width: 67.8%;
202 |
203 | .lds-ring {
204 | margin: auto;
205 | display: block;
206 | position: relative;
207 | width: 80px;
208 | height: 80px;
209 | }
210 | .lds-ring div {
211 | box-sizing: border-box;
212 | display: block;
213 | position: absolute;
214 | width: 64px;
215 | height: 64px;
216 | margin: 8px;
217 | border: 8px solid #ccc;
218 | border-radius: 50%;
219 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
220 | border-color: #ccc transparent transparent transparent;
221 | }
222 | .lds-ring div:nth-child(1) {
223 | animation-delay: -0.45s;
224 | }
225 | .lds-ring div:nth-child(2) {
226 | animation-delay: -0.3s;
227 | }
228 | .lds-ring div:nth-child(3) {
229 | animation-delay: -0.15s;
230 | }
231 | @keyframes lds-ring {
232 | 0% {
233 | transform: rotate(0deg);
234 | }
235 | 100% {
236 | transform: rotate(360deg);
237 | }
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/jupyterlab_tensorboard_pro/handlers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Copyright (c) 2017-2019, Shengpeng Liu. All rights reserved.
3 | # Copyright (c) 2019, Alex Ford. All rights reserved.
4 | # Copyright (c) 2020-2021, NVIDIA CORPORATION. All rights reserved.
5 |
6 | from tornado import web
7 | from tornado.wsgi import WSGIContainer
8 | from jupyter_server.base.handlers import JupyterHandler
9 | from jupyter_server.utils import url_path_join as ujoin
10 | from jupyter_server.base.handlers import path_regex
11 |
12 | notebook_dir = None
13 |
14 |
15 | def load_jupyter_server_extension(nb_app):
16 | global notebook_dir
17 | # notebook_dir should be root_dir of contents_manager
18 | notebook_dir = nb_app.contents_manager.root_dir
19 |
20 | web_app = nb_app.web_app
21 | base_url = web_app.settings['base_url']
22 |
23 | try:
24 | from .tensorboard_manager import manager
25 | except ImportError:
26 | nb_app.log.info("import tensorboard error, check tensorflow install")
27 | handlers = [
28 | (ujoin(
29 | base_url, r"/tensorboard_pro.*"),
30 | TensorboardErrorHandler),
31 | ]
32 | else:
33 | web_app.settings["tensorboard_manager"] = manager
34 | from . import api_handlers
35 |
36 | handlers = [
37 | (ujoin(
38 | base_url, r"/tensorboard_pro/(?P\w+)%s" % path_regex),
39 | TensorboardHandler),
40 | (ujoin(
41 | base_url, r"/api/tensorboard_pro"),
42 | api_handlers.TbRootHandler),
43 | (ujoin(
44 | base_url, r"/api/tensorboard_pro_static_config"),
45 | api_handlers.TbRootConfigHandler),
46 | (ujoin(
47 | base_url, r"/api/tensorboard_pro/(?P\w+)"),
48 | api_handlers.TbInstanceHandler),
49 | (ujoin(
50 | base_url, r"/font-roboto/.*"),
51 | TbFontHandler),
52 | ]
53 |
54 | web_app.add_handlers('.*$', handlers)
55 | nb_app.log.info("jupyterlab_tensorboard_pro extension loaded.")
56 |
57 |
58 | class TensorboardHandler(JupyterHandler):
59 |
60 | def _impl(self, name, path):
61 |
62 | self.request.path = path
63 |
64 | manager = self.settings["tensorboard_manager"]
65 | if name in manager:
66 | tb_app = manager[name].tb_app
67 | WSGIContainer(tb_app)(self.request)
68 | else:
69 | raise web.HTTPError(404)
70 |
71 | @web.authenticated
72 | def get(self, name, path):
73 |
74 | if path == "":
75 | uri = self.request.path + "/"
76 | if self.request.query:
77 | uri += "?" + self.request.query
78 | self.redirect(uri, permanent=True)
79 | return
80 |
81 | self._impl(name, path)
82 |
83 | @web.authenticated
84 | def post(self, name, path):
85 |
86 | if path == "":
87 | raise web.HTTPError(403)
88 |
89 | self._impl(name, path)
90 |
91 | def check_xsrf_cookie(self):
92 | """Expand XSRF check exceptions for POST requests.
93 |
94 | Provides support for TensorBoard plugins that use POST to retrieve
95 | experiment information.
96 |
97 | Expands check_xsrf_cookie exceptions, normally only applied to GET
98 | and HEAD requests, to POST requests, as TensorBoard POST endpoints
99 | do not modify state, so TensorBoard doesn't handle XSRF checks.
100 |
101 | See https://github.com/tensorflow/tensorboard/issues/4685
102 |
103 | """
104 |
105 | # Check XSRF token
106 | try:
107 | return super(TensorboardHandler, self).check_xsrf_cookie()
108 |
109 | except web.HTTPError:
110 | # Note: GET and HEAD exceptions are already handled in
111 | # IPythonHandler.check_xsrf_cookie and will not normally throw 403
112 |
113 | # For TB POSTs, we must loosen our expectations a bit. IPythonHandler
114 | # has some existing exceptions to consider a matching Referer as
115 | # sufficient for GET and HEAD requests; we extend that here to POST
116 |
117 | if self.request.method in {"POST"}:
118 | # Consider Referer a sufficient cross-origin check, mirroring
119 | # logic in IPythonHandler.check_xsrf_cookie.
120 | if not self.check_referer():
121 | referer = self.request.headers.get("Referer")
122 | if referer:
123 | msg = (
124 | "Blocking Cross Origin request from {}."
125 | .format(referer)
126 | )
127 | else:
128 | msg = "Blocking request from unknown origin"
129 | raise web.HTTPError(403, msg)
130 | else:
131 | raise
132 |
133 |
134 | class TbFontHandler(JupyterHandler):
135 |
136 | @web.authenticated
137 | def get(self):
138 | manager = self.settings["tensorboard_manager"]
139 | if "1" in manager:
140 | tb_app = manager["1"].tb_app
141 | WSGIContainer(tb_app)(self.request)
142 | else:
143 | raise web.HTTPError(404)
144 |
145 |
146 | class TensorboardErrorHandler(JupyterHandler):
147 | pass
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JupyterLab-TensorBoard-Pro
2 |
3 |  [](https://pypi.org/project/jupyterlab-tensorboard-pro/)
4 |
5 | [中文文档](./README.zh-cn.md)
6 |
7 | A TensorBoard JupyterLab plugin.
8 |
9 | 
10 |
11 | ## Requirements
12 |
13 | **python >= 3.6**
14 |
15 | Please install the following dependencies before installing this project:
16 |
17 | - jupyterlab
18 | - tensorflow
19 | - tensorboard
20 |
21 | ## Install
22 |
23 | **important:**
24 |
25 | * jupyterlab 4.0+: use 4+:
26 |
27 | ```
28 | pip install jupyterlab-tensorboard-pro
29 | ```
30 |
31 | * jupyterlab 3.x: use 3:
32 |
33 | ```
34 | pip install jupyterlab-tensorboard-pro~=3.0
35 | ```
36 |
37 | > only jupyterlab support, not include notebook
38 |
39 | ## Background
40 |
41 | In fact, there are already [jupyterlab_tensorboard](https://github.com/chaoleili/jupyterlab_tensorboard) (front-end plugin) and [jupyter_tensorboard](https://github.com/lspvic/jupyter_tensorboard) (back-end plugin) in the community, but both repositories have not been updated for a long time, and some new repair PRs have not been merged in time. Based on this, maybe the project author is no longer actively maintaining the corresponding repositories.
42 |
43 | At the same time, the existing community TensorBoard plugin has some experience problems, such as installing two python packages at the same time, no response for a long time after clicking `TensorBoard`, and the TensorBoard Reload time cannot be set. The interactive experience is not friendly enough, which will also affect the user's JupyterLab experience.
44 |
45 | Therefore, this project is forked from the existing projects of the community, and we made some positive changes, contained some previous PRs which are helpful but have not been merged for the time being. This repo will to be maintained for a long time in the future.
46 |
47 | This repo has also changed the api name, so it can be completely independent of the above plugins.
48 |
49 | Special thanks to the developers of the previous related repositories.
50 |
51 | ## Instructions
52 |
53 | ### Create Instance
54 |
55 | #### Create from Launcher Panel
56 |
57 | We can click the TensorBoard icon from the Launcher panel, the first click will take you to a default initialization panel from which we can create a TensorBoard instance. But if there is an active TensorBoard backend at this time, it will be opened directly.
58 |
59 | 
60 |
61 | #### Create by Shortcut Command
62 |
63 | We can also type `Open TensorBoard` in the JupyterLab shortcut panel (evoked by `ctrl + shift + c`)
64 |
65 | 
66 |
67 | #### Parameters
68 |
69 | In the initialization panel, two parameters are provided:
70 |
71 | - **Log Dir**: The default is the **relative directory** of the current sidebar when TensorBoard is clicked. You can also manually fill in the corresponding directory. It is recommended to make the directory as detailed as possible. If the directory content is small, the initialization speed will be improved.
72 | - **Reload Interval**: How often does TensorBoard backend rescan the corresponding directory. This option is set to false by default. It is recommended to disable and use manually Reload for daily use (The continuous scanning of directories by the TensorBoard backend will have some impact on Jupyter's stability and file system).
73 |
74 | Select the parameters and click Create TensorBoard, and the TensorBoard instance will be created synchronously. At this time, the jupyter backend is **blocking**, please wait for the instance to be created before performing other operations.
75 |
76 | 
77 |
78 | ### Manage Instances
79 |
80 | After the instance of TensorBoard is created, we can manage the instance. Currently, the following functions are provided:
81 |
82 | - **Refresh and list all**: TensorBoard backends can be switched to other instances (won't destroy the before)
83 | - **Open in a separate page**: You can open TensorBoard in the form of a separate web page tab.
84 | - **Reload**: Reinitialize the TensorBoard backend. When the content of the file is updated, you can load the new content through this function (Note: The refresh inside TensorBoard will not cause Reload).
85 | - **Destroy**: Destroy the instance, it will close both the backend and the frontend panel.
86 | - **Duplicate**: Open an identical frontend panel, this operation will reuse the TensorBoard backend.
87 | - **New**: Create an additional TensorBoard backend, please refer to the above for precautions.
88 |
89 | In addition, for the TensorBoard instance we created, it can be managed in the Kernel management panel of Jupyter, providing functions such as jumping to the corresponding instance and deleting.
90 |
91 | 
92 |
93 | ### Use AWS S3
94 |
95 | > It is assumed here that you have some experience with aws s3
96 |
97 | TensorBoard supports passing an s3 path via `s3://path/to/dir`, which is also supported in this plugin.
98 |
99 | However, because the s3 path is usually not directly accessible, you need to configure some basic information through `aws configure` ([download](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) aws cli), Usually, the system where JupyterLab is running should have the following files:
100 |
101 | ```shell
102 | # ~/.aws/config
103 | [default]
104 | region = ap-southeast-1
105 | output = json
106 |
107 | # ~/.aws/credentials
108 | [default]
109 | aws_access_key_id = ********
110 | aws_secret_access_key = ********
111 | ```
112 |
113 | Then you need to install some additional dependencies:
114 |
115 | ```
116 | pip install botocore boto3 tensorflow-io
117 | ```
118 |
119 | After that, you can enter an s3 path, then click the refresh button of tensorboard, and wait the loading:
120 |
121 | 
122 |
123 | > In fact, the status prompt of tensorboard itself is not friendly now, and we will further investigate whether there is a better way to experience it later.
124 |
125 | ## Debug
126 |
127 | You can use `jupyter-lab --debug` to enable debug logging for JupyterLab and TensorBoard.
128 |
129 | ## Develop
130 |
131 | ```shell
132 | jlpm install
133 | pip install jupyter_packaging
134 | jlpm run install:client
135 | jlpm run install:server
136 | ln -s /path/to/jupyterlab_tensorboard_pro jupyterlab_tensorboard_pro
137 | # after above maybe you need create use a soft link to hot update
138 | ```
139 |
140 | watch frontend:
141 | ```
142 | jlpm run watch
143 | ```
144 |
145 | build:
146 | ```
147 | python setup.py bdist_wheel --universal
148 | ```
149 |
150 | Under normal circumstances, you can just submit MR, the developers of this project will package and publish to pypi.
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application';
2 | import {
3 | ICommandPalette,
4 | WidgetTracker,
5 | IWidgetTracker,
6 | showDialog,
7 | Dialog,
8 | MainAreaWidget
9 | } from '@jupyterlab/apputils';
10 | import { ILauncher } from '@jupyterlab/launcher';
11 | import { IMainMenu } from '@jupyterlab/mainmenu';
12 | import { FileBrowser, IFileBrowserFactory } from '@jupyterlab/filebrowser';
13 | import { TensorboardManager } from './manager';
14 | import { Tensorboard } from './tensorboard';
15 | import { IRunningSessionManagers, IRunningSessions } from '@jupyterlab/running';
16 | import { LabIcon } from '@jupyterlab/ui-components';
17 | import tensorboardSvgStr from '../style/tensorboard.svg';
18 | import { TensorboardTabReactWidget } from './biz/widget';
19 | import { CommandIDs } from './commands';
20 |
21 | export const tensorboardIcon = new LabIcon({
22 | name: 'jupyterlab-tensorboard-p:tensorboard',
23 | svgstr: tensorboardSvgStr
24 | });
25 |
26 | /**
27 | * Initialization data for the tensorboard extension.
28 | */
29 | const extension: JupyterFrontEndPlugin>> =
30 | {
31 | id: 'tensorboard',
32 | requires: [ICommandPalette, IFileBrowserFactory],
33 | optional: [ILauncher, IMainMenu, IRunningSessionManagers],
34 | autoStart: true,
35 | activate
36 | };
37 |
38 | export default extension;
39 |
40 | async function activate(
41 | app: JupyterFrontEnd,
42 | palette: ICommandPalette,
43 | browserFactory: IFileBrowserFactory,
44 | launcher: ILauncher | null,
45 | menu: IMainMenu | null,
46 | runningSessionManagers: IRunningSessionManagers | null
47 | ): Promise>> {
48 | console.info('activate beign test!!');
49 | const manager = new TensorboardManager();
50 | const namespace = 'tensorboard';
51 | const tracker = new WidgetTracker>({
52 | namespace
53 | });
54 |
55 | const fileBrowser = browserFactory.createFileBrowser('tensorboard_pro');
56 | addCommands(app, manager, tracker, fileBrowser, launcher, menu);
57 |
58 | if (runningSessionManagers) {
59 | await addRunningSessionManager(runningSessionManagers, app, manager);
60 | }
61 |
62 | palette.addItem({ command: CommandIDs.inputDirect, category: 'Tensorboard' });
63 |
64 | return tracker;
65 | }
66 |
67 | // Running Kernels and Terminals,The coin-like tab
68 | function addRunningSessionManager(
69 | managers: IRunningSessionManagers,
70 | app: JupyterFrontEnd,
71 | manager: TensorboardManager
72 | ) {
73 | class RunningTensorboard implements IRunningSessions.IRunningItem {
74 | manager: TensorboardManager;
75 |
76 | constructor(model: Tensorboard.IModel, manager: TensorboardManager) {
77 | this._model = model;
78 | this.manager = manager;
79 | }
80 | open() {
81 | app.commands.execute(CommandIDs.open, { modelName: this._model.name });
82 | }
83 | icon() {
84 | return tensorboardIcon;
85 | }
86 | label() {
87 | return `${this._model.name}:${this.manager.formatDir(this._model.logdir)}`;
88 | }
89 | shutdown() {
90 | app.commands.execute(CommandIDs.close, { tb: this._model });
91 | return manager.shutdown(this._model.name);
92 | }
93 |
94 | private _model: Tensorboard.IModel;
95 | }
96 |
97 | return manager.getStaticConfigPromise.then(() => {
98 | managers.add({
99 | name: 'Tensorboard',
100 | running: () => manager.running().map(model => new RunningTensorboard(model, manager)),
101 | shutdownAll: () => manager.shutdownAll(),
102 | refreshRunning: () => manager.refreshRunning(),
103 | runningChanged: manager.runningChanged
104 | });
105 | });
106 | }
107 |
108 | /**
109 | * Add the commands for the tensorboard.
110 | */
111 | export function addCommands(
112 | app: JupyterFrontEnd,
113 | manager: TensorboardManager,
114 | tracker: WidgetTracker>,
115 | fileBrowser: FileBrowser,
116 | launcher: ILauncher | null,
117 | menu: IMainMenu | null
118 | ): void {
119 | const { commands, serviceManager } = app;
120 |
121 | commands.addCommand(CommandIDs.open, {
122 | execute: args => {
123 | // if select certain
124 |
125 | let modelName = args['modelName'] as string | undefined;
126 | const copy = args['copy'];
127 | console.info('[DEBUG] browserFactory.defaultBrowser:', fileBrowser, fileBrowser.model.path);
128 |
129 | const currentCWD = fileBrowser.model.path;
130 |
131 | let widget: MainAreaWidget | null | undefined = null;
132 |
133 | // step1: find an opened widget
134 | if (!modelName) {
135 | widget = tracker.find(widget => {
136 | return (
137 | manager.formatDir(widget.content.currentLogDir || '') === manager.formatDir(currentCWD)
138 | );
139 | });
140 | } else if (!copy) {
141 | widget = tracker.find(value => {
142 | return value.content.currentTensorBoardModel?.name === modelName;
143 | });
144 | }
145 | // default we have only one tensorboard
146 | if (widget) {
147 | app.shell.activateById(widget.id);
148 | return widget;
149 | } else {
150 | // step2: try find opened backend widgets
151 | if (!modelName) {
152 | const runningTensorboards = [...manager.running()];
153 | // hint: Using runningTensorboards directly may cause setState to fail to respond
154 | for (const model of runningTensorboards) {
155 | if (manager.formatDir(model.logdir) === manager.formatDir(currentCWD)) {
156 | modelName = model.name;
157 | }
158 | }
159 | }
160 |
161 | const tabReact = new TensorboardTabReactWidget({
162 | fileBrowser,
163 | tensorboardManager: manager,
164 | app,
165 | createdModelName: modelName
166 | });
167 | const tabWidget = new MainAreaWidget({ content: tabReact });
168 | tracker.add(tabWidget);
169 | app.shell.add(tabWidget, 'main', {
170 | mode: copy ? 'split-right' : undefined
171 | });
172 | app.shell.activateById(tabWidget.id);
173 | return tabWidget;
174 | }
175 | }
176 | });
177 |
178 | commands.addCommand(CommandIDs.openDoc, {
179 | execute: args => {
180 | window.open('https://github.com/HFAiLab/jupyterlab_tensorboard_pro');
181 | }
182 | });
183 |
184 | commands.addCommand(CommandIDs.close, {
185 | execute: args => {
186 | const model = args['tb'] as Tensorboard.IModel;
187 | tracker.forEach(widget => {
188 | if (
189 | widget.content.currentTensorBoardModel &&
190 | widget.content.currentTensorBoardModel.name === model.name
191 | ) {
192 | widget.dispose();
193 | widget.close();
194 | }
195 | });
196 | }
197 | });
198 |
199 | commands.addCommand(CommandIDs.inputDirect, {
200 | label: () => 'Open TensorBoard',
201 | execute: args => {
202 | return app.commands.execute(CommandIDs.open);
203 | }
204 | });
205 |
206 | commands.addCommand(CommandIDs.createNew, {
207 | label: args => (args['isPalette'] ? 'New TensorBoard' : 'TensorBoard'),
208 | caption: 'Start a new tensorboard',
209 | icon: args => (args['isPalette'] ? undefined : tensorboardIcon),
210 | execute: args => {
211 | const cwd = (args['cwd'] as string) || fileBrowser.model.path;
212 | const logdir = typeof args['logdir'] === 'undefined' ? cwd : (args['logdir'] as string);
213 | return serviceManager.contents.get(logdir, { type: 'directory' }).then(
214 | dir => {
215 | // Try to open the session panel to make it easier for users to observe more active tensorboard instances
216 | try {
217 | app.shell.activateById('jp-running-sessions');
218 | } catch (e) {
219 | // do nothing
220 | }
221 | app.commands.execute(CommandIDs.open);
222 | },
223 | () => {
224 | // no such directory.
225 | return showDialog({
226 | title: 'Cannot create tensorboard.',
227 | body: 'Directory not found',
228 | buttons: [Dialog.okButton()]
229 | });
230 | }
231 | );
232 | }
233 | });
234 |
235 | if (launcher) {
236 | launcher.add({
237 | command: CommandIDs.createNew,
238 | category: 'Other',
239 | rank: 2
240 | });
241 | }
242 |
243 | if (menu) {
244 | menu.fileMenu.newMenu.addGroup(
245 | [
246 | {
247 | command: CommandIDs.createNew
248 | }
249 | ],
250 | 30
251 | );
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/jupyterlab_tensorboard_pro/tensorboard_manager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Copyright (c) 2017-2019, Shengpeng Liu. All rights reserved.
3 | # Copyright (c) 2020-2021, NVIDIA CORPORATION. All rights reserved.
4 | # Copyright (c) 2022, HFAiLab. All rights reserved.
5 |
6 | import os
7 | import sys
8 | import inspect
9 | import itertools
10 | from collections import namedtuple
11 | import logging
12 |
13 | is_debug = True if '--debug' in sys.argv else False
14 |
15 | sys.argv = ["tensorboard"]
16 |
17 | from tensorboard.backend import application # noqa
18 |
19 |
20 | def get_plugins():
21 | # Gather up core plugins as well as dynamic plugins.
22 | # The way this was done varied several times in the later 1.x series
23 | if hasattr(default, 'PLUGIN_LOADERS'): # TB 1.10
24 | return default.PLUGIN_LOADERS[:]
25 |
26 | if hasattr(default, 'get_plugins') and inspect.isfunction(default.get_plugins): # TB 1.11+
27 | if not (hasattr(default, 'get_static_plugins') and inspect.isfunction(default.get_static_plugins)):
28 | # in TB 1.11 through 2.2, get_plugins is really just the static plugins
29 | plugins = default.get_plugins()
30 | else:
31 | # in TB 2.3 and later, get_plugins was renamed to get_static_plugins and
32 | # a new get_plugins was created that returns the static+dynamic set
33 | plugins = default.get_static_plugins()
34 |
35 | if hasattr(default, 'get_dynamic_plugins') and inspect.isfunction(default.get_dynamic_plugins):
36 | # in TB 1.14 there are also dynamic plugins that should be included
37 | plugins += default.get_dynamic_plugins()
38 |
39 | return plugins
40 | return None
41 |
42 |
43 | try:
44 | # Tensorboard 0.4.x above series
45 | from tensorboard import default
46 |
47 | if hasattr(default, 'PLUGIN_LOADERS') or hasattr(default, '_PLUGINS'):
48 | # TensorBoard 1.10 or above series
49 | from tensorboard import program
50 |
51 | def create_tb_app(logdir, reload_interval, purge_orphaned_data, enable_multi_log, additional_args):
52 | argv = [
53 | "",
54 | "--logdir", logdir,
55 | "--reload_interval", str(reload_interval),
56 | "--purge_orphaned_data", str(purge_orphaned_data),
57 | ]
58 |
59 | # example: "--samples_per_plugin", "images=1"
60 | additional_args_arr = additional_args.split()
61 | argv += additional_args_arr
62 |
63 | if enable_multi_log:
64 | argv[1] = "--logdir_spec"
65 |
66 | tensorboard = program.TensorBoard(get_plugins())
67 | tensorboard.configure(argv)
68 |
69 | if (hasattr(application, 'standard_tensorboard_wsgi') and inspect.isfunction(application.standard_tensorboard_wsgi)):
70 | logging.debug("TensorBoard 1.10 or above series detected")
71 | standard_tensorboard_wsgi = application.standard_tensorboard_wsgi
72 | else:
73 | logging.debug("TensorBoard 2.3 or above series detected")
74 |
75 | def standard_tensorboard_wsgi(flags, plugin_loaders, assets_zip_provider):
76 | from tensorboard.backend.event_processing import data_ingester
77 | ingester = data_ingester.LocalDataIngester(flags)
78 | ingester.start()
79 | return application.TensorBoardWSGIApp(flags, plugin_loaders, ingester.data_provider,
80 | assets_zip_provider, ingester.deprecated_multiplexer)
81 |
82 | return manager.add_instance(logdir, reload_interval, enable_multi_log, additional_args, standard_tensorboard_wsgi(
83 | tensorboard.flags,
84 | tensorboard.plugin_loaders,
85 | tensorboard.assets_zip_provider))
86 | else:
87 | logging.debug("TensorBoard 0.4.x series detected")
88 |
89 | def create_tb_app(logdir, reload_interval, purge_orphaned_data, enable_multi_log, additional_args):
90 | return manager.add_instance(logdir, reload_interval, enable_multi_log, additional_args, application.standard_tensorboard_wsgi(
91 | logdir=logdir, reload_interval=reload_interval,
92 | purge_orphaned_data=purge_orphaned_data,
93 | plugins=default.get_plugins()))
94 |
95 | except ImportError:
96 | # Tensorboard 0.3.x series
97 | from tensorboard.plugins.audio import audio_plugin
98 | from tensorboard.plugins.core import core_plugin
99 | from tensorboard.plugins.distribution import distributions_plugin
100 | from tensorboard.plugins.graph import graphs_plugin
101 | from tensorboard.plugins.histogram import histograms_plugin
102 | from tensorboard.plugins.image import images_plugin
103 | from tensorboard.plugins.profile import profile_plugin
104 | from tensorboard.plugins.projector import projector_plugin
105 | from tensorboard.plugins.scalar import scalars_plugin
106 | from tensorboard.plugins.text import text_plugin
107 | logging.debug("Tensorboard 0.3.x series detected")
108 |
109 | _plugins = [
110 | core_plugin.CorePlugin,
111 | scalars_plugin.ScalarsPlugin,
112 | images_plugin.ImagesPlugin,
113 | audio_plugin.AudioPlugin,
114 | graphs_plugin.GraphsPlugin,
115 | distributions_plugin.DistributionsPlugin,
116 | histograms_plugin.HistogramsPlugin,
117 | projector_plugin.ProjectorPlugin,
118 | text_plugin.TextPlugin,
119 | profile_plugin.ProfilePlugin,
120 | ]
121 |
122 | def create_tb_app(logdir, reload_interval, purge_orphaned_data, enable_multi_log, additional_args):
123 | return application.standard_tensorboard_wsgi(
124 | logdir=logdir, reload_interval=reload_interval,
125 | purge_orphaned_data=purge_orphaned_data,
126 | plugins=_plugins)
127 |
128 |
129 | from .handlers import notebook_dir # noqa
130 |
131 | TensorBoardInstance = namedtuple(
132 | 'TensorBoardInstance', ['name', 'logdir', 'reload_interval', 'enable_multi_log', 'additional_args', 'tb_app'])
133 |
134 |
135 | class TensorboardManger(dict):
136 |
137 | def __init__(self):
138 | self._logdir_dict = {}
139 | if is_debug:
140 | _logger = logging.getLogger("tensorboard")
141 | _logger.setLevel(logging.DEBUG)
142 |
143 | def _next_available_name(self):
144 | # hint: 这里实现的机制,让我们可以通过 delete + create 去模拟 reload,并且 name 保持不变
145 | for n in itertools.count(start=1):
146 | name = "%d" % n
147 | if name not in self:
148 | return name
149 |
150 | def format_multi_dir_path(self, dir):
151 | dirs = dir.split(',')
152 |
153 | def format_dir(dir):
154 | name = ""
155 | realdir = ""
156 |
157 | if ':' in dir:
158 | name, realdir = dir.split(':')
159 | else:
160 | realdir = dir
161 |
162 | if not os.path.isabs(realdir) and notebook_dir and not realdir.startswith("s3://"):
163 | realdir = os.path.join(notebook_dir, realdir)
164 |
165 | if name:
166 | return f'{name}:{realdir}'
167 | return realdir
168 |
169 | return ','.join(map(format_dir, dirs))
170 |
171 | def new_instance(self, logdir, reload_interval, enable_multi_log, additional_args):
172 | if not enable_multi_log and not os.path.isabs(logdir) and notebook_dir and not logdir.startswith("s3://"):
173 | logdir = os.path.join(notebook_dir, logdir)
174 |
175 | if logdir not in self._logdir_dict:
176 | purge_orphaned_data = True
177 | reload_interval = 120 if reload_interval is None else reload_interval
178 | if enable_multi_log:
179 | logdir = self.format_multi_dir_path(logdir)
180 | create_tb_app(
181 | logdir=logdir, reload_interval=reload_interval,
182 | purge_orphaned_data=purge_orphaned_data, enable_multi_log=enable_multi_log, additional_args=additional_args)
183 |
184 | return self._logdir_dict[logdir]
185 |
186 | def add_instance(self, logdir, reload_interval, enable_multi_log, additional_args, tb_application):
187 | name = self._next_available_name()
188 | instance = TensorBoardInstance(
189 | name, logdir, reload_interval, enable_multi_log, additional_args, tb_application)
190 | self[name] = instance
191 | self._logdir_dict[logdir] = instance
192 | return tb_application
193 |
194 | def terminate(self, name, force=True):
195 | if name in self:
196 | instance = self[name]
197 | del self[name], self._logdir_dict[instance.logdir]
198 | else:
199 | raise Exception("There's no tensorboard instance named %s" % name)
200 |
201 |
202 | manager = TensorboardManger()
203 |
--------------------------------------------------------------------------------
/src/manager.ts:
--------------------------------------------------------------------------------
1 | //Tensorboard manager
2 | import { ArrayExt } from '@lumino/algorithm';
3 | import { Signal, ISignal } from '@lumino/signaling';
4 | import { JSONExt } from '@lumino/coreutils';
5 | import { Tensorboard } from './tensorboard';
6 | import { ServerConnection } from '@jupyterlab/services';
7 | import { DEFAULT_ENABLE_MULTI_LOG, DEFAULT_REFRESH_INTERVAL } from './consts';
8 |
9 | /**
10 | * A tensorboard manager.
11 | */
12 | export class TensorboardManager implements Tensorboard.IManager {
13 | getStaticConfigPromise: Promise;
14 |
15 | /**
16 | * Construct a new tensorboard manager.
17 | */
18 | constructor(options: TensorboardManager.IOptions = {}) {
19 | this.serverSettings = options.serverSettings || ServerConnection.makeSettings();
20 | this._readyPromise = this._refreshRunning();
21 | this._refreshTimer = (setInterval as any)(() => {
22 | if (typeof document !== 'undefined' && document.hidden) {
23 | return;
24 | }
25 | this._refreshRunning();
26 | }, 10000);
27 | this.getStaticConfigPromise = this._getStaticConfig();
28 | }
29 |
30 | /**
31 | * A signal emitted when the running tensorboards change.
32 | */
33 | get runningChanged(): ISignal {
34 | return this._runningChanged;
35 | }
36 |
37 | /**
38 | * Test whether the terminal manager is disposed.
39 | */
40 | get isDisposed(): boolean {
41 | return this._isDisposed;
42 | }
43 |
44 | /**
45 | * The server settings of the manager.
46 | */
47 | readonly serverSettings: ServerConnection.ISettings;
48 |
49 | /**
50 | * Dispose of the resources used by the manager.
51 | */
52 | dispose(): void {
53 | if (this.isDisposed) {
54 | return;
55 | }
56 | this._isDisposed = true;
57 | clearInterval(this._refreshTimer);
58 | Signal.clearData(this);
59 | this._models = [];
60 | }
61 |
62 | /**
63 | * Test whether the manager is ready.
64 | */
65 | get isReady(): boolean {
66 | return this._isReady;
67 | }
68 |
69 | /**
70 | * A promise that fulfills when the manager is ready.
71 | */
72 | get ready(): Promise {
73 | return this._readyPromise;
74 | }
75 |
76 | /**
77 | * Create an iterator over the most recent running Tensorboards.
78 | *
79 | * @returns A new iterator over the running tensorboards.
80 | */
81 | running(): Array {
82 | return this._models;
83 | }
84 |
85 | formatDir(dir: string): string {
86 | const pageRoot = this._statusConfig?.notebook_dir;
87 |
88 | if (dir.includes(',')) {
89 | const dirs = dir.split(',');
90 | return dirs
91 | .map(dir => {
92 | if (dir.includes(':')) {
93 | return `${dir.split(':')![0]}:${this.formatDir(dir.split(':')![1])}`;
94 | } else {
95 | return this.formatDir(dir);
96 | }
97 | })
98 | .join(',');
99 | }
100 |
101 | if (pageRoot && dir.indexOf(pageRoot) === 0) {
102 | let replaceResult = dir.replace(pageRoot, '');
103 | if (replaceResult === '') {
104 | replaceResult = '/';
105 | }
106 | const formatted = `${replaceResult}`.replace(/^\//, '');
107 | if (!formatted) {
108 | return '';
109 | }
110 | return formatted;
111 | }
112 | return dir;
113 | }
114 |
115 | /**
116 | * Create a new tensorboard.
117 | *
118 | * @param logdir - The logdir used to create a new tensorboard.
119 | *
120 | * @param options - The options used to connect to the tensorboard.
121 | *
122 | * @returns A promise that resolves with the tensorboard instance.
123 | */
124 | async startNew(
125 | logdir: string,
126 | refreshInterval: number = DEFAULT_REFRESH_INTERVAL,
127 | enableMultiLog: boolean = DEFAULT_ENABLE_MULTI_LOG,
128 | additionalArgs = '',
129 | options?: Tensorboard.IOptions
130 | ): Promise {
131 | const tensorboard = await Tensorboard.startNew(
132 | logdir,
133 | refreshInterval,
134 | enableMultiLog,
135 | additionalArgs,
136 | this._getOptions(options)
137 | );
138 | this._onStarted(tensorboard);
139 | return tensorboard;
140 | }
141 |
142 | /**
143 | * Shut down a tensorboard by name.
144 | */
145 | async shutdown(name: string): Promise {
146 | const index = ArrayExt.findFirstIndex(this._models, value => value.name === name);
147 | if (index === -1) {
148 | return;
149 | }
150 |
151 | this._models.splice(index, 1);
152 | this._runningChanged.emit(this._models.slice());
153 |
154 | return Tensorboard.shutdown(name, this.serverSettings).then(() => {
155 | const toRemove: Tensorboard.ITensorboard[] = [];
156 | this._tensorboards.forEach(t => {
157 | if (t.name === name) {
158 | t.dispose();
159 | toRemove.push(t);
160 | }
161 | });
162 | toRemove.forEach(s => {
163 | this._tensorboards.delete(s);
164 | });
165 | });
166 | }
167 |
168 | /**
169 | * Shut down all tensorboards.
170 | *
171 | * @returns A promise that resolves when all of the tensorboards are shut down.
172 | */
173 | shutdownAll(): Promise {
174 | const models = this._models;
175 | if (models.length > 0) {
176 | this._models = [];
177 | this._runningChanged.emit([]);
178 | }
179 |
180 | return this._refreshRunning().then(() => {
181 | return Promise.all(
182 | models.map(model => {
183 | return Tensorboard.shutdown(model.name, this.serverSettings).then(() => {
184 | const toRemove: Tensorboard.ITensorboard[] = [];
185 | this._tensorboards.forEach(t => {
186 | t.dispose();
187 | toRemove.push(t);
188 | });
189 | toRemove.forEach(t => {
190 | this._tensorboards.delete(t);
191 | });
192 | });
193 | })
194 | ).then(() => {
195 | return undefined;
196 | });
197 | });
198 | }
199 |
200 | /**
201 | * Force a refresh of the running tensorboards.
202 | *
203 | * @returns A promise that with the list of running tensorboards.
204 | */
205 | refreshRunning(): Promise {
206 | return this._refreshRunning();
207 | }
208 |
209 | /**
210 | * Handle a tensorboard terminating.
211 | */
212 | private _onTerminated(name: string): void {
213 | const index = ArrayExt.findFirstIndex(this._models, value => value.name === name);
214 | if (index !== -1) {
215 | this._models.splice(index, 1);
216 | this._runningChanged.emit(this._models.slice());
217 | }
218 | }
219 |
220 | /**
221 | * Handle a tensorboard starting.
222 | */
223 | private _onStarted(tensorboard: Tensorboard.ITensorboard): void {
224 | const name = tensorboard.name;
225 | this._tensorboards.add(tensorboard);
226 | const index = ArrayExt.findFirstIndex(this._models, value => value.name === name);
227 | if (index === -1) {
228 | this._models.push(tensorboard.model);
229 | this._runningChanged.emit(this._models.slice());
230 | }
231 | tensorboard.terminated.connect(() => {
232 | this._onTerminated(name);
233 | });
234 | }
235 |
236 | /**
237 | * Refresh the running tensorboards.
238 | */
239 | private _refreshRunning(): Promise {
240 | return Tensorboard.listRunning(this.serverSettings).then(models => {
241 | this._isReady = true;
242 | if (!JSONExt.deepEqual(models, this._models)) {
243 | const names = models.map(r => r.name);
244 | const toRemove: Tensorboard.ITensorboard[] = [];
245 | this._tensorboards.forEach(t => {
246 | if (names.indexOf(t.name) === -1) {
247 | t.dispose();
248 | toRemove.push(t);
249 | }
250 | });
251 | toRemove.forEach(t => {
252 | this._tensorboards.delete(t);
253 | });
254 | this._models = models.slice();
255 | this._runningChanged.emit(models);
256 | }
257 | });
258 | }
259 |
260 | private _getStaticConfig(): Promise {
261 | return Tensorboard.getStaticConfig(this.serverSettings).then(config => {
262 | this._statusConfig = config;
263 | });
264 | }
265 |
266 | /**
267 | * Get a set of options to pass.
268 | */
269 | private _getOptions(options: Tensorboard.IOptions = {}): Tensorboard.IOptions {
270 | return { ...options, serverSettings: this.serverSettings };
271 | }
272 |
273 | private _models: Tensorboard.IModel[] = [];
274 | private _tensorboards = new Set();
275 | private _isDisposed = false;
276 | private _isReady = false;
277 | private _readyPromise: Promise;
278 | private _refreshTimer = -1;
279 | private _runningChanged = new Signal(this);
280 | private _statusConfig: Tensorboard.StaticConfig | null = null;
281 | }
282 | /**
283 | * The namespace for TensorboardManager statics.
284 | */
285 | export namespace TensorboardManager {
286 | /**
287 | * The options used to initialize a tensorboard manager.
288 | */
289 | export interface IOptions {
290 | /**
291 | * The server settings used by the manager.
292 | */
293 | serverSettings?: ServerConnection.ISettings;
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/src/tensorboard.ts:
--------------------------------------------------------------------------------
1 | import { each, map, toArray } from '@lumino/algorithm';
2 | import { IDisposable } from '@lumino/disposable';
3 | import { JSONObject } from '@lumino/coreutils';
4 | import { URLExt } from '@jupyterlab/coreutils';
5 | import { Signal, ISignal } from '@lumino/signaling';
6 | import { ServerConnection } from '@jupyterlab/services';
7 |
8 | /**
9 | * The url for the tensorboard service. tensorboard
10 | * service provided by jupyter_tensorboard.
11 | * ref: https://github.com/lspvic/jupyter_tensorboard
12 | * Maybe rewrite the jupyter_tensorboard service by myself.
13 | */
14 | const TENSORBOARD_SERVICE_URL = 'api/tensorboard_pro';
15 |
16 | const TENSORBOARD_STATIC_CONFIG_URL = 'api/tensorboard_pro_static_config';
17 |
18 | const TENSORBOARD_URL = 'tensorboard_pro';
19 |
20 | /**
21 | * The namespace for Tensorboard statics.
22 | */
23 | export namespace Tensorboard {
24 | /**
25 | * An interface for a tensorboard.
26 | */
27 | export interface ITensorboard extends IDisposable {
28 | /**
29 | * A signal emitted when the tensorboard is shut down.
30 | */
31 | terminated: ISignal;
32 |
33 | /**
34 | * The model associated with the tensorboard.
35 | */
36 | readonly model: IModel;
37 |
38 | /**
39 | * Get the name of the tensorboard.
40 | */
41 | readonly name: string;
42 |
43 | /**
44 | * The server settings for the tensorboard.
45 | */
46 | readonly serverSettings: ServerConnection.ISettings;
47 |
48 | /**
49 | * Shut down the tensorboard.
50 | */
51 | shutdown(): Promise;
52 | }
53 |
54 | /**
55 | * Start a new tensorboard.
56 | *
57 | * @param options - The tensorboard options to use.
58 | *
59 | * @returns A promise that resolves with the tensorboard instance.
60 | */
61 | export function startNew(
62 | logdir: string,
63 | refreshInterval: number,
64 | enableMultiLog: boolean,
65 | additionalArgs: string,
66 | options?: IOptions
67 | ): Promise {
68 | return DefaultTensorboard.startNew(
69 | logdir,
70 | refreshInterval,
71 | enableMultiLog,
72 | additionalArgs,
73 | options
74 | );
75 | }
76 |
77 | export function getStaticConfig(settings?: ServerConnection.ISettings): Promise {
78 | return DefaultTensorboard.getStaticConfig(settings);
79 | }
80 |
81 | /**
82 | * List the running tensorboards.
83 | *
84 | * @param settings - The server settings to use.
85 | *
86 | * @returns A promise that resolves with the list of running tensorboard models.
87 | */
88 | export function listRunning(settings?: ServerConnection.ISettings): Promise {
89 | return DefaultTensorboard.listRunning(settings);
90 | }
91 |
92 | /**
93 | * Shut down a tensorboard by name.
94 | *
95 | * @param name - The name of the target tensorboard.
96 | *
97 | * @param settings - The server settings to use.
98 | *
99 | * @returns A promise that resolves when the tensorboard is shut down.
100 | */
101 | export function shutdown(name: string, settings?: ServerConnection.ISettings): Promise {
102 | return DefaultTensorboard.shutdown(name, settings);
103 | }
104 |
105 | /**
106 | * Shut down all tensorboard.
107 | *
108 | * @returns A promise that resolves when all of the tensorboards are shut down.
109 | */
110 | export function shutdownAll(settings?: ServerConnection.ISettings): Promise {
111 | return DefaultTensorboard.shutdownAll(settings);
112 | }
113 |
114 | /**
115 | * Get tensorboard's url
116 | */
117 | export function getUrl(name: string, settings?: ServerConnection.ISettings): string {
118 | return DefaultTensorboard.getUrl(name, settings);
119 | }
120 |
121 | /**
122 | * The options for intializing a tensorboard object.
123 | */
124 | export interface IOptions {
125 | /**
126 | * The server settings for the tensorboard.
127 | */
128 | serverSettings?: ServerConnection.ISettings;
129 | }
130 |
131 | /**
132 | * The server model for a tensorboard.
133 | */
134 | export interface IModel extends JSONObject {
135 | /**
136 | * The name of the tensorboard.
137 | */
138 | readonly name: string;
139 |
140 | /**
141 | * The logdir Path of the tensorboard.
142 | */
143 | readonly logdir: string;
144 |
145 | /**
146 | * The reload interval of the tensorboard.
147 | */
148 | readonly reload_interval: number;
149 |
150 | /**
151 | * The last reload time of the tensorboard.
152 | */
153 | readonly reload_time: string;
154 |
155 | /**
156 | * Whether to support multiple log parameters to be passed in, the `logdir_spec` of tensorboard will actually be used internally
157 | */
158 | readonly enable_multi_log: boolean;
159 |
160 | /**
161 | * additional args to tensorboard
162 | */
163 | readonly additional_args: string;
164 | }
165 |
166 | export interface StaticConfig extends JSONObject {
167 | /**
168 | * The name of the tensorboard.
169 | */
170 | readonly notebook_dir: string;
171 | }
172 |
173 | /**
174 | * The interface for a tensorboard manager.
175 | *
176 | * The manager is respoonsible for maintaining the state of running
177 | * tensorboard.
178 | */
179 | export interface IManager extends IDisposable {
180 | readonly serverSettings: ServerConnection.ISettings;
181 |
182 | runningChanged: ISignal;
183 |
184 | running(): Array;
185 |
186 | startNew(
187 | logdir: string,
188 | refreshInterval: number,
189 | enableMultiLog: boolean,
190 | additionalArgs: string,
191 | options?: IOptions
192 | ): Promise;
193 |
194 | shutdown(name: string): Promise;
195 |
196 | shutdownAll(): Promise;
197 |
198 | refreshRunning(): Promise;
199 | }
200 | }
201 |
202 | export class DefaultTensorboard implements Tensorboard.ITensorboard {
203 | /**
204 | * Construct a new tensorboard.
205 | */
206 | constructor(
207 | name: string,
208 | logdir: string,
209 | lastReload: string,
210 | reloadInterval: number | undefined,
211 | enableMultiLog: boolean | undefined,
212 | additionalArgs: string | undefined,
213 | options: Tensorboard.IOptions = {}
214 | ) {
215 | this._name = name;
216 | this._logdir = logdir;
217 | this._lastReload = lastReload;
218 | this._reloadInterval = reloadInterval;
219 | this._enableMultiLog = Boolean(enableMultiLog);
220 | this._additionalArgs = additionalArgs || '';
221 | this.serverSettings = options.serverSettings || ServerConnection.makeSettings();
222 | this._url = Private.getTensorboardInstanceUrl(this.serverSettings.baseUrl, this._name);
223 | }
224 |
225 | /**
226 | * Get the name of the tensorboard.
227 | */
228 | get name(): string {
229 | return this._name;
230 | }
231 |
232 | /**
233 | * Get the model for the tensorboard.
234 | */
235 | get model(): Tensorboard.IModel {
236 | return {
237 | name: this._name,
238 | logdir: this._logdir,
239 | reload_time: this._lastReload,
240 | reload_interval: this._reloadInterval || 0,
241 | enable_multi_log: this._enableMultiLog || false,
242 | additional_args: this._additionalArgs || ''
243 | };
244 | }
245 |
246 | /**
247 | * A signal emitted when the tensorboard is shut down.
248 | */
249 | get terminated(): Signal {
250 | return this._terminated;
251 | }
252 |
253 | /**
254 | * Test whether the tensorbaord is disposed.
255 | */
256 | get isDisposed(): boolean {
257 | return this._isDisposed;
258 | }
259 |
260 | /**
261 | * Dispose of the resources held by the tensorboard.
262 | */
263 | dispose(): void {
264 | if (this._isDisposed) {
265 | return;
266 | }
267 |
268 | this.terminated.emit(void 0);
269 | this._isDisposed = true;
270 | delete Private.running[this._url];
271 | Signal.clearData(this);
272 | }
273 |
274 | /**
275 | * The server settings for the tensorboard.
276 | */
277 | readonly serverSettings: ServerConnection.ISettings;
278 |
279 | /**
280 | * Shut down the tensorboard.
281 | */
282 | shutdown(): Promise {
283 | const { name, serverSettings } = this;
284 | return DefaultTensorboard.shutdown(name, serverSettings);
285 | }
286 |
287 | private _isDisposed = false;
288 | private _url: string;
289 | private _name: string;
290 | private _logdir: string;
291 | private _lastReload: string;
292 | private _reloadInterval: number | undefined;
293 | private _enableMultiLog: boolean;
294 | private _additionalArgs: string;
295 | private _terminated = new Signal(this);
296 | }
297 |
298 | /**
299 | * The static namespace for `DefaultTensorboard`.
300 | */
301 | export namespace DefaultTensorboard {
302 | /**
303 | * Start a new tensorboard.
304 | *
305 | * @param options - The tensorboard options to use.
306 | *
307 | * @returns A promise that resolves with the tensorboard instance.
308 | */
309 | export function startNew(
310 | logdir: string,
311 | refreshInterval: number,
312 | enableMultiLog: boolean,
313 | additionalArgs: string,
314 | options: Tensorboard.IOptions = {}
315 | ): Promise {
316 | const serverSettings = options.serverSettings || ServerConnection.makeSettings();
317 | const url = Private.getServiceUrl(serverSettings.baseUrl);
318 | // ServerConnection won't automaticy add this header when the body in not none.
319 | const header = new Headers({ 'Content-Type': 'application/json' });
320 |
321 | const data = JSON.stringify({
322 | logdir: logdir,
323 | reload_interval: refreshInterval,
324 | enable_multi_log: enableMultiLog,
325 | additional_args: additionalArgs
326 | });
327 |
328 | const init = { method: 'POST', headers: header, body: data };
329 |
330 | return ServerConnection.makeRequest(url, init, serverSettings)
331 | .then(response => {
332 | if (response.status !== 200) {
333 | throw new ServerConnection.ResponseError(response);
334 | }
335 | return response.json();
336 | })
337 | .then((data: Tensorboard.IModel) => {
338 | const name = data.name;
339 | const logdir = data.logdir;
340 | const lastReload = data.reload_time;
341 | const reloadInterval = data.reload_interval;
342 | const enableMultiLog = data.enable_multi_log;
343 | const additionalArgs = data.additional_args;
344 | return new DefaultTensorboard(
345 | name,
346 | logdir,
347 | lastReload,
348 | reloadInterval,
349 | enableMultiLog,
350 | additionalArgs,
351 | {
352 | ...options,
353 | serverSettings
354 | }
355 | );
356 | });
357 | }
358 |
359 | export function getStaticConfig(
360 | settings?: ServerConnection.ISettings
361 | ): Promise {
362 | const statis_config_url = Private.getTensorboardStaticConfigUrl(settings!.baseUrl);
363 | return ServerConnection.makeRequest(statis_config_url, {}, settings!)
364 | .then(response => {
365 | if (response.status !== 200) {
366 | throw new ServerConnection.ResponseError(response);
367 | }
368 | return response.json();
369 | })
370 | .then((data: Tensorboard.StaticConfig) => {
371 | return data;
372 | });
373 | }
374 |
375 | /**
376 | * List the running tensorboards.
377 | *
378 | * @param settings - The server settings to use.
379 | *
380 | * @returns A promise that resolves with the list of running tensorboard models.
381 | */
382 | export function listRunning(
383 | settings?: ServerConnection.ISettings
384 | ): Promise {
385 | settings = settings || ServerConnection.makeSettings();
386 | const service_url = Private.getServiceUrl(settings.baseUrl);
387 | const instance_url = Private.getTensorboardInstanceRootUrl(settings.baseUrl);
388 | return ServerConnection.makeRequest(service_url, {}, settings)
389 | .then(response => {
390 | if (response.status !== 200) {
391 | throw new ServerConnection.ResponseError(response);
392 | }
393 | return response.json();
394 | })
395 | .then((data: Tensorboard.IModel[]) => {
396 | if (!Array.isArray(data)) {
397 | throw new Error('Invalid tensorboard data');
398 | }
399 | // Update the local data store.
400 | const urls = toArray(
401 | map(data, item => {
402 | return URLExt.join(instance_url, item.name);
403 | })
404 | );
405 | each(Object.keys(Private.running), runningUrl => {
406 | if (urls.indexOf(runningUrl) === -1) {
407 | const tensorboard = Private.running[runningUrl];
408 | tensorboard.dispose();
409 | }
410 | });
411 | return data;
412 | });
413 | }
414 |
415 | /**
416 | * Shut down a tensorboard by name.
417 | *
418 | * @param name - Then name of the target tensorboard.
419 | *
420 | * @param settings - The server settings to use.
421 | *
422 | * @returns A promise that resolves when the tensorboard is shut down.
423 | */
424 | export function shutdown(name: string, settings?: ServerConnection.ISettings): Promise {
425 | settings = settings || ServerConnection.makeSettings();
426 | const url = Private.getTensorboardUrl(settings.baseUrl, name);
427 | const init = { method: 'DELETE' };
428 | return ServerConnection.makeRequest(url, init, settings).then(response => {
429 | if (response.status === 404) {
430 | return response.json().then(data => {
431 | Private.killTensorboard(url);
432 | });
433 | }
434 | if (response.status !== 204) {
435 | throw new ServerConnection.ResponseError(response);
436 | }
437 | Private.killTensorboard(url);
438 | });
439 | }
440 |
441 | /**
442 | * Shut down all tensorboards.
443 | *
444 | * @param settings - The server settings to use.
445 | *
446 | * @returns A promise that resolves when all the tensorboards are shut down.
447 | */
448 | export function shutdownAll(settings?: ServerConnection.ISettings): Promise {
449 | settings = settings || ServerConnection.makeSettings();
450 | return listRunning(settings).then(running => {
451 | each(running, s => {
452 | shutdown(s.name, settings);
453 | });
454 | });
455 | }
456 |
457 | /**
458 | * According tensorboard's name to get tensorboard's url.
459 | */
460 | export function getUrl(name: string, settings?: ServerConnection.ISettings): string {
461 | settings = settings || ServerConnection.makeSettings();
462 | return Private.getTensorboardInstanceUrl(settings.baseUrl, name);
463 | }
464 | }
465 |
466 | /**
467 | * A namespace for private data.
468 | */
469 | namespace Private {
470 | /**
471 | * A mapping of running tensorboards by url.
472 | */
473 | export const running: { [key: string]: DefaultTensorboard } = Object.create(null);
474 |
475 | /**
476 | * Get the url for a tensorboard.
477 | */
478 | export function getTensorboardUrl(baseUrl: string, name: string): string {
479 | return URLExt.join(baseUrl, TENSORBOARD_SERVICE_URL, name);
480 | }
481 |
482 | /**
483 | * Get the url for a tensorboard.
484 | */
485 | export function getTensorboardStaticConfigUrl(baseUrl: string): string {
486 | return URLExt.join(baseUrl, TENSORBOARD_STATIC_CONFIG_URL);
487 | }
488 |
489 | /**
490 | * Get the base url.
491 | */
492 | export function getServiceUrl(baseUrl: string): string {
493 | return URLExt.join(baseUrl, TENSORBOARD_SERVICE_URL);
494 | }
495 |
496 | /**
497 | * Kill tensorboard by url.
498 | */
499 | export function killTensorboard(url: string): void {
500 | // Update the local data store.
501 | if (Private.running[url]) {
502 | const tensorboard = Private.running[url];
503 | tensorboard.dispose();
504 | }
505 | }
506 |
507 | export function getTensorboardInstanceRootUrl(baseUrl: string): string {
508 | return URLExt.join(baseUrl, TENSORBOARD_URL);
509 | }
510 |
511 | export function getTensorboardInstanceUrl(baseUrl: string, name: string): string {
512 | return URLExt.join(baseUrl, TENSORBOARD_URL, name);
513 | }
514 | }
515 |
--------------------------------------------------------------------------------
/src/biz/tab.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { Button } from '@blueprintjs/core/lib/esm/components/button/buttons';
3 | import { MenuItem } from '@blueprintjs/core/lib/esm/components/menu/menuItem';
4 | import { InputGroup } from '@blueprintjs/core/lib/esm/components/forms/inputGroup';
5 | import { Switch } from '@blueprintjs/core/lib/esm/components/forms/controls';
6 | import { Tag } from '@blueprintjs/core/lib/esm/components/tag/tag';
7 | import classNames from 'classnames';
8 | import { Select } from '@blueprintjs/select';
9 | import { showDialog, Dialog } from '@jupyterlab/apputils';
10 | import { Loading } from './loading';
11 | import { Tensorboard } from '../tensorboard';
12 | import { TensorboardManager } from '../manager';
13 | import { DEFAULT_REFRESH_INTERVAL } from '../consts';
14 | import { copyToClipboard } from '../utils/copy';
15 |
16 | export interface TensorboardCreatorProps {
17 | disable: boolean;
18 | getCWD: () => string;
19 | openDoc: () => void;
20 | startTensorBoard: (
21 | logDir: string,
22 | reloadInterval: number,
23 | enableMultiLog: boolean,
24 | additionalArgs: string
25 | ) => void;
26 | }
27 |
28 | const TensorboardCreator = (props: TensorboardCreatorProps): JSX.Element => {
29 | const [logDir, setLogDir] = useState(props.getCWD());
30 | const [reloadInterval, setReloadInterval] = useState(DEFAULT_REFRESH_INTERVAL);
31 | const [additionalArgs, setAdditionalArgs] = useState('');
32 | const [enableReloadInterval, setEnableReloadInterval] = useState(false);
33 | const [enableMultiLog, setEnableMultiLog] = useState(false);
34 |
35 | return (
36 |
37 |
38 |
39 |
40 | Log Dir
41 |
42 | {
48 | setLogDir(e.target.value);
49 | if (e.target.value.includes(',')) {
50 | setEnableMultiLog(true);
51 | }
52 | }}
53 | />
54 |
55 |
56 | {
60 | setEnableMultiLog(!enableMultiLog);
61 | }}
62 | labelElement={
63 |
64 | Multi LogDir
65 |
66 | }
67 | />
68 | {
72 | setEnableReloadInterval(!enableReloadInterval);
73 | }}
74 | labelElement={
75 |
76 | Reload Interval
77 |
78 | }
79 | />
80 | {enableReloadInterval && (
81 | {
87 | setReloadInterval(Number(e.target.value));
88 | }}
89 | rightElement={s }
90 | type="number"
91 | />
92 | )}
93 |
94 |
95 |
{
103 | setAdditionalArgs(e.target.value);
104 | }}
105 | />
106 |
107 | {
112 | props.startTensorBoard(
113 | logDir,
114 | enableReloadInterval ? reloadInterval : 0,
115 | enableMultiLog,
116 | additionalArgs
117 | );
118 | }}
119 | disabled={props.disable}
120 | >
121 | Create TensorBoard
122 |
123 |
124 |
125 | {
130 | props.openDoc();
131 | }}
132 | >
133 | Document
134 |
135 |
136 | );
137 | };
138 |
139 | export interface TensorboardTabReactProps {
140 | setWidgetName?: (name: string) => void;
141 | createdModelName?: string;
142 | tensorboardManager: TensorboardManager;
143 | closeWidget: () => void;
144 | getCWD: () => string;
145 | openTensorBoard: (modelName: string, copy: boolean) => void;
146 | openDoc: () => void;
147 | update: () => void;
148 | updateCurrentModel: (model: Tensorboard.IModel | null) => void;
149 | startNew: (
150 | logdir: string,
151 | refreshInterval: number,
152 | enableMultiLog: boolean,
153 | additionalArgs: string,
154 | options?: Tensorboard.IOptions
155 | ) => Promise;
156 | }
157 |
158 | const ModelSelector = Select.ofType();
159 |
160 | const useRefState = (initValue: T): [T, { current: T }, (value: T) => void] => {
161 | const [value, setValue] = useState(initValue);
162 | const valueRef = useRef(value);
163 | const updateValue = (value: T) => {
164 | setValue(value);
165 | valueRef.current = value;
166 | };
167 | return [value, valueRef, updateValue];
168 | };
169 |
170 | export const TensorboardTabReact = (props: TensorboardTabReactProps): JSX.Element => {
171 | const [ready, readyRef, updateReady] = useRefState(false);
172 |
173 | const [createPending, createPendingRef, updateCreatePending] = useRefState(false);
174 | const [reloadPending, reloadPendingRef, updateReloadPending] = useRefState(false);
175 |
176 | const [showNewRow, setShowNewRow] = useState(false);
177 | const [showListStatus, setShowListStatus] = useState(false);
178 |
179 | const [currentTensorBoard, setCurrentTensorBoard] = useState(null);
180 | const currentTensorBoardRef = useRef(currentTensorBoard);
181 | const updateCurrentTensorBoard = (model: Tensorboard.IModel | null) => {
182 | props.updateCurrentModel(model);
183 | setCurrentTensorBoard(model);
184 | currentTensorBoardRef.current = model;
185 | };
186 |
187 | const [runningTensorBoards, setRunningTensorBoards] = useState([]);
188 |
189 | // currently inactive
190 | const [notActiveError, setNotActiveError] = useState(false);
191 |
192 | const currentLoading = reloadPending || createPending;
193 |
194 | const refreshRunning = () => {
195 | if (createPendingRef.current || reloadPendingRef.current) {
196 | return;
197 | }
198 | props.tensorboardManager.refreshRunning().then(() => {
199 | const runningTensorboards = [...props.tensorboardManager.running()];
200 |
201 | // hint: Using runningTensorboards directly may cause setState to fail to respond
202 | const modelList = [];
203 | for (const model of runningTensorboards) {
204 | modelList.push(model);
205 | }
206 | setRunningTensorBoards(modelList);
207 |
208 | if (readyRef.current) {
209 | // 如果不是第一次了
210 | if (currentTensorBoardRef.current) {
211 | if (!modelList.find(model => model.name === currentTensorBoardRef.current!.name)) {
212 | setNotActiveError(true);
213 | }
214 | } else {
215 | // do nothing
216 | // Maybe not at the beginning, the user planned to create a new one later, and then did not create a new one. At this time, he found that there was
217 | }
218 |
219 | return;
220 | }
221 |
222 | const model = props.createdModelName
223 | ? modelList.find(model => model.name === props.createdModelName)
224 | : null;
225 |
226 | if (model) {
227 | // if createdModelName exist,maybe from Sidebar kernels tab
228 | updateCurrentTensorBoard(model);
229 | setShowListStatus(true);
230 | if (props.setWidgetName) {
231 | props.setWidgetName(`${model.name}:` + props.tensorboardManager.formatDir(model.logdir));
232 | }
233 | } else {
234 | setShowNewRow(true);
235 | setShowListStatus(false);
236 | }
237 | updateReady(true);
238 | });
239 | };
240 |
241 | const startTensorBoard = (
242 | logDir: string,
243 | reloadInterval: number,
244 | enableMultiLog: boolean,
245 | additionalArgs: string
246 | ) => {
247 | if (Number.isNaN(reloadInterval) || reloadInterval < 0) {
248 | return showDialog({
249 | title: 'Param Check Failed',
250 | body: 'reloadInterval should > 0 when enabled',
251 | buttons: [Dialog.okButton()]
252 | });
253 | }
254 | updateCreatePending(true);
255 | const currentName = currentTensorBoard?.name;
256 | props
257 | .startNew(logDir, reloadInterval, enableMultiLog, additionalArgs)
258 | .then(tb => {
259 | if (currentName === tb.model.name) {
260 | showDialog({
261 | body: 'Existing tensorBoard for the logDir will be reused directly',
262 | buttons: [Dialog.okButton()]
263 | });
264 | }
265 | if (props.setWidgetName) {
266 | props.setWidgetName(
267 | `${tb.model.name}:` + props.tensorboardManager.formatDir(tb.model.logdir)
268 | );
269 | }
270 | updateCurrentTensorBoard(tb.model);
271 | updateCreatePending(false);
272 | refreshRunning();
273 |
274 | setShowListStatus(true);
275 | setShowNewRow(false);
276 | })
277 | .catch(e => {
278 | updateCreatePending(false);
279 |
280 | const getMessage = () =>
281 | e.response.json().then((json: any) => {
282 | return json.message as string;
283 | });
284 | const defaultMessage = 'Start TensorBoard internal error';
285 |
286 | getMessage()
287 | .then((msg: string) => {
288 | showDialog({
289 | body: msg || defaultMessage,
290 | buttons: [Dialog.okButton()]
291 | });
292 | })
293 | .catch(() => {
294 | showDialog({
295 | body: defaultMessage,
296 | buttons: [Dialog.okButton()]
297 | });
298 | });
299 | });
300 | };
301 |
302 | // hint: Because we are simulating reload here, the tab component cannot listen to runningChanged
303 | const reloadTensorBoard = () => {
304 | // There was no reload in the world, so I had to stop and restart to simulate reload
305 | if (!currentTensorBoard) {
306 | return;
307 | }
308 | updateReloadPending(true);
309 | updateCurrentTensorBoard(null);
310 | const reloadInterval =
311 | typeof currentTensorBoard.reload_interval === 'number'
312 | ? currentTensorBoard.reload_interval
313 | : DEFAULT_REFRESH_INTERVAL;
314 | const currentLogDir = currentTensorBoard.logdir;
315 | const enableMultiLog = currentTensorBoard.enable_multi_log;
316 | const additionalArgs = currentTensorBoard.additional_args;
317 |
318 | const errorCallback = (e: any) => {
319 | showDialog({
320 | title: 'TensorBoard Reload Error',
321 | body: 'The panel has been closed, you can reopen to create new',
322 | buttons: [Dialog.okButton()]
323 | });
324 | props.closeWidget();
325 | };
326 |
327 | try {
328 | props.tensorboardManager
329 | .shutdown(currentTensorBoard.name)
330 | .then(res => {
331 | props.tensorboardManager
332 | .startNew(currentLogDir, reloadInterval, enableMultiLog, additionalArgs)
333 | .then(res => {
334 | refreshRunning();
335 | updateReloadPending(false);
336 | updateCurrentTensorBoard(currentTensorBoard);
337 | })
338 | .catch(e => {
339 | errorCallback(e);
340 | });
341 | })
342 | .catch(e => {
343 | errorCallback(e);
344 | });
345 | } catch (e) {
346 | errorCallback(e);
347 | }
348 | };
349 |
350 | const destroyTensorBoard = () => {
351 | if (!currentTensorBoard) {
352 | return;
353 | }
354 | props.tensorboardManager.shutdown(currentTensorBoard.name).then(res => {
355 | props.closeWidget();
356 | });
357 | };
358 |
359 | const copyTensorBoard = () => {
360 | if (!currentTensorBoard) {
361 | return;
362 | }
363 | props.openTensorBoard(currentTensorBoard.name, true);
364 | };
365 |
366 | const getShowName = (model: Tensorboard.IModel) => {
367 | const formattedDir = props.tensorboardManager.formatDir(model.logdir);
368 | return `${model.name} - ${formattedDir}`;
369 | };
370 |
371 | const changeModel = (model: Tensorboard.IModel) => {
372 | if (currentTensorBoard?.name === model.name) {
373 | return;
374 | }
375 | if (props.setWidgetName) {
376 | props.setWidgetName(`${model.name}:` + props.tensorboardManager.formatDir(model.logdir));
377 | }
378 | setCurrentTensorBoard(model);
379 | };
380 |
381 | const toggleNewRow = () => {
382 | setShowNewRow(!showNewRow);
383 | };
384 |
385 | const openInNewTab = () => {
386 | if (!currentTensorBoard) {
387 | return;
388 | }
389 | window.open(Tensorboard.getUrl(currentTensorBoard.name));
390 | };
391 |
392 | useEffect(() => {
393 | refreshRunning();
394 | const refreshIntervalId = setInterval(refreshRunning, 30 * 1000);
395 | return () => {
396 | clearInterval(refreshIntervalId);
397 | };
398 | }, []);
399 |
400 | const getBlankContent = () => {
401 | if (!ready) {
402 | return ;
403 | } else if (createPending) {
404 | return (
405 |
409 | );
410 | } else if (reloadPending) {
411 | return (
412 |
416 | );
417 | } else {
418 | return (
419 |
420 |
421 | No instance for current directory yet, please create a new TensorBoard
422 |
423 |
424 |
425 | If the selected log directory has too much content, tensorboard initialization may
426 | take a long time, during which jupyter will get stuck
427 |
428 |
429 |
430 | );
431 | }
432 | };
433 |
434 | return (
435 |
436 | {ready && (
437 |
442 |
443 |
444 |
445 |
452 |
{
456 | return (
457 |
458 | );
459 | }}
460 | items={runningTensorBoards}
461 | onItemSelect={model => changeModel(model)}
462 | filterable={false}
463 | activeItem={currentTensorBoard}
464 | disabled={currentLoading}
465 | >
466 |
472 | {currentTensorBoard ? getShowName(currentTensorBoard) : 'NONE'}
473 |
474 | }
475 | small={true}
476 | />
477 |
478 |
485 | {currentTensorBoard && currentTensorBoard.enable_multi_log && (
486 |
Multi LogDir
487 | )}
488 | {currentTensorBoard && (
489 |
490 | reload interval(s): {currentTensorBoard?.reload_interval || 'Never'}
491 |
492 | )}
493 | {currentTensorBoard?.additional_args && (
494 | <>
495 |
496 | {currentTensorBoard?.additional_args}
497 |
498 |
{
503 | copyToClipboard(currentTensorBoard?.additional_args);
504 | }}
505 | />
506 | >
507 | )}
508 |
509 |
510 |
511 |
512 |
519 | Reload
520 |
521 |
529 | Destroy
530 |
531 |
538 | Duplicate
539 |
540 |
548 | New..
549 |
550 |
551 |
552 |
561 |
562 | )}
563 |
564 | {currentTensorBoard && (
565 |
571 | )}
572 | {!currentTensorBoard && (
573 |
{getBlankContent()}
574 | )}
575 | {notActiveError && (
576 |
577 |
578 | Current Tensorboard is not active. Please select others or create a new one.
579 |
580 |
581 | )}
582 |
583 |
584 | );
585 | };
586 |
587 | export declare namespace TensorboardTab {
588 | /**
589 | * Options of the tensorboard widget.
590 | */
591 | interface IOptions {
592 | /**
593 | * The model of tensorboard instance.
594 | */
595 | readonly model: Tensorboard.IModel;
596 | }
597 | }
598 |
--------------------------------------------------------------------------------