From bcb77d979d817e1e609adb4d007bbbcc3f61efbd Mon Sep 17 00:00:00 2001 From: Tom van der Lee Date: Thu, 30 Dec 2021 09:51:00 +0100 Subject: Prepare for github --- .github/workflows/python-publish.yml | 40 + .gitignore | 271 ++++++ LICENSE | 29 + MANIFEST.in | 1 + README.rst | 30 + build_requirements.txt | 3 + index.html | 10 + package.json | 26 + pyproject.toml | 6 + requirements.txt | 4 + setup.cfg | 14 + src/_reset.scss | 48 + src/components/App/App.module.scss | 55 ++ src/components/App/App.tsx | 68 ++ src/components/Content/Content.module.scss | 51 ++ src/components/Content/Content.tsx | 100 +++ src/components/Details/Details.module.scss | 84 ++ src/components/Details/Details.tsx | 148 +++ .../RequestSummary/RequestSummary.module.scss | 24 + src/components/RequestSummary/RequestSummary.tsx | 33 + src/hooks/useRequests.tsx | 77 ++ src/index.scss | 16 + src/index.tsx | 7 + src/utils.ts | 4 + tsconfig.json | 17 + ttun/__init__.py | 0 ttun/__main__.py | 62 ++ ttun/client.py | 109 +++ ttun/inspect_server.py | 71 ++ ttun/pubsub.py | 35 + ttun/settings.py | 31 + ttun/types.py | 24 + yarn.lock | 995 +++++++++++++++++++++ 33 files changed, 2493 insertions(+) create mode 100644 .github/workflows/python-publish.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 build_requirements.txt create mode 100644 index.html create mode 100644 package.json create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 src/_reset.scss create mode 100644 src/components/App/App.module.scss create mode 100644 src/components/App/App.tsx create mode 100644 src/components/Content/Content.module.scss create mode 100644 src/components/Content/Content.tsx create mode 100644 src/components/Details/Details.module.scss create mode 100644 src/components/Details/Details.tsx create mode 100644 src/components/RequestSummary/RequestSummary.module.scss create mode 100644 src/components/RequestSummary/RequestSummary.tsx create mode 100644 src/hooks/useRequests.tsx create mode 100644 src/index.scss create mode 100644 src/index.tsx create mode 100644 src/utils.ts create mode 100644 tsconfig.json create mode 100644 ttun/__init__.py create mode 100644 ttun/__main__.py create mode 100644 ttun/client.py create mode 100644 ttun/inspect_server.py create mode 100644 ttun/pubsub.py create mode 100644 ttun/settings.py create mode 100644 ttun/types.py create mode 100644 yarn.lock diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..9246d7e --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,40 @@ +name: Release + +on: + push: + tags: + - 'v*' + pull_request: + branches: + - 'main' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + - name: Set up Node + uses: actions/setup-node@v2 + with: + node-version: '16' + - name: Install node dependencies + run: | + yarn install + - name: Install python dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: | + yarn build + python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + if: github.event_name != 'pull_request' + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3e66e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,271 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python,react,pycharm+all,yarn +# Edit at https://www.toptal.com/developers/gitignore?templates=python,react,pycharm+all,yarn + +### PyCharm+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +### react ### +.DS_* +logs +**/*.backup.* +**/*.back.* + +node_modules +bower_components + +*.sublime* + +psd +thumb +sketch + +### yarn ### +# https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored + +.yarn/* +!.yarn/releases +!.yarn/plugins +!.yarn/sdks +!.yarn/versions + +# if you are NOT using Zero-installs, then: +# comment the following lines +!.yarn/cache + +# and uncomment the following lines +# .pnp.* + +# End of https://www.toptal.com/developers/gitignore/api/python,react,pycharm+all,yarn + +dist +ttun/staticfiles diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a83ceb2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, Tom van der Lee +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..0dd72f5 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include ttun/staticfiles * diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..16538d9 --- /dev/null +++ b/README.rst @@ -0,0 +1,30 @@ +=========== +TTUN Client +=========== + +|Deploy| + +.. |Deploy| image:: https://github.com/tomvanderlee/ttun-client/actions/workflows/python-publish.yml/badge.svg + :target: https://github.com/tomvanderlee/ttun-client/actions/workflows/python-publish.yml + +Expose a localport via you selfhosted `TTUN Server `_ + +Installing +---------- + +:: + + pip install ttun + +Developing +---------- + +1. Create and activate a python 3.10 virtual environment + +2. Install requirements:: + + pip install -r requirements.txt + +3. Run:: + + python -m ttun diff --git a/build_requirements.txt b/build_requirements.txt new file mode 100644 index 0000000..03befb7 --- /dev/null +++ b/build_requirements.txt @@ -0,0 +1,3 @@ +setuptools +build +setuptools-git-versioning diff --git a/index.html b/index.html new file mode 100644 index 0000000..a09a4ed --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + + React Vite Micro Typescript App + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..f49df56 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "client", + "version": "0.1.0", + "description": "", + "scripts": { + "start": "vite", + "build": "vite build --outDir ttun/staticfiles", + "typecheck": "tsc --noEmit", + "typewatch": "tsc --noEmit --watch" + }, + "license": "MIT", + "dependencies": { + "classnames": "^2.3.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-json-view": "^1.21.3" + }, + "devDependencies": { + "@types/react": "^16.9.34", + "@types/react-dom": "^16.9.7", + "babel-preset-nano-react-app": "^0.1.0", + "sass": "^1.45.2", + "typescript": "^3.8.3", + "vite": "^2.6.7" + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ba7a2d8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning", ] +build-backend = "setuptools.build_meta" + +[tool.setuptools-git-versioning] +enabled = true diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..04b1cf3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +-r build_requirements.txt +websockets ~= 10.0 +aiohttp ~= 3.8 +appdirs ~= 1.4 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..e0b1a3a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,14 @@ +[metadata] +name = ttun + +[options] +include_package_data = True +packages = ttun +install_requires = + websockets ~= 10.0 + aiohttp ~= 3.8 + appdirs ~= 1.4 + +[options.entry_points] +console_scripts = + ttun = ttun.__main__:main diff --git a/src/_reset.scss b/src/_reset.scss new file mode 100644 index 0000000..ed11813 --- /dev/null +++ b/src/_reset.scss @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/src/components/App/App.module.scss b/src/components/App/App.module.scss new file mode 100644 index 0000000..2036eb9 --- /dev/null +++ b/src/components/App/App.module.scss @@ -0,0 +1,55 @@ +.app { + display: flex; + flex-flow: column nowrap; + grid-template-rows: auto 1fr; + height: 100vh; + overflow: hidden; +} + +.main { + display: flex; + flex-flow: row nowrap; + flex-grow: 1; + overflow: hidden; +} + +.header { + font-size: 1.2em; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + background: black; + color: white; + padding: 1em; + + a { + color: white; + } +} + +.sidebar { + width: calc((6 / 16) * 100%); + height: 100%; + grid-area: sidebar; + border-right: 1px solid black; + overflow-y: auto; + + li { + border-bottom: 1px solid gray; + } +} + +.details { + width: calc((10 / 16) * 100%); + overflow: hidden; + height: 100%; +} + +.noRequest, .noRequestSelected { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx new file mode 100644 index 0000000..3a7fe9b --- /dev/null +++ b/src/components/App/App.tsx @@ -0,0 +1,68 @@ +import * as React from "react"; +import useRequests, {RequestResponse} from "../../hooks/useRequests"; +import {useEffect, useMemo, useState} from "react"; + +import styles from './App.module.scss'; +import Details from "../Details/Details"; +import RequestSummary from "../RequestSummary/RequestSummary"; +import {getHost} from "../../utils"; + +interface Config { + url: string +} + +export default function App() { + const [config, setConfig]= useState(null) + useEffect(() => { + fetch(`http://${getHost()}/config/`) + .then(response => response.json() as Promise) + .then(setConfig) + }, []) + + const requests = useRequests(); + const [selectedRequestIndex, setSelectedRequestIndex] = useState(null); + const selectedRequest = useMemo(() => ( + selectedRequestIndex === null + ? null + : requests[selectedRequestIndex] + ), [selectedRequestIndex, requests]); + + return config && ( +
+
+ TTUN + {config.url} +
+
+
    + { + requests.length > 0 + ? requests.slice(0).reverse().map((requestResponse, index) => ( +
  • setSelectedRequestIndex(requests.length - index - 1)} key={`request-${index}`}> + +
  • + )) + : ( +
    +

    No requests

    +
    + ) + } +
