diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 801f016ee..b07bc57f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Tests +name: tests on: push: branches: diff --git a/.github/workflows/deploy-beta.yml b/.github/workflows/deploy-beta.yml deleted file mode 100644 index 1508b2f9b..000000000 --- a/.github/workflows/deploy-beta.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Deploy to beta - -on: - push: - branches: - - master - workflow_dispatch: - inputs: - name: - description: "Just a trigger" - required: false - default: "Hello" - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Install ssh key - run: mkdir -p ~/.ssh && echo "${{ secrets.BETA_SSH_KEY }}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa - - - name: Start deploy - run: ssh -o "StrictHostKeyChecking=no" root@${{ secrets.BETA_IP }} '(cd /root; bash infrastructure/ubuntu/ubuntu_20_04_lts.sh > /var/log/deploy.log 2>&1) >&- 2>&- <&- &' - diff --git a/.github/workflows/package-extension.yml b/.github/workflows/package-extension.yml new file mode 100644 index 000000000..d9e31243d --- /dev/null +++ b/.github/workflows/package-extension.yml @@ -0,0 +1,143 @@ +name: package extension (deb) + +on: + workflow_dispatch: + inputs: + packageVersion: + default: "2.2.0" + +jobs: + build: + strategy: + matrix: + os: ["ubuntu-22.04"] + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: pgml-extension + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Validate cargo is working + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: version + - name: Install dependencies + env: + DEBIAN_FRONTEND: noninteractive + TZ: Etc/UTC + run: | + git submodule update --init --recursive + + # PostgreSQL apt + curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null + sudo sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + + sudo apt-get install -y software-properties-common + sudo add-apt-repository ppa:apt-fast/stable --yes + sudo add-apt-repository ppa:deadsnakes/ppa --yes + sudo apt update + + sudo apt-get install -y apt-fast + sudo apt-get update && sudo apt-fast install -y \ + libopenblas-dev \ + libssl-dev \ + bison \ + flex \ + pkg-config \ + cmake \ + libreadline-dev \ + libz-dev \ + curl \ + lsb-release \ + tzdata \ + sudo \ + cmake \ + libpq-dev \ + libclang-dev \ + wget \ + postgresql-15 \ + postgresql-14 \ + postgresql-13 \ + postgresql-12 \ + postgresql-11 \ + postgresql-server-dev-15 \ + postgresql-server-dev-14 \ + postgresql-server-dev-13 \ + postgresql-server-dev-12 \ + postgresql-server-dev-11 \ + lsb-release \ + python3.10 \ + python3-pip \ + libpython3.10-dev \ + python3.10-dev \ + ruby + + curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.4/deb-s3-0.11.4.gem + sudo gem install deb-s3-0.11.4.gem + dpkg-deb --version + - name: Install pgx + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: install + args: cargo-pgx --version "0.7.1" + - name: pgx init + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: init --pg11=/usr/lib/postgresql/11/bin/pg_config --pg12=/usr/lib/postgresql/12/bin/pg_config --pg13=/usr/lib/postgresql/13/bin/pg_config --pg14=/usr/lib/postgresql/14/bin/pg_config --pg15=/usr/lib/postgresql/15/bin/pg_config + - name: Build Postgres 11 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/11/bin/pg_config + - name: Build Postgres 12 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/12/bin/pg_config + - name: Build Postgres 13 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/13/bin/pg_config + - name: Build Postgres 14 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/14/bin/pg_config + - name: Build Postgres 15 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/15/bin/pg_config + - name: Build debs + env: + AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }} + run: | + for pg in {11..15}; do + export PACKAGE_VERSION=${{ inputs.packageVersion }} + export PGVERSION=${pg} + export ARCH=amd64 + + mkdir -p target/release/pgml-pg${pg}/DEBIAN + (cat control | envsubst) > target/release/pgml-pg${pg}/DEBIAN/control + dpkg-deb --root-owner-group --build target/release/pgml-pg${pg} postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu22.04-amd64.deb + + deb-s3 upload \ + --bucket apt.postgresml.org \ + postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu22.04-amd64.deb \ + --codename $(lsb_release -cs) + done diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 1c3625db3..5df861d6f 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -1,4 +1,4 @@ -name: Publish Documentation +name: publish docs on: push: @@ -18,4 +18,4 @@ jobs: python-version: 3.x - run: pip install -r requirements.txt - run: mkdocs gh-deploy --force - \ No newline at end of file + diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index ead0c1aeb..000000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Upload Python Package - -on: - release: - types: [published] - -jobs: - deploy: - runs-on: ubuntu-latest - defaults: - run: - working-directory: pgml-extension - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build twine wheel - - name: Build package - run: python -m build - - name: Publish package - run: twine upload -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} dist/* diff --git a/.gitignore b/.gitignore index 4434b9069..8d5ed3336 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,4 @@ cython_debug/ # local scratch pad scratch.sql +scratch.py diff --git a/docker-compose.yml b/docker-compose.yml index 2852f8788..3d49c620b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,10 +2,10 @@ version: "3" services: postgres: healthcheck: - test: psql -c 'SELECT 1' -U postgres -h 127.0.0.1 - interval: 2s - retries: 3 - timeout: 2s + test: [ "CMD-SHELL", "pg_isready" ] + interval: 1s + timeout: 5s + retries: 100 build: context: ./pgml-extension/ dockerfile: Dockerfile.local @@ -16,7 +16,8 @@ services: - infinity dashboard: depends_on: - - postgres + postgres: + condition: service_healthy build: context: ./pgml-dashboard/ dockerfile: Dockerfile @@ -25,9 +26,7 @@ services: environment: ROCKET_ADDRESS: 0.0.0.0 DATABASE_URL: postgres://postgres:postgres@postgres:5432/pgml_development - command: - - cargo - - run + command: bash -c "sqlx migrate run && cargo run" docs: build: context: ./pgml-docs/ diff --git a/pgml-dashboard/.gitignore b/pgml-dashboard/.gitignore index 485dee64b..706fd07fa 100644 --- a/pgml-dashboard/.gitignore +++ b/pgml-dashboard/.gitignore @@ -1 +1,2 @@ .idea +.vscode diff --git a/pgml-dashboard/Cargo.lock b/pgml-dashboard/Cargo.lock index 4d9318ea3..dbbe5015c 100644 --- a/pgml-dashboard/Cargo.lock +++ b/pgml-dashboard/Cargo.lock @@ -49,7 +49,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom", + "getrandom 0.2.8", "once_cell", "version_check", ] @@ -354,6 +354,12 @@ dependencies = [ "xdg", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cookie" version = "0.16.2" @@ -365,7 +371,7 @@ dependencies = [ "hkdf", "hmac", "percent-encoding", - "rand", + "rand 0.8.5", "sha2", "subtle", "time 0.3.17", @@ -437,10 +443,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "csv-async" version = "1.2.5" @@ -451,7 +484,7 @@ dependencies = [ "cfg-if", "csv-core", "futures", - "itoa", + "itoa 1.0.5", "ryu", "serde", ] @@ -518,6 +551,19 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "devise" version = "0.3.1" @@ -582,20 +628,44 @@ dependencies = [ "winapi", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dotenvy" version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "ego-tree" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" + [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -dependencies = [ - "serde", -] [[package]] name = "encoding_rs" @@ -709,6 +779,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.26" @@ -809,6 +889,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generator" version = "0.7.2" @@ -832,6 +921,26 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -962,6 +1071,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "http" version = "0.2.8" @@ -970,7 +1093,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.5", ] [[package]] @@ -1011,7 +1134,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -1120,6 +1243,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.5" @@ -1217,6 +1346,26 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1226,6 +1375,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "md-5" version = "0.10.5" @@ -1294,6 +1449,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "7.1.3" @@ -1529,22 +1696,116 @@ dependencies = [ [[package]] name = "pgml-dashboard" -version = "0.1.1" +version = "2.2.0" dependencies = [ "anyhow", "bigdecimal", "chrono", "comrak", "csv-async", + "dotenv", "parking_lot 0.12.1", - "rand", + "rand 0.8.5", "rocket", "sailfish", + "scraper", "serde_json", "sqlx", "tokio", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1595,6 +1856,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1619,6 +1886,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.51" @@ -1659,6 +1932,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.5" @@ -1666,8 +1953,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1677,7 +1974,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1686,7 +1992,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.8", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1704,7 +2028,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom", + "getrandom 0.2.8", "redox_syscall", "thiserror", ] @@ -1801,7 +2125,7 @@ dependencies = [ "num_cpus", "parking_lot 0.12.1", "pin-project-lite", - "rand", + "rand 0.8.5", "ref-cast", "rocket_codegen", "rocket_http", @@ -1860,6 +2184,15 @@ dependencies = [ "uncased", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.36.8" @@ -1972,6 +2305,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scraper" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7cb4dae083699a22a65aa9d2699c27f525e35dffaec38b10801e958ed4cf27" +dependencies = [ + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "matches", + "selectors", + "smallvec", + "tendril", +] + [[package]] name = "scratch" version = "1.0.3" @@ -1988,6 +2337,32 @@ dependencies = [ "untrusted", ] +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + [[package]] name = "serde" version = "1.0.152" @@ -2014,11 +2389,21 @@ version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a" dependencies = [ - "itoa", + "itoa 1.0.5", "ryu", "serde", ] +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.5" @@ -2065,6 +2450,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.7" @@ -2151,7 +2542,7 @@ dependencies = [ "hkdf", "hmac", "indexmap", - "itoa", + "itoa 1.0.5", "libc", "log", "md-5", @@ -2160,7 +2551,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rand", + "rand 0.8.5", "rustls", "rustls-pemfile", "serde", @@ -2189,11 +2580,9 @@ dependencies = [ "dotenvy", "either", "heck", - "hex", "once_cell", "proc-macro2", "quote", - "serde", "serde_json", "sha2", "sqlx-core", @@ -2222,6 +2611,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.5.3" @@ -2231,6 +2626,32 @@ dependencies = [ "loom", ] +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot 0.12.1", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + [[package]] name = "stringprep" version = "0.1.2" @@ -2302,6 +2723,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2321,6 +2753,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + [[package]] name = "thiserror" version = "1.0.38" @@ -2367,7 +2805,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa", + "itoa 1.0.5", "serde", "time-core", "time-macros", @@ -2662,6 +3100,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "1.3.0" @@ -2701,6 +3145,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/pgml-dashboard/Cargo.toml b/pgml-dashboard/Cargo.toml index 47523e0b6..0b50f6990 100644 --- a/pgml-dashboard/Cargo.toml +++ b/pgml-dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pgml-dashboard" -version = "2.2.0" +version = "2.3.0" edition = "2021" authors = ["Lev Kokotov "] license = "MIT" @@ -14,7 +14,7 @@ include = ["src/", "sqlx-data.json", "templates/", "migrations/", "static/"] [dependencies] sailfish = "0.5.0" rocket = {version = "0.5.0-rc.2", features = ["secrets"] } -sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "json", "tls", "migrate", "time", "uuid", "bigdecimal", "offline",] } +sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "json", "tls", "migrate", "time", "uuid", "bigdecimal",] } anyhow = "1" tokio = { version = "1", features = ["full"] } rand = "0.8" @@ -24,3 +24,5 @@ bigdecimal = "0.3" chrono = "0.4" serde_json = "1" csv-async = "1" +scraper = "0.14.0" +dotenv = "0.15" diff --git a/pgml-dashboard/Dockerfile b/pgml-dashboard/Dockerfile index a800d772d..a6bef3507 100644 --- a/pgml-dashboard/Dockerfile +++ b/pgml-dashboard/Dockerfile @@ -1,4 +1,4 @@ FROM rust:1 COPY . /app WORKDIR /app -RUN cargo build +RUN cargo install sqlx-cli diff --git a/pgml-dashboard/sqlx-data.json b/pgml-dashboard/sqlx-data.json deleted file mode 100644 index 2cabe6a07..000000000 --- a/pgml-dashboard/sqlx-data.json +++ /dev/null @@ -1,1088 +0,0 @@ -{ - "db": "PostgreSQL", - "2b949ed3fd8c81fac4d4e75bc2aa545f506933ee1310f6443c6da8656090f868": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 5, - "type_info": "Interval" - }, - { - "name": "cell_number", - "ordinal": 6, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 7, - "type_info": "Int4" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - true, - false, - false, - true - ], - "parameters": { - "Left": [ - "Int8", - "Int4", - "Text" - ] - } - }, - "query": "\n WITH\n lock AS (\n SELECT * FROM notebooks WHERE id = $1 FOR UPDATE\n ),\n max_cell AS (\n SELECT COALESCE(MAX(cell_number), 0) AS cell_number\n FROM notebook_cells\n WHERE notebook_id = $1\n AND deleted_at IS NULL\n )\n INSERT INTO notebook_cells\n (notebook_id, cell_type, contents, cell_number, version)\n VALUES\n ($1, $2, $3, (SELECT cell_number + 1 FROM max_cell), 1)\n RETURNING id,\n notebook_id,\n cell_type,\n contents,\n rendering,\n execution_time,\n cell_number,\n version,\n deleted_at" - }, - "30733a476a80f506339edafc421e2a848e3490aed976a1fb98d20632985cb5ca": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "UPDATE notebook_cells\n SET\n execution_time = NULL,\n rendering = NULL\n WHERE notebook_id = $1\n AND cell_type = $2" - }, - "51257105e59d2bce390aae36f75744d49bb1cc7e59de6056234df1010663661d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "created_at", - "ordinal": 1, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "INSERT INTO uploaded_files (id, created_at) VALUES (DEFAULT, DEFAULT)\n RETURNING id, created_at" - }, - "5504a78267b524894a27c15681790984275dc66c320c59e35b4ab8a198482fab": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Varchar" - ] - } - }, - "query": "INSERT INTO notebooks (name) VALUES ($1) RETURNING *" - }, - "5a6f49b8f5a11545a42c1192c05cc567b4f3ebe798117323f711915f60f73d62": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT * FROM notebooks" - }, - "66537e5976abe208d4f7821e0daf731bf259fa1b6851d761cab0460f233e7220": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int4", - "Text", - "Int8" - ] - } - }, - "query": "UPDATE notebook_cells\n SET\n cell_type = $1,\n contents = $2,\n version = version + 1\n WHERE id = $3" - }, - "6f41282ede12eecff1f423e1cf75244c53ac26e8117910efd889fe74c786acbe": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 5, - "type_info": "Interval" - }, - { - "name": "cell_number", - "ordinal": 6, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 7, - "type_info": "Int4" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - true, - false, - false, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "UPDATE notebook_cells\n SET deleted_at = NOW()\n WHERE id = $1\n RETURNING id,\n notebook_id,\n cell_type,\n contents,\n rendering,\n execution_time,\n cell_number,\n version,\n deleted_at" - }, - "7095e7b76e23fa7af3ab2cacc42778645f8cd748e5e0c2ec392208dac6755622": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "snapshot_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "num_features", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "algorithm", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "runtime", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "hyperparams", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "status", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "metrics", - "ordinal": 8, - "type_info": "Jsonb" - }, - { - "name": "search", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "search_params", - "ordinal": 10, - "type_info": "Jsonb" - }, - { - "name": "search_args", - "ordinal": 11, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 12, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 13, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - null, - false, - false, - true, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n project_id,\n snapshot_id,\n num_features,\n algorithm,\n runtime::TEXT,\n hyperparams,\n status,\n metrics,\n search,\n search_params,\n search_args,\n created_at,\n updated_at\n FROM pgml.models\n WHERE snapshot_id = $1\n " - }, - "7285e17ea8ee359929b9df1e6631f6fd94da94c6ff19acc6c144bbe46b9b902b": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "model_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "strategy", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 4, - "type_info": "Timestamp" - }, - { - "name": "active", - "ordinal": 5, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - null, - false, - null - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n a.id,\n project_id,\n model_id,\n strategy::TEXT,\n created_at,\n a.id = last_deployment.id AS active\n FROM pgml.deployments a\n CROSS JOIN LATERAL (\n SELECT id FROM pgml.deployments b\n WHERE b.project_id = a.project_id\n ORDER BY b.id DESC\n LIMIT 1\n ) last_deployment\n WHERE project_id = $1\n ORDER BY a.id DESC" - }, - "78edce72d0197433efa2c7d1fdeaf7bcc98db17edc4941d8069c99de2a636418": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Text", - "Int8" - ] - } - }, - "query": "UPDATE notebook_cells SET rendering = $1 WHERE id = $2" - }, - "96ba78cf2502167ee92b77f34c8955b63a94befd6bfabb209b3f8c477ec1170f": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "snapshot_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "num_features", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "algorithm", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "runtime", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "hyperparams", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "status", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "metrics", - "ordinal": 8, - "type_info": "Jsonb" - }, - { - "name": "search", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "search_params", - "ordinal": 10, - "type_info": "Jsonb" - }, - { - "name": "search_args", - "ordinal": 11, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 12, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 13, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - null, - false, - false, - true, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n project_id,\n snapshot_id,\n num_features,\n algorithm,\n runtime::TEXT,\n hyperparams,\n status,\n metrics,\n search,\n search_params,\n search_args,\n created_at,\n updated_at\n FROM pgml.models\n WHERE project_id = $1\n " - }, - "a1bfdea1adb5c535590879f89be80cd12163c1dc95f290c11ca9dd19617883c9": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "relation_name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "y_column_name", - "ordinal": 2, - "type_info": "TextArray" - }, - { - "name": "test_size", - "ordinal": 3, - "type_info": "Float4" - }, - { - "name": "test_sampling", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "status", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "columns", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "analysis", - "ordinal": 7, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 8, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 9, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - null, - false, - true, - true, - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT id,\n relation_name,\n y_column_name,\n test_size,\n test_sampling::TEXT,\n status,\n columns,\n analysis,\n created_at,\n updated_at\n FROM pgml.snapshots\n " - }, - "a517e8deb38ba9eddc46e4fee9b776f02718384bba453f8f33b38f56578e813d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT * FROM notebooks WHERE id = $1" - }, - "bf168ae743f7b366e4a30a7e891fa1d16b3815181d19ac7a35d553b20d72f983": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 5, - "type_info": "Interval" - }, - { - "name": "cell_number", - "ordinal": 6, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 7, - "type_info": "Int4" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - true, - false, - false, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n notebook_id,\n cell_type,\n contents,\n rendering,\n execution_time,\n cell_number,\n version,\n deleted_at\n FROM notebook_cells\n WHERE id = $1\n " - }, - "bf7d4a13b5e1e0e45e6a6ab6f5bf946e1d4cb16a59c53c79b37a46c34f5aea01": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "cell_number", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 7, - "type_info": "Interval" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT * FROM notebook_cells\n WHERE notebook_id = $1\n AND deleted_at IS NULL\n ORDER BY cell_number" - }, - "c51dddac8ca1272eb957b5cbfd789e63c9e8897d62bc2c57c168eba5ada12dc3": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "task", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - null, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT\n id,\n name,\n task::TEXT,\n created_at\n FROM pgml.projects\n ORDER BY id DESC" - }, - "c5faa3dc630e649d97e10720dbc33351c7d792ee69a4a90ce26d61448e031520": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "model_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "strategy", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 4, - "type_info": "Timestamp" - }, - { - "name": "active", - "ordinal": 5, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - null, - false, - null - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n a.id,\n project_id,\n model_id,\n strategy::TEXT,\n created_at,\n a.id = last_deployment.id AS active\n FROM pgml.deployments a\n CROSS JOIN LATERAL (\n SELECT id FROM pgml.deployments b\n WHERE b.project_id = a.project_id\n ORDER BY b.id DESC\n LIMIT 1\n ) last_deployment\n WHERE a.id = $1\n ORDER BY a.id DESC" - }, - "d8fb565e5ca7f3b60a28e00080902ec34a9036a77ffdde04957f8a6fd543e31d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "task", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - null, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n name,\n task::TEXT,\n created_at\n FROM pgml.projects\n WHERE id = $1" - }, - "da28d578e5935c65851410fbb4e3a260201c16f9bfacfc9bbe05292c292894a2": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "snapshot_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "num_features", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "algorithm", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "runtime", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "hyperparams", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "status", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "metrics", - "ordinal": 8, - "type_info": "Jsonb" - }, - { - "name": "search", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "search_params", - "ordinal": 10, - "type_info": "Jsonb" - }, - { - "name": "search_args", - "ordinal": 11, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 12, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 13, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - null, - false, - false, - true, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n project_id,\n snapshot_id,\n num_features,\n algorithm,\n runtime::TEXT,\n hyperparams,\n status,\n metrics,\n search,\n search_params,\n search_args,\n created_at,\n updated_at\n FROM pgml.models\n WHERE id = $1\n " - }, - "e863424eeed1de411a24bc4dff7bf10fee68ff8c98e499c6fb621e23a6b0d63a": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "relation_name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "y_column_name", - "ordinal": 2, - "type_info": "TextArray" - }, - { - "name": "test_size", - "ordinal": 3, - "type_info": "Float4" - }, - { - "name": "test_sampling", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "status", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "columns", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "analysis", - "ordinal": 7, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 8, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 9, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - null, - false, - true, - true, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT id,\n relation_name,\n y_column_name,\n test_size,\n test_sampling::TEXT,\n status,\n columns,\n analysis,\n created_at,\n updated_at\n FROM pgml.snapshots\n WHERE id = $1" - }, - "f1a0941049c71bee1ea74ede2e3199d88bf0fc739ca2e2510ee9f6178b12e80a": { - "describe": { - "columns": [ - { - "name": "deployed", - "ordinal": 0, - "type_info": "Bool" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - } - }, - "query": "SELECT\n (model_id = $1) AS deployed\n FROM pgml.deployments\n WHERE project_id = $2\n ORDER BY created_at DESC\n LIMIT 1" - } -} \ No newline at end of file diff --git a/pgml-dashboard/src/guards.rs b/pgml-dashboard/src/guards.rs index 6c672405a..d7fb22ac1 100644 --- a/pgml-dashboard/src/guards.rs +++ b/pgml-dashboard/src/guards.rs @@ -49,7 +49,10 @@ impl<'r> FromRequest<'r> for Cluster { None => return Outcome::Failure((Status::BadRequest, ())), }; - Outcome::Success(Cluster { pool, context: shared_state.get_context(cluster_id) }) + Outcome::Success(Cluster { + pool, + context: shared_state.get_context(cluster_id), + }) } } diff --git a/pgml-dashboard/src/lib.rs b/pgml-dashboard/src/lib.rs index dd58ce92c..27ed0fb5b 100644 --- a/pgml-dashboard/src/lib.rs +++ b/pgml-dashboard/src/lib.rs @@ -31,7 +31,7 @@ use sqlx::Executor; #[derive(Debug, Default, Clone)] pub struct Context { pub user: models::User, - pub cluster: models::Cluster, + pub cluster: models::Cluster, } /// Globally shared state, saved in memory. @@ -51,10 +51,13 @@ impl Clusters { .max_connections(5) .idle_timeout(std::time::Duration::from_millis(15_000)) .min_connections(0) - .after_connect(|conn, _meta| Box::pin(async move { - conn.execute("SET application_name = 'pgml_dashboard';").await?; - Ok(()) - })) + .after_connect(|conn, _meta| { + Box::pin(async move { + conn.execute("SET application_name = 'pgml_dashboard';") + .await?; + Ok(()) + }) + }) .connect_lazy(database_url)?; pools.insert(cluster_id, pool.clone()); @@ -417,18 +420,11 @@ pub async fn models_get(cluster: Cluster, id: i64) -> Result Result { let snapshots = models::Snapshot::all(cluster.pool()).await?; - let mut table_sizes = HashMap::new(); - - for snapshot in &snapshots { - let table_size = snapshot.table_size(cluster.pool()).await?; - table_sizes.insert(snapshot.id, table_size); - } Ok(ResponseOk( templates::Snapshots { topic: "snapshots".to_string(), snapshots, - table_sizes, context: cluster.context.clone(), } .render_once() @@ -440,6 +436,7 @@ pub async fn snapshots_index(cluster: Cluster) -> Result Result { let snapshot = models::Snapshot::get_by_id(cluster.pool(), id).await?; let samples = snapshot.samples(cluster.pool(), 500).await?; + let models = snapshot.models(cluster.pool()).await?; let mut projects = HashMap::new(); @@ -450,7 +447,6 @@ pub async fn snapshots_get(cluster: Cluster, id: i64) -> Result Redirect { #[rocket::main] async fn main() { + dotenv::dotenv().ok(); + let clusters = pgml_dashboard::Clusters::new(); clusters .add(-1, &pgml_dashboard::guards::default_database_url()) .unwrap(); - pgml_dashboard::migrate(&clusters.get(-1).unwrap()).await.unwrap(); + pgml_dashboard::migrate(&clusters.get(-1).unwrap()) + .await + .unwrap(); let _ = rocket::build() .manage(clusters) @@ -27,3 +31,159 @@ async fn main() { .await .expect("failed to shut down Rocket"); } + +#[cfg(test)] +mod test { + use pgml_dashboard::Clusters; + use pgml_dashboard::{index, migrate, paths}; + use rocket::fs::FileServer; + use rocket::local::asynchronous::Client; + use rocket::{Build, Rocket}; + use scraper::{Html, Selector}; + use std::vec::Vec; + + async fn rocket() -> Rocket { + let clusters = Clusters::new(); + clusters + .add(-1, &pgml_dashboard::guards::default_database_url()) + .unwrap(); + + migrate(&clusters.get(-1).unwrap()).await.unwrap(); + + rocket::build() + .manage(clusters) + .mount("/", rocket::routes![index,]) + .mount("/dashboard/static", FileServer::from("static")) + .mount("/dashboard", paths()) + } + + fn get_href_links(body: &str, pattern: &str) -> Vec { + let document = Html::parse_document(body); + let selector = Selector::parse("a").unwrap(); + let mut output = Vec::::new(); + for element in document.select(&selector) { + let href = element.value().attr("href").unwrap(); + if href.contains(pattern) && href != pattern { + output.push(String::from(href)); + } + } + output + } + + #[rocket::async_test] + async fn test_notebooks_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/notebooks/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_projects_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/projects/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_models_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/models/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_deployments_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/deployments/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_uploader() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/uploader/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_snapshots_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/snapshots/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_snapshot_entries() { + let snapshots_endpoint = "/dashboard/snapshots/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(snapshots_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let snapshot_links = get_href_links(body.as_str(), snapshots_endpoint); + + for link in snapshot_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_notebook_entries() { + let notebooks_endpoint = "/dashboard/notebooks/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(notebooks_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let notebook_links = get_href_links(body.as_str(), notebooks_endpoint); + + for link in notebook_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_project_entries() { + let projects_endpoint = "/dashboard/projects/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(projects_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let project_links = get_href_links(body.as_str(), projects_endpoint); + + for link in project_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_model_entries() { + let models_endpoint = "/dashboard/models/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(models_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let model_links = get_href_links(body.as_str(), models_endpoint); + + for link in model_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_deployment_entries() { + let deployments_endpoint = "/dashboard/deployments/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(deployments_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let deployment_links = get_href_links(body.as_str(), deployments_endpoint); + + for link in deployment_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } +} diff --git a/pgml-dashboard/src/models.rs b/pgml-dashboard/src/models.rs index 45c78f9b4..3fbcaaf79 100644 --- a/pgml-dashboard/src/models.rs +++ b/pgml-dashboard/src/models.rs @@ -60,16 +60,22 @@ impl Project { pub fn key_metric_name(&self) -> anyhow::Result<&'static str> { match self.task.as_ref().unwrap().as_str() { - "classification" | "text-classification" => Ok("f1"), + "classification" | "text_classification" | "question_answering" => Ok("f1"), "regression" => Ok("r2"), + "summarization" => Ok("rouge_ngram_f1"), + "translation" => Ok("bleu"), + "text_generation" | "text2text" => Ok("perplexity"), task => Err(anyhow::anyhow!("Unhandled task: {}", task)), } } pub fn key_metric_display_name(&self) -> anyhow::Result<&'static str> { match self.task.as_ref().unwrap().as_str() { - "classification" | "text-classification" => Ok("F1"), + "classification" | "text_classification" | "question_answering" => Ok("F1"), "regression" => Ok("R2"), + "summarization" => Ok("Rouge Ngram F1"), + "translation" => Ok("Bleu"), + "text_generation" | "text2text" => Ok("Perplexity"), task => Err(anyhow::anyhow!("Unhandled task: {}", task)), } } @@ -533,6 +539,8 @@ pub struct Snapshot { pub analysis: Option, pub created_at: PrimitiveDateTime, pub updated_at: PrimitiveDateTime, + pub exists: bool, + pub table_size: String, } impl Snapshot { @@ -548,9 +556,22 @@ impl Snapshot { columns, analysis, created_at, - updated_at - FROM pgml.snapshots - " + updated_at, + CASE + WHEN EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) THEN pg_size_pretty(pg_total_relation_size(relation_name::regclass)) + ELSE '0 Bytes' + END AS \"table_size!\", + EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) AS \"exists!\" + FROM pgml.snapshots + " ) .fetch_all(pool) .await?) @@ -567,25 +588,27 @@ impl Snapshot { columns, analysis, created_at, - updated_at - FROM pgml.snapshots - WHERE id = $1", + updated_at, + CASE + WHEN EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) THEN pg_size_pretty(pg_total_relation_size(relation_name::regclass)) + ELSE '0 Bytes' + END AS \"table_size!\", + EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) AS \"exists!\" + FROM pgml.snapshots WHERE id = $1", id, ) .fetch_one(pool) .await?) } - pub async fn table_size(&self, pool: &PgPool) -> anyhow::Result { - let row = - sqlx::query("SELECT pg_size_pretty(pg_total_relation_size($1))::TEXT AS table_size") - .bind(&self.relation_name) - .fetch_one(pool) - .await?; - - Ok(row.try_get("table_size")?) - } - pub fn rows(&self) -> Option { match self.analysis.as_ref() { Some(analysis) => match analysis.get("samples") { @@ -601,26 +624,28 @@ impl Snapshot { pool: &PgPool, rows: i64, ) -> anyhow::Result>> { - let rows = sqlx::query(&format!( - "SELECT row_to_json(row) r - FROM (SELECT * FROM {} LIMIT $1) row", - self.relation_name - )) - .bind(rows) - .fetch_all(pool) - .await?; - let mut samples = HashMap::new(); - rows.iter().for_each(|row| { - let r: serde_json::Value = row.try_get("r").unwrap(); - let obj = r.as_object().unwrap(); + if self.exists { + let rows = sqlx::query(&format!( + "SELECT row_to_json(row) r + FROM (SELECT * FROM {} LIMIT $1) row", + self.relation_name + )) + .bind(rows) + .fetch_all(pool) + .await?; - for (key, value) in obj.iter() { - let rf = samples.entry(key.clone()).or_insert(Vec::new()); - rf.push(value.as_f64().unwrap_or(0.) as f32); - } - }); + rows.iter().for_each(|row| { + let r: serde_json::Value = row.try_get("r").unwrap(); + let obj = r.as_object().unwrap(); + + for (key, value) in obj.iter() { + let rf = samples.entry(key.clone()).or_insert(Vec::new()); + rf.push(value.as_f64().unwrap_or(0.) as f32); + } + }); + } Ok(samples) } @@ -665,7 +690,7 @@ impl Snapshot { } pub fn labels<'a>(&'a self) -> Option>> { - self.columns().map(|columns| + self.columns().map(|columns| { columns .into_iter() .filter(|column| { @@ -673,7 +698,7 @@ impl Snapshot { .contains(&column["name"].as_str().unwrap().to_string()) }) .collect() - ) + }) } pub async fn models(&self, pool: &PgPool) -> anyhow::Result> { @@ -681,32 +706,33 @@ impl Snapshot { } pub fn target_stddev(&self, name: &str) -> f32 { - match self.analysis + match self + .analysis .as_ref() .unwrap() .as_object() .unwrap() - .get(&format!("{}_stddev", name)) { + .get(&format!("{}_stddev", name)) + { // 2.1 Some(value) => value.as_f64().unwrap() as f32, // 2.2+ None => { let columns = self.columns().unwrap(); - let column = columns.iter().find(|column| - &column["name"].as_str().unwrap() == &name - ); + let column = columns + .iter() + .find(|column| &column["name"].as_str().unwrap() == &name); match column { - Some(column) => { - column.get("statistics") - .unwrap() - .as_object() - .unwrap() - .get("std_dev") - .unwrap() - .as_f64() - .unwrap() as f32 - }, - None => 0. + Some(column) => column + .get("statistics") + .unwrap() + .as_object() + .unwrap() + .get("std_dev") + .unwrap() + .as_f64() + .unwrap() as f32, + None => 0., } } } diff --git a/pgml-dashboard/src/templates.rs b/pgml-dashboard/src/templates.rs index b1a612a26..ec8f4235c 100644 --- a/pgml-dashboard/src/templates.rs +++ b/pgml-dashboard/src/templates.rs @@ -231,7 +231,6 @@ pub struct Model { pub struct Snapshots { pub topic: String, pub snapshots: Vec, - pub table_sizes: HashMap, pub context: Context, } @@ -242,7 +241,6 @@ pub struct Snapshot { pub snapshot: models::Snapshot, pub models: Vec, pub projects: HashMap, - pub table_size: String, pub samples: HashMap>, pub context: Context, } diff --git a/pgml-dashboard/static/css/base.css b/pgml-dashboard/static/css/base.css index 8894b13fd..a128940c3 100644 --- a/pgml-dashboard/static/css/base.css +++ b/pgml-dashboard/static/css/base.css @@ -305,67 +305,6 @@ footer { height: 0; } -@media only screen and (max-width: 690px) { - header { - top: unset; - left: 0; - bottom: 0; - position: fixed; - border: 0; - padding: 0; - width: 100%; - } - header nav a { - font-size: 0; - } - header nav ul li.logo { - display: none; - } - header nav ul li { - width: 24%; - padding: 15px 0; - text-align: center; - } - header nav a span.material-symbols-outlined { - font-size: 3rem; - } - main { - padding: 0px 0 100px; - } - dl { - max-height: 6em; - } - dt { - margin-top: 1em; - } - dt, dd { - margin-right: 2em; - } - figure { - display: inline-block; - width: 75vmin; - height: 75vmin; - margin: 30px 0; - } - section { - border-radius: 0; - margin: 0 0 20px; - } -} - -@media only screen and (max-width: 300px) { - dl { - max-height: 12em; - } - dt { - margin-top: 1em; - } - dt, dd { - margin-right: 2em; - } -} - - ol.object_list { width: 100%; } @@ -503,6 +442,10 @@ nav ol, nav ul { list-style: none; } +.notebook-title { + padding: 1rem; + margin: 20px auto 20px auto; +} .notebook-cell.active { border: 1px solid var(--gray-8); @@ -510,7 +453,6 @@ nav ol, nav ul { } .notebook-cell-edit { - border: 1px solid var(--gray-8); margin-bottom: 1rem; } @@ -519,13 +461,12 @@ nav ol, nav ul { } main turbo-frame:first-of-type .notebook-cell { - border-top-left-radius: 10px; - border-top-right-radius: 10px; + border-radius: 10px 10px 0px 0px; border-top: 1px solid var(--gray-8); } .notebook-cell { - margin: 0 20px; + margin: 0 auto; border-radius: 0; padding: 1rem; border-left: 2px solid var(--gray-8); @@ -535,6 +476,10 @@ main turbo-frame:first-of-type .notebook-cell { background: var(--gray-10); } +.notebook-last-cell { + border-radius: 0px 0px 10px 10px; +} + .notebook-cell article.markdown-body { background-color: inherit; } @@ -564,8 +509,10 @@ main turbo-frame:first-of-type .notebook-cell { color: #000; } -.notebook-button { - margin: 0.1rem; +.notebook-buttons { + display: flex; + flex-direction: row; + gap: 0.2rem; } .notebook-contents code { @@ -596,6 +543,10 @@ main turbo-frame:first-of-type .notebook-cell { align-items: flex-end; } +.flex-start { + align-items: flex-start; +} + .flex-row-reverse { flex-direction: row-reverse; } @@ -605,16 +556,9 @@ main turbo-frame:first-of-type .notebook-cell { font-family: monospace; } -.notebook-cell .button { - height: fit-content; - width: fit-content; - padding: 2px 5px; - margin-bottom: 0.12rem; - margin-left: 0.2rem; -} - -.notebook-cell .notebook-button button, .markdown-body button { +.notebook-cell button, .notebook-title button, .markdown-body button { padding: 2px 5px; + height: 2rem; } .notebook-cell .button-delete { @@ -625,15 +569,20 @@ main turbo-frame:first-of-type .notebook-cell { font-weight: bold; background: rgb(240, 240, 240); border: 1px solid var(--gray-7); - padding: 0.5rem 6px; + height: 2rem; border-radius: 2px; - margin-bottom: 2px; } .notebook-duration { margin-top: 1rem; } +.notebook-render-container { + min-width: 0px; + flex: 1; + margin-right: 1rem; +} + .notebook-rendering .markdown-body { margin-top: 1rem; } @@ -651,6 +600,8 @@ main turbo-frame:first-of-type .notebook-cell { */ .CodeMirror { font-size: 1rem; + border: 1px solid var(--gray-8); + border-radius: 10px; } /* @@ -737,3 +688,74 @@ input[type=checkbox]:checked:after { display: flex; align-items: center; } + + +@media only screen and (max-width: 690px) { + header { + top: unset; + left: 0; + bottom: 0; + position: fixed; + border: 0; + padding: 0; + width: 100%; + } + header nav a { + font-size: 0; + } + header nav ul li.logo { + display: none; + } + header nav ul li { + width: 24%; + padding: 15px 0; + text-align: center; + } + header nav a span.material-symbols-outlined { + font-size: 3rem; + } + main { + padding: 0px 0 100px; + } + dl { + max-height: 6em; + } + dt { + margin-top: 1em; + } + dt, dd { + margin-right: 2em; + } + figure { + display: inline-block; + width: 75vmin; + height: 75vmin; + margin: 30px 0; + } + section { + border-radius: 0; + margin: 0 0 20px; + } + main turbo-frame:first-of-type .notebook-cell { + border-radius: 0px; + } + .notebook-last-cell { + border-radius: 0px; + } + .notebook-buttons { + flex-direction: column; + justify-content: flex-start; + } +} + +@media only screen and (max-width: 300px) { + dl { + max-height: 12em; + } + dt { + margin-top: 1em; + } + dt, dd { + margin-right: 2em; + } +} diff --git a/pgml-dashboard/static/css/markdown.css b/pgml-dashboard/static/css/markdown.css index b41ed0112..d6743c5f2 100644 --- a/pgml-dashboard/static/css/markdown.css +++ b/pgml-dashboard/static/css/markdown.css @@ -14,6 +14,7 @@ font-size: 16px; line-height: 1.5; word-wrap: break-word; + max-width: 75vw; } .markdown-body .octicon { diff --git a/pgml-dashboard/templates/cell.html b/pgml-dashboard/templates/cell.html index e4dfe1d0a..7ba13f121 100644 --- a/pgml-dashboard/templates/cell.html +++ b/pgml-dashboard/templates/cell.html @@ -8,18 +8,18 @@
-
+
-
+
- -
@@ -35,7 +35,7 @@
<%= cell.cell_number %>
-
+
<% if cell.code() { %>
<%- cell.contents %>
@@ -43,14 +43,14 @@ <% } %> <% if cell.html().is_some() { %> -
- <%- cell.html().unwrap() %> -
+
+ <%- cell.html().unwrap() %> +
<% } %>
- - <% if cell.code() && !edit { %> -
+ +
+ <% if cell.code() && !edit { %> -
- <% } %> -
+ <% } %>
-
-
@@ -36,19 +34,19 @@

include!("cell.html"); } %> -
+
-
+
-
+
-
diff --git a/pgml-dashboard/templates/snapshot.html b/pgml-dashboard/templates/snapshot.html index 1a9ba32d4..fdd890bdd 100644 --- a/pgml-dashboard/templates/snapshot.html +++ b/pgml-dashboard/templates/snapshot.html @@ -13,7 +13,7 @@

storage<%= snapshot.relation_
Snapshot ID
<%= snapshot.id %>
Table Size
-
<%= table_size %>
+
<%= snapshot.table_size %>
Rows
<%= snapshot.rows().unwrap() %>
Features
@@ -61,6 +61,7 @@

model_trainingModels

<% } %> +<% if snapshot.exists { %>

label_importantLabels

<% for label in snapshot.labels().unwrap().iter() { @@ -131,5 +132,9 @@

<%= name %> <%= feature["pg_type"].as_str().unwrap() | upper %> - +<%} else { %> +
+

Data no longer exists to plot statistics

+
+<% }%> <% include!("footer.html"); %> diff --git a/pgml-dashboard/templates/snapshots.html b/pgml-dashboard/templates/snapshots.html index 24778b701..6d9bf1832 100644 --- a/pgml-dashboard/templates/snapshots.html +++ b/pgml-dashboard/templates/snapshots.html @@ -7,7 +7,7 @@

storageSnapshots

  • <%= snapshot.relation_name %> - <%= table_sizes[&snapshot.id] %> + <%= snapshot.table_size %>
  • diff --git a/pgml-dashboard/templates/undo.html b/pgml-dashboard/templates/undo.html index a329eadc0..39108aa38 100644 --- a/pgml-dashboard/templates/undo.html +++ b/pgml-dashboard/templates/undo.html @@ -10,7 +10,7 @@

    -
    +
    -
    -