+ +
+ { + selectedRequest !== null + ? ( +
+ ) : ( +
+

Select a request to inspect it

+
+ ) + } +
+
+
+ ); +} diff --git a/src/components/Content/Content.module.scss b/src/components/Content/Content.module.scss new file mode 100644 index 0000000..8908516 --- /dev/null +++ b/src/components/Content/Content.module.scss @@ -0,0 +1,51 @@ +.content { + width: 100%; + height: 100%; + display: flex; + flex-flow: column nowrap; + overflow: hidden; +} + +.header { + flex-shrink: 0; + flex-grow: 0; + width: 100%; + display: flex; + padding: 0.5em; + background-color: black; + color: white; +} + +.body { + flex-grow: 1; + flex-shrink: 1; + overflow-y: auto; + + pre { + width: 100%; + height: 100%; + padding: 1em; + font-family: monospace; + overflow: auto; + } + + iframe { + height: 100%; + width: 100%; + } +} + +.renderError { + width: 100%; + height: 100%; + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + + a { + margin-top: 1em; + text-decoration: underline; + color: blue; + } +} diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx new file mode 100644 index 0000000..a7b5949 --- /dev/null +++ b/src/components/Content/Content.tsx @@ -0,0 +1,100 @@ +import styles from "~components/Details/Details.module.scss"; +import * as React from "react"; +import classNames from "classnames"; +import {RequestPayload, ResponsePayload} from "~hooks/useRequests"; +import { + Dispatch, + forwardRef, SetStateAction, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState +} from "react"; +import ReactJson from 'react-json-view'; +import styles from './Content.module.scss'; + +interface ContentProps { + data: RequestPayload | ResponsePayload + setRaw: Dispatch> + raw?: boolean +} + +export default function Content({ raw, setRaw, ...props }: ContentProps): JSX.Element { + return ( +
+
+ setRaw(!raw)}/> + +
+
+ {(() => { + try { + return ContentBody({ ...props, raw }) + } catch { + return ( +
+

Body could not be rendered

+ setRaw(true)}>View raw +
+ ) + } + })()} +
+
+ ) +}; + +function ContentBody({ data, raw = false }: Omit) { + const contentType = useMemo(() => { + if (raw) { + return ''; + } + + const [_, type] = ( + Object + .entries(data.headers) + .find(([key]) => key.toLowerCase() === 'content-type') + ); + + return type.toLowerCase().split(';')[0]; + }, [data, raw]); + + if (raw) { + return
{atob(data.body)}
+ } + + if (['application/pdf', 'text/html'].includes(contentType)) { + return