Compare commits
18 Commits
beta_2025-
...
feature/le
| Author | SHA1 | Date | |
|---|---|---|---|
| 95d16690ef | |||
| df6056fb86 | |||
| c1553d9cd4 | |||
| 2b79adc2f7 | |||
| ddf1fd3515 | |||
| 34589efbde | |||
| 1cccefa991 | |||
| e26d5a21e3 | |||
| 98d62e1d1f | |||
| 13ed86a780 | |||
| f1b4c5aadb | |||
| 37e0e18a28 | |||
| de44c22606 | |||
| a952c08469 | |||
| 551cfc7768 | |||
| 8fda2577ee | |||
| 819a94aced | |||
| 376fd65785 |
@ -16,3 +16,5 @@ POSTGRES_PORT=
|
|||||||
# MAIL
|
# MAIL
|
||||||
EMAIL_HOST_USER=
|
EMAIL_HOST_USER=
|
||||||
EMAIL_HOST_PASSWORD=
|
EMAIL_HOST_PASSWORD=
|
||||||
|
# MATOMO
|
||||||
|
MATOMO_SITE_ID
|
||||||
|
|||||||
116
.gitea/workflows/ci.yaml
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: ${{ vars.POSTGRES_USER }}
|
||||||
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||||
|
POSTGRES_DB: ${{ vars.POSTGRES_DB }}
|
||||||
|
ports:
|
||||||
|
- ${{ vars.POSTGRES_PORT}}:5432
|
||||||
|
options: >-
|
||||||
|
--health-cmd="pg_isready -U postgres"
|
||||||
|
--health-interval=10s
|
||||||
|
--health-timeout=5s
|
||||||
|
--health-retries=5
|
||||||
|
|
||||||
|
#redis:
|
||||||
|
# image: redis:7
|
||||||
|
# ports:
|
||||||
|
# - 6379:6379
|
||||||
|
# options: >-
|
||||||
|
# --health-cmd "redis-cli ping"
|
||||||
|
# --health-interval=10s
|
||||||
|
# --health-timeout=5s
|
||||||
|
# --health-retries=5
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
EP_DATA_DIR: /opt/enviPy/
|
||||||
|
ALLOWED_HOSTS: 127.0.0.1,localhost
|
||||||
|
DEBUG: True
|
||||||
|
LOG_LEVEL: DEBUG
|
||||||
|
MODEL_BUILDING_ENABLED: True
|
||||||
|
APPLICABILITY_DOMAIN_ENABLED: True
|
||||||
|
ENVIFORMER_PRESENT: True
|
||||||
|
ENVIFORMER_DEVICE: cpu
|
||||||
|
FLAG_CELERY_PRESENT: False
|
||||||
|
PLUGINS_ENABLED: True
|
||||||
|
SERVER_URL: http://localhost:8000
|
||||||
|
ADMIN_APPROVAL_REQUIRED: True
|
||||||
|
REGISTRATION_MANDATORY: True
|
||||||
|
LOG_DIR: ''
|
||||||
|
# DB
|
||||||
|
POSTGRES_SERVICE_NAME: postgres
|
||||||
|
POSTGRES_DB: ${{ vars.POSTGRES_DB }}
|
||||||
|
POSTGRES_USER: ${{ vars.POSTGRES_USER }}
|
||||||
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
# SENTRY
|
||||||
|
SENTRY_ENABLED: False
|
||||||
|
# MS ENTRA
|
||||||
|
MS_ENTRA_ENABLED: False
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install system tools via apt
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y postgresql-client redis-tools openjdk-11-jre-headless
|
||||||
|
|
||||||
|
- name: Setup ssh
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.ENVIPY_CI_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan git.envipath.com >> ~/.ssh/known_hosts
|
||||||
|
eval $(ssh-agent -s)
|
||||||
|
ssh-add ~/.ssh/id_ed25519
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
|
||||||
|
- name: Setup venv
|
||||||
|
run: |
|
||||||
|
uv sync --locked --all-extras --dev
|
||||||
|
|
||||||
|
- name: Wait for services
|
||||||
|
run: |
|
||||||
|
until pg_isready -h postgres -U postgres; do sleep 2; done
|
||||||
|
# until redis-cli -h redis ping; do sleep 2; done
|
||||||
|
|
||||||
|
- name: Run Django Migrations
|
||||||
|
run: |
|
||||||
|
source .venv/bin/activate
|
||||||
|
python manage.py migrate --noinput
|
||||||
|
|
||||||
|
- name: Run Django tests
|
||||||
|
run: |
|
||||||
|
source .venv/bin/activate
|
||||||
|
python manage.py test tests --exclude-tag slow
|
||||||
5
.gitignore
vendored
@ -10,3 +10,8 @@ scratches/
|
|||||||
data/
|
data/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
static/css/output.css
|
||||||
|
|
||||||
|
*.code-workspace
|
||||||
|
|||||||
@ -8,6 +8,7 @@ repos:
|
|||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
|
exclude: ^static/images/
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.13.3
|
rev: v0.13.3
|
||||||
@ -20,6 +21,15 @@ repos:
|
|||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
types_or: [python, pyi]
|
types_or: [python, pyi]
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: prettier-jinja-templates
|
||||||
|
name: Format Jinja templates with Prettier
|
||||||
|
entry: pnpm exec prettier --plugin=prettier-plugin-jinja-template --parser=jinja-template --write
|
||||||
|
language: system
|
||||||
|
types: [file]
|
||||||
|
files: ^templates/.*\.html$
|
||||||
|
|
||||||
# - repo: local
|
# - repo: local
|
||||||
# hooks:
|
# hooks:
|
||||||
# - id: django-check
|
# - id: django-check
|
||||||
|
|||||||
11
.prettierrc.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-jinja-template", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "templates/**/*.html",
|
||||||
|
"options": {
|
||||||
|
"parser": "jinja-template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
22
README.md
@ -7,11 +7,13 @@ These instructions will guide you through setting up the project for local devel
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Python 3.11 or later
|
- Python 3.11 or later
|
||||||
- [uv](https://github.com/astral-sh/uv) - A fast Python package installer and resolver.
|
- [uv](https://github.com/astral-sh/uv) - Python package manager
|
||||||
- **Docker and Docker Compose** - Required for running the PostgreSQL database.
|
- **Docker and Docker Compose** - Required for running PostgreSQL database
|
||||||
- Git
|
- Git
|
||||||
|
- Make
|
||||||
|
|
||||||
|
> **Note:** This application requires PostgreSQL (uses `ArrayField`). Docker is the easiest way to run PostgreSQL locally.
|
||||||
|
|
||||||
> **Note:** This application requires PostgreSQL, which uses `ArrayField`. Docker is the recommended way to run PostgreSQL locally.
|
|
||||||
|
|
||||||
### 1. Install Dependencies
|
### 1. Install Dependencies
|
||||||
|
|
||||||
@ -23,7 +25,12 @@ Then, sync the project dependencies. This will create a virtual environment in `
|
|||||||
uv sync --dev
|
uv sync --dev
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note on RDkit:** If you have a different version of rdkit installed globally, the dependency installation may fail. If this happens, please uninstall the global version and run `uv sync` again.
|
Note on RDkit installation: if you have rdkit installed on your system globally with a different version of python, the installation will try to link against that and subsequent calls fail. Only option remove global rdkit and rerun sync.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The frontend requires `pnpm` to correctly display in development.
|
||||||
|
[Install it here](https://pnpm.io/installation).
|
||||||
|
|
||||||
### 2. Set Up Environment File
|
### 2. Set Up Environment File
|
||||||
|
|
||||||
@ -44,6 +51,7 @@ uv run poe setup
|
|||||||
```
|
```
|
||||||
|
|
||||||
This single command will:
|
This single command will:
|
||||||
|
|
||||||
1. Start the PostgreSQL database using Docker Compose.
|
1. Start the PostgreSQL database using Docker Compose.
|
||||||
2. Run database migrations.
|
2. Run database migrations.
|
||||||
3. Bootstrap initial data (anonymous user, default packages, models).
|
3. Bootstrap initial data (anonymous user, default packages, models).
|
||||||
@ -54,9 +62,12 @@ After setup, start the development server:
|
|||||||
uv run poe dev
|
uv run poe dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will start the css-watcher as well as the django-development server,
|
||||||
The application will be available at `http://localhost:8000`.
|
The application will be available at `http://localhost:8000`.
|
||||||
|
|
||||||
#### Other useful Poe commands:
|
**Note:** The development server automatically starts a CSS watcher (`pnpm run dev`) alongside the Django server to rebuild CSS files when changes are detected. This ensures your styles are always up-to-date during development.
|
||||||
|
|
||||||
|
#### Other useful Poe commands
|
||||||
|
|
||||||
You can list all available commands by running `uv run poe --help`.
|
You can list all available commands by running `uv run poe --help`.
|
||||||
|
|
||||||
@ -66,6 +77,7 @@ uv run poe db-down # Stop PostgreSQL
|
|||||||
uv run poe migrate # Run migrations only
|
uv run poe migrate # Run migrations only
|
||||||
uv run poe bootstrap # Bootstrap data only
|
uv run poe bootstrap # Bootstrap data only
|
||||||
uv run poe shell # Open the Django shell
|
uv run poe shell # Open the Django shell
|
||||||
|
uv run poe build # Build frontend assets and collect static files
|
||||||
uv run poe clean # Remove database volumes (WARNING: destroys all data)
|
uv run poe clean # Remove database volumes (WARNING: destroys all data)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -87,11 +87,14 @@ TEMPLATES = [
|
|||||||
"django.template.context_processors.request",
|
"django.template.context_processors.request",
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
"epdb.context_processors.package_context",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ALLOWED_HTML_TAGS = {"b", "i", "u", "br", "em", "mark", "p", "s", "strong"}
|
||||||
|
|
||||||
WSGI_APPLICATION = "envipath.wsgi.application"
|
WSGI_APPLICATION = "envipath.wsgi.application"
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
@ -243,6 +246,7 @@ LOGGING = {
|
|||||||
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
ENVIFORMER_PRESENT = os.environ.get("ENVIFORMER_PRESENT", "False") == "True"
|
||||||
ENVIFORMER_DEVICE = os.environ.get("ENVIFORMER_DEVICE", "cpu")
|
ENVIFORMER_DEVICE = os.environ.get("ENVIFORMER_DEVICE", "cpu")
|
||||||
|
|
||||||
|
|
||||||
# If celery is not present set always eager to true which will cause delayed tasks to block until finished
|
# If celery is not present set always eager to true which will cause delayed tasks to block until finished
|
||||||
FLAG_CELERY_PRESENT = os.environ.get("FLAG_CELERY_PRESENT", "False") == "True"
|
FLAG_CELERY_PRESENT = os.environ.get("FLAG_CELERY_PRESENT", "False") == "True"
|
||||||
if not FLAG_CELERY_PRESENT:
|
if not FLAG_CELERY_PRESENT:
|
||||||
@ -343,6 +347,14 @@ LOGIN_EXEMPT_URLS = [
|
|||||||
"/password_reset/",
|
"/password_reset/",
|
||||||
"/reset/",
|
"/reset/",
|
||||||
"/microsoft/",
|
"/microsoft/",
|
||||||
|
"/terms",
|
||||||
|
"/privacy",
|
||||||
|
"/cookie-policy",
|
||||||
|
"/about",
|
||||||
|
"/contact",
|
||||||
|
"/jobs",
|
||||||
|
"/cite",
|
||||||
|
"/legal",
|
||||||
]
|
]
|
||||||
|
|
||||||
# MS AD/Entra
|
# MS AD/Entra
|
||||||
@ -357,3 +369,6 @@ if MS_ENTRA_ENABLED:
|
|||||||
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
|
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
|
||||||
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
|
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
|
||||||
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
|
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
|
||||||
|
|
||||||
|
# Site ID 10 -> beta.envipath.org
|
||||||
|
MATOMO_SITE_ID = os.environ.get("MATOMO_SITE_ID", "10")
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from .models import (
|
|||||||
GroupPackagePermission,
|
GroupPackagePermission,
|
||||||
Package,
|
Package,
|
||||||
MLRelativeReasoning,
|
MLRelativeReasoning,
|
||||||
|
EnviFormer,
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
SimpleAmbitRule,
|
SimpleAmbitRule,
|
||||||
@ -19,11 +20,13 @@ from .models import (
|
|||||||
Setting,
|
Setting,
|
||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
ExternalIdentifier,
|
ExternalIdentifier,
|
||||||
|
JobLog,
|
||||||
|
License,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(admin.ModelAdmin):
|
||||||
pass
|
list_display = ["username", "email", "is_active"]
|
||||||
|
|
||||||
|
|
||||||
class UserPackagePermissionAdmin(admin.ModelAdmin):
|
class UserPackagePermissionAdmin(admin.ModelAdmin):
|
||||||
@ -38,8 +41,14 @@ class GroupPackagePermissionAdmin(admin.ModelAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EPAdmin(admin.ModelAdmin):
|
class EPAdmin(admin.ModelAdmin):
|
||||||
search_fields = ["name", "description"]
|
search_fields = ["name", "description"]
|
||||||
|
list_display = ["name", "url", "created"]
|
||||||
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
|
||||||
class PackageAdmin(EPAdmin):
|
class PackageAdmin(EPAdmin):
|
||||||
@ -50,6 +59,14 @@ class MLRelativeReasoningAdmin(EPAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EnviFormerAdmin(EPAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["cc_string", "link", "image_link"]
|
||||||
|
|
||||||
|
|
||||||
class CompoundAdmin(EPAdmin):
|
class CompoundAdmin(EPAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -102,8 +119,11 @@ admin.site.register(User, UserAdmin)
|
|||||||
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
|
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
|
||||||
admin.site.register(Group, GroupAdmin)
|
admin.site.register(Group, GroupAdmin)
|
||||||
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
|
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
|
||||||
|
admin.site.register(JobLog, JobLogAdmin)
|
||||||
admin.site.register(Package, PackageAdmin)
|
admin.site.register(Package, PackageAdmin)
|
||||||
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||||
|
admin.site.register(EnviFormer, EnviFormerAdmin)
|
||||||
|
admin.site.register(License, LicenseAdmin)
|
||||||
admin.site.register(Compound, CompoundAdmin)
|
admin.site.register(Compound, CompoundAdmin)
|
||||||
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||||
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||||
|
|||||||
32
epdb/context_processors.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
Context processors for enviPy application.
|
||||||
|
|
||||||
|
Context processors automatically make variables available to all templates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .logic import PackageManager
|
||||||
|
from .models import Package
|
||||||
|
|
||||||
|
|
||||||
|
def package_context(request):
|
||||||
|
"""
|
||||||
|
Provides package data for the search modal which is included globally
|
||||||
|
in framework_modern.html.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Context dictionary with reviewed and unreviewed packages
|
||||||
|
"""
|
||||||
|
current_user = request.user
|
||||||
|
|
||||||
|
reviewed_package_qs = PackageManager.get_reviewed_packages()
|
||||||
|
|
||||||
|
unreviewed_package_qs = Package.objects.none()
|
||||||
|
|
||||||
|
# Only get user-specific packages if user is authenticated
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
unreviewed_package_qs = PackageManager.get_all_readable_packages(current_user)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"reviewed_packages": reviewed_package_qs,
|
||||||
|
"unreviewed_packages": unreviewed_package_qs,
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import json
|
|||||||
from typing import Union, List, Optional, Set, Dict, Any
|
from typing import Union, List, Optional, Set, Dict, Any
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import nh3
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
@ -185,6 +186,12 @@ class UserManager(object):
|
|||||||
def create_user(
|
def create_user(
|
||||||
username, email, password, set_setting=True, add_to_group=True, *args, **kwargs
|
username, email, password, set_setting=True, add_to_group=True, *args, **kwargs
|
||||||
):
|
):
|
||||||
|
# Clean for potential XSS
|
||||||
|
clean_username = nh3.clean(username).strip()
|
||||||
|
clean_email = nh3.clean(email).strip()
|
||||||
|
if clean_username != username or clean_email != email:
|
||||||
|
# This will be caught by the try in view.py/register
|
||||||
|
raise ValueError("Invalid username or password")
|
||||||
# avoid circular import :S
|
# avoid circular import :S
|
||||||
from .tasks import send_registration_mail
|
from .tasks import send_registration_mail
|
||||||
|
|
||||||
@ -262,8 +269,9 @@ class GroupManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def create_group(current_user, name, description):
|
def create_group(current_user, name, description):
|
||||||
g = Group()
|
g = Group()
|
||||||
g.name = name
|
# Clean for potential XSS
|
||||||
g.description = description
|
g.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
g.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
g.owner = current_user
|
g.owner = current_user
|
||||||
g.save()
|
g.save()
|
||||||
|
|
||||||
@ -518,8 +526,13 @@ class PackageManager(object):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_package(current_user, name: str, description: str = None):
|
def create_package(current_user, name: str, description: str = None):
|
||||||
p = Package()
|
p = Package()
|
||||||
p.name = name
|
|
||||||
p.description = description
|
# Clean for potential XSS
|
||||||
|
p.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
if description is not None and description.strip() != "":
|
||||||
|
p.description = nh3.clean(description.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
up = UserPackagePermission()
|
up = UserPackagePermission()
|
||||||
@ -1094,28 +1107,29 @@ class SettingManager(object):
|
|||||||
model: EPModel = None,
|
model: EPModel = None,
|
||||||
model_threshold: float = None,
|
model_threshold: float = None,
|
||||||
):
|
):
|
||||||
s = Setting()
|
new_s = Setting()
|
||||||
s.name = name
|
# Clean for potential XSS
|
||||||
s.description = description
|
new_s.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
s.max_nodes = max_nodes
|
new_s.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
s.max_depth = max_depth
|
new_s.max_nodes = max_nodes
|
||||||
s.model = model
|
new_s.max_depth = max_depth
|
||||||
s.model_threshold = model_threshold
|
new_s.model = model
|
||||||
|
new_s.model_threshold = model_threshold
|
||||||
|
|
||||||
s.save()
|
new_s.save()
|
||||||
|
|
||||||
if rule_packages is not None:
|
if rule_packages is not None:
|
||||||
for r in rule_packages:
|
for r in rule_packages:
|
||||||
s.rule_packages.add(r)
|
new_s.rule_packages.add(r)
|
||||||
s.save()
|
new_s.save()
|
||||||
|
|
||||||
usp = UserSettingPermission()
|
usp = UserSettingPermission()
|
||||||
usp.user = user
|
usp.user = user
|
||||||
usp.setting = s
|
usp.setting = new_s
|
||||||
usp.permission = Permission.ALL[0]
|
usp.permission = Permission.ALL[0]
|
||||||
usp.save()
|
usp.save()
|
||||||
|
|
||||||
return s
|
return new_s
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_setting(user: User):
|
def get_default_setting(user: User):
|
||||||
@ -1544,7 +1558,7 @@ class SPathway(object):
|
|||||||
if self.prediction_setting.model.app_domain:
|
if self.prediction_setting.model.app_domain:
|
||||||
app_domain_assessment = self.prediction_setting.model.app_domain.assess(
|
app_domain_assessment = self.prediction_setting.model.app_domain.assess(
|
||||||
sub.smiles
|
sub.smiles
|
||||||
)[0]
|
)
|
||||||
|
|
||||||
if self.persist is not None:
|
if self.persist is not None:
|
||||||
n = self.snode_persist_lookup[sub]
|
n = self.snode_persist_lookup[sub]
|
||||||
@ -1577,9 +1591,7 @@ class SPathway(object):
|
|||||||
if self.prediction_setting.model:
|
if self.prediction_setting.model:
|
||||||
if self.prediction_setting.model.app_domain:
|
if self.prediction_setting.model.app_domain:
|
||||||
app_domain_assessment = (
|
app_domain_assessment = (
|
||||||
self.prediction_setting.model.app_domain.assess(c)[
|
self.prediction_setting.model.app_domain.assess(c)
|
||||||
0
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.smiles_to_node[c] = SNode(
|
self.smiles_to_node[c] = SNode(
|
||||||
|
|||||||
@ -12,10 +12,16 @@ from epdb.models import (
|
|||||||
Permission,
|
Permission,
|
||||||
User,
|
User,
|
||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
|
License,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"-ol", "--only-licenses", action="store_true", help="Only create licenses."
|
||||||
|
)
|
||||||
|
|
||||||
def create_users(self):
|
def create_users(self):
|
||||||
# Anonymous User
|
# Anonymous User
|
||||||
if not User.objects.filter(email="anon@envipath.com").exists():
|
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||||
@ -83,6 +89,17 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
return anon, admin, g, user0
|
return anon, admin, g, user0
|
||||||
|
|
||||||
|
def create_licenses(self):
|
||||||
|
"""Create the six default licenses supported by enviPath"""
|
||||||
|
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
||||||
|
for cc_string in cc_strings:
|
||||||
|
if not License.objects.filter(cc_string=cc_string).exists():
|
||||||
|
new_license = License()
|
||||||
|
new_license.cc_string = cc_string
|
||||||
|
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
||||||
|
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
||||||
|
new_license.save()
|
||||||
|
|
||||||
def import_package(self, data, owner):
|
def import_package(self, data, owner):
|
||||||
return PackageManager.import_legacy_package(
|
return PackageManager.import_legacy_package(
|
||||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||||
@ -157,6 +174,10 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
# Create licenses
|
||||||
|
self.create_licenses()
|
||||||
|
if options.get("only_licenses", False):
|
||||||
|
return
|
||||||
# Create users
|
# Create users
|
||||||
anon, admin, g, user0 = self.create_users()
|
anon, admin, g, user0 = self.create_users()
|
||||||
|
|
||||||
|
|||||||
@ -7,10 +7,11 @@ from epdb.models import MLRelativeReasoning, EnviFormer, Package
|
|||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""This command can be run with
|
"""This command can be run with
|
||||||
`python manage.py create_ml_models [model_names] -d [data_packages] OPTIONAL: -e [eval_packages]`
|
`python manage.py create_ml_models [model_names] -d [data_packages] FOR MLRR ONLY: -r [rule_packages]
|
||||||
For example, to train both EnviFormer and MLRelativeReasoning on BBD and SOIL and evaluate them on SLUDGE
|
OPTIONAL: -e [eval_packages] -t threshold`
|
||||||
the below command would be used:
|
For example, to train both EnviFormer and MLRelativeReasoning on BBD and SOIL and evaluate them on SLUDGE with a
|
||||||
`python manage.py create_ml_models enviformer mlrr -d bbd soil -e sludge
|
threshold of 0.6, the below command would be used:
|
||||||
|
`python manage.py create_ml_models enviformer mlrr -d bbd soil -e sludge -t 0.6
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
@ -34,6 +35,13 @@ class Command(BaseCommand):
|
|||||||
help="Rule Packages mandatory for MLRR",
|
help="Rule Packages mandatory for MLRR",
|
||||||
default=[],
|
default=[],
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--threshold",
|
||||||
|
type=float,
|
||||||
|
help="Model prediction threshold",
|
||||||
|
default=0.5,
|
||||||
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
@ -67,7 +75,11 @@ class Command(BaseCommand):
|
|||||||
return packages
|
return packages
|
||||||
|
|
||||||
# Iteratively create models in options["model_names"]
|
# Iteratively create models in options["model_names"]
|
||||||
print(f"Creating models: {options['model_names']}")
|
print(f"Creating models: {options['model_names']}\n"
|
||||||
|
f"Data packages: {options['data_packages']}\n"
|
||||||
|
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
||||||
|
f"Eval Packages: {options['eval_packages']}\n"
|
||||||
|
f"Threshold: {options['threshold']:.2f}")
|
||||||
data_packages = decode_packages(options["data_packages"])
|
data_packages = decode_packages(options["data_packages"])
|
||||||
eval_packages = decode_packages(options["eval_packages"])
|
eval_packages = decode_packages(options["eval_packages"])
|
||||||
rule_packages = decode_packages(options["rule_packages"])
|
rule_packages = decode_packages(options["rule_packages"])
|
||||||
@ -78,9 +90,10 @@ class Command(BaseCommand):
|
|||||||
pack,
|
pack,
|
||||||
data_packages=data_packages,
|
data_packages=data_packages,
|
||||||
eval_packages=eval_packages,
|
eval_packages=eval_packages,
|
||||||
threshold=0.5,
|
threshold=options['threshold'],
|
||||||
name="EnviFormer - T0.5",
|
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
description="EnviFormer transformer",
|
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
||||||
|
f"evaluated on {options['eval_packages']}.",
|
||||||
)
|
)
|
||||||
elif model_name == "mlrr":
|
elif model_name == "mlrr":
|
||||||
model = MLRelativeReasoning.create(
|
model = MLRelativeReasoning.create(
|
||||||
@ -88,9 +101,10 @@ class Command(BaseCommand):
|
|||||||
rule_packages=rule_packages,
|
rule_packages=rule_packages,
|
||||||
data_packages=data_packages,
|
data_packages=data_packages,
|
||||||
eval_packages=eval_packages,
|
eval_packages=eval_packages,
|
||||||
threshold=0.5,
|
threshold=options['threshold'],
|
||||||
name="ECC - BBD - T0.5",
|
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
description="ML Relative Reasoning",
|
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
||||||
|
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Cannot create model of type {model_name}, unknown model type")
|
raise ValueError(f"Cannot create model of type {model_name}, unknown model type")
|
||||||
@ -100,6 +114,6 @@ class Command(BaseCommand):
|
|||||||
print(f"Training {model_name}")
|
print(f"Training {model_name}")
|
||||||
model.build_model()
|
model.build_model()
|
||||||
print(f"Evaluating {model_name}")
|
print(f"Evaluating {model_name}")
|
||||||
model.evaluate_model()
|
model.evaluate_model(False, eval_packages=eval_packages)
|
||||||
print(f"Saving {model_name}")
|
print(f"Saving {model_name}")
|
||||||
model.save()
|
model.save()
|
||||||
|
|||||||
59
epdb/management/commands/dump_enviformer.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tarfile
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import EnviFormer
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"model",
|
||||||
|
type=str,
|
||||||
|
help="Model UUID of the Model to Dump",
|
||||||
|
)
|
||||||
|
parser.add_argument("--output", type=str)
|
||||||
|
|
||||||
|
def package_dict_and_folder(self, dict_data, folder_path, output_path):
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
dict_filename = os.path.join(tmpdir, "data.json")
|
||||||
|
|
||||||
|
with open(dict_filename, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(dict_data, f, indent=2)
|
||||||
|
|
||||||
|
with tarfile.open(output_path, "w:gz") as tar:
|
||||||
|
tar.add(dict_filename, arcname="data.json")
|
||||||
|
tar.add(folder_path, arcname=os.path.basename(folder_path))
|
||||||
|
|
||||||
|
os.remove(dict_filename)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
output = options["output"]
|
||||||
|
|
||||||
|
if os.path.exists(output):
|
||||||
|
raise ValueError(f"Output file {output} already exists")
|
||||||
|
|
||||||
|
model = EnviFormer.objects.get(uuid=options["model"])
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"uuid": str(model.uuid),
|
||||||
|
"name": model.name,
|
||||||
|
"description": model.description,
|
||||||
|
"kv": model.kv,
|
||||||
|
"data_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
|
||||||
|
"eval_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
|
||||||
|
"threshold": model.threshold,
|
||||||
|
"eval_results": model.eval_results,
|
||||||
|
"multigen_eval": model.multigen_eval,
|
||||||
|
"model_status": model.model_status,
|
||||||
|
}
|
||||||
|
|
||||||
|
model_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
|
||||||
|
|
||||||
|
self.package_dict_and_folder(data, model_folder, output)
|
||||||
81
epdb/management/commands/load_enviformer.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tarfile
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from django.conf import settings as s
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import EnviFormer, Package
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"input",
|
||||||
|
type=str,
|
||||||
|
help=".tar.gz file containing the Model dump.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"package",
|
||||||
|
type=str,
|
||||||
|
help="Package UUID where the Model should be loaded to.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def read_dict_and_folder_from_archive(self, archive_path, extract_to="extracted_folder"):
|
||||||
|
with tarfile.open(archive_path, "r:gz") as tar:
|
||||||
|
tar.extractall(extract_to)
|
||||||
|
|
||||||
|
dict_path = os.path.join(extract_to, "data.json")
|
||||||
|
|
||||||
|
if not os.path.exists(dict_path):
|
||||||
|
raise FileNotFoundError("data.json not found in the archive.")
|
||||||
|
|
||||||
|
with open(dict_path, "r", encoding="utf-8") as f:
|
||||||
|
data_dict = json.load(f)
|
||||||
|
|
||||||
|
extracted_items = os.listdir(extract_to)
|
||||||
|
folders = [item for item in extracted_items if item != "data.json"]
|
||||||
|
folder_path = os.path.join(extract_to, folders[0]) if folders else None
|
||||||
|
|
||||||
|
return data_dict, folder_path
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if not os.path.exists(options["input"]):
|
||||||
|
raise ValueError(f"Input file {options['input']} does not exist.")
|
||||||
|
|
||||||
|
target_package = Package.objects.get(uuid=options["package"])
|
||||||
|
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
data, folder = self.read_dict_and_folder_from_archive(options["input"], tmpdir)
|
||||||
|
|
||||||
|
model = EnviFormer()
|
||||||
|
model.package = target_package
|
||||||
|
# model.uuid = data["uuid"]
|
||||||
|
model.name = data["name"]
|
||||||
|
model.description = data["description"]
|
||||||
|
model.kv = data["kv"]
|
||||||
|
model.threshold = float(data["threshold"])
|
||||||
|
model.eval_results = data["eval_results"]
|
||||||
|
model.multigen_eval = data["multigen_eval"]
|
||||||
|
model.model_status = data["model_status"]
|
||||||
|
model.save()
|
||||||
|
|
||||||
|
for p_uuid in data["data_packages_uuids"]:
|
||||||
|
p = Package.objects.get(uuid=p_uuid)
|
||||||
|
model.data_packages.add(p)
|
||||||
|
|
||||||
|
for p_uuid in data["eval_packages_uuids"]:
|
||||||
|
p = Package.objects.get(uuid=p_uuid)
|
||||||
|
model.eval_packages.add(p)
|
||||||
|
|
||||||
|
target_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
|
||||||
|
|
||||||
|
shutil.copytree(folder, target_folder)
|
||||||
|
os.rename(
|
||||||
|
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{data['uuid']}.ckpt"),
|
||||||
|
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{model.uuid}.ckpt"),
|
||||||
|
)
|
||||||
38
epdb/management/commands/update_job_logs.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from epdb.models import JobLog
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--cleanup",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Remove all logs older than this number of days. Default is None, which does not remove any logs.",
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if options["cleanup"] is not None:
|
||||||
|
cleanup_dt = date.today() - timedelta(days=options["cleanup"])
|
||||||
|
print(JobLog.objects.filter(created__lt=cleanup_dt).delete())
|
||||||
|
|
||||||
|
logs = JobLog.objects.filter(status="INITIAL")
|
||||||
|
print(f"Found {logs.count()} logs to update")
|
||||||
|
updated = 0
|
||||||
|
for log in logs:
|
||||||
|
res = log.check_for_update()
|
||||||
|
if res:
|
||||||
|
updated += 1
|
||||||
|
|
||||||
|
print(f"Updated {updated} logs")
|
||||||
|
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
qs = JobLog.objects.values("status").annotate(total=Count("status"))
|
||||||
|
for r in qs:
|
||||||
|
print(r["status"], r["total"])
|
||||||
66
epdb/migrations/0009_joblog.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-27 09:39
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0008_enzymelink"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="JobLog",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now, editable=False, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("task_id", models.UUIDField(unique=True)),
|
||||||
|
("job_name", models.TextField()),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("INITIAL", "Initial"),
|
||||||
|
("SUCCESS", "Success"),
|
||||||
|
("FAILURE", "Failure"),
|
||||||
|
("REVOKED", "Revoked"),
|
||||||
|
("IGNORED", "Ignored"),
|
||||||
|
],
|
||||||
|
default="INITIAL",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("done_at", models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
("task_result", models.TextField(blank=True, default=None, null=True)),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
epdb/migrations/0010_license_cc_string.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-11 14:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0009_joblog"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="license",
|
||||||
|
name="cc_string",
|
||||||
|
field=models.TextField(default="by-nc-sa", verbose_name="CC string"),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
59
epdb/migrations/0011_auto_20251111_1413.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-11 14:13
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.models import Min
|
||||||
|
|
||||||
|
|
||||||
|
def set_cc(apps, schema_editor):
|
||||||
|
License = apps.get_model("epdb", "License")
|
||||||
|
|
||||||
|
# For all existing licenses extract cc_string from link
|
||||||
|
for license in License.objects.all():
|
||||||
|
pattern = r"/licenses/([^/]+)/4\.0"
|
||||||
|
match = re.search(pattern, license.link)
|
||||||
|
if match:
|
||||||
|
license.cc_string = match.group(1)
|
||||||
|
license.save()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Could not find license for {license.link}")
|
||||||
|
|
||||||
|
# Ensure we have all licenses
|
||||||
|
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
|
||||||
|
for cc_string in cc_strings:
|
||||||
|
if not License.objects.filter(cc_string=cc_string).exists():
|
||||||
|
new_license = License()
|
||||||
|
new_license.cc_string = cc_string
|
||||||
|
new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/"
|
||||||
|
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
||||||
|
new_license.save()
|
||||||
|
|
||||||
|
# As we might have existing Licenses representing the same License,
|
||||||
|
# get min pk and all pks as a list
|
||||||
|
license_lookup_qs = License.objects.values("cc_string").annotate(
|
||||||
|
lowest_pk=Min("id"), all_pks=ArrayAgg("id", order_by=("id",))
|
||||||
|
)
|
||||||
|
|
||||||
|
license_lookup = {
|
||||||
|
row["cc_string"]: (row["lowest_pk"], row["all_pks"]) for row in license_lookup_qs
|
||||||
|
}
|
||||||
|
|
||||||
|
Packages = apps.get_model("epdb", "Package")
|
||||||
|
|
||||||
|
for k, v in license_lookup.items():
|
||||||
|
# Set min pk to all packages pointing to any of the duplicates
|
||||||
|
Packages.objects.filter(pk__in=v[1]).update(license_id=v[0])
|
||||||
|
# remove the min pk from "other" pks as we use them for deletion
|
||||||
|
v[1].remove(v[0])
|
||||||
|
# Delete redundant License objects
|
||||||
|
License.objects.filter(pk__in=v[1]).delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0010_license_cc_string"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(set_cc)]
|
||||||
525
epdb/models.py
@ -11,6 +11,7 @@ from typing import Union, List, Optional, Dict, Tuple, Set, Any
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import math
|
import math
|
||||||
import joblib
|
import joblib
|
||||||
|
import nh3
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
@ -28,7 +29,14 @@ from sklearn.metrics import precision_score, recall_score, jaccard_score
|
|||||||
from sklearn.model_selection import ShuffleSplit
|
from sklearn.model_selection import ShuffleSplit
|
||||||
|
|
||||||
from utilities.chem import FormatConverter, ProductSet, PredictionResult, IndigoUtils
|
from utilities.chem import FormatConverter, ProductSet, PredictionResult, IndigoUtils
|
||||||
from utilities.ml import Dataset, ApplicabilityDomainPCA, EnsembleClassifierChain, RelativeReasoning
|
from utilities.ml import (
|
||||||
|
RuleBasedDataset,
|
||||||
|
ApplicabilityDomainPCA,
|
||||||
|
EnsembleClassifierChain,
|
||||||
|
RelativeReasoning,
|
||||||
|
EnviFormerDataset,
|
||||||
|
Dataset,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -310,7 +318,7 @@ class ExternalDatabase(TimeStampedModel):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"database": ExternalDatabase.objects.get(name="ChEBI"),
|
"database": ExternalDatabase.objects.get(name="ChEBI"),
|
||||||
"placeholder": "ChEBI ID without prefix e.g. 12345",
|
"placeholder": "ChEBI ID without prefix e.g. 10576",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"structure": [
|
"structure": [
|
||||||
@ -328,7 +336,7 @@ class ExternalDatabase(TimeStampedModel):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"database": ExternalDatabase.objects.get(name="ChEBI"),
|
"database": ExternalDatabase.objects.get(name="ChEBI"),
|
||||||
"placeholder": "ChEBI ID without prefix e.g. 12345",
|
"placeholder": "ChEBI ID without prefix e.g. 10576",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"reaction": [
|
"reaction": [
|
||||||
@ -342,7 +350,7 @@ class ExternalDatabase(TimeStampedModel):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"database": ExternalDatabase.objects.get(name="UniProt"),
|
"database": ExternalDatabase.objects.get(name="UniProt"),
|
||||||
"placeholder": "Query ID for UniPro e.g. rhea:12345",
|
"placeholder": "Query ID for UniProt e.g. rhea:12345",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@ -477,7 +485,7 @@ class ChemicalIdentifierMixin(ExternalIdentifierMixin):
|
|||||||
return self.add_external_identifier("CAS", cas_number)
|
return self.add_external_identifier("CAS", cas_number)
|
||||||
|
|
||||||
def get_pubchem_identifiers(self):
|
def get_pubchem_identifiers(self):
|
||||||
return self.get_external_identifier("PubChem Compound") or self.get_external_identifier(
|
return self.get_external_identifier("PubChem Compound") | self.get_external_identifier(
|
||||||
"PubChem Substance"
|
"PubChem Substance"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -647,6 +655,7 @@ class ScenarioMixin(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class License(models.Model):
|
class License(models.Model):
|
||||||
|
cc_string = models.TextField(blank=False, null=False, verbose_name="CC string")
|
||||||
link = models.URLField(blank=False, null=False, verbose_name="link")
|
link = models.URLField(blank=False, null=False, verbose_name="link")
|
||||||
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
||||||
|
|
||||||
@ -802,14 +811,16 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
|
|||||||
c = Compound()
|
c = Compound()
|
||||||
c.package = package
|
c.package = package
|
||||||
|
|
||||||
if name is None or name.strip() == "":
|
if name is not None:
|
||||||
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
|
name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
c.name = name
|
c.name = name
|
||||||
|
|
||||||
# We have a default here only set the value if it carries some payload
|
# We have a default here only set the value if it carries some payload
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
c.description = description.strip()
|
c.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
@ -981,11 +992,11 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti
|
|||||||
raise ValueError("Unpersisted Compound! Persist compound first!")
|
raise ValueError("Unpersisted Compound! Persist compound first!")
|
||||||
|
|
||||||
cs = CompoundStructure()
|
cs = CompoundStructure()
|
||||||
|
# Clean for potential XSS
|
||||||
if name is not None:
|
if name is not None:
|
||||||
cs.name = name
|
cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if description is not None:
|
if description is not None:
|
||||||
cs.description = description
|
cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
cs.smiles = smiles
|
cs.smiles = smiles
|
||||||
cs.compound = compound
|
cs.compound = compound
|
||||||
@ -1187,21 +1198,29 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
r = SimpleAmbitRule()
|
r = SimpleAmbitRule()
|
||||||
r.package = package
|
r.package = package
|
||||||
|
|
||||||
if name is None or name.strip() == "":
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
|
name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
r.name = name
|
r.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
r.description = description
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r.smirks = smirks
|
r.smirks = smirks
|
||||||
|
|
||||||
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
||||||
r.reactant_filter_smarts = reactant_filter_smarts
|
if not FormatConverter.is_valid_smarts(reactant_filter_smarts.strip()):
|
||||||
|
raise ValueError(f'Reactant Filter SMARTS "{reactant_filter_smarts}" is invalid!')
|
||||||
|
else:
|
||||||
|
r.reactant_filter_smarts = reactant_filter_smarts.strip()
|
||||||
|
|
||||||
if product_filter_smarts is not None and product_filter_smarts.strip() != "":
|
if product_filter_smarts is not None and product_filter_smarts.strip() != "":
|
||||||
r.product_filter_smarts = product_filter_smarts
|
if not FormatConverter.is_valid_smarts(product_filter_smarts.strip()):
|
||||||
|
raise ValueError(f'Product Filter SMARTS "{product_filter_smarts}" is invalid!')
|
||||||
|
else:
|
||||||
|
r.product_filter_smarts = product_filter_smarts.strip()
|
||||||
|
|
||||||
r.save()
|
r.save()
|
||||||
return r
|
return r
|
||||||
@ -1402,12 +1421,11 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
|||||||
|
|
||||||
r = Reaction()
|
r = Reaction()
|
||||||
r.package = package
|
r.package = package
|
||||||
|
# Clean for potential XSS
|
||||||
if name is not None and name.strip() != "":
|
if name is not None and name.strip() != "":
|
||||||
r.name = name
|
r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if description is not None and name.strip() != "":
|
if description is not None and name.strip() != "":
|
||||||
r.description = description
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r.multi_step = multi_step
|
r.multi_step = multi_step
|
||||||
|
|
||||||
@ -1715,14 +1733,15 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
):
|
):
|
||||||
pw = Pathway()
|
pw = Pathway()
|
||||||
pw.package = package
|
pw.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Pathway {Pathway.objects.filter(package=package).count() + 1}"
|
name = f"Pathway {Pathway.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
pw.name = name
|
pw.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
pw.description = description
|
pw.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
pw.save()
|
pw.save()
|
||||||
try:
|
try:
|
||||||
@ -2017,11 +2036,16 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
for node in end_nodes:
|
for node in end_nodes:
|
||||||
e.end_nodes.add(node)
|
e.end_nodes.add(node)
|
||||||
|
|
||||||
if name is None:
|
# Clean for potential XSS
|
||||||
|
# Cleaning technically not needed as it is also done in Reaction.create, including it here for consistency
|
||||||
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Reaction {pathway.package.reactions.count() + 1}"
|
name = f"Reaction {pathway.package.reactions.count() + 1}"
|
||||||
|
|
||||||
if description is None:
|
if description is None:
|
||||||
description = s.DEFAULT_VALUES["description"]
|
description = s.DEFAULT_VALUES["description"]
|
||||||
|
description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r = Reaction.create(
|
r = Reaction.create(
|
||||||
pathway.package,
|
pathway.package,
|
||||||
@ -2175,7 +2199,7 @@ class PackageBasedModel(EPModel):
|
|||||||
|
|
||||||
applicable_rules = self.applicable_rules
|
applicable_rules = self.applicable_rules
|
||||||
reactions = list(self._get_reactions())
|
reactions = list(self._get_reactions())
|
||||||
ds = Dataset.generate_dataset(reactions, applicable_rules, educts_only=True)
|
ds = RuleBasedDataset.generate_dataset(reactions, applicable_rules, educts_only=True)
|
||||||
|
|
||||||
end = datetime.now()
|
end = datetime.now()
|
||||||
logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds")
|
logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds")
|
||||||
@ -2184,7 +2208,7 @@ class PackageBasedModel(EPModel):
|
|||||||
ds.save(f)
|
ds.save(f)
|
||||||
return ds
|
return ds
|
||||||
|
|
||||||
def load_dataset(self) -> "Dataset":
|
def load_dataset(self) -> "Dataset | RuleBasedDataset | EnviFormerDataset":
|
||||||
ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.pkl")
|
ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.pkl")
|
||||||
return Dataset.load(ds_path)
|
return Dataset.load(ds_path)
|
||||||
|
|
||||||
@ -2225,10 +2249,18 @@ class PackageBasedModel(EPModel):
|
|||||||
self.model_status = self.BUILT_NOT_EVALUATED
|
self.model_status = self.BUILT_NOT_EVALUATED
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def evaluate_model(self):
|
def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None, **kwargs):
|
||||||
if self.model_status != self.BUILT_NOT_EVALUATED:
|
if self.model_status != self.BUILT_NOT_EVALUATED:
|
||||||
raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
|
raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
|
||||||
|
|
||||||
|
if multigen:
|
||||||
|
self.multigen_eval = multigen
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
if eval_packages is not None:
|
||||||
|
for p in eval_packages:
|
||||||
|
self.eval_packages.add(p)
|
||||||
|
|
||||||
self.model_status = self.EVALUATING
|
self.model_status = self.EVALUATING
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@ -2335,37 +2367,39 @@ class PackageBasedModel(EPModel):
|
|||||||
eval_reactions = list(
|
eval_reactions = list(
|
||||||
Reaction.objects.filter(package__in=self.eval_packages.all()).distinct()
|
Reaction.objects.filter(package__in=self.eval_packages.all()).distinct()
|
||||||
)
|
)
|
||||||
ds = Dataset.generate_dataset(eval_reactions, self.applicable_rules, educts_only=True)
|
ds = RuleBasedDataset.generate_dataset(
|
||||||
|
eval_reactions, self.applicable_rules, educts_only=True
|
||||||
|
)
|
||||||
if isinstance(self, RuleBasedRelativeReasoning):
|
if isinstance(self, RuleBasedRelativeReasoning):
|
||||||
X = np.array(ds.X(exclude_id_col=False, na_replacement=None))
|
X = ds.X(exclude_id_col=False, na_replacement=None).to_numpy()
|
||||||
y = np.array(ds.y(na_replacement=np.nan))
|
y = ds.y(na_replacement=np.nan).to_numpy()
|
||||||
else:
|
else:
|
||||||
X = np.array(ds.X(na_replacement=np.nan))
|
X = ds.X(na_replacement=np.nan).to_numpy()
|
||||||
y = np.array(ds.y(na_replacement=np.nan))
|
y = ds.y(na_replacement=np.nan).to_numpy()
|
||||||
single_gen_result = evaluate_sg(self.model, X, y, np.arange(len(X)), self.threshold)
|
single_gen_result = evaluate_sg(self.model, X, y, np.arange(len(X)), self.threshold)
|
||||||
self.eval_results = self.compute_averages([single_gen_result])
|
self.eval_results = self.compute_averages([single_gen_result])
|
||||||
else:
|
else:
|
||||||
ds = self.load_dataset()
|
ds = self.load_dataset()
|
||||||
|
|
||||||
if isinstance(self, RuleBasedRelativeReasoning):
|
if isinstance(self, RuleBasedRelativeReasoning):
|
||||||
X = np.array(ds.X(exclude_id_col=False, na_replacement=None))
|
X = ds.X(exclude_id_col=False, na_replacement=None).to_numpy()
|
||||||
y = np.array(ds.y(na_replacement=np.nan))
|
y = ds.y(na_replacement=np.nan).to_numpy()
|
||||||
else:
|
else:
|
||||||
X = np.array(ds.X(na_replacement=np.nan))
|
X = ds.X(na_replacement=np.nan).to_numpy()
|
||||||
y = np.array(ds.y(na_replacement=np.nan))
|
y = ds.y(na_replacement=np.nan).to_numpy()
|
||||||
|
|
||||||
n_splits = 20
|
n_splits = kwargs.get("n_splits", 20)
|
||||||
|
|
||||||
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42)
|
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42)
|
||||||
splits = list(shuff.split(X))
|
splits = list(shuff.split(X))
|
||||||
|
|
||||||
from joblib import Parallel, delayed
|
from joblib import Parallel, delayed
|
||||||
|
|
||||||
models = Parallel(n_jobs=10)(
|
models = Parallel(n_jobs=min(10, len(splits)))(
|
||||||
delayed(train_func)(X, y, train_index, self._model_args())
|
delayed(train_func)(X, y, train_index, self._model_args())
|
||||||
for train_index, _ in splits
|
for train_index, _ in splits
|
||||||
)
|
)
|
||||||
evaluations = Parallel(n_jobs=10)(
|
evaluations = Parallel(n_jobs=min(10, len(splits)))(
|
||||||
delayed(evaluate_sg)(model, X, y, test_index, self.threshold)
|
delayed(evaluate_sg)(model, X, y, test_index, self.threshold)
|
||||||
for model, (_, test_index) in zip(models, splits)
|
for model, (_, test_index) in zip(models, splits)
|
||||||
)
|
)
|
||||||
@ -2525,7 +2559,6 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
|
|||||||
package: "Package",
|
package: "Package",
|
||||||
rule_packages: List["Package"],
|
rule_packages: List["Package"],
|
||||||
data_packages: List["Package"],
|
data_packages: List["Package"],
|
||||||
eval_packages: List["Package"],
|
|
||||||
threshold: float = 0.5,
|
threshold: float = 0.5,
|
||||||
min_count: int = 10,
|
min_count: int = 10,
|
||||||
max_count: int = 0,
|
max_count: int = 0,
|
||||||
@ -2534,14 +2567,15 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
|
|||||||
):
|
):
|
||||||
rbrr = RuleBasedRelativeReasoning()
|
rbrr = RuleBasedRelativeReasoning()
|
||||||
rbrr.package = package
|
rbrr.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"RuleBasedRelativeReasoning {RuleBasedRelativeReasoning.objects.filter(package=package).count() + 1}"
|
name = f"RuleBasedRelativeReasoning {RuleBasedRelativeReasoning.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
rbrr.name = name
|
rbrr.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
rbrr.description = description
|
rbrr.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
||||||
raise ValueError("Threshold must be a float between 0 and 1.")
|
raise ValueError("Threshold must be a float between 0 and 1.")
|
||||||
@ -2574,19 +2608,15 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
|
|||||||
for p in rule_packages:
|
for p in rule_packages:
|
||||||
rbrr.data_packages.add(p)
|
rbrr.data_packages.add(p)
|
||||||
|
|
||||||
if eval_packages:
|
|
||||||
for p in eval_packages:
|
|
||||||
rbrr.eval_packages.add(p)
|
|
||||||
|
|
||||||
rbrr.save()
|
rbrr.save()
|
||||||
|
|
||||||
return rbrr
|
return rbrr
|
||||||
|
|
||||||
def _fit_model(self, ds: Dataset):
|
def _fit_model(self, ds: RuleBasedDataset):
|
||||||
X, y = ds.X(exclude_id_col=False, na_replacement=None), ds.y(na_replacement=None)
|
X, y = ds.X(exclude_id_col=False, na_replacement=None), ds.y(na_replacement=None)
|
||||||
model = RelativeReasoning(
|
model = RelativeReasoning(
|
||||||
start_index=ds.triggered()[0],
|
start_index=ds.triggered()[0],
|
||||||
end_index=ds.triggered()[1],
|
end_index=ds.triggered()[-1],
|
||||||
)
|
)
|
||||||
model.fit(X, y)
|
model.fit(X, y)
|
||||||
return model
|
return model
|
||||||
@ -2596,7 +2626,7 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
|
|||||||
return {
|
return {
|
||||||
"clz": "RuleBaseRelativeReasoning",
|
"clz": "RuleBaseRelativeReasoning",
|
||||||
"start_index": ds.triggered()[0],
|
"start_index": ds.triggered()[0],
|
||||||
"end_index": ds.triggered()[1],
|
"end_index": ds.triggered()[-1],
|
||||||
}
|
}
|
||||||
|
|
||||||
def _save_model(self, model):
|
def _save_model(self, model):
|
||||||
@ -2632,7 +2662,6 @@ class MLRelativeReasoning(PackageBasedModel):
|
|||||||
package: "Package",
|
package: "Package",
|
||||||
rule_packages: List["Package"],
|
rule_packages: List["Package"],
|
||||||
data_packages: List["Package"],
|
data_packages: List["Package"],
|
||||||
eval_packages: List["Package"],
|
|
||||||
threshold: float = 0.5,
|
threshold: float = 0.5,
|
||||||
name: "str" = None,
|
name: "str" = None,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
@ -2643,14 +2672,15 @@ class MLRelativeReasoning(PackageBasedModel):
|
|||||||
):
|
):
|
||||||
mlrr = MLRelativeReasoning()
|
mlrr = MLRelativeReasoning()
|
||||||
mlrr.package = package
|
mlrr.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"MLRelativeReasoning {MLRelativeReasoning.objects.filter(package=package).count() + 1}"
|
name = f"MLRelativeReasoning {MLRelativeReasoning.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
mlrr.name = name
|
mlrr.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
mlrr.description = description
|
mlrr.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
||||||
raise ValueError("Threshold must be a float between 0 and 1.")
|
raise ValueError("Threshold must be a float between 0 and 1.")
|
||||||
@ -2672,10 +2702,6 @@ class MLRelativeReasoning(PackageBasedModel):
|
|||||||
for p in rule_packages:
|
for p in rule_packages:
|
||||||
mlrr.data_packages.add(p)
|
mlrr.data_packages.add(p)
|
||||||
|
|
||||||
if eval_packages:
|
|
||||||
for p in eval_packages:
|
|
||||||
mlrr.eval_packages.add(p)
|
|
||||||
|
|
||||||
if build_app_domain:
|
if build_app_domain:
|
||||||
ad = ApplicabilityDomain.create(
|
ad = ApplicabilityDomain.create(
|
||||||
mlrr,
|
mlrr,
|
||||||
@ -2689,11 +2715,11 @@ class MLRelativeReasoning(PackageBasedModel):
|
|||||||
|
|
||||||
return mlrr
|
return mlrr
|
||||||
|
|
||||||
def _fit_model(self, ds: Dataset):
|
def _fit_model(self, ds: RuleBasedDataset):
|
||||||
X, y = ds.X(na_replacement=np.nan), ds.y(na_replacement=np.nan)
|
X, y = ds.X(na_replacement=np.nan), ds.y(na_replacement=np.nan)
|
||||||
|
|
||||||
model = EnsembleClassifierChain(**s.DEFAULT_MODEL_PARAMS)
|
model = EnsembleClassifierChain(**s.DEFAULT_MODEL_PARAMS)
|
||||||
model.fit(X, y)
|
model.fit(X.to_numpy(), y.to_numpy())
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def _model_args(self):
|
def _model_args(self):
|
||||||
@ -2716,7 +2742,7 @@ class MLRelativeReasoning(PackageBasedModel):
|
|||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
ds = self.load_dataset()
|
ds = self.load_dataset()
|
||||||
classify_ds, classify_prods = ds.classification_dataset([smiles], self.applicable_rules)
|
classify_ds, classify_prods = ds.classification_dataset([smiles], self.applicable_rules)
|
||||||
pred = self.model.predict_proba(classify_ds.X())
|
pred = self.model.predict_proba(classify_ds.X().to_numpy())
|
||||||
|
|
||||||
res = MLRelativeReasoning.combine_products_and_probs(
|
res = MLRelativeReasoning.combine_products_and_probs(
|
||||||
self.applicable_rules, pred[0], classify_prods[0]
|
self.applicable_rules, pred[0], classify_prods[0]
|
||||||
@ -2761,7 +2787,9 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def training_set_probs(self):
|
def training_set_probs(self):
|
||||||
return joblib.load(os.path.join(s.MODEL_DIR, f"{self.model.uuid}_train_probs.pkl"))
|
ds = self.model.load_dataset()
|
||||||
|
col_ids = ds.block_indices("prob")
|
||||||
|
return ds[:, col_ids]
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
ds = self.model.load_dataset()
|
ds = self.model.load_dataset()
|
||||||
@ -2769,9 +2797,9 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
|
|
||||||
# Get Trainingset probs and dump them as they're required when using the app domain
|
# Get Trainingset probs and dump them as they're required when using the app domain
|
||||||
probs = self.model.model.predict_proba(ds.X())
|
probs = self.model.model.predict_proba(ds.X().to_numpy())
|
||||||
f = os.path.join(s.MODEL_DIR, f"{self.model.uuid}_train_probs.pkl")
|
ds.add_probs(probs)
|
||||||
joblib.dump(probs, f)
|
ds.save(os.path.join(s.MODEL_DIR, f"{self.model.uuid}_ds.pkl"))
|
||||||
|
|
||||||
ad = ApplicabilityDomainPCA(num_neighbours=self.num_neighbours)
|
ad = ApplicabilityDomainPCA(num_neighbours=self.num_neighbours)
|
||||||
ad.build(ds)
|
ad.build(ds)
|
||||||
@ -2794,15 +2822,20 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
joblib.dump(ad, f)
|
joblib.dump(ad, f)
|
||||||
|
|
||||||
def assess(self, structure: Union[str, "CompoundStructure"]):
|
def assess(self, structure: Union[str, "CompoundStructure"]):
|
||||||
|
return self.assess_batch([structure])[0]
|
||||||
|
|
||||||
|
def assess_batch(self, structures: List["CompoundStructure | str"]):
|
||||||
ds = self.model.load_dataset()
|
ds = self.model.load_dataset()
|
||||||
|
|
||||||
if isinstance(structure, CompoundStructure):
|
smiles = []
|
||||||
smiles = structure.smiles
|
for struct in structures:
|
||||||
|
if isinstance(struct, CompoundStructure):
|
||||||
|
smiles.append(structures.smiles)
|
||||||
else:
|
else:
|
||||||
smiles = structure
|
smiles.append(structures)
|
||||||
|
|
||||||
assessment_ds, assessment_prods = ds.classification_dataset(
|
assessment_ds, assessment_prods = ds.classification_dataset(
|
||||||
[structure], self.model.applicable_rules
|
structures, self.model.applicable_rules
|
||||||
)
|
)
|
||||||
|
|
||||||
# qualified_neighbours_per_rule is a nested dictionary structured as:
|
# qualified_neighbours_per_rule is a nested dictionary structured as:
|
||||||
@ -2816,82 +2849,61 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
# it identifies all training structures that have the same trigger reaction activated (i.e., value 1).
|
# it identifies all training structures that have the same trigger reaction activated (i.e., value 1).
|
||||||
# This is used to find "qualified neighbours" — training examples that share the same triggered feature
|
# This is used to find "qualified neighbours" — training examples that share the same triggered feature
|
||||||
# with a given assessment structure under a particular rule.
|
# with a given assessment structure under a particular rule.
|
||||||
qualified_neighbours_per_rule: Dict[int, Dict[int, List[int]]] = defaultdict(
|
qualified_neighbours_per_rule: Dict = {}
|
||||||
lambda: defaultdict(list)
|
|
||||||
)
|
|
||||||
|
|
||||||
for rule_idx, feature_index in enumerate(range(*assessment_ds.triggered())):
|
import polars as pl
|
||||||
feature = ds.columns[feature_index]
|
|
||||||
if feature.startswith("trig_"):
|
|
||||||
# TODO unroll loop
|
|
||||||
for i, cx in enumerate(assessment_ds.X(exclude_id_col=False)):
|
|
||||||
if int(cx[feature_index]) == 1:
|
|
||||||
for j, tx in enumerate(ds.X(exclude_id_col=False)):
|
|
||||||
if int(tx[feature_index]) == 1:
|
|
||||||
qualified_neighbours_per_rule[i][rule_idx].append(j)
|
|
||||||
|
|
||||||
probs = self.training_set_probs
|
# Select only the triggered columns
|
||||||
# preds = self.model.model.predict_proba(assessment_ds.X())
|
for i, row in enumerate(assessment_ds[:, assessment_ds.triggered()].iter_rows(named=True)):
|
||||||
|
# Find the rules the structure triggers. For each rule, filter the training dataset to rows that also
|
||||||
|
# trigger that rule.
|
||||||
|
train_trig = {
|
||||||
|
trig_uuid.split("_")[-1]: ds.filter(pl.col(trig_uuid).eq(1))
|
||||||
|
for trig_uuid, value in row.items()
|
||||||
|
if value == 1
|
||||||
|
}
|
||||||
|
qualified_neighbours_per_rule[i] = train_trig
|
||||||
|
rule_to_i = {str(r.uuid): i for i, r in enumerate(self.model.applicable_rules)}
|
||||||
preds = self.model.combine_products_and_probs(
|
preds = self.model.combine_products_and_probs(
|
||||||
self.model.applicable_rules,
|
self.model.applicable_rules,
|
||||||
self.model.model.predict_proba(assessment_ds.X())[0],
|
self.model.model.predict_proba(assessment_ds.X().to_numpy())[0],
|
||||||
assessment_prods[0],
|
assessment_prods[0],
|
||||||
)
|
)
|
||||||
|
|
||||||
assessments = list()
|
assessments = list()
|
||||||
|
|
||||||
# loop through our assessment dataset
|
# loop through our assessment dataset
|
||||||
for i, instance in enumerate(assessment_ds):
|
for i, instance in enumerate(assessment_ds[:, assessment_ds.struct_features()]):
|
||||||
rule_reliabilities = dict()
|
rule_reliabilities = dict()
|
||||||
local_compatibilities = dict()
|
local_compatibilities = dict()
|
||||||
neighbours_per_rule = dict()
|
neighbours_per_rule = dict()
|
||||||
neighbor_probs_per_rule = dict()
|
neighbor_probs_per_rule = dict()
|
||||||
|
|
||||||
# loop through rule indices together with the collected neighbours indices from train dataset
|
# loop through rule indices together with the collected neighbours indices from train dataset
|
||||||
for rule_idx, vals in qualified_neighbours_per_rule[i].items():
|
for rule_uuid, train_instances in qualified_neighbours_per_rule[i].items():
|
||||||
# collect the train dataset instances and store it along with the index (a.k.a. row number) of the
|
# compute tanimoto distance for all neighbours and add to dataset
|
||||||
# train dataset
|
|
||||||
train_instances = []
|
|
||||||
for v in vals:
|
|
||||||
train_instances.append((v, ds.at(v)))
|
|
||||||
|
|
||||||
# sf is a tuple with start/end index of the features
|
|
||||||
sf = ds.struct_features()
|
|
||||||
|
|
||||||
# compute tanimoto distance for all neighbours
|
|
||||||
# result ist a list of tuples with train index and computed distance
|
|
||||||
dists = self._compute_distances(
|
dists = self._compute_distances(
|
||||||
instance.X()[0][sf[0] : sf[1]],
|
assessment_ds[i, assessment_ds.struct_features()].to_numpy()[0],
|
||||||
[ti[1].X()[0][sf[0] : sf[1]] for ti in train_instances],
|
train_instances[:, train_instances.struct_features()].to_numpy(),
|
||||||
)
|
)
|
||||||
|
train_instances = train_instances.with_columns(dist=pl.Series(dists))
|
||||||
dists_with_index = list()
|
|
||||||
for ti, dist in zip(train_instances, dists):
|
|
||||||
dists_with_index.append((ti[0], dist[1]))
|
|
||||||
|
|
||||||
# sort them in a descending way and take at most `self.num_neighbours`
|
# sort them in a descending way and take at most `self.num_neighbours`
|
||||||
dists_with_index = sorted(dists_with_index, key=lambda x: x[1], reverse=True)
|
# TODO: Should this be descending? If we want the most similar then we want values close to zero (ascending)
|
||||||
dists_with_index = dists_with_index[: self.num_neighbours]
|
train_instances = train_instances.sort("dist", descending=True)[
|
||||||
|
: self.num_neighbours
|
||||||
|
]
|
||||||
# compute average distance
|
# compute average distance
|
||||||
rule_reliabilities[rule_idx] = (
|
rule_reliabilities[rule_uuid] = (
|
||||||
sum([d[1] for d in dists_with_index]) / len(dists_with_index)
|
train_instances.select(pl.mean("dist")).fill_nan(0.0).item()
|
||||||
if len(dists_with_index) > 0
|
|
||||||
else 0.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# for local_compatibility we'll need the datasets for the indices having the highest similarity
|
# for local_compatibility we'll need the datasets for the indices having the highest similarity
|
||||||
neighbour_datasets = [(d[0], ds.at(d[0])) for d in dists_with_index]
|
local_compatibilities[rule_uuid] = self._compute_compatibility(
|
||||||
local_compatibilities[rule_idx] = self._compute_compatibility(
|
rule_uuid, train_instances
|
||||||
rule_idx, probs, neighbour_datasets
|
|
||||||
)
|
)
|
||||||
neighbours_per_rule[rule_idx] = [
|
neighbours_per_rule[rule_uuid] = list(
|
||||||
CompoundStructure.objects.get(uuid=ds[1].structure_id())
|
CompoundStructure.objects.filter(uuid__in=train_instances["structure_id"])
|
||||||
for ds in neighbour_datasets
|
)
|
||||||
]
|
neighbor_probs_per_rule[rule_uuid] = train_instances[f"prob_{rule_uuid}"].to_list()
|
||||||
neighbor_probs_per_rule[rule_idx] = [
|
|
||||||
probs[d[0]][rule_idx] for d in dists_with_index
|
|
||||||
]
|
|
||||||
|
|
||||||
ad_res = {
|
ad_res = {
|
||||||
"ad_params": {
|
"ad_params": {
|
||||||
@ -2902,23 +2914,21 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
"local_compatibility_threshold": self.local_compatibilty_threshold,
|
"local_compatibility_threshold": self.local_compatibilty_threshold,
|
||||||
},
|
},
|
||||||
"assessment": {
|
"assessment": {
|
||||||
"smiles": smiles,
|
"smiles": smiles[i],
|
||||||
"inside_app_domain": self.pca.is_applicable(instance)[0],
|
"inside_app_domain": self.pca.is_applicable(assessment_ds[i])[0],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
transformations = list()
|
transformations = list()
|
||||||
for rule_idx in rule_reliabilities.keys():
|
for rule_uuid in rule_reliabilities.keys():
|
||||||
rule = Rule.objects.get(
|
rule = Rule.objects.get(uuid=rule_uuid)
|
||||||
uuid=instance.columns[instance.observed()[0] + rule_idx].replace("obs_", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
rule_data = rule.simple_json()
|
rule_data = rule.simple_json()
|
||||||
rule_data["image"] = f"{rule.url}?image=svg"
|
rule_data["image"] = f"{rule.url}?image=svg"
|
||||||
|
|
||||||
neighbors = []
|
neighbors = []
|
||||||
for n, n_prob in zip(
|
for n, n_prob in zip(
|
||||||
neighbours_per_rule[rule_idx], neighbor_probs_per_rule[rule_idx]
|
neighbours_per_rule[rule_uuid], neighbor_probs_per_rule[rule_uuid]
|
||||||
):
|
):
|
||||||
neighbor = n.simple_json()
|
neighbor = n.simple_json()
|
||||||
neighbor["image"] = f"{n.url}?image=svg"
|
neighbor["image"] = f"{n.url}?image=svg"
|
||||||
@ -2935,14 +2945,14 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
|
|
||||||
transformation = {
|
transformation = {
|
||||||
"rule": rule_data,
|
"rule": rule_data,
|
||||||
"reliability": rule_reliabilities[rule_idx],
|
"reliability": rule_reliabilities[rule_uuid],
|
||||||
# We're setting it here to False, as we don't know whether "assess" is called during pathway
|
# We're setting it here to False, as we don't know whether "assess" is called during pathway
|
||||||
# prediction or from Model Page. For persisted Nodes this field will be overwritten at runtime
|
# prediction or from Model Page. For persisted Nodes this field will be overwritten at runtime
|
||||||
"is_predicted": False,
|
"is_predicted": False,
|
||||||
"local_compatibility": local_compatibilities[rule_idx],
|
"local_compatibility": local_compatibilities[rule_uuid],
|
||||||
"probability": preds[rule_idx].probability,
|
"probability": preds[rule_to_i[rule_uuid]].probability,
|
||||||
"transformation_products": [
|
"transformation_products": [
|
||||||
x.product_set for x in preds[rule_idx].product_sets
|
x.product_set for x in preds[rule_to_i[rule_uuid]].product_sets
|
||||||
],
|
],
|
||||||
"times_triggered": ds.times_triggered(str(rule.uuid)),
|
"times_triggered": ds.times_triggered(str(rule.uuid)),
|
||||||
"neighbors": neighbors,
|
"neighbors": neighbors,
|
||||||
@ -2960,32 +2970,24 @@ class ApplicabilityDomain(EnviPathModel):
|
|||||||
def _compute_distances(classify_instance: List[int], train_instances: List[List[int]]):
|
def _compute_distances(classify_instance: List[int], train_instances: List[List[int]]):
|
||||||
from utilities.ml import tanimoto_distance
|
from utilities.ml import tanimoto_distance
|
||||||
|
|
||||||
distances = [
|
distances = [tanimoto_distance(classify_instance, train) for train in train_instances]
|
||||||
(i, tanimoto_distance(classify_instance, train))
|
|
||||||
for i, train in enumerate(train_instances)
|
|
||||||
]
|
|
||||||
return distances
|
return distances
|
||||||
|
|
||||||
@staticmethod
|
def _compute_compatibility(self, rule_idx: int, neighbours: "RuleBasedDataset"):
|
||||||
def _compute_compatibility(rule_idx: int, preds, neighbours: List[Tuple[int, "Dataset"]]):
|
|
||||||
tp, tn, fp, fn = 0.0, 0.0, 0.0, 0.0
|
|
||||||
accuracy = 0.0
|
accuracy = 0.0
|
||||||
|
import polars as pl
|
||||||
|
|
||||||
for n in neighbours:
|
obs_pred = neighbours.select(
|
||||||
obs = n[1].y()[0][rule_idx]
|
obs=pl.col(f"obs_{rule_idx}").cast(pl.Boolean),
|
||||||
pred = preds[n[0]][rule_idx]
|
pred=pl.col(f"prob_{rule_idx}") >= self.model.threshold,
|
||||||
if obs and pred:
|
)
|
||||||
tp += 1
|
# Compute tp, tn, fp, fn using polars expressions
|
||||||
elif not obs and pred:
|
tp = obs_pred.filter((pl.col("obs")) & (pl.col("pred"))).height
|
||||||
fp += 1
|
tn = obs_pred.filter((~pl.col("obs")) & (~pl.col("pred"))).height
|
||||||
elif obs and not pred:
|
fp = obs_pred.filter((~pl.col("obs")) & (pl.col("pred"))).height
|
||||||
fn += 1
|
fn = obs_pred.filter((pl.col("obs")) & (~pl.col("pred"))).height
|
||||||
else:
|
|
||||||
tn += 1
|
|
||||||
# Jaccard Index
|
|
||||||
if tp + tn > 0.0:
|
if tp + tn > 0.0:
|
||||||
accuracy = (tp + tn) / (tp + tn + fp + fn)
|
accuracy = (tp + tn) / (tp + tn + fp + fn)
|
||||||
|
|
||||||
return accuracy
|
return accuracy
|
||||||
|
|
||||||
|
|
||||||
@ -2995,7 +2997,6 @@ class EnviFormer(PackageBasedModel):
|
|||||||
def create(
|
def create(
|
||||||
package: "Package",
|
package: "Package",
|
||||||
data_packages: List["Package"],
|
data_packages: List["Package"],
|
||||||
eval_packages: List["Package"],
|
|
||||||
threshold: float = 0.5,
|
threshold: float = 0.5,
|
||||||
name: "str" = None,
|
name: "str" = None,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
@ -3006,14 +3007,15 @@ class EnviFormer(PackageBasedModel):
|
|||||||
):
|
):
|
||||||
mod = EnviFormer()
|
mod = EnviFormer()
|
||||||
mod.package = package
|
mod.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"EnviFormer {EnviFormer.objects.filter(package=package).count() + 1}"
|
name = f"EnviFormer {EnviFormer.objects.filter(package=package).count() + 1}"
|
||||||
|
|
||||||
mod.name = name
|
mod.name = name
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
mod.description = description
|
mod.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
if threshold is None or (threshold <= 0 or 1 <= threshold):
|
||||||
raise ValueError("Threshold must be a float between 0 and 1.")
|
raise ValueError("Threshold must be a float between 0 and 1.")
|
||||||
@ -3028,10 +3030,6 @@ class EnviFormer(PackageBasedModel):
|
|||||||
for p in data_packages:
|
for p in data_packages:
|
||||||
mod.data_packages.add(p)
|
mod.data_packages.add(p)
|
||||||
|
|
||||||
if eval_packages:
|
|
||||||
for p in eval_packages:
|
|
||||||
mod.eval_packages.add(p)
|
|
||||||
|
|
||||||
# if build_app_domain:
|
# if build_app_domain:
|
||||||
# ad = ApplicabilityDomain.create(mod, app_domain_num_neighbours, app_domain_reliability_threshold,
|
# ad = ApplicabilityDomain.create(mod, app_domain_num_neighbours, app_domain_reliability_threshold,
|
||||||
# app_domain_local_compatibility_threshold)
|
# app_domain_local_compatibility_threshold)
|
||||||
@ -3045,7 +3043,8 @@ class EnviFormer(PackageBasedModel):
|
|||||||
from enviformer import load
|
from enviformer import load
|
||||||
|
|
||||||
ckpt = os.path.join(s.MODEL_DIR, "enviformer", str(self.uuid), f"{self.uuid}.ckpt")
|
ckpt = os.path.join(s.MODEL_DIR, "enviformer", str(self.uuid), f"{self.uuid}.ckpt")
|
||||||
return load(device=s.ENVIFORMER_DEVICE, ckpt_path=ckpt)
|
mod = load(device=s.ENVIFORMER_DEVICE, ckpt_path=ckpt)
|
||||||
|
return mod
|
||||||
|
|
||||||
def predict(self, smiles) -> List["PredictionResult"]:
|
def predict(self, smiles) -> List["PredictionResult"]:
|
||||||
return self.predict_batch([smiles])[0]
|
return self.predict_batch([smiles])[0]
|
||||||
@ -3059,8 +3058,12 @@ class EnviFormer(PackageBasedModel):
|
|||||||
for smiles in smiles_list
|
for smiles in smiles_list
|
||||||
]
|
]
|
||||||
logger.info(f"Submitting {canon_smiles} to {self.name}")
|
logger.info(f"Submitting {canon_smiles} to {self.name}")
|
||||||
|
start = datetime.now()
|
||||||
products_list = self.model.predict_batch(canon_smiles)
|
products_list = self.model.predict_batch(canon_smiles)
|
||||||
logger.info(f"Got results {products_list}")
|
end = datetime.now()
|
||||||
|
logger.info(
|
||||||
|
f"Prediction took {(end - start).total_seconds():.2f} seconds. Got results {products_list}"
|
||||||
|
)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for products in products_list:
|
for products in products_list:
|
||||||
@ -3086,42 +3089,24 @@ class EnviFormer(PackageBasedModel):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
# Standardise reactions for the training data, EnviFormer ignores stereochemistry currently
|
ds = EnviFormerDataset.generate_dataset(self._get_reactions())
|
||||||
ds = []
|
|
||||||
for reaction in self._get_reactions():
|
|
||||||
educts = ".".join(
|
|
||||||
[
|
|
||||||
FormatConverter.standardize(smile.smiles, remove_stereo=True)
|
|
||||||
for smile in reaction.educts.all()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
products = ".".join(
|
|
||||||
[
|
|
||||||
FormatConverter.standardize(smile.smiles, remove_stereo=True)
|
|
||||||
for smile in reaction.products.all()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
ds.append(f"{educts}>>{products}")
|
|
||||||
|
|
||||||
end = datetime.now()
|
end = datetime.now()
|
||||||
logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds")
|
logger.debug(f"build_dataset took {(end - start).total_seconds()} seconds")
|
||||||
f = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json")
|
f = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json")
|
||||||
with open(f, "w") as d_file:
|
ds.save(f)
|
||||||
json.dump(ds, d_file)
|
|
||||||
return ds
|
return ds
|
||||||
|
|
||||||
def load_dataset(self) -> "Dataset":
|
def load_dataset(self):
|
||||||
ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json")
|
ds_path = os.path.join(s.MODEL_DIR, f"{self.uuid}_ds.json")
|
||||||
with open(ds_path) as d_file:
|
return EnviFormerDataset.load(ds_path)
|
||||||
ds = json.load(d_file)
|
|
||||||
return ds
|
|
||||||
|
|
||||||
def _fit_model(self, ds):
|
def _fit_model(self, ds):
|
||||||
# Call to enviFormer's fine_tune function and return the model
|
# Call to enviFormer's fine_tune function and return the model
|
||||||
from enviformer.finetune import fine_tune
|
from enviformer.finetune import fine_tune
|
||||||
|
|
||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
model = fine_tune(ds, s.MODEL_DIR, str(self.uuid), device=s.ENVIFORMER_DEVICE)
|
model = fine_tune(ds.X(), ds.y(), s.MODEL_DIR, str(self.uuid), device=s.ENVIFORMER_DEVICE)
|
||||||
end = datetime.now()
|
end = datetime.now()
|
||||||
logger.debug(f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds")
|
logger.debug(f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds")
|
||||||
return model
|
return model
|
||||||
@ -3137,28 +3122,35 @@ class EnviFormer(PackageBasedModel):
|
|||||||
args = {"clz": "EnviFormer"}
|
args = {"clz": "EnviFormer"}
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def evaluate_model(self):
|
def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None, **kwargs):
|
||||||
if self.model_status != self.BUILT_NOT_EVALUATED:
|
if self.model_status != self.BUILT_NOT_EVALUATED:
|
||||||
raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
|
raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
|
||||||
|
|
||||||
|
if multigen:
|
||||||
|
self.multigen_eval = multigen
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
if eval_packages is not None:
|
||||||
|
for p in eval_packages:
|
||||||
|
self.eval_packages.add(p)
|
||||||
|
|
||||||
self.model_status = self.EVALUATING
|
self.model_status = self.EVALUATING
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def evaluate_sg(test_reactions, predictions, model_thresh):
|
def evaluate_sg(test_ds, predictions, model_thresh):
|
||||||
# Group the true products of reactions with the same reactant together
|
# Group the true products of reactions with the same reactant together
|
||||||
|
assert len(test_ds) == len(predictions)
|
||||||
true_dict = {}
|
true_dict = {}
|
||||||
for r in test_reactions:
|
for r in test_ds:
|
||||||
reactant, true_product_set = r.split(">>")
|
reactant, true_product_set = r
|
||||||
true_product_set = {p for p in true_product_set.split(".")}
|
true_product_set = {p for p in true_product_set.split(".")}
|
||||||
true_dict[reactant] = true_dict.setdefault(reactant, []) + [true_product_set]
|
true_dict[reactant] = true_dict.setdefault(reactant, []) + [true_product_set]
|
||||||
assert len(test_reactions) == len(predictions)
|
|
||||||
assert sum(len(v) for v in true_dict.values()) == len(test_reactions)
|
|
||||||
|
|
||||||
# Group the predicted products of reactions with the same reactant together
|
# Group the predicted products of reactions with the same reactant together
|
||||||
pred_dict = {}
|
pred_dict = {}
|
||||||
for k, pred in enumerate(predictions):
|
for k, pred in enumerate(predictions):
|
||||||
pred_smiles, pred_proba = zip(*pred.items())
|
pred_smiles, pred_proba = zip(*pred.items())
|
||||||
reactant, true_product = test_reactions[k].split(">>")
|
reactant, _ = test_ds[k, "educts"], test_ds[k, "products"]
|
||||||
pred_dict.setdefault(reactant, {"predict": [], "scores": []})
|
pred_dict.setdefault(reactant, {"predict": [], "scores": []})
|
||||||
for smiles, proba in zip(pred_smiles, pred_proba):
|
for smiles, proba in zip(pred_smiles, pred_proba):
|
||||||
smiles = set(smiles.split("."))
|
smiles = set(smiles.split("."))
|
||||||
@ -3193,7 +3185,7 @@ class EnviFormer(PackageBasedModel):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Recall is TP (correct) / TP + FN (len(test_reactions))
|
# Recall is TP (correct) / TP + FN (len(test_reactions))
|
||||||
rec = {f"{k:.2f}": v / len(test_reactions) for k, v in correct.items()}
|
rec = {f"{k:.2f}": v / len(test_ds) for k, v in correct.items()}
|
||||||
# Precision is TP (correct) / TP + FP (predicted)
|
# Precision is TP (correct) / TP + FP (predicted)
|
||||||
prec = {
|
prec = {
|
||||||
f"{k:.2f}": v / predicted[k] if predicted[k] > 0 else 0 for k, v in correct.items()
|
f"{k:.2f}": v / predicted[k] if predicted[k] > 0 else 0 for k, v in correct.items()
|
||||||
@ -3272,47 +3264,35 @@ class EnviFormer(PackageBasedModel):
|
|||||||
|
|
||||||
# If there are eval packages perform single generation evaluation on them instead of random splits
|
# If there are eval packages perform single generation evaluation on them instead of random splits
|
||||||
if self.eval_packages.count() > 0:
|
if self.eval_packages.count() > 0:
|
||||||
ds = []
|
ds = EnviFormerDataset.generate_dataset(
|
||||||
for reaction in Reaction.objects.filter(
|
Reaction.objects.filter(package__in=self.eval_packages.all()).distinct()
|
||||||
package__in=self.eval_packages.all()
|
|
||||||
).distinct():
|
|
||||||
educts = ".".join(
|
|
||||||
[
|
|
||||||
FormatConverter.standardize(smile.smiles, remove_stereo=True)
|
|
||||||
for smile in reaction.educts.all()
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
products = ".".join(
|
test_result = self.model.predict_batch(ds.X())
|
||||||
[
|
|
||||||
FormatConverter.standardize(smile.smiles, remove_stereo=True)
|
|
||||||
for smile in reaction.products.all()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
ds.append(f"{educts}>>{products}")
|
|
||||||
test_result = self.model.predict_batch([smirk.split(">>")[0] for smirk in ds])
|
|
||||||
single_gen_result = evaluate_sg(ds, test_result, self.threshold)
|
single_gen_result = evaluate_sg(ds, test_result, self.threshold)
|
||||||
self.eval_results = self.compute_averages([single_gen_result])
|
self.eval_results = self.compute_averages([single_gen_result])
|
||||||
else:
|
else:
|
||||||
from enviformer.finetune import fine_tune
|
from enviformer.finetune import fine_tune
|
||||||
|
|
||||||
ds = self.load_dataset()
|
ds = self.load_dataset()
|
||||||
n_splits = 20
|
n_splits = kwargs.get("n_splits", 20)
|
||||||
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42)
|
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.1, random_state=42)
|
||||||
|
|
||||||
# Single gen eval is done in one loop of train then evaluate rather than storing all n_splits trained models
|
# Single gen eval is done in one loop of train then evaluate rather than storing all n_splits trained models
|
||||||
# this helps reduce the memory footprint.
|
# this helps reduce the memory footprint.
|
||||||
single_gen_results = []
|
single_gen_results = []
|
||||||
for split_id, (train_index, test_index) in enumerate(shuff.split(ds)):
|
for split_id, (train_index, test_index) in enumerate(shuff.split(ds)):
|
||||||
train = [ds[i] for i in train_index]
|
train = ds[train_index]
|
||||||
test = [ds[i] for i in test_index]
|
test = ds[test_index]
|
||||||
start = datetime.now()
|
start = datetime.now()
|
||||||
model = fine_tune(train, s.MODEL_DIR, str(split_id), device=s.ENVIFORMER_DEVICE)
|
model = fine_tune(
|
||||||
|
train.X(), train.y(), s.MODEL_DIR, str(split_id), device=s.ENVIFORMER_DEVICE
|
||||||
|
)
|
||||||
end = datetime.now()
|
end = datetime.now()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds"
|
f"EnviFormer finetuning took {(end - start).total_seconds():.2f} seconds"
|
||||||
)
|
)
|
||||||
model.to(s.ENVIFORMER_DEVICE)
|
model.to(s.ENVIFORMER_DEVICE)
|
||||||
test_result = model.predict_batch([smirk.split(">>")[0] for smirk in test])
|
test_result = model.predict_batch(test.X())
|
||||||
single_gen_results.append(evaluate_sg(test, test_result, self.threshold))
|
single_gen_results.append(evaluate_sg(test, test_result, self.threshold))
|
||||||
|
|
||||||
self.eval_results = self.compute_averages(single_gen_results)
|
self.eval_results = self.compute_averages(single_gen_results)
|
||||||
@ -3365,7 +3345,7 @@ class EnviFormer(PackageBasedModel):
|
|||||||
# Compute splits of the collected pathway and evaluate. Like single gen we train and evaluate in each
|
# Compute splits of the collected pathway and evaluate. Like single gen we train and evaluate in each
|
||||||
# iteration instead of storing all trained models.
|
# iteration instead of storing all trained models.
|
||||||
for split_id, (train, test) in enumerate(
|
for split_id, (train, test) in enumerate(
|
||||||
ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42).split(pathways)
|
ShuffleSplit(n_splits=n_splits, test_size=0.1, random_state=42).split(pathways)
|
||||||
):
|
):
|
||||||
train_pathways = [pathways[i] for i in train]
|
train_pathways = [pathways[i] for i in train]
|
||||||
test_pathways = [pathways[i] for i in test]
|
test_pathways = [pathways[i] for i in test]
|
||||||
@ -3391,23 +3371,12 @@ class EnviFormer(PackageBasedModel):
|
|||||||
):
|
):
|
||||||
overlap += 1
|
overlap += 1
|
||||||
continue
|
continue
|
||||||
educts = ".".join(
|
train_reactions.append(reaction)
|
||||||
[
|
train_ds = EnviFormerDataset.generate_dataset(train_reactions)
|
||||||
FormatConverter.standardize(smile.smiles, remove_stereo=True)
|
|
||||||
for smile in reaction.educts.all()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
products = ".".join(
|
|
||||||
[
|
|
||||||
FormatConverter.standardize(smile.smiles, remove_stereo=True)
|
|
||||||
for smile in reaction.products.all()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
train_reactions.append(f"{educts}>>{products}")
|
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"{overlap} compounds had to be removed from multigen split due to overlap within pathways"
|
f"{overlap} compounds had to be removed from multigen split due to overlap within pathways"
|
||||||
)
|
)
|
||||||
model = fine_tune(train_reactions, s.MODEL_DIR, f"mg_{split_id}")
|
model = fine_tune(train_ds.X(), train_ds.y(), s.MODEL_DIR, f"mg_{split_id}")
|
||||||
multi_gen_results.append(evaluate_mg(model, test_pathways, self.threshold))
|
multi_gen_results.append(evaluate_mg(model, test_pathways, self.threshold))
|
||||||
|
|
||||||
self.eval_results.update(
|
self.eval_results.update(
|
||||||
@ -3456,41 +3425,44 @@ class Scenario(EnviPathModel):
|
|||||||
scenario_type: str,
|
scenario_type: str,
|
||||||
additional_information: List["EnviPyModel"],
|
additional_information: List["EnviPyModel"],
|
||||||
):
|
):
|
||||||
s = Scenario()
|
new_s = Scenario()
|
||||||
s.package = package
|
new_s.package = package
|
||||||
|
if name is not None:
|
||||||
if name is None or name.strip() == "":
|
# Clean for potential XSS
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
if name is None or name == "":
|
||||||
name = f"Scenario {Scenario.objects.filter(package=package).count() + 1}"
|
name = f"Scenario {Scenario.objects.filter(package=package).count() + 1}"
|
||||||
|
new_s.name = name
|
||||||
s.name = name
|
|
||||||
|
|
||||||
if description is not None and description.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
s.description = description
|
new_s.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if scenario_date is not None and scenario_date.strip() != "":
|
if scenario_date is not None and scenario_date.strip() != "":
|
||||||
s.scenario_date = scenario_date
|
new_s.scenario_date = nh3.clean(scenario_date).strip()
|
||||||
|
|
||||||
if scenario_type is not None and scenario_type.strip() != "":
|
if scenario_type is not None and scenario_type.strip() != "":
|
||||||
s.scenario_type = scenario_type
|
new_s.scenario_type = scenario_type
|
||||||
|
|
||||||
add_inf = defaultdict(list)
|
add_inf = defaultdict(list)
|
||||||
|
|
||||||
for info in additional_information:
|
for info in additional_information:
|
||||||
cls_name = info.__class__.__name__
|
cls_name = info.__class__.__name__
|
||||||
ai_data = json.loads(info.model_dump_json())
|
# Clean for potential XSS hidden in the additional information fields.
|
||||||
|
ai_data = json.loads(nh3.clean(info.model_dump_json()).strip())
|
||||||
ai_data["uuid"] = f"{uuid4()}"
|
ai_data["uuid"] = f"{uuid4()}"
|
||||||
add_inf[cls_name].append(ai_data)
|
add_inf[cls_name].append(ai_data)
|
||||||
|
|
||||||
s.additional_information = add_inf
|
new_s.additional_information = add_inf
|
||||||
|
|
||||||
s.save()
|
new_s.save()
|
||||||
|
|
||||||
return s
|
return new_s
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def add_additional_information(self, data: "EnviPyModel"):
|
def add_additional_information(self, data: "EnviPyModel"):
|
||||||
cls_name = data.__class__.__name__
|
cls_name = data.__class__.__name__
|
||||||
ai_data = json.loads(data.model_dump_json())
|
# Clean for potential XSS hidden in the additional information fields.
|
||||||
|
ai_data = json.loads(nh3.clean(data.model_dump_json()).strip())
|
||||||
ai_data["uuid"] = f"{uuid4()}"
|
ai_data["uuid"] = f"{uuid4()}"
|
||||||
|
|
||||||
if cls_name not in self.additional_information:
|
if cls_name not in self.additional_information:
|
||||||
@ -3525,7 +3497,8 @@ class Scenario(EnviPathModel):
|
|||||||
new_ais = defaultdict(list)
|
new_ais = defaultdict(list)
|
||||||
for k, vals in data.items():
|
for k, vals in data.items():
|
||||||
for v in vals:
|
for v in vals:
|
||||||
ai_data = json.loads(v.model_dump_json())
|
# Clean for potential XSS hidden in the additional information fields.
|
||||||
|
ai_data = json.loads(nh3.clean(v.model_dump_json()).strip())
|
||||||
if hasattr(v, "uuid"):
|
if hasattr(v, "uuid"):
|
||||||
ai_data["uuid"] = str(v.uuid)
|
ai_data["uuid"] = str(v.uuid)
|
||||||
else:
|
else:
|
||||||
@ -3664,3 +3637,53 @@ class Setting(EnviPathModel):
|
|||||||
self.public = True
|
self.public = True
|
||||||
self.global_default = True
|
self.global_default = True
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogStatus(models.TextChoices):
|
||||||
|
INITIAL = "INITIAL", "Initial"
|
||||||
|
SUCCESS = "SUCCESS", "Success"
|
||||||
|
FAILURE = "FAILURE", "Failure"
|
||||||
|
REVOKED = "REVOKED", "Revoked"
|
||||||
|
IGNORED = "IGNORED", "Ignored"
|
||||||
|
|
||||||
|
|
||||||
|
class JobLog(TimeStampedModel):
|
||||||
|
user = models.ForeignKey("epdb.User", models.CASCADE)
|
||||||
|
task_id = models.UUIDField(unique=True)
|
||||||
|
job_name = models.TextField(null=False, blank=False)
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=JobLogStatus.choices,
|
||||||
|
default=JobLogStatus.INITIAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
done_at = models.DateTimeField(null=True, blank=True, default=None)
|
||||||
|
task_result = models.TextField(null=True, blank=True, default=None)
|
||||||
|
|
||||||
|
def check_for_update(self):
|
||||||
|
async_res = self.get_result()
|
||||||
|
new_status = async_res.state
|
||||||
|
|
||||||
|
TERMINAL_STATES = [
|
||||||
|
"SUCCESS",
|
||||||
|
"FAILURE",
|
||||||
|
"REVOKED",
|
||||||
|
"IGNORED",
|
||||||
|
]
|
||||||
|
|
||||||
|
if new_status != self.status and new_status in TERMINAL_STATES:
|
||||||
|
self.status = new_status
|
||||||
|
self.done_at = async_res.date_done
|
||||||
|
|
||||||
|
if new_status == "SUCCESS":
|
||||||
|
self.task_result = async_res.result
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_result(self):
|
||||||
|
from celery.result import AsyncResult
|
||||||
|
|
||||||
|
return AsyncResult(str(self.task_id))
|
||||||
|
|||||||
220
epdb/tasks.py
@ -1,12 +1,58 @@
|
|||||||
|
import csv
|
||||||
|
import io
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from datetime import datetime
|
||||||
|
from typing import Any, Callable, List, Optional
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from epdb.models import Pathway, Node, EPModel, Setting
|
from celery.utils.functional import LRUCache
|
||||||
from epdb.logic import SPathway
|
|
||||||
|
|
||||||
|
from epdb.logic import SPathway
|
||||||
|
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||||
|
|
||||||
|
|
||||||
|
def get_ml_model(model_pk: int):
|
||||||
|
if model_pk not in ML_CACHE:
|
||||||
|
ML_CACHE[model_pk] = EPModel.objects.get(id=model_pk)
|
||||||
|
return ML_CACHE[model_pk]
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
x = job(*args, **kwargs)
|
||||||
|
log = JobLog()
|
||||||
|
log.user = user
|
||||||
|
log.task_id = uuid4()
|
||||||
|
log.job_name = job.__name__
|
||||||
|
log.status = "SUCCESS"
|
||||||
|
log.done_at = datetime.now()
|
||||||
|
log.task_result = str(x) if x else None
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
return x
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch(user: "User", job: Callable, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
x = job.delay(*args, **kwargs)
|
||||||
|
log = JobLog()
|
||||||
|
log.user = user
|
||||||
|
log.task_id = x.task_id
|
||||||
|
log.job_name = job.__name__
|
||||||
|
log.status = "INITIAL"
|
||||||
|
log.save()
|
||||||
|
|
||||||
|
return x.result
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="background")
|
@shared_task(queue="background")
|
||||||
@ -16,7 +62,7 @@ def mul(a, b):
|
|||||||
|
|
||||||
@shared_task(queue="predict")
|
@shared_task(queue="predict")
|
||||||
def predict_simple(model_pk: int, smiles: str):
|
def predict_simple(model_pk: int, smiles: str):
|
||||||
mod = EPModel.objects.get(id=model_pk)
|
mod = get_ml_model(model_pk)
|
||||||
res = mod.predict(smiles)
|
res = mod.predict(smiles)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@ -26,17 +72,55 @@ def send_registration_mail(user_pk: int):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="model")
|
@shared_task(bind=True, queue="model")
|
||||||
def build_model(model_pk: int):
|
def build_model(self, model_pk: int):
|
||||||
mod = EPModel.objects.get(id=model_pk)
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
|
||||||
|
|
||||||
|
try:
|
||||||
mod.build_dataset()
|
mod.build_dataset()
|
||||||
mod.build_model()
|
mod.build_model()
|
||||||
|
except Exception as e:
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(
|
||||||
|
status="FAILED", task_result=mod.url
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
|
||||||
|
|
||||||
|
return mod.url
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="model")
|
@shared_task(bind=True, queue="model")
|
||||||
def evaluate_model(model_pk: int):
|
def evaluate_model(self, model_pk: int, multigen: bool, package_pks: Optional[list] = None):
|
||||||
|
packages = None
|
||||||
|
|
||||||
|
if package_pks:
|
||||||
|
packages = Package.objects.filter(pk__in=package_pks)
|
||||||
|
|
||||||
mod = EPModel.objects.get(id=model_pk)
|
mod = EPModel.objects.get(id=model_pk)
|
||||||
mod.evaluate_model()
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mod.evaluate_model(multigen, eval_packages=packages)
|
||||||
|
except Exception as e:
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(
|
||||||
|
status="FAILED", task_result=mod.url
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
|
||||||
|
|
||||||
|
return mod.url
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="model")
|
@shared_task(queue="model")
|
||||||
@ -45,16 +129,26 @@ def retrain(model_pk: int):
|
|||||||
mod.retrain()
|
mod.retrain()
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="predict")
|
@shared_task(bind=True, queue="predict")
|
||||||
def predict(
|
def predict(
|
||||||
pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None
|
self,
|
||||||
|
pw_pk: int,
|
||||||
|
pred_setting_pk: int,
|
||||||
|
limit: Optional[int] = None,
|
||||||
|
node_pk: Optional[int] = None,
|
||||||
) -> Pathway:
|
) -> Pathway:
|
||||||
pw = Pathway.objects.get(id=pw_pk)
|
pw = Pathway.objects.get(id=pw_pk)
|
||||||
setting = Setting.objects.get(id=pred_setting_pk)
|
setting = Setting.objects.get(id=pred_setting_pk)
|
||||||
|
# If the setting has a model add/restore it from the cache
|
||||||
|
if setting.model is not None:
|
||||||
|
setting.model = get_ml_model(setting.model.pk)
|
||||||
|
|
||||||
pw.kv.update(**{"status": "running"})
|
pw.kv.update(**{"status": "running"})
|
||||||
pw.save()
|
pw.save()
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=pw.url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# regular prediction
|
# regular prediction
|
||||||
if limit is not None:
|
if limit is not None:
|
||||||
@ -79,7 +173,111 @@ def predict(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pw.kv.update({"status": "failed"})
|
pw.kv.update({"status": "failed"})
|
||||||
pw.save()
|
pw.save()
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(
|
||||||
|
status="FAILED", task_result=pw.url
|
||||||
|
)
|
||||||
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
pw.kv.update(**{"status": "completed"})
|
pw.kv.update(**{"status": "completed"})
|
||||||
pw.save()
|
pw.save()
|
||||||
|
|
||||||
|
if JobLog.objects.filter(task_id=self.request.id).exists():
|
||||||
|
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=pw.url)
|
||||||
|
|
||||||
|
return pw.url
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(bind=True, queue="background")
|
||||||
|
def identify_missing_rules(
|
||||||
|
self,
|
||||||
|
pw_pks: List[int],
|
||||||
|
rule_package_pk: int,
|
||||||
|
):
|
||||||
|
from utilities.misc import PathwayUtils
|
||||||
|
|
||||||
|
rules = Package.objects.get(pk=rule_package_pk).get_applicable_rules()
|
||||||
|
|
||||||
|
rows: List[Any] = []
|
||||||
|
header = [
|
||||||
|
"Package Name",
|
||||||
|
"Pathway Name",
|
||||||
|
"Educt Name",
|
||||||
|
"Educt SMILES",
|
||||||
|
"Reaction Name",
|
||||||
|
"Reaction SMIRKS",
|
||||||
|
"Triggered Rules",
|
||||||
|
"Reactant SMARTS",
|
||||||
|
"Product SMARTS",
|
||||||
|
"Product Names",
|
||||||
|
"Product SMILES",
|
||||||
|
]
|
||||||
|
|
||||||
|
rows.append(header)
|
||||||
|
|
||||||
|
for pw in Pathway.objects.filter(pk__in=pw_pks):
|
||||||
|
pu = PathwayUtils(pw)
|
||||||
|
|
||||||
|
missing_rules = pu.find_missing_rules(rules)
|
||||||
|
|
||||||
|
package_name = pw.package.name
|
||||||
|
pathway_name = pw.name
|
||||||
|
|
||||||
|
for edge_url, rule_chain in missing_rules.items():
|
||||||
|
row: List[Any] = [package_name, pathway_name]
|
||||||
|
edge = Edge.objects.get(url=edge_url)
|
||||||
|
educts = edge.start_nodes.all()
|
||||||
|
|
||||||
|
for educt in educts:
|
||||||
|
row.append(educt.default_node_label.name)
|
||||||
|
row.append(educt.default_node_label.smiles)
|
||||||
|
|
||||||
|
row.append(edge.edge_label.name)
|
||||||
|
row.append(edge.edge_label.smirks())
|
||||||
|
|
||||||
|
rule_names = []
|
||||||
|
reactant_smarts = []
|
||||||
|
product_smarts = []
|
||||||
|
|
||||||
|
for r in rule_chain:
|
||||||
|
r = Rule.objects.get(url=r[0])
|
||||||
|
rule_names.append(r.name)
|
||||||
|
|
||||||
|
rs = r.reactants_smarts
|
||||||
|
if isinstance(rs, set):
|
||||||
|
rs = list(rs)
|
||||||
|
|
||||||
|
ps = r.products_smarts
|
||||||
|
if isinstance(ps, set):
|
||||||
|
ps = list(ps)
|
||||||
|
|
||||||
|
reactant_smarts.append(rs)
|
||||||
|
product_smarts.append(ps)
|
||||||
|
|
||||||
|
row.append(rule_names)
|
||||||
|
row.append(reactant_smarts)
|
||||||
|
row.append(product_smarts)
|
||||||
|
|
||||||
|
products = edge.end_nodes.all()
|
||||||
|
product_names = []
|
||||||
|
product_smiles = []
|
||||||
|
|
||||||
|
for product in products:
|
||||||
|
product_names.append(product.default_node_label.name)
|
||||||
|
product_smiles.append(product.default_node_label.smiles)
|
||||||
|
|
||||||
|
row.append(product_names)
|
||||||
|
row.append(product_smiles)
|
||||||
|
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
buffer = io.StringIO()
|
||||||
|
|
||||||
|
writer = csv.writer(buffer)
|
||||||
|
writer.writerows(rows)
|
||||||
|
|
||||||
|
buffer.seek(0)
|
||||||
|
|
||||||
|
return buffer.getvalue()
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
from django import template
|
from django import template
|
||||||
|
from pydantic import AnyHttpUrl, ValidationError
|
||||||
|
from pydantic.type_adapter import TypeAdapter
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
url_adapter = TypeAdapter(AnyHttpUrl)
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def classname(obj):
|
def classname(obj):
|
||||||
return obj.__class__.__name__
|
return obj.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def is_url(value):
|
||||||
|
try:
|
||||||
|
url_adapter.validate_python(value)
|
||||||
|
return True
|
||||||
|
except ValidationError:
|
||||||
|
return False
|
||||||
|
|||||||
11
epdb/urls.py
@ -48,6 +48,7 @@ urlpatterns = [
|
|||||||
re_path(r"^user$", v.users, name="users"),
|
re_path(r"^user$", v.users, name="users"),
|
||||||
re_path(r"^group$", v.groups, name="groups"),
|
re_path(r"^group$", v.groups, name="groups"),
|
||||||
re_path(r"^search$", v.search, name="search"),
|
re_path(r"^search$", v.search, name="search"),
|
||||||
|
re_path(r"^predict$", v.predict_pathway, name="predict_pathway"),
|
||||||
# User Detail
|
# User Detail
|
||||||
re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"),
|
re_path(rf"^user/(?P<user_uuid>{UUID})", v.user, name="user"),
|
||||||
# Group Detail
|
# Group Detail
|
||||||
@ -190,6 +191,16 @@ urlpatterns = [
|
|||||||
re_path(r"^indigo/dearomatize$", v.dearomatize, name="indigo_dearomatize"),
|
re_path(r"^indigo/dearomatize$", v.dearomatize, name="indigo_dearomatize"),
|
||||||
re_path(r"^indigo/layout$", v.layout, name="indigo_layout"),
|
re_path(r"^indigo/layout$", v.layout, name="indigo_layout"),
|
||||||
re_path(r"^depict$", v.depict, name="depict"),
|
re_path(r"^depict$", v.depict, name="depict"),
|
||||||
|
re_path(r"^jobs", v.jobs, name="jobs"),
|
||||||
# OAuth Stuff
|
# OAuth Stuff
|
||||||
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
|
||||||
|
# Static Pages
|
||||||
|
re_path(r"^terms$", v.static_terms_of_use, name="terms_of_use"),
|
||||||
|
re_path(r"^privacy$", v.static_privacy_policy, name="privacy_policy"),
|
||||||
|
re_path(r"^cookie-policy$", v.static_cookie_policy, name="cookie_policy"),
|
||||||
|
re_path(r"^about$", v.static_about_us, name="about_us"),
|
||||||
|
re_path(r"^contact$", v.static_contact_support, name="contact_support"),
|
||||||
|
re_path(r"^careers$", v.static_careers, name="careers"),
|
||||||
|
re_path(r"^cite$", v.static_cite, name="cite"),
|
||||||
|
re_path(r"^legal$", v.static_legal, name="legal"),
|
||||||
]
|
]
|
||||||
|
|||||||
372
epdb/views.py
@ -10,6 +10,7 @@ from django.urls import reverse
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from envipy_additional_information import NAME_MAPPING
|
from envipy_additional_information import NAME_MAPPING
|
||||||
from oauth2_provider.decorators import protected_resource
|
from oauth2_provider.decorators import protected_resource
|
||||||
|
import nh3
|
||||||
|
|
||||||
from utilities.chem import FormatConverter, IndigoUtils
|
from utilities.chem import FormatConverter, IndigoUtils
|
||||||
from utilities.decorators import package_permission_required
|
from utilities.decorators import package_permission_required
|
||||||
@ -47,6 +48,7 @@ from .models import (
|
|||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
ExternalIdentifier,
|
ExternalIdentifier,
|
||||||
EnzymeLink,
|
EnzymeLink,
|
||||||
|
JobLog,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -84,7 +86,11 @@ def login(request):
|
|||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
|
|
||||||
username = request.POST.get("username")
|
username = request.POST.get("username").strip()
|
||||||
|
if username != request.POST.get("username"):
|
||||||
|
context["message"] = "Login failed!"
|
||||||
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
password = request.POST.get("password")
|
password = request.POST.get("password")
|
||||||
|
|
||||||
# Get email for username and check if the account is active
|
# Get email for username and check if the account is active
|
||||||
@ -99,6 +105,7 @@ def login(request):
|
|||||||
except get_user_model().DoesNotExist:
|
except get_user_model().DoesNotExist:
|
||||||
context["message"] = "Login failed!"
|
context["message"] = "Login failed!"
|
||||||
return render(request, "static/login.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = authenticate(username=email, password=password)
|
user = authenticate(username=email, password=password)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -136,9 +143,14 @@ def register(request):
|
|||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
context["title"] = "enviPath"
|
# Redirect to unified login page with signup tab
|
||||||
context["next"] = request.GET.get("next", "")
|
next_url = request.GET.get("next", "")
|
||||||
return render(request, "static/register.html", context)
|
redirect_url = reverse("login") + "#signup"
|
||||||
|
|
||||||
|
if next_url:
|
||||||
|
redirect_url += f"?next={next_url}"
|
||||||
|
|
||||||
|
return redirect(redirect_url)
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
context["title"] = "enviPath"
|
context["title"] = "enviPath"
|
||||||
if next := request.POST.get("next"):
|
if next := request.POST.get("next"):
|
||||||
@ -151,18 +163,18 @@ def register(request):
|
|||||||
|
|
||||||
if not (username and email and password):
|
if not (username and email and password):
|
||||||
context["message"] = "Invalid username/email/password"
|
context["message"] = "Invalid username/email/password"
|
||||||
return render(request, "static/register.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
if password != rpassword or password == "":
|
if password != rpassword or password == "":
|
||||||
context["message"] = "Registration failed, provided passwords differ!"
|
context["message"] = "Registration failed, provided passwords differ!"
|
||||||
return render(request, "static/register.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
u = UserManager.create_user(username, email, password)
|
u = UserManager.create_user(username, email, password)
|
||||||
logger.info(f"Created user {u.username} ({u.pk})")
|
logger.info(f"Created user {u.username} ({u.pk})")
|
||||||
except Exception:
|
except Exception:
|
||||||
context["message"] = "Registration failed! Couldn't create User Account."
|
context["message"] = "Registration failed! Couldn't create User Account."
|
||||||
return render(request, "static/register.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
if s.ADMIN_APPROVAL_REQUIRED:
|
if s.ADMIN_APPROVAL_REQUIRED:
|
||||||
context["success_message"] = (
|
context["success_message"] = (
|
||||||
@ -236,6 +248,7 @@ def get_base_context(request, for_user=None) -> Dict[str, Any]:
|
|||||||
"enabled_features": s.FLAGS,
|
"enabled_features": s.FLAGS,
|
||||||
"debug": s.DEBUG,
|
"debug": s.DEBUG,
|
||||||
"external_databases": ExternalDatabase.get_databases(),
|
"external_databases": ExternalDatabase.get_databases(),
|
||||||
|
"site_id": s.MATOMO_SITE_ID,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,6 +362,18 @@ def index(request):
|
|||||||
return render(request, "index/index.html", context)
|
return render(request, "index/index.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def predict_pathway(request):
|
||||||
|
"""Top-level predict pathway view using user's default package."""
|
||||||
|
if request.method != "GET":
|
||||||
|
return HttpResponseNotAllowed(["GET"])
|
||||||
|
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Predict Pathway"
|
||||||
|
context["meta"]["current_package"] = context["meta"]["user"].default_package
|
||||||
|
|
||||||
|
return render(request, "predict_pathway.html", context)
|
||||||
|
|
||||||
|
|
||||||
def packages(request):
|
def packages(request):
|
||||||
current_user = _anonymous_or_real(request)
|
current_user = _anonymous_or_real(request)
|
||||||
|
|
||||||
@ -668,7 +693,7 @@ def search(request):
|
|||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
package_urls = request.GET.getlist("packages")
|
package_urls = request.GET.getlist("packages")
|
||||||
searchterm = request.GET.get("search")
|
searchterm = request.GET.get("search", "").strip()
|
||||||
mode = request.GET.get("mode")
|
mode = request.GET.get("mode")
|
||||||
|
|
||||||
# add HTTP_ACCEPT check to differentiate between index and ajax call
|
# add HTTP_ACCEPT check to differentiate between index and ajax call
|
||||||
@ -754,8 +779,8 @@ def package_models(request, package_uuid):
|
|||||||
context["unreviewed_objects"] = unreviewed_model_qs
|
context["unreviewed_objects"] = unreviewed_model_qs
|
||||||
|
|
||||||
context["model_types"] = {
|
context["model_types"] = {
|
||||||
"ML Relative Reasoning": "ml-relative-reasoning",
|
"ML Relative Reasoning": "mlrr",
|
||||||
"Rule Based Relative Reasoning": "rule-based-relative-reasoning",
|
"Rule Based Relative Reasoning": "rbrr",
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.FLAGS.get("ENVIFORMER", False):
|
if s.FLAGS.get("ENVIFORMER", False):
|
||||||
@ -775,48 +800,40 @@ def package_models(request, package_uuid):
|
|||||||
|
|
||||||
model_type = request.POST.get("model-type")
|
model_type = request.POST.get("model-type")
|
||||||
|
|
||||||
if model_type == "enviformer":
|
|
||||||
threshold = float(request.POST.get(f"{model_type}-threshold", 0.5))
|
|
||||||
|
|
||||||
mod = EnviFormer.create(current_package, name, description, threshold)
|
|
||||||
|
|
||||||
elif model_type == "ml-relative-reasoning" or model_type == "rule-based-relative-reasoning":
|
|
||||||
# Generic fields for ML and Rule Based
|
# Generic fields for ML and Rule Based
|
||||||
rule_packages = request.POST.getlist("package-based-relative-reasoning-rule-packages")
|
rule_packages = request.POST.getlist("model-rule-packages")
|
||||||
data_packages = request.POST.getlist("package-based-relative-reasoning-data-packages")
|
data_packages = request.POST.getlist("model-data-packages")
|
||||||
eval_packages = request.POST.getlist(
|
|
||||||
"package-based-relative-reasoning-evaluation-packages", []
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generic params
|
# Generic params
|
||||||
params = {
|
params = {
|
||||||
"package": current_package,
|
"package": current_package,
|
||||||
"name": name,
|
"name": name,
|
||||||
"description": description,
|
"description": description,
|
||||||
"rule_packages": [
|
|
||||||
PackageManager.get_package_by_url(current_user, p) for p in rule_packages
|
|
||||||
],
|
|
||||||
"data_packages": [
|
"data_packages": [
|
||||||
PackageManager.get_package_by_url(current_user, p) for p in data_packages
|
PackageManager.get_package_by_url(current_user, p) for p in data_packages
|
||||||
],
|
],
|
||||||
"eval_packages": [
|
|
||||||
PackageManager.get_package_by_url(current_user, p) for p in eval_packages
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if model_type == "ml-relative-reasoning":
|
if model_type == "enviformer":
|
||||||
|
threshold = float(request.POST.get("model-threshold", 0.5))
|
||||||
|
params["threshold"] = threshold
|
||||||
|
|
||||||
|
mod = EnviFormer.create(**params)
|
||||||
|
elif model_type == "mlrr":
|
||||||
# ML Specific
|
# ML Specific
|
||||||
threshold = float(request.POST.get(f"{model_type}-threshold", 0.5))
|
threshold = float(request.POST.get("model-threshold", 0.5))
|
||||||
# TODO handle additional fingerprinter
|
# TODO handle additional fingerprinter
|
||||||
# fingerprinter = request.POST.get(f"{model_type}-fingerprinter")
|
# fingerprinter = request.POST.get("model-fingerprinter")
|
||||||
|
|
||||||
|
params["rule_packages"] = [
|
||||||
|
PackageManager.get_package_by_url(current_user, p) for p in rule_packages
|
||||||
|
]
|
||||||
|
|
||||||
# App Domain related parameters
|
# App Domain related parameters
|
||||||
build_ad = request.POST.get("build-app-domain", False) == "on"
|
build_ad = request.POST.get("build-app-domain", False) == "on"
|
||||||
num_neighbors = request.POST.get("num-neighbors", 5)
|
num_neighbors = request.POST.get("num-neighbors", 5)
|
||||||
reliability_threshold = request.POST.get("reliability-threshold", 0.5)
|
reliability_threshold = request.POST.get("reliability-threshold", 0.5)
|
||||||
local_compatibility_threshold = request.POST.get(
|
local_compatibility_threshold = request.POST.get("local-compatibility-threshold", 0.5)
|
||||||
"local-compatibility-threshold", 0.5
|
|
||||||
)
|
|
||||||
|
|
||||||
params["threshold"] = threshold
|
params["threshold"] = threshold
|
||||||
# params['fingerprinter'] = fingerprinter
|
# params['fingerprinter'] = fingerprinter
|
||||||
@ -826,18 +843,24 @@ def package_models(request, package_uuid):
|
|||||||
params["app_domain_local_compatibility_threshold"] = local_compatibility_threshold
|
params["app_domain_local_compatibility_threshold"] = local_compatibility_threshold
|
||||||
|
|
||||||
mod = MLRelativeReasoning.create(**params)
|
mod = MLRelativeReasoning.create(**params)
|
||||||
else:
|
elif model_type == "rbrr":
|
||||||
|
params["rule_packages"] = [
|
||||||
|
PackageManager.get_package_by_url(current_user, p) for p in rule_packages
|
||||||
|
]
|
||||||
|
|
||||||
mod = RuleBasedRelativeReasoning.create(**params)
|
mod = RuleBasedRelativeReasoning.create(**params)
|
||||||
|
elif s.FLAGS.get("PLUGINS", False) and model_type in s.CLASSIFIER_PLUGINS.values():
|
||||||
from .tasks import build_model
|
pass
|
||||||
|
|
||||||
build_model.delay(mod.pk)
|
|
||||||
else:
|
else:
|
||||||
return error(
|
return error(
|
||||||
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
|
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
|
||||||
)
|
)
|
||||||
return redirect(mod.url)
|
|
||||||
|
|
||||||
|
from .tasks import dispatch, build_model
|
||||||
|
|
||||||
|
dispatch(current_user, build_model, mod.pk)
|
||||||
|
|
||||||
|
return redirect(mod.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseNotAllowed(["GET", "POST"])
|
return HttpResponseNotAllowed(["GET", "POST"])
|
||||||
|
|
||||||
@ -865,6 +888,10 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
return JsonResponse({"error": f'"{smiles}" is not a valid SMILES'}, status=400)
|
return JsonResponse({"error": f'"{smiles}" is not a valid SMILES'}, status=400)
|
||||||
|
|
||||||
if classify:
|
if classify:
|
||||||
|
from epdb.tasks import dispatch_eager, predict_simple
|
||||||
|
|
||||||
|
res = dispatch_eager(current_user, predict_simple, current_model.pk, stand_smiles)
|
||||||
|
|
||||||
pred_res = current_model.predict(stand_smiles)
|
pred_res = current_model.predict(stand_smiles)
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
@ -888,7 +915,7 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
return JsonResponse(res, safe=False)
|
return JsonResponse(res, safe=False)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
app_domain_assessment = current_model.app_domain.assess(stand_smiles)[0]
|
app_domain_assessment = current_model.app_domain.assess(stand_smiles)
|
||||||
return JsonResponse(app_domain_assessment, safe=False)
|
return JsonResponse(app_domain_assessment, safe=False)
|
||||||
|
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
@ -909,15 +936,43 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
current_model.delete()
|
current_model.delete()
|
||||||
return redirect(current_package.url + "/model")
|
return redirect(current_package.url + "/model")
|
||||||
elif hidden == "evaluate":
|
elif hidden == "evaluate":
|
||||||
from .tasks import evaluate_model
|
from .tasks import dispatch, evaluate_model
|
||||||
|
|
||||||
|
eval_type = request.POST.get("model-evaluation-type")
|
||||||
|
|
||||||
|
if eval_type not in ["sg", "mg"]:
|
||||||
|
return error(
|
||||||
|
request,
|
||||||
|
"Invalid evaluation type",
|
||||||
|
f'Evaluation type "{eval_type}" is not supported. Only "sg" and "mg" are supported.',
|
||||||
|
)
|
||||||
|
|
||||||
|
multigen = eval_type == "mg"
|
||||||
|
|
||||||
|
eval_packages = request.POST.getlist("model-evaluation-packages")
|
||||||
|
eval_package_ids = [
|
||||||
|
PackageManager.get_package_by_url(current_user, p).id for p in eval_packages
|
||||||
|
]
|
||||||
|
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
||||||
|
|
||||||
|
return redirect(current_model.url)
|
||||||
|
elif hidden == "retrain":
|
||||||
|
from .tasks import dispatch, retrain
|
||||||
|
|
||||||
|
dispatch(current_user, retrain, current_model.pk)
|
||||||
|
|
||||||
evaluate_model.delay(current_model.pk)
|
|
||||||
return redirect(current_model.url)
|
return redirect(current_model.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
else:
|
else:
|
||||||
name = request.POST.get("model-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
description = request.POST.get("model-description", "").strip()
|
name = request.POST.get("model-name")
|
||||||
|
if name is not None:
|
||||||
|
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
description = request.POST.get("model-description")
|
||||||
|
if description is not None:
|
||||||
|
description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if any([name, description]):
|
if any([name, description]):
|
||||||
if name:
|
if name:
|
||||||
@ -1019,17 +1074,23 @@ def package(request, package_uuid):
|
|||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
# TODO: Move cleaning to property updater
|
||||||
new_package_name = request.POST.get("package-name")
|
new_package_name = request.POST.get("package-name")
|
||||||
|
if new_package_name is not None:
|
||||||
|
new_package_name = nh3.clean(new_package_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
new_package_description = request.POST.get("package-description")
|
new_package_description = request.POST.get("package-description")
|
||||||
|
if new_package_description is not None:
|
||||||
|
new_package_description = nh3.clean(
|
||||||
|
new_package_description, tags=s.ALLOWED_HTML_TAGS
|
||||||
|
).strip()
|
||||||
|
|
||||||
grantee_url = request.POST.get("grantee")
|
grantee_url = request.POST.get("grantee")
|
||||||
read = request.POST.get("read") == "on"
|
read = request.POST.get("read") == "on"
|
||||||
write = request.POST.get("write") == "on"
|
write = request.POST.get("write") == "on"
|
||||||
owner = request.POST.get("owner") == "on"
|
owner = request.POST.get("owner") == "on"
|
||||||
|
|
||||||
license = request.POST.get("license")
|
cc_string = request.POST.get("license")
|
||||||
license_link = request.POST.get("license-link")
|
|
||||||
license_image_link = request.POST.get("license-image-link")
|
|
||||||
|
|
||||||
if new_package_name:
|
if new_package_name:
|
||||||
current_package.name = new_package_name
|
current_package.name = new_package_name
|
||||||
@ -1057,24 +1118,15 @@ def package(request, package_uuid):
|
|||||||
|
|
||||||
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
elif license is not None:
|
|
||||||
if license == "no-license":
|
|
||||||
if current_package.license is not None:
|
|
||||||
current_package.license.delete()
|
|
||||||
|
|
||||||
|
elif cc_string is not None:
|
||||||
|
cc_string = cc_string.strip()
|
||||||
|
if cc_string == "no-license": # Reset the package's license
|
||||||
current_package.license = None
|
current_package.license = None
|
||||||
current_package.save()
|
current_package.save()
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
else:
|
else: # Get the license and assign it to the package
|
||||||
if current_package.license is not None:
|
current_package.license = License.objects.get(cc_string=cc_string)
|
||||||
current_package.license.delete()
|
|
||||||
|
|
||||||
license = License()
|
|
||||||
license.link = license_link
|
|
||||||
license.image_link = license_image_link
|
|
||||||
license.save()
|
|
||||||
|
|
||||||
current_package.license = license
|
|
||||||
current_package.save()
|
current_package.save()
|
||||||
|
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
@ -1182,8 +1234,16 @@ def package_compound(request, package_uuid, compound_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_compound.url})
|
return JsonResponse({"success": current_compound.url})
|
||||||
|
|
||||||
new_compound_name = request.POST.get("compound-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
new_compound_description = request.POST.get("compound-description", "").strip()
|
new_compound_name = request.POST.get("compound-name")
|
||||||
|
if new_compound_name is not None:
|
||||||
|
new_compound_name = nh3.clean(new_compound_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
new_compound_description = request.POST.get("compound-description")
|
||||||
|
if new_compound_description is not None:
|
||||||
|
new_compound_description = nh3.clean(
|
||||||
|
new_compound_description, tags=s.ALLOWED_HTML_TAGS
|
||||||
|
).strip()
|
||||||
|
|
||||||
if new_compound_name:
|
if new_compound_name:
|
||||||
current_compound.name = new_compound_name
|
current_compound.name = new_compound_name
|
||||||
@ -1251,7 +1311,16 @@ def package_compound_structures(request, package_uuid, compound_uuid):
|
|||||||
structure_smiles = request.POST.get("structure-smiles")
|
structure_smiles = request.POST.get("structure-smiles")
|
||||||
structure_description = request.POST.get("structure-description")
|
structure_description = request.POST.get("structure-description")
|
||||||
|
|
||||||
cs = current_compound.add_structure(structure_smiles, structure_name, structure_description)
|
try:
|
||||||
|
cs = current_compound.add_structure(
|
||||||
|
structure_smiles, structure_name, structure_description
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return error(
|
||||||
|
request,
|
||||||
|
"Adding structure failed!",
|
||||||
|
"The structure could not be added as normalized structures don't match!",
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(cs.url)
|
return redirect(cs.url)
|
||||||
|
|
||||||
@ -1310,8 +1379,16 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u
|
|||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
new_structure_name = request.POST.get("compound-structure-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
new_structure_description = request.POST.get("compound-structure-description", "").strip()
|
new_structure_name = request.POST.get("compound-structure-name")
|
||||||
|
if new_structure_name is not None:
|
||||||
|
new_structure_name = nh3.clean(new_structure_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
new_structure_description = request.POST.get("compound-structure-description")
|
||||||
|
if new_structure_description is not None:
|
||||||
|
new_structure_description = nh3.clean(
|
||||||
|
new_structure_description, tags=s.ALLOWED_HTML_TAGS
|
||||||
|
).strip()
|
||||||
|
|
||||||
if new_structure_name:
|
if new_structure_name:
|
||||||
current_structure.name = new_structure_name
|
current_structure.name = new_structure_name
|
||||||
@ -1518,8 +1595,14 @@ def package_rule(request, package_uuid, rule_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_rule.url})
|
return JsonResponse({"success": current_rule.url})
|
||||||
|
|
||||||
rule_name = request.POST.get("rule-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
rule_description = request.POST.get("rule-description", "").strip()
|
rule_name = request.POST.get("rule-name")
|
||||||
|
if rule_name is not None:
|
||||||
|
rule_name = nh3.clean(rule_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
rule_description = request.POST.get("rule-description")
|
||||||
|
if rule_description is not None:
|
||||||
|
rule_description = nh3.clean(rule_description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if rule_name:
|
if rule_name:
|
||||||
current_rule.name = rule_name
|
current_rule.name = rule_name
|
||||||
@ -1609,7 +1692,6 @@ def package_reactions(request, package_uuid):
|
|||||||
reaction_name = request.POST.get("reaction-name")
|
reaction_name = request.POST.get("reaction-name")
|
||||||
reaction_description = request.POST.get("reaction-description")
|
reaction_description = request.POST.get("reaction-description")
|
||||||
reactions_smirks = request.POST.get("reaction-smirks")
|
reactions_smirks = request.POST.get("reaction-smirks")
|
||||||
|
|
||||||
educts = reactions_smirks.split(">>")[0].split(".")
|
educts = reactions_smirks.split(">>")[0].split(".")
|
||||||
products = reactions_smirks.split(">>")[1].split(".")
|
products = reactions_smirks.split(">>")[1].split(".")
|
||||||
|
|
||||||
@ -1670,8 +1752,16 @@ def package_reaction(request, package_uuid, reaction_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_reaction.url})
|
return JsonResponse({"success": current_reaction.url})
|
||||||
|
|
||||||
new_reaction_name = request.POST.get("reaction-name", "").strip()
|
# TODO: Move cleaning to property updater
|
||||||
new_reaction_description = request.POST.get("reaction-description", "").strip()
|
new_reaction_name = request.POST.get("reaction-name")
|
||||||
|
if new_reaction_name is not None:
|
||||||
|
new_reaction_name = nh3.clean(new_reaction_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
new_reaction_description = request.POST.get("reaction-description")
|
||||||
|
if new_reaction_description is not None:
|
||||||
|
new_reaction_description = nh3.clean(
|
||||||
|
new_reaction_description, tags=s.ALLOWED_HTML_TAGS
|
||||||
|
).strip()
|
||||||
|
|
||||||
if new_reaction_name:
|
if new_reaction_name:
|
||||||
current_reaction.name = new_reaction_name
|
current_reaction.name = new_reaction_name
|
||||||
@ -1748,8 +1838,9 @@ def package_pathways(request, package_uuid):
|
|||||||
|
|
||||||
name = request.POST.get("name")
|
name = request.POST.get("name")
|
||||||
description = request.POST.get("description")
|
description = request.POST.get("description")
|
||||||
pw_mode = request.POST.get("predict", "predict").strip()
|
|
||||||
smiles = request.POST.get("smiles", "").strip()
|
smiles = request.POST.get("smiles", "").strip()
|
||||||
|
pw_mode = request.POST.get("predict", "predict").strip()
|
||||||
|
|
||||||
if "smiles" in request.POST and smiles == "":
|
if "smiles" in request.POST and smiles == "":
|
||||||
return error(
|
return error(
|
||||||
@ -1758,8 +1849,6 @@ def package_pathways(request, package_uuid):
|
|||||||
"Pathway prediction failed due to missing or empty SMILES",
|
"Pathway prediction failed due to missing or empty SMILES",
|
||||||
)
|
)
|
||||||
|
|
||||||
smiles = smiles.strip()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stand_smiles = FormatConverter.standardize(smiles)
|
stand_smiles = FormatConverter.standardize(smiles)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -1800,9 +1889,9 @@ def package_pathways(request, package_uuid):
|
|||||||
pw.setting = prediction_setting
|
pw.setting = prediction_setting
|
||||||
pw.save()
|
pw.save()
|
||||||
|
|
||||||
from .tasks import predict
|
from .tasks import dispatch, predict
|
||||||
|
|
||||||
predict.delay(pw.pk, prediction_setting.pk, limit=limit)
|
dispatch(current_user, predict, pw.pk, prediction_setting.pk, limit=limit)
|
||||||
|
|
||||||
return redirect(pw.url)
|
return redirect(pw.url)
|
||||||
|
|
||||||
@ -1838,6 +1927,25 @@ def package_pathway(request, package_uuid, pathway_uuid):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
if (
|
||||||
|
request.GET.get("identify-missing-rules", False) == "true"
|
||||||
|
and request.GET.get("rule-package") is not None
|
||||||
|
):
|
||||||
|
from .tasks import dispatch_eager, identify_missing_rules
|
||||||
|
|
||||||
|
rule_package = PackageManager.get_package_by_url(
|
||||||
|
current_user, request.GET.get("rule-package")
|
||||||
|
)
|
||||||
|
res = dispatch_eager(
|
||||||
|
current_user, identify_missing_rules, [current_pathway.pk], rule_package.pk
|
||||||
|
)
|
||||||
|
|
||||||
|
filename = f"{current_pathway.name.replace(' ', '_')}_{current_pathway.uuid}.csv"
|
||||||
|
response = HttpResponse(res, content_type="text/csv")
|
||||||
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
# Pathway d3_json() relies on a lot of related objects (Nodes, Structures, Edges, Reaction, Rules, ...)
|
# Pathway d3_json() relies on a lot of related objects (Nodes, Structures, Edges, Reaction, Rules, ...)
|
||||||
# we will again fetch the current pathway identified by this url, but this time together with nearly all
|
# we will again fetch the current pathway identified by this url, but this time together with nearly all
|
||||||
# related objects
|
# related objects
|
||||||
@ -1899,8 +2007,14 @@ def package_pathway(request, package_uuid, pathway_uuid):
|
|||||||
|
|
||||||
return JsonResponse({"success": current_pathway.url})
|
return JsonResponse({"success": current_pathway.url})
|
||||||
|
|
||||||
|
# TODO: Move cleaning to property updater
|
||||||
pathway_name = request.POST.get("pathway-name")
|
pathway_name = request.POST.get("pathway-name")
|
||||||
|
if pathway_name is not None:
|
||||||
|
pathway_name = nh3.clean(pathway_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
pathway_description = request.POST.get("pathway-description")
|
pathway_description = request.POST.get("pathway-description")
|
||||||
|
if pathway_description is not None:
|
||||||
|
pathway_description = nh3.clean(pathway_description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if any([pathway_name, pathway_description]):
|
if any([pathway_name, pathway_description]):
|
||||||
if pathway_name is not None and pathway_name.strip() != "":
|
if pathway_name is not None and pathway_name.strip() != "":
|
||||||
@ -1921,10 +2035,16 @@ def package_pathway(request, package_uuid, pathway_uuid):
|
|||||||
if node_url:
|
if node_url:
|
||||||
n = current_pathway.get_node(node_url)
|
n = current_pathway.get_node(node_url)
|
||||||
|
|
||||||
from .tasks import predict
|
from .tasks import dispatch, predict
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
current_user,
|
||||||
|
predict,
|
||||||
|
current_pathway.pk,
|
||||||
|
current_pathway.setting.pk,
|
||||||
|
node_pk=n.pk,
|
||||||
|
)
|
||||||
|
|
||||||
# Dont delay?
|
|
||||||
predict(current_pathway.pk, current_pathway.setting.pk, node_pk=n.pk)
|
|
||||||
return JsonResponse({"success": current_pathway.url})
|
return JsonResponse({"success": current_pathway.url})
|
||||||
|
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
@ -1982,8 +2102,8 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
|
|||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
node_name = request.POST.get("node-name")
|
node_name = request.POST.get("node-name")
|
||||||
node_description = request.POST.get("node-description")
|
node_description = request.POST.get("node-description")
|
||||||
node_smiles = request.POST.get("node-smiles")
|
|
||||||
|
|
||||||
|
node_smiles = request.POST.get("node-smiles").strip()
|
||||||
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
||||||
|
|
||||||
return redirect(current_pathway.url)
|
return redirect(current_pathway.url)
|
||||||
@ -2148,6 +2268,7 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
|
|||||||
|
|
||||||
edge_name = request.POST.get("edge-name")
|
edge_name = request.POST.get("edge-name")
|
||||||
edge_description = request.POST.get("edge-description")
|
edge_description = request.POST.get("edge-description")
|
||||||
|
|
||||||
edge_substrates = request.POST.getlist("edge-substrates")
|
edge_substrates = request.POST.getlist("edge-substrates")
|
||||||
edge_products = request.POST.getlist("edge-products")
|
edge_products = request.POST.getlist("edge-products")
|
||||||
|
|
||||||
@ -2234,7 +2355,7 @@ def package_scenarios(request, package_uuid):
|
|||||||
"all", False
|
"all", False
|
||||||
):
|
):
|
||||||
scens = Scenario.objects.filter(package=current_package).order_by("name")
|
scens = Scenario.objects.filter(package=current_package).order_by("name")
|
||||||
res = [{"name": s.name, "url": s.url, "uuid": s.uuid} for s in scens]
|
res = [{"name": s_.name, "url": s_.url, "uuid": s_.uuid} for s_ in scens]
|
||||||
return JsonResponse(res, safe=False)
|
return JsonResponse(res, safe=False)
|
||||||
|
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
@ -2282,21 +2403,21 @@ def package_scenarios(request, package_uuid):
|
|||||||
"name": "soil",
|
"name": "soil",
|
||||||
"widgets": [
|
"widgets": [
|
||||||
HTMLGenerator.generate_html(ai, prefix=f"soil_{0}")
|
HTMLGenerator.generate_html(ai, prefix=f"soil_{0}")
|
||||||
for ai in [x for s in SOIL_ADDITIONAL_INFORMATION.values() for x in s]
|
for ai in [x for sv in SOIL_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"Sludge Data": {
|
"Sludge Data": {
|
||||||
"name": "sludge",
|
"name": "sludge",
|
||||||
"widgets": [
|
"widgets": [
|
||||||
HTMLGenerator.generate_html(ai, prefix=f"sludge_{0}")
|
HTMLGenerator.generate_html(ai, prefix=f"sludge_{0}")
|
||||||
for ai in [x for s in SLUDGE_ADDITIONAL_INFORMATION.values() for x in s]
|
for ai in [x for sv in SLUDGE_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"Water-Sediment System Data": {
|
"Water-Sediment System Data": {
|
||||||
"name": "sediment",
|
"name": "sediment",
|
||||||
"widgets": [
|
"widgets": [
|
||||||
HTMLGenerator.generate_html(ai, prefix=f"sediment_{0}")
|
HTMLGenerator.generate_html(ai, prefix=f"sediment_{0}")
|
||||||
for ai in [x for s in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in s]
|
for ai in [x for sv in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in sv]
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -2311,6 +2432,7 @@ def package_scenarios(request, package_uuid):
|
|||||||
|
|
||||||
scenario_name = request.POST.get("scenario-name")
|
scenario_name = request.POST.get("scenario-name")
|
||||||
scenario_description = request.POST.get("scenario-description")
|
scenario_description = request.POST.get("scenario-description")
|
||||||
|
|
||||||
scenario_date_year = request.POST.get("scenario-date-year")
|
scenario_date_year = request.POST.get("scenario-date-year")
|
||||||
scenario_date_month = request.POST.get("scenario-date-month")
|
scenario_date_month = request.POST.get("scenario-date-month")
|
||||||
scenario_date_day = request.POST.get("scenario-date-day")
|
scenario_date_day = request.POST.get("scenario-date-day")
|
||||||
@ -2324,9 +2446,9 @@ def package_scenarios(request, package_uuid):
|
|||||||
scenario_type = request.POST.get("scenario-type")
|
scenario_type = request.POST.get("scenario-type")
|
||||||
|
|
||||||
additional_information = HTMLGenerator.build_models(request.POST.dict())
|
additional_information = HTMLGenerator.build_models(request.POST.dict())
|
||||||
additional_information = [x for s in additional_information.values() for x in s]
|
additional_information = [x for sv in additional_information.values() for x in sv]
|
||||||
|
|
||||||
s = Scenario.create(
|
new_scen = Scenario.create(
|
||||||
current_package,
|
current_package,
|
||||||
name=scenario_name,
|
name=scenario_name,
|
||||||
description=scenario_description,
|
description=scenario_description,
|
||||||
@ -2335,7 +2457,7 @@ def package_scenarios(request, package_uuid):
|
|||||||
additional_information=additional_information,
|
additional_information=additional_information,
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect(s.url)
|
return redirect(new_scen.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseNotAllowed(
|
return HttpResponseNotAllowed(
|
||||||
[
|
[
|
||||||
@ -2635,6 +2757,7 @@ def settings(request):
|
|||||||
|
|
||||||
name = request.POST.get("prediction-setting-name")
|
name = request.POST.get("prediction-setting-name")
|
||||||
description = request.POST.get("prediction-setting-description")
|
description = request.POST.get("prediction-setting-description")
|
||||||
|
|
||||||
new_default = request.POST.get("prediction-setting-new-default", "off") == "on"
|
new_default = request.POST.get("prediction-setting-new-default", "off") == "on"
|
||||||
|
|
||||||
max_nodes = min(
|
max_nodes = min(
|
||||||
@ -2696,6 +2819,24 @@ def setting(request, setting_uuid):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def jobs(request):
|
||||||
|
current_user = _anonymous_or_real(request)
|
||||||
|
context = get_base_context(request)
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
context["object_type"] = "joblog"
|
||||||
|
context["breadcrumbs"] = [
|
||||||
|
{"Home": s.SERVER_URL},
|
||||||
|
{"Jobs": s.SERVER_URL + "/jobs"},
|
||||||
|
]
|
||||||
|
if current_user.is_superuser:
|
||||||
|
context["jobs"] = JobLog.objects.all().order_by("-created")
|
||||||
|
else:
|
||||||
|
context["jobs"] = JobLog.objects.filter(user=current_user).order_by("-created")
|
||||||
|
|
||||||
|
return render(request, "collections/joblog.html", context)
|
||||||
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
# KETCHER #
|
# KETCHER #
|
||||||
###########
|
###########
|
||||||
@ -2767,3 +2908,60 @@ def userinfo(request):
|
|||||||
"email_verified": user.is_active,
|
"email_verified": user.is_active,
|
||||||
}
|
}
|
||||||
return JsonResponse(res)
|
return JsonResponse(res)
|
||||||
|
|
||||||
|
|
||||||
|
# Static Pages
|
||||||
|
def static_terms_of_use(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Terms of Use"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/terms_of_use.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def static_privacy_policy(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Privacy Policy"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/privacy_policy.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def static_cookie_policy(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Cookie Policy"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/cookie_policy.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def static_about_us(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - About Us"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/about_us.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def static_contact_support(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Contact & Support"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/contact.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def static_careers(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Careers"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/careers.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def static_cite(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - How to Cite"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/cite.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def static_legal(request):
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = "enviPath - Legal Information"
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "static/legal.html", context)
|
||||||
|
|||||||
25
package.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "envipy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "enviPath UI - Tailwind CSS + DaisyUI",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tailwindcss -i static/css/input.css -o static/css/output.css --watch=always",
|
||||||
|
"build": "tailwindcss -i static/css/input.css -o static/css/output.css --minify"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/cli": "^4.1.16",
|
||||||
|
"@tailwindcss/postcss": "^4.1.16",
|
||||||
|
"daisyui": "^5.4.3",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"prettier-plugin-jinja-template": "^2.1.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||||
|
"tailwindcss": "^4.1.16"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"django",
|
||||||
|
"tailwindcss",
|
||||||
|
"daisyui"
|
||||||
|
]
|
||||||
|
}
|
||||||
740
pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
'@tailwindcss/cli':
|
||||||
|
specifier: ^4.1.16
|
||||||
|
version: 4.1.16
|
||||||
|
'@tailwindcss/postcss':
|
||||||
|
specifier: ^4.1.16
|
||||||
|
version: 4.1.16
|
||||||
|
daisyui:
|
||||||
|
specifier: ^5.4.3
|
||||||
|
version: 5.4.3
|
||||||
|
postcss:
|
||||||
|
specifier: ^8.5.6
|
||||||
|
version: 8.5.6
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.6.2
|
||||||
|
version: 3.6.2
|
||||||
|
prettier-plugin-jinja-template:
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0(prettier@3.6.2)
|
||||||
|
prettier-plugin-tailwindcss:
|
||||||
|
specifier: ^0.7.1
|
||||||
|
version: 0.7.1(prettier@3.6.2)
|
||||||
|
tailwindcss:
|
||||||
|
specifier: ^4.1.16
|
||||||
|
version: 4.1.16
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@alloc/quick-lru@5.2.0':
|
||||||
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
|
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||||
|
|
||||||
|
'@jridgewell/remapping@2.3.5':
|
||||||
|
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||||
|
|
||||||
|
'@jridgewell/resolve-uri@3.1.2':
|
||||||
|
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5':
|
||||||
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-arm64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-x64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@parcel/watcher-freebsd-x64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
||||||
|
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
|
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
|
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
|
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
|
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
|
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-ia32@2.5.1':
|
||||||
|
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-x64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@parcel/watcher@2.5.1':
|
||||||
|
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
||||||
|
'@tailwindcss/cli@4.1.16':
|
||||||
|
resolution: {integrity: sha512-dsnANPrh2ZooHyZ/8uJhc9ecpcYtufToc21NY09NS9vF16rxPCjJ8dP7TUAtPqlUJTHSmRkN2hCdoYQIlgh4fw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@tailwindcss/node@4.1.16':
|
||||||
|
resolution: {integrity: sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==}
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-android-arm64@4.1.16':
|
||||||
|
resolution: {integrity: sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-darwin-arm64@4.1.16':
|
||||||
|
resolution: {integrity: sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-darwin-x64@4.1.16':
|
||||||
|
resolution: {integrity: sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-freebsd-x64@4.1.16':
|
||||||
|
resolution: {integrity: sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
|
||||||
|
resolution: {integrity: sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
|
||||||
|
resolution: {integrity: sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-arm64-musl@4.1.16':
|
||||||
|
resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-x64-gnu@4.1.16':
|
||||||
|
resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-x64-musl@4.1.16':
|
||||||
|
resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-wasm32-wasi@4.1.16':
|
||||||
|
resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [wasm32]
|
||||||
|
bundledDependencies:
|
||||||
|
- '@napi-rs/wasm-runtime'
|
||||||
|
- '@emnapi/core'
|
||||||
|
- '@emnapi/runtime'
|
||||||
|
- '@tybys/wasm-util'
|
||||||
|
- '@emnapi/wasi-threads'
|
||||||
|
- tslib
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
|
||||||
|
resolution: {integrity: sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-win32-x64-msvc@4.1.16':
|
||||||
|
resolution: {integrity: sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@tailwindcss/oxide@4.1.16':
|
||||||
|
resolution: {integrity: sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
|
'@tailwindcss/postcss@4.1.16':
|
||||||
|
resolution: {integrity: sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A==}
|
||||||
|
|
||||||
|
braces@3.0.3:
|
||||||
|
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
daisyui@5.4.3:
|
||||||
|
resolution: {integrity: sha512-dfDCJnN4utErGoWfElgdEE252FtfHV9Mxj5Dq1+JzUq3nVkluWdF3JYykP0Xy/y/yArnPXYztO1tLNCow3kjmg==}
|
||||||
|
|
||||||
|
detect-libc@1.0.3:
|
||||||
|
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
detect-libc@2.1.2:
|
||||||
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
enhanced-resolve@5.18.3:
|
||||||
|
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
fill-range@7.1.1:
|
||||||
|
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
graceful-fs@4.2.11:
|
||||||
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
|
is-extglob@2.1.1:
|
||||||
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-glob@4.0.3:
|
||||||
|
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-number@7.0.0:
|
||||||
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
|
jiti@2.6.1:
|
||||||
|
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
lightningcss-android-arm64@1.30.2:
|
||||||
|
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
lightningcss-darwin-arm64@1.30.2:
|
||||||
|
resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
lightningcss-darwin-x64@1.30.2:
|
||||||
|
resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
lightningcss-freebsd-x64@1.30.2:
|
||||||
|
resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||||
|
resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
lightningcss-linux-arm64-gnu@1.30.2:
|
||||||
|
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
lightningcss-linux-arm64-musl@1.30.2:
|
||||||
|
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
lightningcss-linux-x64-gnu@1.30.2:
|
||||||
|
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
lightningcss-linux-x64-musl@1.30.2:
|
||||||
|
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
lightningcss-win32-arm64-msvc@1.30.2:
|
||||||
|
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
lightningcss-win32-x64-msvc@1.30.2:
|
||||||
|
resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
lightningcss@1.30.2:
|
||||||
|
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
micromatch@4.0.8:
|
||||||
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
mri@1.2.0:
|
||||||
|
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
nanoid@3.3.11:
|
||||||
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
node-addon-api@7.1.1:
|
||||||
|
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||||
|
|
||||||
|
picocolors@1.1.1:
|
||||||
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
picomatch@2.3.1:
|
||||||
|
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||||
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
postcss@8.5.6:
|
||||||
|
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||||
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
prettier-plugin-jinja-template@2.1.0:
|
||||||
|
resolution: {integrity: sha512-mzoCp2Oy9BDSug80fw3B3J4n4KQj1hRvoQOL1akqcDKBb5nvYxrik9zUEDs4AEJ6nK7QDTGoH0y9rx7AlnQ78Q==}
|
||||||
|
peerDependencies:
|
||||||
|
prettier: ^3.0.0
|
||||||
|
|
||||||
|
prettier-plugin-tailwindcss@0.7.1:
|
||||||
|
resolution: {integrity: sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==}
|
||||||
|
engines: {node: '>=20.19'}
|
||||||
|
peerDependencies:
|
||||||
|
'@ianvs/prettier-plugin-sort-imports': '*'
|
||||||
|
'@prettier/plugin-hermes': '*'
|
||||||
|
'@prettier/plugin-oxc': '*'
|
||||||
|
'@prettier/plugin-pug': '*'
|
||||||
|
'@shopify/prettier-plugin-liquid': '*'
|
||||||
|
'@trivago/prettier-plugin-sort-imports': '*'
|
||||||
|
'@zackad/prettier-plugin-twig': '*'
|
||||||
|
prettier: ^3.0
|
||||||
|
prettier-plugin-astro: '*'
|
||||||
|
prettier-plugin-css-order: '*'
|
||||||
|
prettier-plugin-jsdoc: '*'
|
||||||
|
prettier-plugin-marko: '*'
|
||||||
|
prettier-plugin-multiline-arrays: '*'
|
||||||
|
prettier-plugin-organize-attributes: '*'
|
||||||
|
prettier-plugin-organize-imports: '*'
|
||||||
|
prettier-plugin-sort-imports: '*'
|
||||||
|
prettier-plugin-svelte: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@ianvs/prettier-plugin-sort-imports':
|
||||||
|
optional: true
|
||||||
|
'@prettier/plugin-hermes':
|
||||||
|
optional: true
|
||||||
|
'@prettier/plugin-oxc':
|
||||||
|
optional: true
|
||||||
|
'@prettier/plugin-pug':
|
||||||
|
optional: true
|
||||||
|
'@shopify/prettier-plugin-liquid':
|
||||||
|
optional: true
|
||||||
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
|
optional: true
|
||||||
|
'@zackad/prettier-plugin-twig':
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-astro:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-css-order:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-jsdoc:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-marko:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-multiline-arrays:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-organize-attributes:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-organize-imports:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-sort-imports:
|
||||||
|
optional: true
|
||||||
|
prettier-plugin-svelte:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
prettier@3.6.2:
|
||||||
|
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
source-map-js@1.2.1:
|
||||||
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
tailwindcss@4.1.16:
|
||||||
|
resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==}
|
||||||
|
|
||||||
|
tapable@2.3.0:
|
||||||
|
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
to-regex-range@5.0.1:
|
||||||
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
|
engines: {node: '>=8.0'}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
|
||||||
|
'@jridgewell/remapping@2.3.5':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
|
||||||
|
'@jridgewell/resolve-uri@3.1.2': {}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-arm64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-x64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-freebsd-x64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-ia32@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-x64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher@2.5.1':
|
||||||
|
dependencies:
|
||||||
|
detect-libc: 1.0.3
|
||||||
|
is-glob: 4.0.3
|
||||||
|
micromatch: 4.0.8
|
||||||
|
node-addon-api: 7.1.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@parcel/watcher-android-arm64': 2.5.1
|
||||||
|
'@parcel/watcher-darwin-arm64': 2.5.1
|
||||||
|
'@parcel/watcher-darwin-x64': 2.5.1
|
||||||
|
'@parcel/watcher-freebsd-x64': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm-glibc': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm-musl': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm64-glibc': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm64-musl': 2.5.1
|
||||||
|
'@parcel/watcher-linux-x64-glibc': 2.5.1
|
||||||
|
'@parcel/watcher-linux-x64-musl': 2.5.1
|
||||||
|
'@parcel/watcher-win32-arm64': 2.5.1
|
||||||
|
'@parcel/watcher-win32-ia32': 2.5.1
|
||||||
|
'@parcel/watcher-win32-x64': 2.5.1
|
||||||
|
|
||||||
|
'@tailwindcss/cli@4.1.16':
|
||||||
|
dependencies:
|
||||||
|
'@parcel/watcher': 2.5.1
|
||||||
|
'@tailwindcss/node': 4.1.16
|
||||||
|
'@tailwindcss/oxide': 4.1.16
|
||||||
|
enhanced-resolve: 5.18.3
|
||||||
|
mri: 1.2.0
|
||||||
|
picocolors: 1.1.1
|
||||||
|
tailwindcss: 4.1.16
|
||||||
|
|
||||||
|
'@tailwindcss/node@4.1.16':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/remapping': 2.3.5
|
||||||
|
enhanced-resolve: 5.18.3
|
||||||
|
jiti: 2.6.1
|
||||||
|
lightningcss: 1.30.2
|
||||||
|
magic-string: 0.30.21
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
tailwindcss: 4.1.16
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-android-arm64@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-darwin-arm64@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-darwin-x64@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-freebsd-x64@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-arm64-musl@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-x64-gnu@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-linux-x64-musl@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-wasm32-wasi@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-win32-x64-msvc@4.1.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide@4.1.16':
|
||||||
|
optionalDependencies:
|
||||||
|
'@tailwindcss/oxide-android-arm64': 4.1.16
|
||||||
|
'@tailwindcss/oxide-darwin-arm64': 4.1.16
|
||||||
|
'@tailwindcss/oxide-darwin-x64': 4.1.16
|
||||||
|
'@tailwindcss/oxide-freebsd-x64': 4.1.16
|
||||||
|
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.16
|
||||||
|
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.16
|
||||||
|
'@tailwindcss/oxide-linux-arm64-musl': 4.1.16
|
||||||
|
'@tailwindcss/oxide-linux-x64-gnu': 4.1.16
|
||||||
|
'@tailwindcss/oxide-linux-x64-musl': 4.1.16
|
||||||
|
'@tailwindcss/oxide-wasm32-wasi': 4.1.16
|
||||||
|
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.16
|
||||||
|
'@tailwindcss/oxide-win32-x64-msvc': 4.1.16
|
||||||
|
|
||||||
|
'@tailwindcss/postcss@4.1.16':
|
||||||
|
dependencies:
|
||||||
|
'@alloc/quick-lru': 5.2.0
|
||||||
|
'@tailwindcss/node': 4.1.16
|
||||||
|
'@tailwindcss/oxide': 4.1.16
|
||||||
|
postcss: 8.5.6
|
||||||
|
tailwindcss: 4.1.16
|
||||||
|
|
||||||
|
braces@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
fill-range: 7.1.1
|
||||||
|
|
||||||
|
daisyui@5.4.3: {}
|
||||||
|
|
||||||
|
detect-libc@1.0.3: {}
|
||||||
|
|
||||||
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
|
enhanced-resolve@5.18.3:
|
||||||
|
dependencies:
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
tapable: 2.3.0
|
||||||
|
|
||||||
|
fill-range@7.1.1:
|
||||||
|
dependencies:
|
||||||
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
|
is-extglob@2.1.1: {}
|
||||||
|
|
||||||
|
is-glob@4.0.3:
|
||||||
|
dependencies:
|
||||||
|
is-extglob: 2.1.1
|
||||||
|
|
||||||
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
|
jiti@2.6.1: {}
|
||||||
|
|
||||||
|
lightningcss-android-arm64@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-darwin-arm64@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-darwin-x64@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-freebsd-x64@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-linux-arm64-gnu@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-linux-arm64-musl@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-linux-x64-gnu@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-linux-x64-musl@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-win32-arm64-msvc@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss-win32-x64-msvc@1.30.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
lightningcss@1.30.2:
|
||||||
|
dependencies:
|
||||||
|
detect-libc: 2.1.2
|
||||||
|
optionalDependencies:
|
||||||
|
lightningcss-android-arm64: 1.30.2
|
||||||
|
lightningcss-darwin-arm64: 1.30.2
|
||||||
|
lightningcss-darwin-x64: 1.30.2
|
||||||
|
lightningcss-freebsd-x64: 1.30.2
|
||||||
|
lightningcss-linux-arm-gnueabihf: 1.30.2
|
||||||
|
lightningcss-linux-arm64-gnu: 1.30.2
|
||||||
|
lightningcss-linux-arm64-musl: 1.30.2
|
||||||
|
lightningcss-linux-x64-gnu: 1.30.2
|
||||||
|
lightningcss-linux-x64-musl: 1.30.2
|
||||||
|
lightningcss-win32-arm64-msvc: 1.30.2
|
||||||
|
lightningcss-win32-x64-msvc: 1.30.2
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
micromatch@4.0.8:
|
||||||
|
dependencies:
|
||||||
|
braces: 3.0.3
|
||||||
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
mri@1.2.0: {}
|
||||||
|
|
||||||
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
|
node-addon-api@7.1.1: {}
|
||||||
|
|
||||||
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
picomatch@2.3.1: {}
|
||||||
|
|
||||||
|
postcss@8.5.6:
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.11
|
||||||
|
picocolors: 1.1.1
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
prettier-plugin-jinja-template@2.1.0(prettier@3.6.2):
|
||||||
|
dependencies:
|
||||||
|
prettier: 3.6.2
|
||||||
|
|
||||||
|
prettier-plugin-tailwindcss@0.7.1(prettier@3.6.2):
|
||||||
|
dependencies:
|
||||||
|
prettier: 3.6.2
|
||||||
|
|
||||||
|
prettier@3.6.2: {}
|
||||||
|
|
||||||
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
tailwindcss@4.1.16: {}
|
||||||
|
|
||||||
|
tapable@2.3.0: {}
|
||||||
|
|
||||||
|
to-regex-range@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
is-number: 7.0.0
|
||||||
@ -27,10 +27,12 @@ dependencies = [
|
|||||||
"scikit-learn>=1.6.1",
|
"scikit-learn>=1.6.1",
|
||||||
"sentry-sdk[django]>=2.32.0",
|
"sentry-sdk[django]>=2.32.0",
|
||||||
"setuptools>=80.8.0",
|
"setuptools>=80.8.0",
|
||||||
|
"nh3==0.3.2",
|
||||||
|
"polars==1.35.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.2" }
|
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.4" }
|
||||||
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
|
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
|
||||||
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"}
|
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"}
|
||||||
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
|
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
|
||||||
@ -65,22 +67,54 @@ docstring-code-format = true
|
|||||||
[tool.poe.tasks]
|
[tool.poe.tasks]
|
||||||
# Main tasks
|
# Main tasks
|
||||||
setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
||||||
dev = { cmd = "python manage.py runserver", help = "Start the development server", deps = ["db-up"] }
|
dev = { shell = """
|
||||||
|
# Start pnpm CSS watcher in background
|
||||||
|
pnpm run dev &
|
||||||
|
PNPM_PID=$!
|
||||||
|
echo "Started CSS watcher (PID: $PNPM_PID)"
|
||||||
|
|
||||||
|
# Cleanup function
|
||||||
|
cleanup() {
|
||||||
|
echo "\nShutting down..."
|
||||||
|
if kill -0 $PNPM_PID 2>/dev/null; then
|
||||||
|
kill $PNPM_PID
|
||||||
|
echo "✓ CSS watcher stopped"
|
||||||
|
fi
|
||||||
|
if [ ! -z "${DJ_PID:-}" ] && kill -0 $DJ_PID 2>/dev/null; then
|
||||||
|
kill $DJ_PID
|
||||||
|
echo "✓ Django server stopped"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set trap for cleanup
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
# Start Django dev server in background
|
||||||
|
uv run python manage.py runserver &
|
||||||
|
DJ_PID=$!
|
||||||
|
|
||||||
|
# Wait for Django to finish
|
||||||
|
wait $DJ_PID
|
||||||
|
""", help = "Start the development server with CSS watcher", deps = ["db-up", "js-deps"] }
|
||||||
|
build = { sequence = ["build-frontend", "collectstatic"], help = "Build frontend assets and collect static files" }
|
||||||
|
|
||||||
# Database tasks
|
# Database tasks
|
||||||
db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" }
|
db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" }
|
||||||
db-down = { cmd = "docker compose -f docker-compose.dev.yml down", help = "Stop PostgreSQL database" }
|
db-down = { cmd = "docker compose -f docker-compose.dev.yml down", help = "Stop PostgreSQL database" }
|
||||||
|
|
||||||
|
# Frontend tasks
|
||||||
|
js-deps = { cmd = "pnpm install", help = "Install frontend dependencies" }
|
||||||
|
|
||||||
# Full cleanup tasks
|
# Full cleanup tasks
|
||||||
clean = { sequence = ["clean-db"], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
|
clean = { sequence = ["clean-db"], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
|
||||||
clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." }
|
clean-db = { cmd = "docker compose -f docker-compose.dev.yml down -v", help = "Removes the database container and volume." }
|
||||||
|
|
||||||
# Django tasks
|
# Django tasks
|
||||||
migrate = { cmd = "python manage.py migrate", help = "Run database migrations" }
|
migrate = { cmd = "uv run python manage.py migrate", help = "Run database migrations" }
|
||||||
bootstrap = { shell = """
|
bootstrap = { shell = """
|
||||||
echo "Bootstrapping initial data..."
|
echo "Bootstrapping initial data..."
|
||||||
echo "This will take a bit ⏱️. Get yourself some coffee..."
|
echo "This will take a bit ⏱️. Get yourself some coffee..."
|
||||||
python manage.py bootstrap
|
uv run python manage.py bootstrap
|
||||||
echo "✓ Bootstrap complete"
|
echo "✓ Bootstrap complete"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Default admin credentials:"
|
echo "Default admin credentials:"
|
||||||
@ -88,4 +122,8 @@ echo " Username: admin"
|
|||||||
echo " Email: admin@envipath.com"
|
echo " Email: admin@envipath.com"
|
||||||
echo " Password: SuperSafe"
|
echo " Password: SuperSafe"
|
||||||
""", help = "Bootstrap initial data (anonymous user, packages, models)" }
|
""", help = "Bootstrap initial data (anonymous user, packages, models)" }
|
||||||
shell = { cmd = "python manage.py shell", help = "Open Django shell" }
|
shell = { cmd = "uv run python manage.py shell", help = "Open Django shell" }
|
||||||
|
|
||||||
|
# Build tasks
|
||||||
|
build-frontend = { cmd = "pnpm run build", help = "Build frontend assets using pnpm", deps = ["js-deps"] }
|
||||||
|
collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = ["build-frontend"] }
|
||||||
|
|||||||
84
static/css/daisyui-theme.css
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* DaisyUI Themes - Generated by Style Dictionary
|
||||||
|
* Theme mappings defined in tokens/daisyui-themes.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Light theme (default) */
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "envipath";
|
||||||
|
default: true;
|
||||||
|
color-scheme: light;
|
||||||
|
|
||||||
|
--color-base-100: var(--color-neutral-50);
|
||||||
|
--color-base-200: var(--color-neutral-100);
|
||||||
|
--color-base-300: var(--color-neutral-200);
|
||||||
|
--color-base-content: var(--color-neutral-900);
|
||||||
|
--color-primary: var(--color-primary-500);
|
||||||
|
--color-primary-content: var(--color-primary-50);
|
||||||
|
--color-secondary: var(--color-secondary-500);
|
||||||
|
--color-secondary-content: var(--color-secondary-50);
|
||||||
|
--color-accent: var(--color-accent-500);
|
||||||
|
--color-accent-content: var(--color-accent-50);
|
||||||
|
--color-neutral: var(--color-neutral-950);
|
||||||
|
--color-neutral-content: var(--color-neutral-100);
|
||||||
|
--color-info: var(--color-info-500);
|
||||||
|
--color-info-content: var(--color-info-950);
|
||||||
|
--color-success: var(--color-success-500);
|
||||||
|
--color-success-content: var(--color-success-950);
|
||||||
|
--color-warning: var(--color-warning-500);
|
||||||
|
--color-warning-content: var(--color-warning-950);
|
||||||
|
--color-error: var(--color-error-500);
|
||||||
|
--color-error-content: var(--color-error-950);
|
||||||
|
|
||||||
|
/* border radius */
|
||||||
|
--radius-selector: 1rem;
|
||||||
|
--radius-field: 0.25rem;
|
||||||
|
--radius-box: 0.5rem;
|
||||||
|
|
||||||
|
/* base sizes */
|
||||||
|
--size-selector: 0.25rem;
|
||||||
|
--size-field: 0.25rem;
|
||||||
|
|
||||||
|
/* border size */
|
||||||
|
--border: 1px;
|
||||||
|
|
||||||
|
/* effects */
|
||||||
|
--depth: 1;
|
||||||
|
--noise: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme (prefers-color-scheme: dark) */
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "envipath-dark";
|
||||||
|
prefersdark: true;
|
||||||
|
color-scheme: dark;
|
||||||
|
|
||||||
|
--color-primary: var(--color-primary-400);
|
||||||
|
--color-primary-content: var(--color-neutral-950);
|
||||||
|
--color-secondary: var(--color-secondary-400);
|
||||||
|
--color-secondary-content: var(--color-neutral-950);
|
||||||
|
--color-accent: var(--color-primary-500);
|
||||||
|
--color-accent-content: var(--color-neutral-950);
|
||||||
|
--color-neutral: var(--color-neutral-300);
|
||||||
|
--color-neutral-content: var(--color-neutral-900);
|
||||||
|
--color-base-100: var(--color-neutral-900);
|
||||||
|
--color-base-200: var(--color-neutral-800);
|
||||||
|
--color-base-300: var(--color-neutral-700);
|
||||||
|
--color-base-content: var(--color-neutral-50);
|
||||||
|
--color-info: var(--color-primary-400);
|
||||||
|
--color-info-content: var(--color-neutral-950);
|
||||||
|
--color-success: var(--color-success-400);
|
||||||
|
--color-success-content: var(--color-neutral-950);
|
||||||
|
--color-warning: var(--color-warning-400);
|
||||||
|
--color-warning-content: var(--color-neutral-950);
|
||||||
|
--color-error: var(--color-error-400);
|
||||||
|
--color-error-content: var(--color-neutral-950);
|
||||||
|
--radius-selector: 1rem;
|
||||||
|
--radius-field: 0.25rem;
|
||||||
|
--radius-box: 0.5rem;
|
||||||
|
--size-selector: 0.25rem;
|
||||||
|
--size-field: 0.25rem;
|
||||||
|
--border: 1px;
|
||||||
|
--depth: 1;
|
||||||
|
--noise: 0;
|
||||||
|
}
|
||||||
36
static/css/input.css
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* fira-code-latin-wght-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fira Code Variable';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300 700;
|
||||||
|
src: url(https://cdn.jsdelivr.net/fontsource/fonts/fira-code:vf@latest/latin-wght-normal.woff2) format('woff2-variations');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inter-latin-wght-normal */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter Variable';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100 900;
|
||||||
|
src: url(https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2) format('woff2-variations');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tell Tailwind where to find Django templates and Python files */
|
||||||
|
@source "../../templates";
|
||||||
|
|
||||||
|
/* Custom theme configuration - must come before plugins */
|
||||||
|
@import "./theme.css";
|
||||||
|
|
||||||
|
/* Import DaisyUI plugin */
|
||||||
|
@plugin "daisyui" {
|
||||||
|
logs: true;
|
||||||
|
exclude: rootscrollgutter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@import "./daisyui-theme.css";
|
||||||
111
static/css/theme.css
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Tailwind v4 Theme - Generated by Style Dictionary
|
||||||
|
* This creates Tailwind utility classes from design tokens
|
||||||
|
*/
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* Colors */
|
||||||
|
--color-primary-50: oklch(0.98 0.02 201);
|
||||||
|
--color-primary-100: oklch(0.96 0.04 203);
|
||||||
|
--color-primary-200: oklch(0.92 0.08 205);
|
||||||
|
--color-primary-300: oklch(0.87 0.12 207);
|
||||||
|
--color-primary-400: oklch(0.80 0.13 212);
|
||||||
|
--color-primary-500: oklch(0.71 0.13 215);
|
||||||
|
--color-primary-600: oklch(0.61 0.11 222);
|
||||||
|
--color-primary-700: oklch(0.52 0.09 223);
|
||||||
|
--color-primary-800: oklch(0.45 0.08 224);
|
||||||
|
--color-primary-900: oklch(0.40 0.07 227);
|
||||||
|
--color-primary-950: oklch(0.30 0.05 230);
|
||||||
|
--color-secondary-50: oklch(0.98 0.02 166);
|
||||||
|
--color-secondary-100: oklch(0.95 0.05 163);
|
||||||
|
--color-secondary-200: oklch(0.90 0.09 164);
|
||||||
|
--color-secondary-300: oklch(0.85 0.13 165);
|
||||||
|
--color-secondary-400: oklch(0.77 0.15 163);
|
||||||
|
--color-secondary-500: oklch(0.70 0.15 162);
|
||||||
|
--color-secondary-600: oklch(0.60 0.13 163);
|
||||||
|
--color-secondary-700: oklch(0.51 0.10 166);
|
||||||
|
--color-secondary-800: oklch(0.43 0.09 167);
|
||||||
|
--color-secondary-900: oklch(0.38 0.07 169);
|
||||||
|
--color-secondary-950: oklch(0.26 0.05 173);
|
||||||
|
--color-success-50: oklch(0.98 0.02 156);
|
||||||
|
--color-success-100: oklch(0.96 0.04 157);
|
||||||
|
--color-success-200: oklch(0.93 0.08 156);
|
||||||
|
--color-success-300: oklch(0.87 0.14 154);
|
||||||
|
--color-success-400: oklch(0.80 0.18 152);
|
||||||
|
--color-success-500: oklch(0.72 0.19 150);
|
||||||
|
--color-success-600: oklch(0.63 0.17 149);
|
||||||
|
--color-success-700: oklch(0.53 0.14 150);
|
||||||
|
--color-success-800: oklch(0.45 0.11 151);
|
||||||
|
--color-success-900: oklch(0.39 0.09 153);
|
||||||
|
--color-success-950: oklch(0.27 0.06 153);
|
||||||
|
--color-warning-50: oklch(0.99 0.03 102);
|
||||||
|
--color-warning-100: oklch(0.97 0.07 103);
|
||||||
|
--color-warning-200: oklch(0.95 0.12 102);
|
||||||
|
--color-warning-300: oklch(0.91 0.17 98);
|
||||||
|
--color-warning-400: oklch(0.86 0.17 92);
|
||||||
|
--color-warning-500: oklch(0.80 0.16 86);
|
||||||
|
--color-warning-600: oklch(0.68 0.14 76);
|
||||||
|
--color-warning-700: oklch(0.55 0.12 66);
|
||||||
|
--color-warning-800: oklch(0.48 0.10 62);
|
||||||
|
--color-warning-900: oklch(0.42 0.09 58);
|
||||||
|
--color-warning-950: oklch(0.29 0.06 54);
|
||||||
|
--color-error-50: oklch(0.97 0.01 17);
|
||||||
|
--color-error-100: oklch(0.94 0.03 18);
|
||||||
|
--color-error-200: oklch(0.88 0.06 18);
|
||||||
|
--color-error-300: oklch(0.81 0.10 20);
|
||||||
|
--color-error-400: oklch(0.71 0.17 22);
|
||||||
|
--color-error-500: oklch(0.64 0.21 25);
|
||||||
|
--color-error-600: oklch(0.58 0.22 27);
|
||||||
|
--color-error-700: oklch(0.51 0.19 28);
|
||||||
|
--color-error-800: oklch(0.44 0.16 27);
|
||||||
|
--color-error-900: oklch(0.40 0.13 26);
|
||||||
|
--color-error-950: oklch(0.26 0.09 26);
|
||||||
|
--color-neutral-50: oklch(0.98 0.00 248);
|
||||||
|
--color-neutral-100: oklch(0.97 0.01 248);
|
||||||
|
--color-neutral-200: oklch(0.93 0.01 256);
|
||||||
|
--color-neutral-300: oklch(0.87 0.02 253);
|
||||||
|
--color-neutral-400: oklch(0.71 0.04 257);
|
||||||
|
--color-neutral-500: oklch(0.55 0.04 257);
|
||||||
|
--color-neutral-600: oklch(0.45 0.04 257);
|
||||||
|
--color-neutral-700: oklch(0.37 0.04 257);
|
||||||
|
--color-neutral-800: oklch(0.28 0.04 260);
|
||||||
|
--color-neutral-900: oklch(0.28 0.04 260);
|
||||||
|
--color-neutral-950: oklch(0.28 0.04 260);
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--spacing-0: 0;
|
||||||
|
--spacing-1: 0.25rem;
|
||||||
|
--spacing-2: 0.5rem;
|
||||||
|
--spacing-3: 0.75rem;
|
||||||
|
--spacing-4: 1rem;
|
||||||
|
--spacing-5: 1.25rem;
|
||||||
|
--spacing-6: 1.5rem;
|
||||||
|
--spacing-7: 1.75rem;
|
||||||
|
--spacing-8: 2rem;
|
||||||
|
--spacing-10: 2.5rem;
|
||||||
|
--spacing-12: 3rem;
|
||||||
|
--spacing-16: 4rem;
|
||||||
|
--spacing-20: 5rem;
|
||||||
|
--spacing-24: 6rem;
|
||||||
|
--spacing-32: 8rem;
|
||||||
|
--spacing-40: 10rem;
|
||||||
|
--spacing-48: 12rem;
|
||||||
|
--spacing-56: 14rem;
|
||||||
|
--spacing-64: 16rem;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-family-sans: 'Inter Variable', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--font-family-mono: 'Fira Code Variable', 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
|
||||||
|
--font-family-base: 'Inter Variable', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--font-size-xs: 0.75rem;
|
||||||
|
--font-size-sm: 0.875rem;
|
||||||
|
--font-size-base: 1rem;
|
||||||
|
--font-size-lg: 1.125rem;
|
||||||
|
--font-size-xl: 1.25rem;
|
||||||
|
--font-size-2xl: 1.5rem;
|
||||||
|
--font-size-3xl: 1.875rem;
|
||||||
|
--font-size-4xl: 2.25rem;
|
||||||
|
--font-size-5xl: 3rem;
|
||||||
|
--font-size-6xl: 3.75rem;
|
||||||
|
--font-size-7xl: 4.5rem;
|
||||||
|
}
|
||||||
BIN
static/images/ep-rule-artwork.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
static/images/hero.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
static/images/linkedin.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
3
static/images/logo-eawag.svg
Normal file
|
After Width: | Height: | Size: 21 KiB |
@ -1,225 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
version="1.1"
|
|
||||||
width="314.98749"
|
|
||||||
height="28.8125"
|
|
||||||
id="svg3004"
|
|
||||||
xml:space="preserve"><metadata
|
|
||||||
id="metadata3010"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs3008" /><g
|
|
||||||
transform="matrix(1.25,0,0,-1.25,0,28.8125)"
|
|
||||||
id="g3012"><g
|
|
||||||
transform="scale(0.1,0.1)"
|
|
||||||
id="g3014"><path
|
|
||||||
d="m 957.473,175.816 0,-4.296 -18.453,0 0,-48.614 -5.04,0 0,48.614 -18.378,0 0,4.296 41.871,0"
|
|
||||||
id="path3016"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 969.695,175.816 0,-22.968 31.425,0 0,22.968 5.04,0 0,-52.91 -5.04,0 0,25.637 -31.425,0 0,-25.637 -5.039,0 0,52.91 5.039,0"
|
|
||||||
id="path3018"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1055.58,175.816 0,-4.296 -31.49,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.53,0"
|
|
||||||
id="path3020"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1124.58,175.816 0,-4.296 -31.5,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.54,0"
|
|
||||||
id="path3022"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1139.76,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
|
||||||
id="path3024"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1188.15,175.816 17.19,-47.355 0.15,0 17.06,47.355 5.34,0 -19.65,-52.91 -5.86,0 -19.56,52.91 5.33,0"
|
|
||||||
id="path3026"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1235.15,122.906 5.043,0 0,52.9102 -5.043,0 0,-52.9102 z"
|
|
||||||
id="path3028"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1277.3,150.691 c 1.54,0 2.99,0.243 4.38,0.711 1.39,0.469 2.59,1.145 3.63,2.036 1.04,0.886 1.86,1.968 2.48,3.222 0.63,1.258 0.92,2.703 0.92,4.336 0,3.262 -0.94,5.824 -2.81,7.703 -1.88,1.875 -4.74,2.821 -8.6,2.821 l -18.82,0 0,-20.829 18.82,0 z m 0.38,25.125 c 2.16,0 4.23,-0.269 6.19,-0.816 1.95,-0.543 3.65,-1.367 5.1,-2.48 1.46,-1.118 2.62,-2.54 3.49,-4.297 0.86,-1.758 1.29,-3.821 1.29,-6.192 0,-3.359 -0.86,-6.273 -2.6,-8.742 -1.72,-2.473 -4.29,-4.055 -7.69,-4.746 l 0,-0.145 c 1.73,-0.25 3.16,-0.703 4.29,-1.367 1.14,-0.668 2.06,-1.527 2.78,-2.558 0.72,-1.035 1.23,-2.239 1.56,-3.594 0.31,-1.363 0.53,-2.832 0.62,-4.41 0.05,-0.887 0.1,-1.977 0.16,-3.262 0.05,-1.285 0.15,-2.582 0.29,-3.891 0.15,-1.312 0.38,-2.543 0.71,-3.711 0.32,-1.156 0.74,-2.054 1.3,-2.699 l -5.56,0 c -0.29,0.496 -0.54,1.098 -0.7,1.809 -0.18,0.723 -0.31,1.461 -0.37,2.234 -0.08,0.762 -0.14,1.512 -0.2,2.254 -0.05,0.742 -0.1,1.387 -0.14,1.926 -0.09,1.875 -0.26,3.742 -0.49,5.598 -0.22,1.851 -0.69,3.503 -1.4,4.961 -0.72,1.46 -1.76,2.632 -3.11,3.523 -1.36,0.887 -3.23,1.281 -5.6,1.191 l -19.12,0 0,-23.496 -5.03,0 0,52.91 24.23,0"
|
|
||||||
id="path3030"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1308.46,140.879 c 0.77,-2.793 1.95,-5.289 3.56,-7.484 1.61,-2.2 3.66,-3.965 6.19,-5.301 2.52,-1.336 5.54,-2.004 9.04,-2.004 3.51,0 6.51,0.668 9,2.004 2.5,1.336 4.55,3.101 6.15,5.301 1.6,2.195 2.8,4.691 3.56,7.484 0.77,2.789 1.15,5.613 1.15,8.48 0,2.914 -0.38,5.758 -1.15,8.52 -0.76,2.769 -1.96,5.25 -3.56,7.449 -1.6,2.199 -3.65,3.969 -6.15,5.297 -2.49,1.336 -5.49,2.008 -9,2.008 -3.5,0 -6.52,-0.672 -9.04,-2.008 -2.53,-1.328 -4.58,-3.098 -6.19,-5.297 -1.61,-2.199 -2.79,-4.68 -3.56,-7.449 -0.75,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.4,-5.691 1.15,-8.48 z m -4.63,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.14,1.504 6.79,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.71,-3.531 7.78,-6.078 2.08,-2.547 3.64,-5.469 4.67,-8.777 1.05,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.51,-7.136 -1.56,-10.449 -1.03,-3.308 -2.59,-6.226 -4.67,-8.738 -2.07,-2.52 -4.67,-4.531 -7.78,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.14,0 -7.79,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.54,6.793 -1.54,10.449 0,3.657 0.5,7.141 1.54,10.454"
|
|
||||||
id="path3032"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1367.77,175.816 30.84,-44.757 0.15,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.62,0"
|
|
||||||
id="path3034"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1423.89,175.816 18.3,-46.386 18.22,46.386 7.41,0 0,-52.91 -5.03,0 0,45.723 -0.15,0 -18.09,-45.723 -4.74,0 -18.15,45.723 -0.15,0 0,-45.723 -5.04,0 0,52.91 7.42,0"
|
|
||||||
id="path3036"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1517.1,175.816 0,-4.296 -31.48,0 0,-19.122 29.48,0 0,-4.293 -29.48,0 0,-20.898 31.86,0 0,-4.301 -36.9,0 0,52.91 36.52,0"
|
|
||||||
id="path3038"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1532.29,175.816 30.82,-44.757 0.14,0 0,44.757 5.03,0 0,-52.91 -5.62,0 -30.81,44.754 -0.15,0 0,-44.754 -5.03,0 0,52.91 5.62,0"
|
|
||||||
id="path3040"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1617.34,175.816 0,-4.296 -18.44,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.86,0"
|
|
||||||
id="path3042"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1647.8,143.656 -10.22,27.121 -10.6,-27.121 20.82,0 z m -7.18,32.16 20.74,-52.91 -5.4,0 -6.46,16.449 -24.07,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.64,0"
|
|
||||||
id="path3044"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1673.44,175.816 0,-48.609 29.65,0 0,-4.301 -34.68,0 0,52.91 5.03,0"
|
|
||||||
id="path3046"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1769.74,165.254 c -1.02,1.605 -2.25,2.953 -3.71,4.043 -1.46,1.082 -3.06,1.914 -4.81,2.48 -1.76,0.567 -3.6,0.856 -5.52,0.856 -3.51,0 -6.53,-0.672 -9.05,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.77,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.38,-5.691 1.15,-8.48 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.54,-2.004 9.05,-2.004 2.46,0 4.69,0.449 6.66,1.336 1.98,0.89 3.68,2.101 5.12,3.633 1.43,1.523 2.59,3.324 3.48,5.371 0.89,2.05 1.45,4.261 1.71,6.633 l 5.03,0 c -0.35,-3.258 -1.11,-6.204 -2.3,-8.821 -1.18,-2.617 -2.7,-4.84 -4.59,-6.664 -1.88,-1.824 -4.08,-3.238 -6.63,-4.223 -2.54,-0.992 -5.37,-1.492 -8.48,-1.492 -4.17,0 -7.81,0.766 -10.93,2.266 -3.15,1.512 -5.76,3.523 -7.83,6.043 -2.07,2.512 -3.62,5.43 -4.65,8.738 -1.04,3.313 -1.57,6.793 -1.57,10.449 0,3.657 0.53,7.141 1.57,10.454 1.03,3.308 2.58,6.23 4.65,8.777 2.07,2.547 4.68,4.57 7.83,6.078 3.12,1.504 6.76,2.254 10.93,2.254 2.52,0 4.97,-0.371 7.38,-1.106 2.39,-0.738 4.56,-1.843 6.51,-3.296 1.95,-1.461 3.58,-3.254 4.89,-5.372 1.3,-2.125 2.13,-4.574 2.47,-7.335 l -5.04,0 c -0.43,2.019 -1.16,3.839 -2.17,5.441"
|
|
||||||
id="path3048"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1791.01,140.879 c 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.53,-2.004 9.04,-2.004 3.51,0 6.5,0.668 9.01,2.004 2.49,1.336 4.54,3.101 6.14,5.301 1.61,2.195 2.79,4.691 3.57,7.484 0.75,2.789 1.14,5.613 1.14,8.48 0,2.914 -0.39,5.758 -1.14,8.52 -0.78,2.769 -1.96,5.25 -3.57,7.449 -1.6,2.199 -3.65,3.969 -6.14,5.297 -2.51,1.336 -5.5,2.008 -9.01,2.008 -3.51,0 -6.52,-0.672 -9.04,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.78,-2.762 -1.16,-5.606 -1.16,-8.52 0,-2.867 0.38,-5.691 1.16,-8.48 z m -4.64,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.13,1.504 6.77,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.72,-3.531 7.79,-6.078 2.07,-2.547 3.62,-5.469 4.66,-8.777 1.04,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.52,-7.136 -1.56,-10.449 -1.04,-3.308 -2.59,-6.226 -4.66,-8.738 -2.07,-2.52 -4.68,-4.531 -7.79,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.16,0 -7.8,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.56,6.793 -1.56,10.449 0,3.657 0.52,7.141 1.56,10.454"
|
|
||||||
id="path3050"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1850.33,175.816 30.82,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.64,0"
|
|
||||||
id="path3052"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1935.39,175.816 0,-4.296 -18.45,0 0,-48.614 -5.04,0 0,48.614 -18.37,0 0,4.296 41.86,0"
|
|
||||||
id="path3054"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1965.86,143.656 -10.23,27.121 -10.6,-27.121 20.83,0 z m -7.21,32.16 20.76,-52.91 -5.41,0 -6.44,16.449 -24.08,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.62,0"
|
|
||||||
id="path3056"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1993.71,175.816 18.3,-46.386 18.24,46.386 7.41,0 0,-52.91 -5.04,0 0,45.723 -0.15,0 -18.09,-45.723 -4.73,0 -18.16,45.723 -0.14,0 0,-45.723 -5.05,0 0,52.91 7.41,0"
|
|
||||||
id="path3058"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2050.78,122.906 5.0273,0 0,52.9102 -5.0273,0 0,-52.9102 z"
|
|
||||||
id="path3060"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2074.63,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.63,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
|
||||||
id="path3062"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2151.78,143.656 -10.24,27.121 -10.58,-27.121 20.82,0 z m -7.2,32.16 20.76,-52.91 -5.41,0 -6.45,16.449 -24.09,0 -6.36,-16.449 -5.34,0 21.27,52.91 5.62,0"
|
|
||||||
id="path3064"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2177.93,175.816 30.82,-44.757 0.16,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.84,44.754 -0.14,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
|
|
||||||
id="path3066"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2263.01,175.816 0,-4.296 -18.46,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.88,0"
|
|
||||||
id="path3068"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 945.785,32.6602 c 1.875,0 3.656,0.1562 5.336,0.4804 1.676,0.3164 3.16,0.8985 4.445,1.7383 1.286,0.8477 2.301,1.9649 3.043,3.375 0.739,1.4102 1.11,3.1719 1.11,5.2969 0,3.4101 -1.203,5.9648 -3.602,7.668 -2.387,1.7031 -5.836,2.5585 -10.332,2.5585 l -17.344,0 0,-21.1171 17.344,0 z m 0,25.414 c 2.028,0 3.778,0.2344 5.262,0.711 1.48,0.4609 2.719,1.1015 3.711,1.9218 0.98,0.8125 1.726,1.7696 2.215,2.8555 0.5,1.082 0.742,2.2422 0.742,3.4766 0,6.6211 -3.977,9.9375 -11.93,9.9375 l -17.344,0 0,-18.9024 17.344,0 z m 0,23.1953 c 2.223,0 4.36,-0.2109 6.41,-0.6289 2.051,-0.4218 3.852,-1.1328 5.41,-2.1484 1.559,-1.0156 2.805,-2.3438 3.743,-4 0.937,-1.6563 1.406,-3.7227 1.406,-6.1914 0,-1.3867 -0.219,-2.7305 -0.668,-4.0391 -0.445,-1.3086 -1.074,-2.4961 -1.887,-3.5547 -0.808,-1.0625 -1.781,-1.9687 -2.89,-2.7031 -1.114,-0.7422 -2.36,-1.2617 -3.739,-1.5586 l 0,-0.1523 c 3.403,-0.4453 6.121,-1.8321 8.145,-4.1797 2.031,-2.3477 3.043,-5.25 3.043,-8.711 0,-0.8398 -0.074,-1.7851 -0.227,-2.8515 -0.144,-1.0625 -0.437,-2.1524 -0.886,-3.2617 -0.446,-1.1133 -1.086,-2.2149 -1.93,-3.2969 -0.836,-1.0859 -1.957,-2.0391 -3.363,-2.8516 -1.414,-0.8203 -3.141,-1.4883 -5.192,-2.0039 -2.051,-0.5195 -4.508,-0.7812 -7.375,-0.7812 l -22.379,0 0,52.914 22.379,0"
|
|
||||||
id="path3070"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 975.426,28.3555 5.04297,0 0,52.9141 -5.04297,0 0,-52.9141 z"
|
|
||||||
id="path3072"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 997.105,46.3281 c 0.762,-2.789 1.95,-5.2812 3.555,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.53,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.01,2.0039 2.5,1.3281 4.54,3.0976 6.15,5.2969 1.61,2.1992 2.79,4.6914 3.55,7.4804 0.77,2.793 1.16,5.6211 1.16,8.4844 0,2.918 -0.39,5.7578 -1.16,8.5273 -0.76,2.7618 -1.94,5.2461 -3.55,7.4454 -1.61,2.2031 -3.65,3.9648 -6.15,5.3007 -2.49,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.51,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.605,-2.1993 -2.793,-4.6836 -3.555,-7.4454 -0.773,-2.7695 -1.152,-5.6093 -1.152,-8.5273 0,-2.8633 0.379,-5.6914 1.152,-8.4844 z m -4.632,18.9336 c 1.035,3.3125 2.59,6.2383 4.668,8.7813 2.074,2.5429 4.679,4.5742 7.819,6.0781 3.13,1.5078 6.77,2.2578 10.92,2.2578 4.15,0 7.79,-0.75 10.9,-2.2578 3.11,-1.5039 5.71,-3.5352 7.78,-6.0781 2.08,-2.543 3.63,-5.4688 4.67,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.67,-8.7461 -2.07,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.11,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.15,0 -7.79,0.7617 -10.92,2.2656 -3.14,1.5079 -5.745,3.5196 -7.819,6.0391 -2.078,2.5156 -3.633,5.4297 -4.668,8.7461 -1.035,3.3008 -1.559,6.7812 -1.559,10.4414 0,3.6602 0.524,7.1445 1.559,10.4492"
|
|
||||||
id="path3074"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1087.1,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
|
|
||||||
id="path3076"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1118.15,56.1484 c 1.53,0 2.99,0.2383 4.37,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.04,0.8907 1.86,1.9688 2.49,3.2227 0.61,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.95,5.836 -2.82,7.7031 -1.88,1.8829 -4.75,2.8282 -8.6,2.8282 l -18.82,0 0,-20.8282 18.82,0 z m 0.37,25.1211 c 2.17,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.65,-1.3633 5.11,-2.4765 1.46,-1.1133 2.62,-2.543 3.49,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.16,-0.7032 4.3,-1.3672 1.14,-0.668 2.06,-1.5235 2.78,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.33,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.3,-3.8868 0.15,-1.3125 0.38,-2.5468 0.71,-3.707 0.31,-1.1562 0.74,-2.0586 1.29,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.7,1.8203 -0.17,0.7187 -0.3,1.4531 -0.37,2.2265 -0.07,0.7618 -0.14,1.5118 -0.19,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.48,5.6016 -0.22,1.8555 -0.69,3.5 -1.41,4.9609 -0.71,1.4532 -1.75,2.6289 -3.11,3.5235 -1.36,0.8906 -3.22,1.2812 -5.59,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
|
||||||
id="path3078"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1174.25,49.1055 -10.23,27.125 -10.6,-27.125 20.83,0 z m -7.19,32.164 20.75,-52.914 -5.41,0 -6.45,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.63,0"
|
|
||||||
id="path3080"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1200.41,81.2695 30.83,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.63,0 -30.84,44.7578 -0.15,0 0,-44.7578 -5.04,0 0,52.914 5.64,0"
|
|
||||||
id="path3082"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1252.87,38.9609 c 0.9,-1.8281 2.12,-3.289 3.67,-4.375 1.57,-1.0898 3.4,-1.8671 5.52,-2.3359 2.12,-0.4727 4.4,-0.7031 6.83,-0.7031 1.37,0 2.89,0.1914 4.52,0.5976 1.62,0.3907 3.14,1.0196 4.55,1.8828 1.42,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.41,3.0039 1.41,4.9258 0,1.4843 -0.33,2.7695 -1.01,3.8593 -0.66,1.086 -1.53,1.9961 -2.58,2.7383 -1.07,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.55,0.8555 -3.78,1.1524 l -11.78,2.8789 c -1.54,0.4062 -3.02,0.8906 -4.49,1.4922 -1.44,0.5898 -2.72,1.375 -3.81,2.3672 -1.09,0.9843 -1.95,2.2031 -2.63,3.6289 -0.67,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.25,2.7929 0.74,4.5234 0.49,1.7266 1.42,3.3594 2.79,4.8906 1.35,1.5274 3.21,2.8243 5.59,3.8907 2.37,1.0625 5.4,1.5898 9.1,1.5898 2.62,0 5.13,-0.3398 7.49,-1.0351 2.38,-0.6915 4.47,-1.7305 6.22,-3.1133 1.8,-1.3789 3.21,-3.0977 4.26,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.56,3.7969 -1.37,5.3047 -0.82,1.5039 -1.88,2.7617 -3.19,3.7773 -1.3,1.0117 -2.81,1.7852 -4.53,2.3008 -1.7,0.5195 -3.49,0.7773 -5.37,0.7773 -1.72,0 -3.39,-0.1914 -5,-0.5586 -1.61,-0.371 -3.01,-0.9609 -4.22,-1.7812 -1.21,-0.8164 -2.18,-1.8906 -2.93,-3.2227 -0.74,-1.332 -1.11,-2.9882 -1.11,-4.9648 0,-1.2305 0.22,-2.3086 0.64,-3.2227 0.42,-0.914 0.98,-1.6953 1.72,-2.332 0.75,-0.6484 1.61,-1.1601 2.56,-1.5547 0.98,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.88,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.27,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.47,-1.1133 -1.14,-2.2344 -2,-3.3711 -0.87,-1.1368 -2.06,-2.1602 -3.56,-3.0782 -1.51,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.88,-0.8516 -8,-0.8516 -3.11,0 -6,0.3594 -8.68,1.0781 -2.65,0.7188 -4.94,1.8125 -6.81,3.2969 -1.88,1.4844 -3.32,3.3789 -4.34,5.707 -1,2.3204 -1.43,5.1055 -1.3,8.375 l 5.05,0 c -0.06,-2.7148 0.37,-4.9921 1.25,-6.8164"
|
|
||||||
id="path3084"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1331.72,81.2695 0,-4.2929 -28.53,0 0,-19.1211 25.35,0 0,-4.3008 -25.35,0 0,-25.1992 -5.04,0 0,52.914 33.57,0"
|
|
||||||
id="path3086"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1343.54,46.3281 c 0.78,-2.789 1.95,-5.2812 3.55,-7.4804 1.6,-2.1993 3.68,-3.9688 6.2,-5.2969 2.51,-1.3399 5.53,-2.0039 9.03,-2.0039 3.51,0 6.51,0.664 9.01,2.0039 2.5,1.3281 4.55,3.0976 6.15,5.2969 1.6,2.1992 2.78,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.78,2.7618 -1.96,5.2461 -3.56,7.4454 -1.6,2.2031 -3.65,3.9648 -6.15,5.3007 -2.5,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.52,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.2,-5.3007 -1.6,-2.1993 -2.77,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.37,-5.6914 1.14,-8.4844 z m -4.63,18.9336 c 1.03,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.69,4.5742 7.82,6.0781 3.14,1.5078 6.79,2.2578 10.93,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.78,-6.0781 2.09,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.57,-6.789 1.57,-10.4492 0,-3.6602 -0.53,-7.1406 -1.57,-10.4414 -1.03,-3.3164 -2.57,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.73,3.5196 -7.82,6.0391 -2.07,2.5156 -3.63,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
|
|
||||||
id="path3088"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1421.16,56.1484 c 1.54,0 3,0.2383 4.39,0.7071 1.37,0.4687 2.58,1.1484 3.62,2.0351 1.04,0.8907 1.87,1.9688 2.49,3.2227 0.61,1.2617 0.91,2.7109 0.91,4.332 0,3.2656 -0.93,5.836 -2.81,7.7031 -1.88,1.8829 -4.73,2.8282 -8.6,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.37,25.1211 c 2.18,0 4.24,-0.2695 6.18,-0.8203 1.96,-0.539 3.67,-1.3633 5.12,-2.4765 1.47,-1.1133 2.63,-2.543 3.49,-4.3008 0.86,-1.7578 1.3,-3.8125 1.3,-6.1875 0,-3.3594 -0.87,-6.2696 -2.6,-8.7461 -1.72,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.74,-0.25 3.17,-0.7032 4.3,-1.3672 1.13,-0.668 2.06,-1.5235 2.78,-2.5586 0.72,-1.0391 1.24,-2.2344 1.56,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.16,-1.3125 0.38,-2.5468 0.7,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.31,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.29,1.4531 -0.37,2.2265 -0.08,0.7618 -0.13,1.5118 -0.18,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.25,3.7461 -0.48,5.6016 -0.22,1.8555 -0.7,3.5 -1.41,4.9609 -0.73,1.4532 -1.76,2.6289 -3.12,3.5235 -1.36,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.13,0 0,-23.5 -5.03,0 0,52.914 24.23,0"
|
|
||||||
id="path3090"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1456.21,81.2695 18.3,-46.3906 18.24,46.3906 7.41,0 0,-52.914 -5.04,0 0,45.7265 -0.15,0 -18.08,-45.7265 -4.74,0 -18.17,45.7265 -0.13,0 0,-45.7265 -5.05,0 0,52.914 7.41,0"
|
|
||||||
id="path3092"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1541.21,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.74,-52.914 -5.42,0 -6.42,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.62,0"
|
|
||||||
id="path3094"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1593,81.2695 0,-4.2929 -18.47,0 0,-48.6211 -5.04,0 0,48.6211 -18.37,0 0,4.2929 41.88,0"
|
|
||||||
id="path3096"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1600.55,28.3555 5.0391,0 0,52.9141 -5.0391,0 0,-52.9141 z"
|
|
||||||
id="path3098"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1622.23,46.3281 c 0.76,-2.789 1.94,-5.2812 3.55,-7.4804 1.6,-2.1993 3.67,-3.9688 6.19,-5.2969 2.52,-1.3399 5.53,-2.0039 9.04,-2.0039 3.5,0 6.5,0.664 9.01,2.0039 2.48,1.3281 4.54,3.0976 6.14,5.2969 1.61,2.1992 2.8,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.76,2.7618 -1.95,5.2461 -3.56,7.4454 -1.6,2.2031 -3.66,3.9648 -6.14,5.3007 -2.51,1.3321 -5.51,2 -9.01,2 -3.51,0 -6.52,-0.6679 -9.04,-2 -2.52,-1.3359 -4.59,-3.0976 -6.19,-5.3007 -1.61,-2.1993 -2.79,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.16,-5.6093 -1.16,-8.5273 0,-2.8633 0.39,-5.6914 1.16,-8.4844 z m -4.64,18.9336 c 1.03,3.3125 2.6,6.2383 4.68,8.7813 2.07,2.5429 4.67,4.5742 7.8,6.0781 3.14,1.5078 6.79,2.2578 10.94,2.2578 4.16,0 7.78,-0.75 10.88,-2.2578 3.13,-1.5039 5.72,-3.5352 7.8,-6.0781 2.07,-2.543 3.62,-5.4688 4.66,-8.7813 1.03,-3.3047 1.55,-6.789 1.55,-10.4492 0,-3.6602 -0.52,-7.1406 -1.55,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.8,-6.0391 -3.1,-1.5039 -6.72,-2.2656 -10.88,-2.2656 -4.15,0 -7.8,0.7617 -10.94,2.2656 -3.13,1.5079 -5.73,3.5196 -7.8,6.0391 -2.08,2.5156 -3.65,5.4297 -4.68,8.7461 -1.03,3.3008 -1.55,6.7812 -1.55,10.4414 0,3.6602 0.52,7.1445 1.55,10.4492"
|
|
||||||
id="path3100"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1681.55,81.2695 30.82,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.64,0 -30.82,44.7578 -0.15,0 0,-44.7578 -5.03,0 0,52.914 5.63,0"
|
|
||||||
id="path3102"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1775.58,55.332 c 3.51,0 6.35,0.8946 8.52,2.6719 2.17,1.7774 3.26,4.4922 3.26,8.1484 0,3.6602 -1.09,6.3711 -3.26,8.1485 -2.17,1.7851 -5.01,2.6758 -8.52,2.6758 l -17.35,0 0,-21.6446 17.35,0 z m 1.12,25.9375 c 2.36,0 4.51,-0.3359 6.43,-0.9961 1.94,-0.6679 3.58,-1.6562 4.99,-2.9648 1.37,-1.3125 2.43,-2.9063 3.17,-4.7852 0.74,-1.875 1.11,-4.0039 1.11,-6.3711 0,-2.3671 -0.37,-4.4921 -1.11,-6.371 -0.74,-1.8829 -1.8,-3.4766 -3.17,-4.7852 -1.41,-1.3047 -3.05,-2.293 -4.99,-2.9609 -1.92,-0.6641 -4.07,-0.9961 -6.43,-0.9961 l -18.47,0 0,-22.6836 -5.04,0 0,52.914 23.51,0"
|
|
||||||
id="path3104"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1824.93,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.76,-52.914 -5.41,0 -6.45,16.457 -24.09,0 -6.38,-16.457 -5.33,0 21.28,52.914 5.62,0"
|
|
||||||
id="path3106"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1876.73,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
|
|
||||||
id="path3108"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1888.96,81.2695 0,-22.9687 31.41,0 0,22.9687 5.04,0 0,-52.914 -5.04,0 0,25.6445 -31.41,0 0,-25.6445 -5.04,0 0,52.914 5.04,0"
|
|
||||||
id="path3110"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 1938.38,81.2695 12.01,-46.3125 0.15,0 12.9,46.3125 6.3,0 12.96,-46.3125 0.15,0 12.07,46.3125 5.04,0 -14.59,-52.914 -5.34,0 -13.42,47.3515 -0.14,0 -13.34,-47.3515 -5.48,0 -14.67,52.914 5.4,0"
|
|
||||||
id="path3112"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2034.73,49.1055 -10.24,27.125 -10.59,-27.125 20.83,0 z m -7.2,32.164 20.75,-52.914 -5.41,0 -6.44,16.457 -24.09,0 -6.37,-16.457 -5.34,0 21.27,52.914 5.63,0"
|
|
||||||
id="path3114"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2043.84,81.2695 5.93,0 17.41,-26.8281 17.33,26.8281 6.01,0 -20.9,-31.125 0,-21.789 -5.04,0 0,21.789 -20.74,31.125"
|
|
||||||
id="path3116"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2144.01,56.1484 c 1.55,0 3,0.2383 4.38,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.05,0.8907 1.88,1.9688 2.48,3.2227 0.62,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.94,5.836 -2.81,7.7031 -1.89,1.8829 -4.75,2.8282 -8.61,2.8282 l -18.81,0 0,-20.8282 18.81,0 z m 0.38,25.1211 c 2.18,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.66,-1.3633 5.1,-2.4765 1.47,-1.1133 2.63,-2.543 3.5,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.7,-4.7383 l 0,-0.1484 c 1.72,-0.25 3.15,-0.7032 4.28,-1.3672 1.15,-0.668 2.07,-1.5235 2.79,-2.5586 0.72,-1.0391 1.24,-2.2344 1.55,-3.5977 0.32,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.15,-1.3125 0.39,-2.5468 0.71,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.3,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.3,1.4531 -0.38,2.2265 -0.07,0.7618 -0.13,1.5118 -0.18,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.49,5.6016 -0.22,1.8555 -0.68,3.5 -1.39,4.9609 -0.73,1.4532 -1.76,2.6289 -3.13,3.5235 -1.35,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.11,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
|
||||||
id="path3118"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2208.34,81.2695 0,-4.2929 -31.49,0 0,-19.1211 29.49,0 0,-4.3008 -29.49,0 0,-20.8945 31.87,0 0,-4.3047 -36.91,0 0,52.914 36.53,0"
|
|
||||||
id="path3120"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2221.6,38.9609 c 0.9,-1.8281 2.13,-3.289 3.66,-4.375 1.58,-1.0898 3.4,-1.8671 5.53,-2.3359 2.12,-0.4727 4.41,-0.7031 6.82,-0.7031 1.38,0 2.9,0.1914 4.53,0.5976 1.62,0.3907 3.13,1.0196 4.55,1.8828 1.41,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.4,3.0039 1.4,4.9258 0,1.4843 -0.32,2.7695 -0.99,3.8593 -0.66,1.086 -1.55,1.9961 -2.59,2.7383 -1.06,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.54,0.8555 -3.78,1.1524 l -11.77,2.8789 c -1.55,0.4062 -3.03,0.8906 -4.5,1.4922 -1.45,0.5898 -2.73,1.375 -3.82,2.3672 -1.08,0.9843 -1.95,2.2031 -2.62,3.6289 -0.66,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.24,2.7929 0.74,4.5234 0.5,1.7266 1.42,3.3594 2.78,4.8906 1.36,1.5274 3.23,2.8243 5.59,3.8907 2.38,1.0625 5.41,1.5898 9.12,1.5898 2.61,0 5.11,-0.3398 7.48,-1.0351 2.37,-0.6915 4.46,-1.7305 6.24,-3.1133 1.77,-1.3789 3.19,-3.0977 4.24,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.55,3.7969 -1.38,5.3047 -0.81,1.5039 -1.87,2.7617 -3.18,3.7773 -1.31,1.0117 -2.82,1.7852 -4.53,2.3008 -1.71,0.5195 -3.48,0.7773 -5.37,0.7773 -1.73,0 -3.4,-0.1914 -5.01,-0.5586 -1.6,-0.371 -3,-0.9609 -4.21,-1.7812 -1.21,-0.8164 -2.19,-1.8906 -2.94,-3.2227 -0.74,-1.332 -1.1,-2.9882 -1.1,-4.9648 0,-1.2305 0.21,-2.3086 0.63,-3.2227 0.43,-0.914 0.99,-1.6953 1.73,-2.332 0.75,-0.6484 1.62,-1.1601 2.56,-1.5547 0.97,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.87,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.26,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.48,-1.1133 -1.15,-2.2344 -2.01,-3.3711 -0.86,-1.1368 -2.05,-2.1602 -3.55,-3.0782 -1.5,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.89,-0.8516 -8.01,-0.8516 -3.11,0 -5.99,0.3594 -8.67,1.0781 -2.66,0.7188 -4.94,1.8125 -6.8,3.2969 -1.89,1.4844 -3.33,3.3789 -4.36,5.707 -0.99,2.3204 -1.43,5.1055 -1.29,8.375 l 5.04,0 c -0.05,-2.7148 0.38,-4.9921 1.26,-6.8164"
|
|
||||||
id="path3122"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2270.24,46.3281 c 0.79,-2.789 1.96,-5.2812 3.57,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.52,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.02,2.0039 2.49,1.3281 4.54,3.0976 6.15,5.2969 1.6,2.1992 2.77,4.6914 3.55,7.4804 0.77,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.38,5.7578 -1.15,8.5273 -0.78,2.7618 -1.95,5.2461 -3.55,7.4454 -1.61,2.2031 -3.66,3.9648 -6.15,5.3007 -2.5,1.3321 -5.51,2 -9.02,2 -3.5,0 -6.52,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.61,-2.1993 -2.78,-4.6836 -3.57,-7.4454 -0.75,-2.7695 -1.13,-5.6093 -1.13,-8.5273 0,-2.8633 0.38,-5.6914 1.13,-8.4844 z m -4.62,18.9336 c 1.04,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.68,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.92,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.79,-6.0781 2.07,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.03,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.09,-2.5195 -4.68,-4.5312 -7.79,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.78,0.7617 -10.92,2.2656 -3.15,1.5079 -5.74,3.5196 -7.83,6.0391 -2.07,2.5156 -3.62,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
|
|
||||||
id="path3124"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2328.53,81.2695 0,-32.7539 c 0,-3.0625 0.35,-5.664 1.03,-7.8242 0.69,-2.1406 1.71,-3.8984 3.05,-5.2578 1.33,-1.3555 2.97,-2.3477 4.88,-2.9648 1.93,-0.6172 4.11,-0.9219 6.52,-0.9219 2.47,0 4.67,0.3047 6.61,0.9219 1.93,0.6171 3.55,1.6093 4.88,2.9648 1.34,1.3594 2.35,3.1172 3.04,5.2578 0.69,2.1602 1.04,4.7617 1.04,7.8242 l 0,32.7539 5.04,0 0,-33.8672 c 0,-2.7148 -0.38,-5.2968 -1.14,-7.7382 -0.77,-2.4493 -1.99,-4.5899 -3.65,-6.418 -1.66,-1.8242 -3.76,-3.2695 -6.36,-4.332 -2.6,-1.0586 -5.74,-1.5938 -9.46,-1.5938 -3.65,0 -6.77,0.5352 -9.37,1.5938 -2.59,1.0625 -4.71,2.5078 -6.37,4.332 -1.66,1.8281 -2.87,3.9687 -3.63,6.418 -0.77,2.4414 -1.13,5.0234 -1.13,7.7382 l 0,33.8672 5.02,0"
|
|
||||||
id="path3126"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2400.87,56.1484 c 1.51,0 2.99,0.2383 4.36,0.7071 1.39,0.4687 2.59,1.1484 3.63,2.0351 1.03,0.8907 1.86,1.9688 2.49,3.2227 0.62,1.2617 0.92,2.7109 0.92,4.332 0,3.2656 -0.94,5.836 -2.82,7.7031 -1.86,1.8829 -4.73,2.8282 -8.58,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.36,25.1211 c 2.18,0 4.23,-0.2695 6.2,-0.8203 1.94,-0.539 3.64,-1.3633 5.11,-2.4765 1.45,-1.1133 2.61,-2.543 3.47,-4.3008 0.88,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.85,-6.2696 -2.58,-8.7461 -1.73,-2.4688 -4.29,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.17,-0.7032 4.31,-1.3672 1.11,-0.668 2.05,-1.5235 2.77,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.11,-1.9766 0.16,-3.2656 0.04,-1.2852 0.15,-2.5782 0.29,-3.8868 0.14,-1.3125 0.38,-2.5468 0.7,-3.707 0.31,-1.1562 0.75,-2.0586 1.3,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.71,1.8203 -0.16,0.7187 -0.29,1.4531 -0.36,2.2265 -0.09,0.7618 -0.14,1.5118 -0.19,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.09,1.875 -0.27,3.7461 -0.47,5.6016 -0.23,1.8555 -0.69,3.5 -1.42,4.9609 -0.71,1.4532 -1.75,2.6289 -3.1,3.5235 -1.37,0.8906 -3.23,1.2812 -5.6,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
|
|
||||||
id="path3128"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2465.16,70.7109 c -1.02,1.6094 -2.27,2.9493 -3.71,4.0391 -1.47,1.0859 -3.08,1.9141 -4.82,2.4844 -1.75,0.5625 -3.59,0.8515 -5.53,0.8515 -3.5,0 -6.51,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.18,-5.3007 -1.62,-2.1993 -2.8,-4.6836 -3.58,-7.4454 -0.76,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.38,-5.6914 1.14,-8.4844 0.78,-2.789 1.96,-5.2812 3.58,-7.4804 1.58,-2.1993 3.66,-3.9688 6.18,-5.2969 2.52,-1.3399 5.53,-2.0039 9.03,-2.0039 2.47,0 4.7,0.4453 6.66,1.332 2,0.8906 3.7,2.1016 5.13,3.6289 1.44,1.5274 2.6,3.3281 3.48,5.3711 0.9,2.0508 1.46,4.2695 1.71,6.6367 l 5.04,0 c -0.35,-3.2578 -1.13,-6.1953 -2.3,-8.8203 -1.19,-2.6172 -2.72,-4.8359 -4.59,-6.668 -1.88,-1.8281 -4.09,-3.2304 -6.63,-4.2226 -2.56,-0.9844 -5.37,-1.4844 -8.5,-1.4844 -4.15,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.74,3.5196 -7.83,6.0391 -2.05,2.5156 -3.62,5.4297 -4.65,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492 1.03,3.3125 2.6,6.2383 4.65,8.7813 2.09,2.5429 4.7,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.93,2.2578 2.52,0 4.97,-0.3711 7.38,-1.1094 2.39,-0.7382 4.57,-1.8398 6.52,-3.2968 1.95,-1.461 3.57,-3.25 4.89,-5.3711 1.31,-2.1289 2.14,-4.5703 2.48,-7.3399 l -5.04,0 c -0.45,2.0274 -1.17,3.8399 -2.17,5.4492"
|
|
||||||
id="path3130"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 2519.59,81.2695 0,-4.2929 -31.5,0 0,-19.1211 29.5,0 0,-4.3008 -29.5,0 0,-20.8945 31.86,0 0,-4.3047 -36.9,0 0,52.914 36.54,0"
|
|
||||||
id="path3132"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 109.109,130.879 c -2.171,6.246 -5.242,11.781 -9.2145,16.601 -3.9765,4.825 -8.8007,8.7 -14.4765,11.637 -5.6758,2.938 -12.1133,4.395 -19.2969,4.395 -7.375,0 -13.9023,-1.457 -19.5781,-4.395 -5.6797,-2.937 -10.5039,-6.812 -14.4766,-11.637 -3.9726,-4.82 -7.1406,-10.41 -9.5078,-16.75 -2.3633,-6.332 -3.9258,-12.816 -4.6758,-19.433 l 94.7732,0 c -0.179,6.812 -1.371,13.348 -3.547,19.582 z M 20.5703,76.2539 c 1.8008,-6.9141 4.6875,-13.1055 8.6563,-18.5937 3.9765,-5.4883 8.9922,-10.0235 15.0429,-13.6133 6.0547,-3.6016 13.336,-5.3985 21.8516,-5.3985 13.0586,0 23.2734,3.4102 30.6484,10.2188 7.3755,6.8008 12.4885,15.8867 15.3205,27.2344 l 17.883,0 C 126.184,59.457 119.234,46.5898 109.109,37.5195 98.9922,28.4258 84.6602,23.8906 66.1211,23.8906 c -11.5352,0 -21.5234,2.043 -29.9336,6.1133 C 27.7578,34.0664 20.9023,39.6445 15.6094,46.7422 10.3125,53.8281 6.39063,62.0586 3.83203,71.4336 1.28125,80.7891 0,90.6719 0,101.086 c 0,9.641 1.28125,19.098 3.83203,28.371 2.5586,9.27 6.48047,17.551 11.77737,24.828 5.2929,7.285 12.1484,13.153 20.5781,17.606 8.4102,4.437 18.3984,6.664 29.9336,6.664 11.7266,0 21.7539,-2.375 30.0859,-7.102 8.32,-4.719 15.078,-10.922 20.285,-18.578 5.196,-7.66 8.938,-16.461 11.203,-26.395 2.278,-9.937 3.219,-20 2.84,-30.2222 l -112.6522,0 c 0,-6.4336 0.8867,-13.1055 2.6875,-20.0039"
|
|
||||||
id="path3134"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 157.859,174.297 0,-25.258 0.571,0 c 3.41,8.895 9.457,16.039 18.164,21.426 8.695,5.398 18.254,8.09 28.66,8.09 10.219,0 18.777,-1.325 25.68,-3.977 6.91,-2.648 12.441,-6.379 16.601,-11.203 4.16,-4.824 7.098,-10.738 8.797,-17.734 1.699,-7.008 2.555,-14.864 2.555,-23.555 l 0,-94.2188 -17.875,0 0,91.3708 c 0,6.246 -0.567,12.059 -1.703,17.461 -1.141,5.387 -3.121,10.067 -5.957,14.047 -2.844,3.969 -6.676,7.09 -11.5,9.359 -4.821,2.274 -10.829,3.407 -18.02,3.407 -7.191,0 -13.578,-1.278 -19.152,-3.828 -5.578,-2.555 -10.309,-6.055 -14.192,-10.5 -3.879,-4.442 -6.91,-9.743 -9.082,-15.895 -2.172,-6.156 -3.355,-12.82 -3.547,-20.004 l 0,-85.4178 -17.871,0 0,146.4298 17.871,0"
|
|
||||||
id="path3136"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 274.938,174.297 45.976,-128.5509 0.566,0 45.407,128.5509 18.449,0 -54.77,-146.4298 -19.019,0 -56.465,146.4298 19.856,0"
|
|
||||||
id="path3138"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 409.707,174.297 0,-146.4298 -17.875,0 0,146.4298 17.875,0 z m 0,56.187 0,-28.664 -17.875,0 0,28.664 17.875,0"
|
|
||||||
id="path3140"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 487.02,104.863 c 3.683,0 7.222,0.27 10.632,0.809 3.407,0.547 6.403,1.601 8.993,3.172 2.589,1.566 4.668,3.785 6.238,6.648 1.558,2.852 2.347,6.613 2.347,11.235 0,4.636 -0.789,8.39 -2.347,11.25 -1.57,2.859 -3.649,5.074 -6.238,6.64 -2.59,1.571 -5.586,2.629 -8.993,3.172 -3.41,0.547 -6.949,0.813 -10.632,0.813 l -24.934,0 0,-43.739 24.934,0 z m 8.793,68.68 c 9.128,0 16.898,-1.32 23.304,-3.988 6.406,-2.66 11.613,-6.16 15.633,-10.524 4.023,-4.359 6.961,-9.34 8.797,-14.922 1.84,-5.589 2.758,-11.379 2.758,-17.382 0,-5.852 -0.918,-11.614 -2.758,-17.27 -1.836,-5.652 -4.774,-10.664 -8.797,-15.0273 -4.02,-4.3633 -9.227,-7.8672 -15.633,-10.5234 -6.406,-2.6602 -14.176,-3.9844 -23.304,-3.9844 l -33.727,0 0,-52.336 -32.102,0 0,145.9571 65.829,0"
|
|
||||||
id="path3142"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 614.273,76.7539 c -1.835,-0.6211 -3.812,-1.1289 -5.929,-1.5351 -2.114,-0.4102 -4.324,-0.75 -6.641,-1.0196 -2.316,-0.2773 -4.637,-0.6211 -6.953,-1.0273 -2.176,-0.4102 -4.328,-0.9571 -6.437,-1.6328 -2.114,-0.6836 -3.958,-1.6016 -5.52,-2.7618 -1.566,-1.1562 -2.832,-2.625 -3.777,-4.3945 -0.957,-1.7773 -1.438,-4.0234 -1.438,-6.7461 0,-2.5937 0.481,-4.7695 1.438,-6.5429 0.945,-1.7696 2.246,-3.168 3.879,-4.1915 1.636,-1.0234 3.543,-1.7382 5.726,-2.1484 2.176,-0.4101 4.426,-0.6094 6.746,-0.6094 5.723,0 10.145,0.9532 13.285,2.8672 3.133,1.9024 5.45,4.1875 6.953,6.8438 1.497,2.6562 2.418,5.3476 2.758,8.0742 0.336,2.7226 0.516,4.9101 0.516,6.543 l 0,10.8398 c -1.234,-1.1055 -2.766,-1.9492 -4.606,-2.5586 z m -57.339,40.9841 c 3,4.496 6.812,8.106 11.449,10.832 4.637,2.723 9.844,4.672 15.637,5.828 5.789,1.157 11.617,1.735 17.48,1.735 5.316,0 10.691,-0.375 16.145,-1.125 5.457,-0.75 10.425,-2.211 14.921,-4.395 4.504,-2.175 8.18,-5.207 11.043,-9.093 2.86,-3.883 4.297,-9.032 4.297,-15.434 l 0,-54.9922 c 0,-4.7774 0.27,-9.336 0.817,-13.6954 0.539,-4.3632 1.496,-7.6328 2.859,-9.8125 l -29.437,0 c -0.543,1.6329 -0.989,3.3008 -1.329,5.0118 -0.343,1.6992 -0.582,3.4414 -0.718,5.2109 -4.633,-4.7734 -10.082,-8.1133 -16.348,-10.0156 -6.273,-1.9063 -12.676,-2.8672 -19.219,-2.8672 -5.043,0 -9.746,0.6172 -14.105,1.8398 -4.367,1.2266 -8.18,3.1367 -11.449,5.7305 -3.27,2.5859 -5.825,5.8594 -7.668,9.8125 -1.84,3.9492 -2.758,8.6523 -2.758,14.1016 0,5.9961 1.058,10.9375 3.168,14.8242 2.109,3.8789 4.836,6.9726 8.179,9.2969 3.34,2.3164 7.157,4.0585 11.446,5.2148 4.297,1.1562 8.617,2.0742 12.98,2.7539 4.364,0.6836 8.656,1.2266 12.879,1.6367 4.227,0.4102 7.973,1.0235 11.25,1.8438 3.266,0.8125 5.856,2.0117 7.762,3.582 1.91,1.5664 2.793,3.8477 2.664,6.8435 0,3.137 -0.516,5.617 -1.535,7.461 -1.028,1.844 -2.395,3.266 -4.09,4.293 -1.707,1.02 -3.68,1.699 -5.93,2.039 -2.25,0.344 -4.672,0.516 -7.258,0.516 -5.718,0 -10.222,-1.223 -13.488,-3.68 -3.277,-2.453 -5.183,-6.543 -5.726,-12.265 l -29.032,0 c 0.41,6.816 2.118,12.46 5.114,16.968"
|
|
||||||
id="path3144"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 720.172,133.277 0,-19.422 -21.254,0 0,-52.3355 c 0,-4.9023 0.812,-8.1718 2.449,-9.8047 1.637,-1.6406 4.903,-2.4609 9.817,-2.4609 1.632,0 3.195,0.0703 4.699,0.2031 1.496,0.1328 2.926,0.3438 4.289,0.6172 l 0,-22.4883 c -2.453,-0.414 -5.176,-0.6836 -8.176,-0.8203 -2.996,-0.1289 -5.926,-0.1992 -8.789,-0.1992 -4.5,0 -8.754,0.3047 -12.773,0.918 -4.024,0.6094 -7.563,1.8086 -10.629,3.5781 -3.071,1.7695 -5.489,4.293 -7.254,7.5586 -1.781,3.2773 -2.664,7.5703 -2.664,12.8828 l 0,62.3511 -17.578,0 0,19.422 17.578,0 0,31.684 29.031,0 0,-31.684 21.254,0"
|
|
||||||
id="path3146"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 755.918,173.543 0,-54.984 0.613,0 c 3.684,6.125 8.387,10.586 14.11,13.379 5.722,2.796 11.312,4.195 16.761,4.195 7.77,0 14.137,-1.059 19.114,-3.164 4.972,-2.114 8.894,-5.043 11.754,-8.793 2.863,-3.75 4.871,-8.317 6.031,-13.699 1.152,-5.383 1.734,-11.3481 1.734,-17.8832 l 0,-65.0079 -29.027,0 0,59.6954 c 0,8.7148 -1.363,15.2227 -4.086,19.5157 -2.731,4.293 -7.567,6.433 -14.516,6.433 -7.902,0 -13.633,-2.343 -17.172,-7.039 -3.543,-4.703 -5.316,-12.441 -5.316,-23.2144 l 0,-55.3907 -29.023,0 0,145.9571 29.023,0"
|
|
||||||
id="path3148"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
|
|
||||||
d="m 863.082,0 21.875,0 0,190.547 -21.875,0 0,-190.547 z"
|
|
||||||
id="path3150"
|
|
||||||
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 41 KiB |
9
static/images/logo-mission.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
3
static/images/logo-name.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104 26" role="img" fill="currentColor">
|
||||||
|
<path id="ep-logo-name" d="M13.638 12.451a6.613 6.613 0 0 0-1.152-2.075 5.687 5.687 0 0 0-1.81-1.455c-.708-.369-1.513-.55-2.411-.55-.922 0-1.739.181-2.447.55a5.702 5.702 0 0 0-1.81 1.455 7.21 7.21 0 0 0-1.188 2.092 10.28 10.28 0 0 0-.586 2.43h11.848a8.013 8.013 0 0 0-.444-2.447zM2.571 19.277a6.885 6.885 0 0 0 1.082 2.324 6.187 6.187 0 0 0 1.88 1.702c.757.452 1.667.676 2.732.676 1.633 0 2.91-.427 3.83-1.277.923-.852 1.563-1.987 1.917-3.405h2.234c-.474 2.08-1.342 3.689-2.608 4.824-1.264 1.135-3.056 1.702-5.373 1.702-1.442 0-2.69-.254-3.742-.765-1.053-.507-1.91-1.203-2.572-2.092C1.29 22.082.8 21.052.48 19.88A13.991 13.991 0 0 1 0 16.174c0-1.206.16-2.388.479-3.547a9.56 9.56 0 0 1 1.472-3.103c.662-.91 1.519-1.643 2.572-2.2 1.051-.554 2.3-.833 3.742-.833 1.466 0 2.72.296 3.76.887A7.487 7.487 0 0 1 14.562 9.7c.65.959 1.118 2.058 1.401 3.3.284 1.243.402 2.5.354 3.777H2.234c0 .806.113 1.638.337 2.5M19.732 7.024v3.156h.07c.428-1.113 1.184-2.004 2.271-2.678a6.654 6.654 0 0 1 3.584-1.01c1.277 0 2.346.163 3.21.495.862.332 1.555.799 2.075 1.402.52.603.887 1.342 1.1 2.216.212.877.32 1.858.32 2.945v11.777h-2.235v-11.42c0-.782-.071-1.51-.214-2.184-.142-.673-.39-1.26-.745-1.757a3.61 3.61 0 0 0-1.436-1.17c-.603-.283-1.354-.425-2.253-.425-.898 0-1.697.16-2.395.479a5.221 5.221 0 0 0-1.772 1.31 6.034 6.034 0 0 0-1.136 1.988 8.128 8.128 0 0 0-.444 2.5v10.679h-2.234V7.024h2.234M34.367 7.024l5.747 16.067h.071L45.86 7.024h2.307l-6.846 18.303h-2.378L31.885 7.024h2.482M51.214 7.024v18.303h-2.235V7.024h2.235zm0-7.024V3.58h-2.235V0h2.235M61.037 15.703c.46 0 .902-.034 1.33-.103.424-.068.799-.2 1.122-.395.325-.195.584-.474.78-.833.196-.356.294-.825.294-1.404 0-.578-.098-1.047-.294-1.406a2.162 2.162 0 0 0-.78-.83 3.104 3.104 0 0 0-1.123-.395 8.39 8.39 0 0 0-1.329-.103H57.92v5.469h3.117zm1.099-8.587c1.14 0 2.112.166 2.913.498.801.335 1.452.772 1.953 1.316.503.545.871 1.167 1.1 1.866a6.89 6.89 0 0 1 .346 2.172c0 .733-.115 1.453-.346 2.159a5.043 5.043 0 0 1-1.1 1.88c-.501.544-1.152.984-1.953 1.316-.8.332-1.772.498-2.913.498H57.92v6.54h-4.013V7.116h8.229M76.944 19.216a5.26 5.26 0 0 1-.742.19c-.264.052-.54.096-.83.13-.29.034-.579.076-.87.127-.27.051-.54.12-.804.205-.263.085-.494.2-.69.344-.195.144-.354.33-.472.55-.12.222-.18.502-.18.844 0 .323.06.596.18.818.118.22.28.396.485.523.205.129.443.217.716.268.272.051.553.076.844.076.715 0 1.267-.117 1.66-.357.392-.239.68-.525.869-.857.187-.332.303-.668.344-1.008a6.93 6.93 0 0 0 .065-.818v-1.355a1.615 1.615 0 0 1-.575.32zm-7.168-5.124a4.345 4.345 0 0 1 1.43-1.353 6.257 6.257 0 0 1 1.956-.73 11.148 11.148 0 0 1 2.185-.215c.664 0 1.336.047 2.018.14a6.193 6.193 0 0 1 1.866.549 3.69 3.69 0 0 1 1.38 1.137c.356.486.537 1.128.537 1.93v6.874c0 .596.033 1.167.102 1.712.066.544.186.954.357 1.225h-3.68a5.23 5.23 0 0 1-.256-1.277 4.735 4.735 0 0 1-2.043 1.253 8.261 8.261 0 0 1-2.402.356c-.63 0-1.219-.076-1.763-.23a4.016 4.016 0 0 1-1.432-.715 3.34 3.34 0 0 1-.958-1.228c-.23-.493-.344-1.081-.344-1.762 0-.75.131-1.368.395-1.853a3.324 3.324 0 0 1 1.023-1.163 4.648 4.648 0 0 1 1.43-.651 15.515 15.515 0 0 1 1.623-.345 27.623 27.623 0 0 1 1.61-.202c.527-.052.996-.13 1.406-.232.408-.1.732-.252.97-.447.239-.195.349-.48.333-.857 0-.39-.065-.7-.192-.933a1.406 1.406 0 0 0-.511-.534 2.014 2.014 0 0 0-.741-.257 6.208 6.208 0 0 0-.907-.063c-.715 0-1.278.151-1.686.459-.41.308-.648.818-.716 1.533h-3.63c.052-.852.265-1.557.64-2.121M90.181 12.15v2.427h-2.657v6.543c0 .613.101 1.02.306 1.226.205.205.613.308 1.227.308.204 0 .399-.01.587-.027.188-.015.366-.042.537-.076v2.81a8.619 8.619 0 0 1-1.023.103c-.373.017-.74.024-1.098.024a10.41 10.41 0 0 1-1.597-.115 3.728 3.728 0 0 1-1.328-.446 2.356 2.356 0 0 1-.907-.945c-.222-.41-.333-.945-.333-1.61v-7.795h-2.198v-2.426h2.198V8.19h3.629v3.96h2.657M94.703 7.116v6.873h.075c.462-.764 1.05-1.323 1.764-1.672.716-.35 1.415-.523 2.096-.523.97 0 1.767.132 2.388.396.622.263 1.113.63 1.47 1.098.358.47.609 1.04.754 1.712.144.674.217 1.418.217 2.236v8.125h-3.629v-7.46c0-1.09-.17-1.905-.511-2.44-.34-.537-.945-.805-1.814-.805-.988 0-1.704.293-2.146.88-.443.587-.664 1.556-.664 2.901v6.924h-3.628V7.116h3.628" class="logo-main"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
3
static/images/logo-square.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg fill="currentColor" id="ep-logo-square" role="img" viewBox="0 0 65 65" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M26.538 25.283c-.532-1.519-1.274-2.861-2.24-4.037a11.163 11.163 0 0 0-3.522-2.828c-1.381-.712-2.948-1.07-4.697-1.07-1.791 0-3.379.358-4.76 1.07a11.11 11.11 0 0 0-3.522 2.828c-.966 1.176-1.737 2.533-2.308 4.07a19.839 19.839 0 0 0-1.139 4.728h23.047a15.559 15.559 0 0 0-.86-4.76zM5 38.57c.44 1.685 1.143 3.189 2.11 4.527.966 1.333 2.187 2.436 3.656 3.31 1.475.875 3.243 1.308 5.313 1.308 3.178 0 5.665-.83 7.456-2.485 1.793-1.65 3.037-3.867 3.726-6.626h4.35c-.922 4.054-2.613 7.179-5.073 9.39-2.46 2.208-5.947 3.315-10.46 3.315-2.802 0-5.233-.502-7.274-1.489-2.056-.986-3.721-2.348-5.01-4.072-1.284-1.724-2.241-3.725-2.861-6.005C.313 37.465 0 35.058 0 32.529c0-2.343.313-4.649.933-6.9.62-2.254 1.577-4.267 2.861-6.04 1.289-1.772 2.954-3.197 5.01-4.281 2.041-1.08 4.472-1.616 7.275-1.616 2.856 0 5.293.571 7.32 1.724 2.026 1.147 3.666 2.66 4.931 4.521 1.265 1.86 2.177 3.999 2.725 6.416a27.9 27.9 0 0 1 .692 7.348H4.35c0 1.567.215 3.188.65 4.868M49.301 31.456c.914 0 1.793-.07 2.638-.202.845-.136 1.586-.4 2.23-.78.641-.391 1.159-.942 1.548-1.656.387-.702.582-1.64.582-2.782 0-1.148-.195-2.08-.582-2.79-.39-.707-.907-1.26-1.547-1.65-.645-.386-1.386-.65-2.231-.78a16.554 16.554 0 0 0-2.638-.206h-6.176v10.846h6.176zm2.184-17.032c2.26 0 4.189.331 5.776.995 1.592.66 2.88 1.524 3.877 2.608a10.007 10.007 0 0 1 2.177 3.696c.46 1.393.684 2.828.684 4.313 0 1.455-.224 2.88-.684 4.28a9.955 9.955 0 0 1-2.177 3.727c-.997 1.079-2.285 1.953-3.877 2.607-1.587.664-3.516.992-5.776.992h-8.36v12.974h-7.964V14.424h16.324" class="logo-main"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
static/images/uoa-logo-small.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 91 KiB |
170
static/js/discourse-api.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/**
|
||||||
|
* Discourse API Integration for enviPath Community
|
||||||
|
* Handles fetching topics from the Discourse forum API
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DiscourseAPI {
|
||||||
|
constructor() {
|
||||||
|
this.baseUrl = 'https://community.envipath.org';
|
||||||
|
this.categoryId = 10; // Announcements category
|
||||||
|
this.limit = 3; // Number of topics to fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch topics from Discourse API
|
||||||
|
* @param {number} limit - Number of topics to fetch
|
||||||
|
* @returns {Promise<Array>} Array of topic objects
|
||||||
|
*/
|
||||||
|
async fetchTopics(limit = this.limit) {
|
||||||
|
try {
|
||||||
|
const url = `${this.baseUrl}/c/announcements/${this.categoryId}.json`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return this.processTopics(data.topic_list.topics, limit);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching Discourse topics:', error);
|
||||||
|
return this.getFallbackTopics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process raw Discourse topics into standardized format
|
||||||
|
* @param {Array} topics - Raw topics from Discourse API
|
||||||
|
* @param {number} limit - Number of topics to return
|
||||||
|
* @returns {Array} Processed topics
|
||||||
|
*/
|
||||||
|
processTopics(topics, limit) {
|
||||||
|
return topics
|
||||||
|
.slice(0, limit)
|
||||||
|
.map(topic => ({
|
||||||
|
id: topic.id,
|
||||||
|
title: topic.title,
|
||||||
|
excerpt: this.extractExcerpt(topic.excerpt),
|
||||||
|
url: `${this.baseUrl}/t/${topic.slug}/${topic.id}`,
|
||||||
|
replies: topic.reply_count,
|
||||||
|
views: topic.views,
|
||||||
|
created_at: topic.created_at,
|
||||||
|
category: 'Announcements',
|
||||||
|
category_id: this.categoryId,
|
||||||
|
author: topic.last_poster_username,
|
||||||
|
author_avatar: this.getAvatarUrl(topic.last_poster_username)
|
||||||
|
}))
|
||||||
|
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // Latest first
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract excerpt from topic content
|
||||||
|
* @param {string} excerpt - Raw excerpt from Discourse
|
||||||
|
* @returns {string} Cleaned excerpt
|
||||||
|
*/
|
||||||
|
extractExcerpt(excerpt) {
|
||||||
|
if (!excerpt) return 'Click to read more';
|
||||||
|
|
||||||
|
// Remove HTML tags and clean up; collapse whitespace; do not add manual ellipsis
|
||||||
|
return excerpt
|
||||||
|
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
||||||
|
.replace(/ /g, ' ') // Replace with spaces
|
||||||
|
.replace(/&/g, '&') // Replace & with &
|
||||||
|
.replace(/</g, '<') // Replace < with <
|
||||||
|
.replace(/>/g, '>') // Replace > with >
|
||||||
|
.replace(/\s+/g, ' ') // Collapse all whitespace/newlines
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get avatar URL for user
|
||||||
|
* @param {string} username - Username
|
||||||
|
* @returns {string} Avatar URL
|
||||||
|
*/
|
||||||
|
getAvatarUrl(username) {
|
||||||
|
if (!username) return `${this.baseUrl}/letter_avatar_proxy/v4/letter/u/1.png`;
|
||||||
|
return `${this.baseUrl}/user_avatar/${this.baseUrl.replace('https://', '')}/${username}/40/1_1.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fallback topics when API fails
|
||||||
|
* @returns {Array} Fallback topics
|
||||||
|
*/
|
||||||
|
getFallbackTopics() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 110,
|
||||||
|
title: "enviPath Beta Update: Major Improvements to Prediction, Analysis & Collaboration!",
|
||||||
|
excerpt: "We're excited to announce major updates to the enviPath beta platform! This release includes significant improvements to our prediction algorithms, enhanced analysis tools, and new collaboration features that will make environmental biotransformation research more accessible and efficient.",
|
||||||
|
url: "https://community.envipath.org/t/envipath-beta-update-major-improvements-to-prediction-analysis-collaboration/110",
|
||||||
|
replies: 0,
|
||||||
|
views: 16,
|
||||||
|
created_at: "2025-09-23T00:00:00Z",
|
||||||
|
category: "Announcements",
|
||||||
|
category_id: 10,
|
||||||
|
author: "wicker",
|
||||||
|
author_avatar: "https://community.envipath.org/user_avatar/community.envipath.org/wicker/40/1_1.png"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date for display
|
||||||
|
* @param {string} dateString - ISO date string
|
||||||
|
* @returns {string} Formatted date
|
||||||
|
*/
|
||||||
|
formatDate(dateString) {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load topics and call render function
|
||||||
|
* @param {string} containerId - ID of container element
|
||||||
|
* @param {Function} renderCallback - Function to render topics
|
||||||
|
*/
|
||||||
|
async loadTopics(containerId = 'community-news-container', renderCallback = null) {
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
if (!container) {
|
||||||
|
console.error(`Container with ID '${containerId}' not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide loading spinner
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
if (loading) {
|
||||||
|
loading.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const topics = await this.fetchTopics();
|
||||||
|
|
||||||
|
if (renderCallback && typeof renderCallback === 'function') {
|
||||||
|
renderCallback(topics);
|
||||||
|
} else {
|
||||||
|
// Default rendering - just log topics
|
||||||
|
console.log('Topics loaded:', topics);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading topics:', error);
|
||||||
|
container.innerHTML = '<p class="text-neutral">No updates found. Head over to the <a href="https://community.envipath.org" target="_blank" class="link link-primary">community</a> to see the latest discussions.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in other scripts
|
||||||
|
window.DiscourseAPI = DiscourseAPI;
|
||||||
|
|
||||||
|
// Auto-initialize if container exists
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
if (document.getElementById('community-news-container')) {
|
||||||
|
const discourseAPI = new DiscourseAPI();
|
||||||
|
discourseAPI.loadTopics('community-news-container', function(topics) {
|
||||||
|
// This will be handled by the template's render function
|
||||||
|
if (window.renderDiscourseTopics) {
|
||||||
|
window.renderDiscourseTopics(topics);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#predict_modal">
|
<a href="{{ meta.server_url }}/predict">
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
|
<span class="glyphicon glyphicon-plus"></span> New Pathway</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -22,6 +22,10 @@
|
|||||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a>
|
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
|
<li>
|
||||||
|
<a class="button" data-toggle="modal" data-target="#identify_missing_rules_modal">
|
||||||
|
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing Rules</a>
|
||||||
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
|
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
|
||||||
|
|||||||
70
templates/collections/joblog.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{% extends "framework.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="panel-group" id="reviewListAccordion">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
|
Jobs
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>
|
||||||
|
Job Logs Desc
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="job-accordion-link" data-toggle="collapse" data-parent="#job-accordion" href="#jobs">
|
||||||
|
Jobs
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="jobs"
|
||||||
|
class="panel-collapse collapse in">
|
||||||
|
<div class="panel-body list-group-item" id="job-content">
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
<tr style="background-color: rgba(0, 0, 0, 0.08);">
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">Queued</th>
|
||||||
|
<th scope="col">Done</th>
|
||||||
|
<th scope="col">Result</th>
|
||||||
|
</tr>
|
||||||
|
<tbody>
|
||||||
|
{% for job in jobs %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ job.task_id }}</td>
|
||||||
|
<td>{{ job.job_name }}</td>
|
||||||
|
<td>{{ job.status }}</td>
|
||||||
|
<td>{{ job.created }}</td>
|
||||||
|
<td>{{ job.done_at }}</td>
|
||||||
|
{% if job.task_result and job.task_result|is_url == True %}
|
||||||
|
<td><a href="{{ job.task_result }}">Result</a></td>
|
||||||
|
{% elif job.task_result %}
|
||||||
|
<td>{{ job.task_result|slice:"40" }}...</td>
|
||||||
|
{% else %}
|
||||||
|
<td>Empty</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||||
|
<ul class='list-group'>
|
||||||
|
{% for obj in objects %}
|
||||||
|
{% if object_type == 'user' %}
|
||||||
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
@ -192,7 +192,7 @@
|
|||||||
<div class="panel-body list-group-item" id="ReviewedContent">
|
<div class="panel-body list-group-item" id="ReviewedContent">
|
||||||
{% if object_type == 'package' %}
|
{% if object_type == 'package' %}
|
||||||
{% for obj in reviewed_objects %}
|
{% for obj in reviewed_objects %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}
|
||||||
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
||||||
style="float:right" data-toggle="tooltip"
|
style="float:right" data-toggle="tooltip"
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
data-placement="top" title="" data-original-title="Reviewed">
|
||||||
@ -201,7 +201,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for obj in reviewed_objects|slice:":50" %}
|
{% for obj in reviewed_objects|slice:":50" %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}{# <i>({{ obj.package.name }})</i> #}
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}{# <i>({{ obj.package.name }})</i> #}
|
||||||
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
<span class="glyphicon glyphicon-star" aria-hidden="true"
|
||||||
style="float:right" data-toggle="tooltip"
|
style="float:right" data-toggle="tooltip"
|
||||||
data-placement="top" title="" data-original-title="Reviewed">
|
data-placement="top" title="" data-original-title="Reviewed">
|
||||||
@ -221,11 +221,11 @@
|
|||||||
<div class="panel-body list-group-item" id="UnreviewedContent">
|
<div class="panel-body list-group-item" id="UnreviewedContent">
|
||||||
{% if object_type == 'package' %}
|
{% if object_type == 'package' %}
|
||||||
{% for obj in unreviewed_objects %}
|
{% for obj in unreviewed_objects %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for obj in unreviewed_objects|slice:":50" %}
|
{% for obj in unreviewed_objects|slice:":50" %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -236,9 +236,9 @@
|
|||||||
<ul class='list-group'>
|
<ul class='list-group'>
|
||||||
{% for obj in objects %}
|
{% for obj in objects %}
|
||||||
{% if object_type == 'user' %}
|
{% if object_type == 'user' %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username|safe }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
|
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -1,46 +1,68 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html data-theme="envipath">
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<head>
|
<head>
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token }}" />
|
||||||
|
{# Favicon #}
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
type="image/png"
|
||||||
|
href="{% static 'images/favicon.ico' %}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{# Tailwind CSS disabled for legacy Bootstrap framework #}
|
||||||
|
{# Pages using this framework will be migrated to framework_modern.html incrementally #}
|
||||||
|
{# <link href="{% static 'css/output.css' %}" rel="stylesheet" type="text/css"/> #}
|
||||||
|
|
||||||
|
{# Legacy Bootstrap 3.3.7 - scoped to .legacy-bootstrap #}
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
|
||||||
|
/>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||||
|
<script src="https://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
|
||||||
|
<link
|
||||||
|
href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css"
|
||||||
|
/>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/js/bootstrap-select.min.js"></script>
|
||||||
|
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
|
||||||
|
<!-- CDN END -->
|
||||||
|
|
||||||
|
{# Bootstrap compatibility styles #}
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
/* Ensure proper viewport behavior */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
height: 100%; /* ensure body fills viewport */
|
height: 100%; /* ensure body fills viewport */
|
||||||
overflow-x: hidden; /* prevent horizontal scroll */
|
overflow-x: hidden; /* prevent horizontal scroll */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{# TODO use bundles from bootstrap 3.3.7 #}
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
|
||||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
|
|
||||||
<link href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet"
|
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/css/bootstrap-select.min.css">
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.7.3/js/bootstrap-select.min.js"></script>
|
|
||||||
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
|
|
||||||
<!-- CDN END -->
|
|
||||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
|
||||||
<script>
|
<script>
|
||||||
const csrftoken = document.querySelector('[name=csrf-token]').content;
|
const csrftoken = document.querySelector("[name=csrf-token]").content;
|
||||||
|
|
||||||
// Setup CSRF header for all jQuery AJAX requests
|
// Setup CSRF header for all jQuery AJAX requests
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
beforeSend: function(xhr, settings) {
|
beforeSend: function (xhr, settings) {
|
||||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
|
||||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{# Favicon #}
|
|
||||||
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
|
|
||||||
<!-- {# C3 CSS #}-->
|
<!-- {# C3 CSS #}-->
|
||||||
<!-- <link id="css-c3" href="{% static 'css/c3.css' %}" rel="stylesheet" type="text/css"/>-->
|
<!-- <link id="css-c3" href="{% static 'css/c3.css' %}" rel="stylesheet" type="text/css"/>-->
|
||||||
<!-- {# EP CSS #}-->
|
<!-- {# EP CSS #}-->
|
||||||
<!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>-->
|
<!-- <link id="css-pps_white_general" href="{% static 'css/epp.css' %}" rel="stylesheet" type="text/css"/>-->
|
||||||
|
|
||||||
|
|
||||||
{# General EP JS #}
|
{# General EP JS #}
|
||||||
<script src="{% static 'js/pps.js' %}"></script>
|
<script src="{% static 'js/pps.js' %}"></script>
|
||||||
{# Modal Steps for Stepwise Modal Wizards #}
|
{# Modal Steps for Stepwise Modal Wizards #}
|
||||||
@ -49,26 +71,33 @@
|
|||||||
{% if not debug %}
|
{% if not debug %}
|
||||||
<!-- Matomo -->
|
<!-- Matomo -->
|
||||||
<script>
|
<script>
|
||||||
var _paq = window._paq = window._paq || [];
|
var _paq = (window._paq = window._paq || []);
|
||||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
_paq.push(['trackPageView']);
|
_paq.push(["trackPageView"]);
|
||||||
_paq.push(['enableLinkTracking']);
|
_paq.push(["enableLinkTracking"]);
|
||||||
(function () {
|
(function () {
|
||||||
var u = "//matomo.envipath.com/";
|
var u = "//matomo.envipath.com/";
|
||||||
_paq.push(['setTrackerUrl', u + 'matomo.php']);
|
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||||
_paq.push(['setSiteId', '10']);
|
_paq.push(["setSiteId", "{{ meta.site_id }}"]);
|
||||||
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
var d = document,
|
||||||
|
g = d.createElement("script"),
|
||||||
|
s = d.getElementsByTagName("script")[0];
|
||||||
g.async = true;
|
g.async = true;
|
||||||
g.src = u + 'matomo.js';
|
g.src = u + "matomo.js";
|
||||||
s.parentNode.insertBefore(g, s);
|
s.parentNode.insertBefore(g, s);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<!-- End Matomo Code -->
|
<!-- End Matomo Code -->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</head>
|
||||||
</head>
|
<body>
|
||||||
<body>
|
<!-- Legacy Bootstrap navbar - isolated from Tailwind -->
|
||||||
<nav class="navbar navbar-default navbar-inverse" style="border-radius:0px;" role="navigation">
|
<div class="legacy-bootstrap">
|
||||||
|
<nav
|
||||||
|
class="navbar navbar-default navbar-inverse"
|
||||||
|
style="border-radius:0px;"
|
||||||
|
role="navigation"
|
||||||
|
>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!-- Brand and toggle get grouped for better mobile display -->
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
<div class="navbar-header navbar-header-framework">
|
<div class="navbar-header navbar-header-framework">
|
||||||
@ -79,96 +108,186 @@
|
|||||||
<!-- <span class="icon-bar"></span>-->
|
<!-- <span class="icon-bar"></span>-->
|
||||||
<!-- <span class="icon-bar"></span>-->
|
<!-- <span class="icon-bar"></span>-->
|
||||||
<!-- </button>-->
|
<!-- </button>-->
|
||||||
<a id="pictureLink" href="{{ meta.server_url }}" class="navbar-brand">
|
<a
|
||||||
<img id="image-logo-short-white.svg" src='{% static "/images/logo-short-white.svg" %}' width="100"
|
id="pictureLink"
|
||||||
alt="enviPath">
|
href="{{ meta.server_url }}"
|
||||||
|
class="navbar-brand"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
id="image-logo-short-white.svg"
|
||||||
|
src="{% static "/images/logo-short-white.svg" %}"
|
||||||
|
width="100"
|
||||||
|
alt="enviPath"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
|
<div
|
||||||
|
class="collapse navbar-collapse collapse-framework navbar-collapse-framework"
|
||||||
|
id="navbarCollapse"
|
||||||
|
>
|
||||||
<ul class="nav navbar-nav navbar-nav-framework">
|
<ul class="nav navbar-nav navbar-nav-framework">
|
||||||
<li>
|
<li>
|
||||||
<a href="#" data-toggle="modal" data-target="#predict_modal">
|
<a href="{{ meta.server_url }}/predict"> Predict Pathway </a>
|
||||||
Predict Pathway
|
</li>
|
||||||
</a>
|
{# <li class="dropdown">#}
|
||||||
|
{# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
|
||||||
|
{# <ul role="menu" class="dropdown-menu">#}
|
||||||
|
{# <li>#}
|
||||||
|
{# <a class="button" data-toggle="modal" data-target="#predict_modal">#}
|
||||||
|
{# <i class=" glyphicon glyphicon-tag"></i> Predict Pathway#}
|
||||||
|
{# </a>#}
|
||||||
|
{# </li>#}
|
||||||
|
{# <li>#}
|
||||||
|
{# <a class="button" data-toggle="modal" data-target="#batch_predict_modal">#}
|
||||||
|
{# <i class=" glyphicon glyphicon-tags"></i> Batch Prediction#}
|
||||||
|
{# </a>#}
|
||||||
|
{# </li>#}
|
||||||
|
{# </ul>#}
|
||||||
|
{# </li>#}
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/package" id="packageLink"
|
||||||
|
>Package</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/search" id="searchLink"
|
||||||
|
>Search</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/model" id="modelLink"
|
||||||
|
>Modelling</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{# <li class="dropdown">#}
|
|
||||||
{# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
|
|
||||||
{# <ul role="menu" class="dropdown-menu">#}
|
|
||||||
{# <li>#}
|
|
||||||
{# <a class="button" data-toggle="modal" data-target="#predict_modal">#}
|
|
||||||
{# <i class=" glyphicon glyphicon-tag"></i> Predict Pathway#}
|
|
||||||
{# </a>#}
|
|
||||||
{# </li>#}
|
|
||||||
{# <li>#}
|
|
||||||
{# <a class="button" data-toggle="modal" data-target="#batch_predict_modal">#}
|
|
||||||
{# <i class=" glyphicon glyphicon-tags"></i> Batch Prediction#}
|
|
||||||
{# </a>#}
|
|
||||||
{# </li>#}
|
|
||||||
{# </ul>#}
|
|
||||||
{# </li>#}
|
|
||||||
<li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li>
|
|
||||||
<li><a href="{{ meta.server_url }}/search" id="searchLink">Search</a></li>
|
|
||||||
<li><a href="{{ meta.server_url }}/model" id="modelLink">Modelling</a></li>
|
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Browse Data<b class="caret"></b></a>
|
<a data-toggle="dropdown" class="dropdown-toggle" href="#"
|
||||||
|
>Browse Data<b class="caret"></b
|
||||||
|
></a>
|
||||||
<ul role="menu" class="dropdown-menu">
|
<ul role="menu" class="dropdown-menu">
|
||||||
<li><a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a></li>
|
<li>
|
||||||
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li>
|
<a href="{{ meta.server_url }}/pathway" id="pathwayLink"
|
||||||
<li><a href="{{ meta.server_url }}/compound" id="compoundLink">Compound</a></li>
|
>Pathway</a
|
||||||
<li><a href="{{ meta.server_url }}/reaction" id="reactionLink">Reaction</a></li>
|
>
|
||||||
<li><a href="{{ meta.server_url }}/model" id="relative-reasoningLink">Model</a></li>
|
</li>
|
||||||
<li><a href="{{ meta.server_url }}/scenario" id="scenarioLink">Scenario</a></li>
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/compound" id="compoundLink"
|
||||||
|
>Compound</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/reaction" id="reactionLink"
|
||||||
|
>Reaction</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="{{ meta.server_url }}/model"
|
||||||
|
id="relative-reasoningLink"
|
||||||
|
>Model</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/scenario" id="scenarioLink"
|
||||||
|
>Scenario</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
{# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#}
|
{# <li><a href="{{ meta.server_url }}/setting" id="settingLink">Setting</a></li>#}
|
||||||
{# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#}
|
{# <li><a href="{{ meta.server_url }}/user" id="userLink">User</a></li>#}
|
||||||
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
|
{# <li><a href="{{ meta.server_url }}/group" id="groupLink">Group</a></li>#}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework">
|
<ul
|
||||||
<li><a href="https://community.envipath.org/" id="communityLink">Community</a></li>
|
class="nav navbar-nav navbar-right navbar-nav-framework navbar-right-framework"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a href="https://community.envipath.org/" id="communityLink"
|
||||||
|
>Community</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Info <b class="caret"></b></a>
|
<a data-toggle="dropdown" class="dropdown-toggle" href="#"
|
||||||
|
>Info <b class="caret"></b
|
||||||
|
></a>
|
||||||
<ul role="menu" class="dropdown-menu">
|
<ul role="menu" class="dropdown-menu">
|
||||||
<!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>-->
|
<!--<li><a href="{{ meta.server_url }}/funding" id="fundingLink">Funding</a></li>-->
|
||||||
<li><a href="https://community.envipath.org/t/envipath-license/109" id="licenceLink">Licences</a></li>
|
<li>
|
||||||
<li class="divider"></li>
|
<a
|
||||||
<li><a target="_blank" href="https://wiki.envipath.org/" id="wikiLink">Documentation Wiki</a>
|
href="https://community.envipath.org/t/envipath-license/109"
|
||||||
|
id="licenceLink"
|
||||||
|
>Licences</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/"
|
||||||
|
id="wikiLink"
|
||||||
|
>Documentation Wiki</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
id="citeButton"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#citemodal"
|
||||||
|
>How to cite enviPath</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#" id="citeButton" data-toggle="modal" data-target="#citemodal">How to cite
|
|
||||||
enviPath</a></li>
|
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li><a>Version: {{ meta.version }}</a></li>
|
<li><a>Version: {{ meta.version }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.user.username == 'anonymous' %}
|
{% if meta.user.username == 'anonymous' %}
|
||||||
<li>
|
<li>
|
||||||
<a href="#signup" id="loginButton" data-toggle="modal" data-target="#signupmodal"
|
<a
|
||||||
style="margin-right:10px">Login</a>
|
href="{% url 'login' %}"
|
||||||
|
id="loginButton"
|
||||||
|
style="margin-right:10px"
|
||||||
|
>Login</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown" id="loggedInButton" class="dropdown-toggle" id="logedInButton"
|
<a
|
||||||
href="#">
|
data-toggle="dropdown"
|
||||||
|
id="loggedInButton"
|
||||||
|
class="dropdown-toggle"
|
||||||
|
id="logedInButton"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
<div id="username">
|
<div id="username">
|
||||||
{{ user.username }}<b class="caret"></b>
|
{{ user.username }}<b class="caret"></b>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<ul role="menu" class="dropdown-menu">
|
<ul role="menu" class="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ meta.user.url }}" id="accountbutton">My Account</a>
|
<a href="{{ meta.user.url }}" id="accountbutton"
|
||||||
|
>My Account</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<form class="navbar-form navbar-left navbar-left-framework" role="logout"
|
<form
|
||||||
action="{% url 'logout' %}" method="post">
|
class="navbar-form navbar-left navbar-left-framework"
|
||||||
|
role="logout"
|
||||||
|
action="{% url 'logout' %}"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="hidden" name="logout" value="true">
|
<input type="hidden" name="logout" value="true" />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-default">Logout</button>
|
<button type="submit" class="btn btn-default">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -176,8 +295,11 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="docContent" class="content container">
|
</div>
|
||||||
|
<!-- End legacy Bootstrap navbar -->
|
||||||
|
|
||||||
|
<div id="docContent" class="content container">
|
||||||
{% if breadcrumbs %}
|
{% if breadcrumbs %}
|
||||||
<div id="bread">
|
<div id="bread">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
@ -196,81 +318,105 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if message %}
|
{% if message %}
|
||||||
<div id="message">
|
<div id="message">{{ message }}</div>
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% if meta.url_contains_package and meta.current_package.license %}
|
{% if meta.url_contains_package and meta.current_package.license %}
|
||||||
<p></p>
|
<p></p>
|
||||||
<div class="panel-group" id="license_accordion">
|
<div class="panel-group" id="license_accordion">
|
||||||
<div class="panel panel-default list-group-item" style="background-color:#f5f5f5">
|
<div
|
||||||
|
class="panel panel-default list-group-item"
|
||||||
|
style="background-color:#f5f5f5"
|
||||||
|
>
|
||||||
<div class="panel-title">
|
<div class="panel-title">
|
||||||
<a data-toggle="collapse" data-parent="#licence_accordion" href="#license">License</a>
|
<a
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#licence_accordion"
|
||||||
|
href="#license"
|
||||||
|
>License</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="license" class="panel-collapse collapse in">
|
<div id="license" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<a target="_blank" href="{{ meta.current_package.license.link }}">
|
<a target="_blank" href="{{ meta.current_package.license.link }}">
|
||||||
<img src="{{ meta.current_package.license.image_link }}">
|
<img src="{{ meta.current_package.license.image_link }}" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FOOTER -->
|
<!-- FOOTER - Legacy Bootstrap -->
|
||||||
<div class="container text-center">
|
<div class="legacy-bootstrap">
|
||||||
<hr/>
|
<div class="container text-center">
|
||||||
|
<hr />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<ul class="nav nav-pills nav-justified">
|
<ul class="nav nav-pills nav-justified">
|
||||||
<li>
|
<li>
|
||||||
<a href="http://ml.auckland.ac.nz" target="_blank">
|
<a href="http://ml.auckland.ac.nz" target="_blank">
|
||||||
<img id="image-uoalogo" height="60" src='{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}'
|
<img
|
||||||
alt="The Univserity of Auckland"/>
|
id="image-uoalogo"
|
||||||
|
height="60"
|
||||||
|
src="{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}"
|
||||||
|
alt="The Univserity of Auckland"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://eawag.ch" target="_blank">
|
<a href="https://eawag.ch" target="_blank">
|
||||||
<img id="image-ealogo" height="60" src='{% static "/images/ealogo.gif" %}' alt="Eawag"/>
|
<img
|
||||||
|
id="image-ealogo"
|
||||||
|
height="60"
|
||||||
|
src="{% static "/images/ealogo.gif" %}"
|
||||||
|
alt="Eawag"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://www.uzh.ch/" target="_blank">
|
<a href="https://www.uzh.ch/" target="_blank">
|
||||||
<img id="image-ufzlogo" height="60" src='{% static "/images/uzh-logo.svg" %}'
|
<img
|
||||||
alt="University of Zurich"/>
|
id="image-ufzlogo"
|
||||||
|
height="60"
|
||||||
|
src="{% static "/images/uzh-logo.svg" %}"
|
||||||
|
alt="University of Zurich"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<ul class="nav nav-pills nav-justified">
|
<ul class="nav nav-pills nav-justified">
|
||||||
<!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>-->
|
<!-- <li><a href="https://envipath.com/imprint/" target="_blank">Impressum/Imprint</a></li>-->
|
||||||
<li><a href="mailto:admin@envipath.org" target="_blank">Contact</a></li>
|
<li>
|
||||||
|
<a href="mailto:admin@envipath.org" target="_blank">Contact</a>
|
||||||
|
</li>
|
||||||
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschränkt) & Co. KG ©-->
|
<!-- <li><a href="http://envipath.com" target="_blank"> enviPath UG (haftungsbeschränkt) & Co. KG ©-->
|
||||||
<!-- {{ YEAR }}</a></li>-->
|
<!-- {{ YEAR }}</a></li>-->
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
</div>
|
||||||
|
<!-- End legacy Bootstrap footer -->
|
||||||
|
|
||||||
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
// Hide actionsbutton if theres no action defined
|
// Hide actionsbutton if theres no action defined
|
||||||
if ($('#actionsButton ul').children().length > 0) {
|
if ($("#actionsButton ul").children().length > 0) {
|
||||||
$('#actionsButton').show();
|
$("#actionsButton").show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% block modals %}
|
{% block modals %}
|
||||||
{% include "modals/cite_modal.html" %}
|
{% include "modals/cite_modal.html" %}
|
||||||
{% include "modals/signup_modal.html" %}
|
|
||||||
{% include "modals/predict_modal.html" %}
|
{% include "modals/predict_modal.html" %}
|
||||||
{% include "modals/batch_predict_modal.html" %}
|
{% include "modals/batch_predict_modal.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
206
templates/framework_modern.html
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html data-theme="envipath" lang="en">
|
||||||
|
{% load static %}
|
||||||
|
<head>
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token }}" />
|
||||||
|
|
||||||
|
{# Favicon #}
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
type="image/png"
|
||||||
|
href="{% static 'images/favicon.ico' %}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{# Tailwind CSS + DaisyUI Output #}
|
||||||
|
<link
|
||||||
|
href="{% static 'css/output.css' %}"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{# jQuery - Keep for compatibility with existing JS #}
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
|
|
||||||
|
{# Font Awesome #}
|
||||||
|
<link
|
||||||
|
href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{# Discourse embed for community #}
|
||||||
|
<script src="https://community.envipath.org/javascripts/embed-topics.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const csrftoken = document.querySelector("[name=csrf-token]").content;
|
||||||
|
|
||||||
|
// Setup CSRF header for all jQuery AJAX requests
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function (xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{# General EP JS #}
|
||||||
|
<script src="{% static 'js/pps.js' %}"></script>
|
||||||
|
{# Modal Steps for Stepwise Modal Wizards #}
|
||||||
|
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
|
||||||
|
|
||||||
|
{% if not debug %}
|
||||||
|
<!-- Matomo -->
|
||||||
|
<script>
|
||||||
|
var _paq = (window._paq = window._paq || []);
|
||||||
|
_paq.push(["trackPageView"]);
|
||||||
|
_paq.push(["enableLinkTracking"]);
|
||||||
|
(function () {
|
||||||
|
var u = "//matomo.envipath.com/";
|
||||||
|
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||||
|
_paq.push(["setSiteId", "{{ meta.site_id }}"]);
|
||||||
|
var d = document,
|
||||||
|
g = d.createElement("script"),
|
||||||
|
s = d.getElementsByTagName("script")[0];
|
||||||
|
g.async = true;
|
||||||
|
g.src = u + "matomo.js";
|
||||||
|
s.parentNode.insertBefore(g, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<!-- End Matomo Code -->
|
||||||
|
{% endif %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-base-300 min-h-screen">
|
||||||
|
{% include "includes/navbar.html" %}
|
||||||
|
|
||||||
|
{# Main Content Area #}
|
||||||
|
<main class="w-full">
|
||||||
|
{% block main_content %}
|
||||||
|
{# Breadcrumbs - outside main content, optional #}
|
||||||
|
{% if breadcrumbs %}
|
||||||
|
<div id="bread" class="max-w-7xl mx-auto px-4 py-4">
|
||||||
|
<div class="breadcrumbs text-sm">
|
||||||
|
<ul>
|
||||||
|
{% for elem in breadcrumbs %}
|
||||||
|
{% for name, url in elem.items %}
|
||||||
|
{% if forloop.parentloop.last %}
|
||||||
|
<li>{{ name }}</li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{ url }}">{{ name }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Main content container - paper effect on medium+ screens #}
|
||||||
|
<div
|
||||||
|
id="docContent"
|
||||||
|
class="bg-base-100 mx-auto md:my-8 md:max-w-6xl md:rounded-lg md:shadow-xl"
|
||||||
|
>
|
||||||
|
{# Messages - inside paper #}
|
||||||
|
{% if message %}
|
||||||
|
<div id="message" class="alert alert-info m-4">{{ message }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Page content - no enforced styles #}
|
||||||
|
{% block content %}
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{# License - inside paper if present #}
|
||||||
|
{% if meta.url_contains_package and meta.current_package.license %}
|
||||||
|
<div class="collapse collapse-arrow bg-base-200 m-8">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-xl font-medium">License</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="{{ meta.current_package.license.link }}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="{{ meta.current_package.license.image_link }}"
|
||||||
|
alt="License"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock main_content %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% include "includes/footer.html" %}
|
||||||
|
|
||||||
|
{# Floating Help Tab #}
|
||||||
|
{% if not public_mode %}
|
||||||
|
<div class="fixed right-0 top-1/2 -translate-y-1/2 z-50">
|
||||||
|
<a
|
||||||
|
href="https://community.envipath.org/"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-secondary hover:btn-secondary-focus text-secondary-content flex items-center justify-center text-sm shadow-lg transition-all duration-300 hover:-translate-x-1 hover:scale-105"
|
||||||
|
title="Get Help from the Community"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-message-circle-question-mark-icon lucide-message-circle-question-mark"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
|
||||||
|
/>
|
||||||
|
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
||||||
|
<path d="M12 17h.01" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{% include "modals/search_modal.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
// Hide actionsbutton if there's no action defined
|
||||||
|
if ($("#actionsButton ul").children().length > 0) {
|
||||||
|
$("#actionsButton").show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
||||||
|
document.addEventListener("keydown", function (event) {
|
||||||
|
// Check if user is typing in an input field
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
const isInputField =
|
||||||
|
activeElement &&
|
||||||
|
(activeElement.tagName === "INPUT" ||
|
||||||
|
activeElement.tagName === "TEXTAREA" ||
|
||||||
|
activeElement.contentEditable === "true");
|
||||||
|
|
||||||
|
if (isInputField) {
|
||||||
|
return; // Don't trigger shortcut when typing in input fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Cmd+K (Mac) or Ctrl+K (Windows/Linux)
|
||||||
|
const isMac = /Mac/.test(navigator.platform);
|
||||||
|
const isCorrectModifier = isMac ? event.metaKey : event.ctrlKey;
|
||||||
|
|
||||||
|
if (isCorrectModifier && event.key === "k") {
|
||||||
|
event.preventDefault();
|
||||||
|
search_modal.showModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
templates/includes/footer.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{% load static %}
|
||||||
|
<div class="lg:max-w-5xl mt-10 mx-auto bg-base-300 text-base-content">
|
||||||
|
|
||||||
|
<footer class="footer sm:footer-horizontal p-10">
|
||||||
|
{% if not public_mode %}
|
||||||
|
<nav>
|
||||||
|
<h6 class="footer-title">Services</h6>
|
||||||
|
<a class="link link-hover" href="/">Predict</a>
|
||||||
|
<a class="link link-hover" href="/search">Search</a>
|
||||||
|
<a class="link link-hover" href="/package">Browse</a>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<a class="link link-hover" href="/model">Your Collections</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="https://wiki.envipath.org/" target="_blank" class="link link-hover">Documentation</a>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
<nav>
|
||||||
|
<h6 class="footer-title">Company</h6>
|
||||||
|
<a class="link link-hover" href="/about">About us</a>
|
||||||
|
<a class="link link-hover" href="/contact">Contact us</a>
|
||||||
|
<a class="link link-hover" href="/careers">Careers</a>
|
||||||
|
<a class="link link-hover" href="/legal">Legal</a>
|
||||||
|
</nav>
|
||||||
|
<nav>
|
||||||
|
<h6 class="footer-title">Legal</h6>
|
||||||
|
<a class="link link-hover" href="/terms">Terms of use</a>
|
||||||
|
<a class="link link-hover" href="/privacy">Privacy policy</a>
|
||||||
|
<a class="link link-hover" href="/cookie-policy">Cookie policy</a>
|
||||||
|
<a class="link link-hover" href="/cite">Cite enviPath</a>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
<footer class="footer border-neutral-300 border-t-2 px-10 py-4">
|
||||||
|
<div class="flex flex-row justify-between w-full items-start">
|
||||||
|
<aside class="grid-flow-col items-center">
|
||||||
|
<svg class="fill-neutral-content flex-shrink-0 h-14 m-2" viewbox="0 0 65 65" >
|
||||||
|
<use
|
||||||
|
href="{% static "/images/logo-square.svg" %}#ep-logo-square"
|
||||||
|
>
|
||||||
|
</use>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
enviPath Ltd.
|
||||||
|
<br />
|
||||||
|
Biodegredation prediction since 2015.
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
<aside class="text-sm text-base-200 mt-2"><span class="text-xs tracking-tight">Version</span> <span class="text-base font-bold">{{ meta.version }}</span></aside>
|
||||||
|
</div>
|
||||||
|
<nav class="md:place-self-center md:justify-self-end">
|
||||||
|
<div class="grid grid-flow-col gap-4">
|
||||||
|
<a href="https://www.youtube.com/@envipath7231" target="_blank">
|
||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 fill-current">
|
||||||
|
<title>YouTube</title>
|
||||||
|
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://community.envipath.org/" target="_blank">
|
||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 fill-current">
|
||||||
|
<title>Discourse</title>
|
||||||
|
<path d="M12.103 0C18.666 0 24 5.485 24 11.997c0 6.51-5.33 11.99-11.9 11.99L0 24V11.79C0 5.28 5.532 0 12.103 0zm.116 4.563c-2.593-.003-4.996 1.352-6.337 3.57-1.33 2.208-1.387 4.957-.148 7.22L4.4 19.61l4.794-1.074c2.745 1.225 5.965.676 8.136-1.39 2.17-2.054 2.86-5.228 1.737-7.997-1.135-2.778-3.84-4.59-6.84-4.585h-.008z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.linkedin.com/company/envipath/" target="_blank">
|
||||||
|
<img src="{% static "/images/linkedin.png" %}" alt="LinkedIn" class="w-6 h-6">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
147
templates/includes/navbar.html
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
{% load static %}
|
||||||
|
{# Modern DaisyUI Navbar #}
|
||||||
|
<div class="navbar bg-neutral-50 text-neutral-950 shadow-lg x-50">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a href="{{ meta.server_url }}" class="btn btn-ghost normal-case text-xl">
|
||||||
|
<svg class="h-8 fill-base-content" viewBox="0 0 104 26" role="img">
|
||||||
|
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not public_mode %}
|
||||||
|
<div class="navbar-center hidden lg:flex">
|
||||||
|
<a
|
||||||
|
href="{{ meta.server_url }}/predict"
|
||||||
|
role="button"
|
||||||
|
class="btn btn-ghost"
|
||||||
|
id="predictLink"
|
||||||
|
>Predict</a
|
||||||
|
>
|
||||||
|
<!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> -->
|
||||||
|
<!--<li><a href="{{ meta.server_url }}/browse" id="browseLink">Browse</a></li>-->
|
||||||
|
<div class="dropdown dropdown-center">
|
||||||
|
<div tabindex="0" role="button" class="btn btn-ghost">Browse</div>
|
||||||
|
<ul
|
||||||
|
tabindex="-1"
|
||||||
|
class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/compound" id="compoundLink"
|
||||||
|
>Compound</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/reaction" id="reactionLink"
|
||||||
|
>Reaction</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/model" id="relative-reasoningLink"
|
||||||
|
>Model</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/scenario" id="scenarioLink"
|
||||||
|
>Scenario</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="navbar-end">
|
||||||
|
{% if not public_mode %}
|
||||||
|
<a href="/search" role="button">
|
||||||
|
<div
|
||||||
|
class="flex items-center badge badge-dash space-x-1 bg-base-200 text-base-content/50 p-2 m-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-search-icon lucide-search"
|
||||||
|
>
|
||||||
|
<path d="m21 21-4.34-4.34" />
|
||||||
|
<circle cx="11" cy="11" r="8" />
|
||||||
|
</svg>
|
||||||
|
<span id="search-shortcut">⌘K</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if meta.user.username == 'anonymous' or public_mode %}
|
||||||
|
<a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a>
|
||||||
|
{% else %}
|
||||||
|
<div class="dropdown dropdown-end">
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
class="btn btn-ghost m-1 btn-circle"
|
||||||
|
id="loggedInButton"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-circle-user-icon lucide-circle-user"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<circle cx="12" cy="10" r="3" />
|
||||||
|
<path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
tabindex="-1"
|
||||||
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-sm"
|
||||||
|
>
|
||||||
|
<li><a href="{{ meta.user.url }}" id="accountbutton">Settings</a></li>
|
||||||
|
<li>
|
||||||
|
<form
|
||||||
|
id="logoutForm"
|
||||||
|
action="{% url 'logout' %}"
|
||||||
|
method="post"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="logout" value="true" />
|
||||||
|
</form>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
id="logoutButton"
|
||||||
|
onclick="event.preventDefault(); document.getElementById('logoutForm').submit();"
|
||||||
|
>Logout</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// OS-aware search shortcut display
|
||||||
|
(function () {
|
||||||
|
const isMac = /Mac/.test(navigator.platform);
|
||||||
|
const shortcutElement = document.getElementById("search-shortcut");
|
||||||
|
if (shortcutElement) {
|
||||||
|
shortcutElement.textContent = isMac ? "⌘K" : "Ctrl+K";
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@ -1,186 +1,439 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block main_content %}
|
||||||
<!-- TODO rename ids as well as remove pathways if modal is closed!-->
|
<!-- Hero Section with Logo and Search -->
|
||||||
<div class="modal fade" tabindex="-1" id="foundMatching" role="dialog" aria-labelledby="foundModal"
|
<section class="hero h-fit max-w-5xl w-full shadow-none mx-auto relative">
|
||||||
aria-hidden="true">
|
<div
|
||||||
<div class="modal-dialog">
|
class="hero min-h-[calc(100vh*0.4)] bg-gradient-to-br from-primary-800 to-primary-600"
|
||||||
<div class="modal-content">
|
style="background-image: url('{% static "/images/hero.png" %}'); background-size: cover; background-position: center;"
|
||||||
<div class="modal-header">
|
>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
<div class="hero-overlay"></div>
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Predict Pathway text over the image -->
|
||||||
<span class="sr-only">Close</span>
|
<div class="absolute bottom-40 left-1/8 -translate-x-8 z-10">
|
||||||
</button>
|
<h2 class="text-3xl text-base-100 text-shadow-lg text-left">
|
||||||
<h4 class="modal-title" id="newPackMod">Found Pathway in Database</h4>
|
Predict Your Pathway
|
||||||
</div>
|
</h2>
|
||||||
<div class="modal-body">
|
|
||||||
<p>We found at least one pathway in the database with the given root
|
|
||||||
compound. Do you want to open any of the existing pathways or
|
|
||||||
predict a new one? To open an existing pathway, simply click
|
|
||||||
on the pathway, to predict a new one, click Predict. The predicted
|
|
||||||
pathway might differ from the ones in the database due to the
|
|
||||||
settings used in the prediction.</p>
|
|
||||||
<div id="foundPathways"></div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a id="modal-predict" class="btn btn-primary" href="#">Predict</a>
|
|
||||||
<button type="button" id="cancel-predict" class="btn btn-default" data-dismiss="modal">Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="shadow-md max-w-5xl mx-auto bg-base-200">
|
||||||
|
<!-- Predict Pathway Section -->
|
||||||
|
<div
|
||||||
|
class="flex-col lg:flex-row-reverse w-full mx-auto -mt-32 relative z-20 mb-10 "
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card bg-base-100 shrink-0 shadow-xl w-3/4 mx-auto transition-all duration-300 ease-in-out"
|
||||||
|
>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Input Mode Toggle - Fixed position outside fieldset -->
|
||||||
|
<div class="flex flex-row justify-start items-center h-fit ml-8 my-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<!-- <span class="text-sm text-neutral-500">Input Mode:</span> -->
|
||||||
|
<label class="toggle text-base-content toggle-md">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<svg
|
||||||
|
aria-label="smiles mode"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
class="size-5"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-label="draw mode"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="none"
|
||||||
|
class="size-5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-8">
|
|
||||||
<div class="jumbotron">
|
|
||||||
<h1>
|
|
||||||
<img id="image-logo-long" class="img-responsive" alt="enviPath" width="1000ex"
|
|
||||||
src='{% static "/images/logo-long.svg" %}'/>
|
|
||||||
</h1>
|
|
||||||
<p>enviPath is a database and prediction system for the microbial
|
|
||||||
biotransformation of organic environmental contaminants. The
|
|
||||||
database provides the possibility to store and view experimentally
|
|
||||||
observed biotransformation pathways. The pathway prediction system
|
|
||||||
provides different relative reasoning models to predict likely biotransformation
|
|
||||||
pathways and products. You can try it out below.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="btn" style="background-color:#222222;color:#9d9d9d" role="button" target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/Main_Page">Learn more >></a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<fieldset
|
||||||
<div id="loading"></div>
|
class="fieldset transition-all duration-300 ease-in-out overflow-hidden"
|
||||||
<div class="col-xs-4">
|
>
|
||||||
<d-topics-list discourse-url="https://community.envipath.org" per-page="10" category="10"
|
<form
|
||||||
template="complete"></d-topics-list>
|
id="index-form"
|
||||||
</div>
|
action="{{ meta.current_package.url }}/pathway"
|
||||||
</div>
|
method="POST"
|
||||||
<div class="row">
|
>
|
||||||
<form id="index-form" action="{{ meta.current_package.url }}/pathway" method="POST">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="input-group" id="index-form-bar">
|
<div
|
||||||
<div class="input-group-btn">
|
id="text-input-container"
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
class="transition-all duration-300 ease-in-out opacity-100 transform scale-100"
|
||||||
aria-expanded="false">
|
>
|
||||||
<span class="caret"></span>
|
<div class="join w-full mx-auto">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="index-form-text-input"
|
||||||
|
placeholder="canonical SMILES string"
|
||||||
|
class="input grow input-md join-item"
|
||||||
|
/>
|
||||||
|
<button class="btn btn-neutral join-item">Predict!</button>
|
||||||
|
</div>
|
||||||
|
<div class="label relative w-full mt-1">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="example-link cursor-pointer hover:text-primary"
|
||||||
|
data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
|
||||||
|
title="load example"
|
||||||
|
>Caffeine</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="example-link cursor-pointer hover:text-primary"
|
||||||
|
data-smiles="CC(C)CC1=CC=C(C=C1)C(C)C(=O)O"
|
||||||
|
title="load example"
|
||||||
|
>Ibuprofen</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#"
|
||||||
|
>Advanced</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="ketcher-container"
|
||||||
|
class="hidden w-full transition-all duration-300 ease-in-out opacity-0 transform scale-95"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
id="index-ketcher"
|
||||||
|
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
|
width="100%"
|
||||||
|
height="400"
|
||||||
|
class="rounded-lg"
|
||||||
|
></iframe>
|
||||||
|
<button
|
||||||
|
class="btn btn-lg bg-primary-950 text-primary-50 join-item w-full mt-2"
|
||||||
|
>
|
||||||
|
Predict!
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" role="menu">
|
<a class="label mx-auto w-full mt-1" href="#">Advanced</a>
|
||||||
<li>
|
|
||||||
<iframe id="index-form-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
|
|
||||||
height="510"></iframe>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" id='index-form-text-input'
|
<input
|
||||||
placeholder="Enter a SMILES to predict a Pathway or type something to search">
|
type="hidden"
|
||||||
<div class="input-group-btn">
|
id="index-form-smiles"
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
name="smiles"
|
||||||
aria-haspopup="true"
|
value="smiles"
|
||||||
aria-expanded="false" id="action-button">Predict <span class="caret"></span></button>
|
/>
|
||||||
<ul class="dropdown-menu">
|
<input
|
||||||
<li><a id="dropdown-predict">Predict</a></li>
|
type="hidden"
|
||||||
<li><a id="dropdown-search">Search</a></li>
|
id="index-form-predict"
|
||||||
</ul>
|
name="predict"
|
||||||
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="run-button">Go!
|
value="predict"
|
||||||
</button>
|
/>
|
||||||
</div>
|
<input type="hidden" id="current-action" value="predict" />
|
||||||
</div>
|
|
||||||
<input type="hidden" id="index-form-smiles" name="smiles" value="smiles">
|
|
||||||
<input type="hidden" id="index-form-predict" name="predict" value="predict">
|
|
||||||
</form>
|
</form>
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Community News Section -->
|
||||||
|
<section class="py-16 bg-base-200 z-10 mx-8">
|
||||||
|
<div class="max-w-7xl mx-auto px-4">
|
||||||
|
<h2 class="h2 font-bold text-left mb-8">Community Updates</h2>
|
||||||
|
|
||||||
|
<div id="community-news-container" class="flex gap-4 justify-center">
|
||||||
|
<!-- News cards will be populated here -->
|
||||||
|
<div id="loading" class="flex justify-center w-full">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-right mt-6">
|
||||||
|
<a
|
||||||
|
href="https://community.envipath.org/c/announcements/10"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-ghost btn-sm"
|
||||||
|
>
|
||||||
|
Read More Announcements
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Discourse API integration -->
|
||||||
|
<script src="{% static 'js/discourse-api.js' %}"></script>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Mission Statement Section -->
|
||||||
|
<section class="py-16 from-base-200 to-base-100 bg-gradient-to-b">
|
||||||
|
<div class="mx-auto px-8 md:px-12">
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
<div class="w-1/3">
|
||||||
|
<img
|
||||||
|
src="{% static "/images/ep-rule-artwork.png" %}"
|
||||||
|
alt="rule-based iterative tree greneration"
|
||||||
|
class="w-full h-full object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4 text-left w-2/3 mr-8">
|
||||||
|
<h2 class="h2 font-bold mb-8">About enviPath</h2>
|
||||||
|
<p class="">
|
||||||
|
enviPath is a database and prediction system for the microbial
|
||||||
|
biotransformation of organic environmental contaminants. The
|
||||||
|
database provides the possibility to store and view experimentally
|
||||||
|
observed biotransformation pathways.
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
The pathway prediction system provides different relative
|
||||||
|
reasoning models to predict likely biotransformation pathways and
|
||||||
|
products. Explore our tools and contribute to advancing
|
||||||
|
environmental biotransformation research.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-row gap-4 float-right">
|
||||||
|
<a href="/about" class="btn btn-ghost-neutral">Read More</a>
|
||||||
|
<a href="/about" class="btn btn-neutral">Publications</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Partners Section -->
|
||||||
|
<section class="py-14 sm:py-12 bg-base-100">
|
||||||
|
<div class="mx-auto px-6 lg:px-8">
|
||||||
|
<div class="divider">
|
||||||
|
<h2 class="text-center text-lg/8 font-semibold">Backed by Science</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-3"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="{% static "/images/uoa-logo-small.png" %}"
|
||||||
|
alt="The University of Auckland"
|
||||||
|
class=" max-h-20 w-full object-contain lg:col-span-1"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="{% static "/images/logo-eawag.svg" %}"
|
||||||
|
alt="Eawag"
|
||||||
|
class=" max-h-12 w-full object-contain lg:col-span-1"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="{% static "/images/uzh-logo.svg" %}"
|
||||||
|
alt="University of Zurich"
|
||||||
|
class="2 max-h-16 w-full object-contain lg:col-span-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script language="javascript">
|
<script language="javascript">
|
||||||
var currentPackage = "{{ meta.current_package.url }}";
|
var currentPackage = "{{ meta.current_package.url }}";
|
||||||
|
|
||||||
function goButtonClicked() {
|
// Discourse API integration is now handled by discourse-api.js
|
||||||
$(this).prop("disabled", true);
|
|
||||||
|
|
||||||
var action = $('#action-button').text().trim();
|
// Function to render Discourse topics into cards
|
||||||
|
function renderDiscourseTopics(topics) {
|
||||||
|
const container = document.getElementById("community-news-container");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
var textSmiles = $('#index-form-text-input').val().trim();
|
// Clear container
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
if (textSmiles === '') {
|
// Create cards for each topic
|
||||||
$(this).prop("disabled", false);
|
topics.forEach((topic) => {
|
||||||
return;
|
const card = createDiscourseCard(topic);
|
||||||
|
container.insertAdjacentHTML("beforeend", card);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var ketcherSmiles = getKetcher('index-form-ketcher').getSmiles().trim();
|
// Function to create HTML card for a topic
|
||||||
|
function createDiscourseCard(topic) {
|
||||||
|
const date = new Date(topic.created_at).toLocaleDateString();
|
||||||
|
|
||||||
if (action !== 'Search' && ketcherSmiles !== '' && textSmiles !== ketcherSmiles) {
|
return `
|
||||||
console.log("Ketcher and TextInput differ!");
|
<div class="card bg-white shadow-xs hover:shadow-lg transition-shadow duration-300 h-64 w-75 flex-shrink-0">
|
||||||
|
<div class="card-body flex flex-col h-full">
|
||||||
|
<h3 class="card-title leading-tight font-normal tracking-tight h-12 mb-2 line-clamp-2 text-ellipsis wrap-break-word overflow-hidden">
|
||||||
|
<a href="${topic.url}" target="_blank" class="hover:text-primary">
|
||||||
|
${topic.title}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<div class="text-sm line-clamp-4 break-words" >
|
||||||
|
${topic.excerpt}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="avatar tooltip tooltip-right" data-tip="${topic.author}">
|
||||||
|
<div class="w-8 rounded-full">
|
||||||
|
<img src="${topic.author_avatar}" alt="${topic.author}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-500">${date}</span>
|
||||||
|
</div>
|
||||||
|
<a href="${topic.url}" target="_blank" class="btn btn-ghost text-neutral-500 rounded-full p-2">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 7v14"/>
|
||||||
|
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === 'Search') {
|
// Make render function globally available
|
||||||
var par = {};
|
window.renderDiscourseTopics = renderDiscourseTopics;
|
||||||
par['search'] = textSmiles;
|
|
||||||
par['mode'] = 'text';
|
// Toggle functionality with smooth animations
|
||||||
var queryString = $.param(par, true);
|
function toggleInputMode() {
|
||||||
window.location.href = "/search?" + queryString;
|
const toggle = $('input[type="checkbox"]');
|
||||||
|
const textContainer = $("#text-input-container");
|
||||||
|
const ketcherContainer = $("#ketcher-container");
|
||||||
|
const formCard = $(".card");
|
||||||
|
const fieldset = $(".fieldset");
|
||||||
|
|
||||||
|
if (toggle.is(":checked")) {
|
||||||
|
// Draw mode - show Ketcher, hide text input
|
||||||
|
textContainer.addClass("opacity-0 transform scale-95");
|
||||||
|
textContainer.removeClass("opacity-100 transform scale-100");
|
||||||
|
|
||||||
|
// Adjust fieldset padding for Ketcher mode - reduce padding and make more compact
|
||||||
|
fieldset.removeClass("p-8");
|
||||||
|
fieldset.addClass("p-4");
|
||||||
|
|
||||||
|
// Wait for fade out to complete, then hide and show new content
|
||||||
|
setTimeout(() => {
|
||||||
|
textContainer.addClass("hidden");
|
||||||
|
ketcherContainer.removeClass("hidden opacity-0 transform scale-95");
|
||||||
|
ketcherContainer.addClass("opacity-100 transform scale-100");
|
||||||
|
|
||||||
|
// Force re-evaluation of iframe size
|
||||||
|
const iframe = document.getElementById("index-ketcher");
|
||||||
|
if (iframe) {
|
||||||
|
iframe.style.height = "400px";
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
} else {
|
} else {
|
||||||
$('#index-form-smiles').val(textSmiles);
|
// SMILES mode - show text input, hide Ketcher
|
||||||
$('#index-form').submit();
|
ketcherContainer.addClass("opacity-0 transform scale-95");
|
||||||
|
ketcherContainer.removeClass("opacity-100 transform scale-100");
|
||||||
|
|
||||||
|
// Restore fieldset padding for text input mode
|
||||||
|
fieldset.removeClass("p-4");
|
||||||
|
fieldset.addClass("p-8");
|
||||||
|
|
||||||
|
// Wait for fade out to complete, then hide and show new content
|
||||||
|
setTimeout(() => {
|
||||||
|
ketcherContainer.addClass("hidden");
|
||||||
|
textContainer.removeClass("hidden opacity-0 transform scale-95");
|
||||||
|
textContainer.addClass("opacity-100 transform scale-100");
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// Transfer SMILES from Ketcher to text input if available
|
||||||
|
if (window.indexKetcher && window.indexKetcher.getSmiles) {
|
||||||
|
const smiles = window.indexKetcher.getSmiles();
|
||||||
|
if (smiles && smiles.trim() !== "") {
|
||||||
|
$("#index-form-text-input").val(smiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function actionDropdownClicked() {
|
// Ketcher integration
|
||||||
var suffix = ' <span class="caret"></span>';
|
function indexKetcherToTextInput() {
|
||||||
var dropdownVal = $(this).text();
|
$("#index-form-smiles").val(this.ketcher.getSmiles());
|
||||||
|
|
||||||
if (dropdownVal === 'Search') {
|
|
||||||
$("#index-form").attr("action", '/search');
|
|
||||||
$("#index-form").attr("method", 'GET');
|
|
||||||
} else {
|
|
||||||
$("#index-form").attr("action", currentPackage + "/pathway");
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#action-button').html(dropdownVal + suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ketcherToTextInput() {
|
|
||||||
$('#index-form-text-input').val(this.ketcher.getSmiles());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
|
// Initialize fieldset with proper padding
|
||||||
|
$(".fieldset").addClass("p-8");
|
||||||
|
|
||||||
$('#index-form').on("keydown", function (e) {
|
// Toggle event listener
|
||||||
if (e.key === "Enter") {
|
$('input[type="checkbox"]').on("change", toggleInputMode);
|
||||||
e.preventDefault();
|
|
||||||
goButtonClicked();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Code that should be executed once DOM is ready goes here
|
// Ketcher iframe load handler
|
||||||
$('#dropdown-predict').on('click', actionDropdownClicked);
|
$("#index-ketcher").on("load", function () {
|
||||||
$('#dropdown-search').on('click', actionDropdownClicked);
|
|
||||||
|
|
||||||
$('#run-button').on('click', goButtonClicked);
|
|
||||||
|
|
||||||
// Update Ketcher Width
|
|
||||||
var fullWidth = $('#index-form-bar').width();
|
|
||||||
$('#index-form-ketcher').width(fullWidth);
|
|
||||||
|
|
||||||
// add a listener that gets triggered whenever the structure in ketcher has changed
|
|
||||||
$('#index-form-ketcher').on('load', function () {
|
|
||||||
const checkKetcherReady = () => {
|
const checkKetcherReady = () => {
|
||||||
win = this.contentWindow
|
const win = this.contentWindow;
|
||||||
if (win.ketcher && 'editor' in win.ketcher) {
|
if (win.ketcher && "editor" in win.ketcher) {
|
||||||
|
window.indexKetcher = win.ketcher;
|
||||||
win.ketcher.editor.event.change.handlers.push({
|
win.ketcher.editor.event.change.handlers.push({
|
||||||
once: false,
|
once: false,
|
||||||
priority: 0,
|
priority: 0,
|
||||||
f: ketcherToTextInput,
|
f: indexKetcherToTextInput,
|
||||||
ketcher: win.ketcher
|
ketcher: win.ketcher,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkKetcherReady, 100);
|
setTimeout(checkKetcherReady, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkKetcherReady();
|
checkKetcherReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle example link clicks
|
||||||
|
$(".example-link").on("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const smiles = $(this).data("smiles");
|
||||||
|
const title = $(this).attr("title");
|
||||||
|
|
||||||
|
// Check if we're in Ketcher mode or text input mode
|
||||||
|
if ($('input[type="checkbox"]').is(":checked")) {
|
||||||
|
// In Ketcher mode - set the SMILES in Ketcher
|
||||||
|
if (window.indexKetcher && window.indexKetcher.setMolecule) {
|
||||||
|
window.indexKetcher.setMolecule(smiles);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In text input mode - set the SMILES in the text input
|
||||||
|
$("#index-form-text-input").val(smiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a brief feedback
|
||||||
|
const originalText = $(this).text();
|
||||||
|
$(this).text(`loaded!`);
|
||||||
|
setTimeout(() => {
|
||||||
|
$(this).text(originalText);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle form submission on Enter
|
||||||
|
$("#index-form").on("submit", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var textSmiles = "";
|
||||||
|
|
||||||
|
// Check if we're in Ketcher mode and extract SMILES
|
||||||
|
if ($('input[type="checkbox"]').is(":checked") && window.indexKetcher) {
|
||||||
|
textSmiles = window.indexKetcher.getSmiles().trim();
|
||||||
|
} else {
|
||||||
|
textSmiles = $("#index-form-text-input").val().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textSmiles === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#index-form-smiles").val(textSmiles);
|
||||||
|
$("#index-form").attr("action", currentPackage + "/pathway");
|
||||||
|
$("#index-form").attr("method", "POST");
|
||||||
|
this.submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Discourse topics are now loaded automatically by discourse-api.js
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock content %}
|
{% endblock main_content %}
|
||||||
|
|||||||
@ -26,12 +26,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
||||||
href="#{{ obj.id }}">{{ obj.name }}</a>
|
href="#{{ obj.id }}">{{ obj.name|safe }}</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<a class="list-group-item" href="{{ obj.detail_url }}">{{ obj.name }} Migration Detail Page</a>
|
<a class="list-group-item" href="{{ obj.detail_url }}">{{ obj.name|safe }} Migration Detail Page</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
<a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
|
||||||
href="#{{ obj.id }}">{{ obj.name }}</a>
|
href="#{{ obj.id }}">{{ obj.name|safe }}</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
<div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<div class="modal fade" tabindex="-1" id="new_model_modal" role="dialog" aria-labelledby="new_model_modal"
|
<div class="modal fade" tabindex="-1" id="new_model_modal" role="dialog" aria-labelledby="new_model_modal"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
@ -18,13 +19,19 @@
|
|||||||
prediction. You just need to set a name and the packages
|
prediction. You just need to set a name and the packages
|
||||||
you want the object to be based on. There are multiple types of models available.
|
you want the object to be based on. There are multiple types of models available.
|
||||||
For additional information have a look at our
|
For additional information have a look at our
|
||||||
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki >></a>
|
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki
|
||||||
|
>></a>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Name -->
|
||||||
<label for="model-name">Name</label>
|
<label for="model-name">Name</label>
|
||||||
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
|
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
<label for="model-description">Description</label>
|
<label for="model-description">Description</label>
|
||||||
<input id="model-description" name="model-description" class="form-control"
|
<input id="model-description" name="model-description" class="form-control"
|
||||||
placeholder="Description"/>
|
placeholder="Description"/>
|
||||||
|
|
||||||
|
<!-- Model Type -->
|
||||||
<label for="model-type">Model Type</label>
|
<label for="model-type">Model Type</label>
|
||||||
<select id="model-type" name="model-type" class="form-control" data-width='100%'>
|
<select id="model-type" name="model-type" class="form-control" data-width='100%'>
|
||||||
<option disabled selected>Select Model Type</option>
|
<option disabled selected>Select Model Type</option>
|
||||||
@ -32,70 +39,72 @@
|
|||||||
<option value="{{ v }}">{{ k }}</option>
|
<option value="{{ v }}">{{ k }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<!-- ML and Rule Based Based Form-->
|
|
||||||
<div id="package-based-relative-reasoning-specific-form">
|
|
||||||
<!-- Rule Packages -->
|
<!-- Rule Packages -->
|
||||||
<label for="package-based-relative-reasoning-rule-packages">Rule Packages</label>
|
<div id="rule-packages" class="ep-model-param mlrr rbrr">
|
||||||
<select id="package-based-relative-reasoning-rule-packages" name="package-based-relative-reasoning-rule-packages"
|
<label for="model-rule-packages">Rule Packages</label>
|
||||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
<select id="model-rule-packages" name="model-rule-packages" data-actions-box='true'
|
||||||
|
class="form-control" multiple data-width='100%'>
|
||||||
<option disabled>Reviewed Packages</option>
|
<option disabled>Reviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<option disabled>Unreviewed Packages</option>
|
<option disabled>Unreviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if not obj.reviewed %}
|
{% if not obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Data Packages -->
|
<!-- Data Packages -->
|
||||||
<label for="package-based-relative-reasoning-data-packages" >Data Packages</label>
|
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
|
||||||
<select id="package-based-relative-reasoning-data-packages" name="package-based-relative-reasoning-data-packages"
|
<label for="model-data-packages">Data Packages</label>
|
||||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
<select id="model-data-packages" name="model-data-packages" data-actions-box='true'
|
||||||
|
class="form-control" multiple data-width='100%'>
|
||||||
<option disabled>Reviewed Packages</option>
|
<option disabled>Reviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<option disabled>Unreviewed Packages</option>
|
<option disabled>Unreviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if not obj.reviewed %}
|
{% if not obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="ml-relative-reasoning-specific-form">
|
|
||||||
<!-- Fingerprinter -->
|
<!-- Fingerprinter -->
|
||||||
<label for="ml-relative-reasoning-fingerprinter">Fingerprinter</label>
|
<div id="fingerprinter" class="ep-model-param mlrr">
|
||||||
<select id="ml-relative-reasoning-fingerprinter" name="ml-relative-reasoning-fingerprinter"
|
<label for="model-fingerprinter">Fingerprinter</label>
|
||||||
class="form-control">
|
<select id="model-fingerprinter" name="model-fingerprinter" data-actions-box='true'
|
||||||
|
class="form-control" multiple data-width='100%'>
|
||||||
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
||||||
</select>
|
|
||||||
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
||||||
<!-- Property Plugins go here -->
|
|
||||||
<label for="ml-relative-reasoning-additional-fingerprinter">Additional Fingerprinter /
|
|
||||||
Descriptors</label>
|
|
||||||
<select id="ml-relative-reasoning-additional-fingerprinter"
|
|
||||||
name="ml-relative-reasoning-additional-fingerprinter" class="form-control">
|
|
||||||
<option disabled selected>Select Additional Fingerprinter / Descriptor</option>
|
<option disabled selected>Select Additional Fingerprinter / Descriptor</option>
|
||||||
{% for k, v in additional_descriptors.items %}
|
{% for k, v in additional_descriptors.items %}
|
||||||
<option value="{{ v }}">{{ k }}</option>
|
<option value="{{ v }}">{{ k }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</select>
|
||||||
<label for="ml-relative-reasoning-threshold">Threshold</label>
|
|
||||||
<input type="number" min="0" max="1" step="0.05" value="0.5"
|
|
||||||
id="ml-relative-reasoning-threshold"
|
|
||||||
name="ml-relative-reasoning-threshold" class="form-control">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Threshold -->
|
||||||
|
<div id="threshold" class="ep-model-param mlrr enviformer">
|
||||||
|
<label for="model-threshold">Threshold</label>
|
||||||
|
<input type="number" min="0" max="1" step="0.05" value="0.5" id="model-threshold"
|
||||||
|
name="model-threshold" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="appdomain" class="ep-model-param mlrr">
|
||||||
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
|
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
|
||||||
<!-- Build AD? -->
|
<!-- Build AD? -->
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
@ -107,11 +116,13 @@
|
|||||||
<div id="ad-params" style="display:none">
|
<div id="ad-params" style="display:none">
|
||||||
<!-- Num Neighbors -->
|
<!-- Num Neighbors -->
|
||||||
<label for="num-neighbors">Number of Neighbors</label>
|
<label for="num-neighbors">Number of Neighbors</label>
|
||||||
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control" value="5"
|
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control"
|
||||||
|
value="5"
|
||||||
step="1" min="0" max="10">
|
step="1" min="0" max="10">
|
||||||
<!-- Local Compatibility -->
|
<!-- Local Compatibility -->
|
||||||
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
|
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
|
||||||
<input id="local-compatibility-threshold" name="local-compatibility-threshold" type="number"
|
<input id="local-compatibility-threshold" name="local-compatibility-threshold"
|
||||||
|
type="number"
|
||||||
class="form-control" value="0.5" step="0.01" min="0" max="1">
|
class="form-control" value="0.5" step="0.01" min="0" max="1">
|
||||||
<!-- Reliability -->
|
<!-- Reliability -->
|
||||||
<label for="reliability-threshold">Reliability Threshold</label>
|
<label for="reliability-threshold">Reliability Threshold</label>
|
||||||
@ -120,12 +131,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<!-- EnviFormer-->
|
|
||||||
<div id="enviformer-specific-form">
|
|
||||||
<label for="enviformer-threshold">Threshold</label>
|
|
||||||
<input type="number" min="0" max="1" step="0.05" value="0.5" id="enviformer-threshold"
|
|
||||||
name="enviformer-threshold" class="form-control">
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -137,20 +142,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function() {
|
$(function () {
|
||||||
|
// Built in Model Types
|
||||||
|
var nativeModelTypes = [
|
||||||
|
"mlrr",
|
||||||
|
"rbrr",
|
||||||
|
"enviformer",
|
||||||
|
]
|
||||||
|
|
||||||
// Initially hide all "specific" forms
|
// Initially hide all "specific" forms
|
||||||
$("div[id$='-specific-form']").each( function() {
|
$(".ep-model-param").each(function () {
|
||||||
$(this).hide();
|
$(this).hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#model-type').selectpicker();
|
$('#model-type').selectpicker();
|
||||||
$("#ml-relative-reasoning-fingerprinter").selectpicker();
|
$("#model-fingerprinter").selectpicker();
|
||||||
$("#package-based-relative-reasoning-rule-packages").selectpicker();
|
$("#model-rule-packages").selectpicker();
|
||||||
$("#package-based-relative-reasoning-data-packages").selectpicker();
|
$("#model-data-packages").selectpicker();
|
||||||
$("#package-based-relative-reasoning-evaluation-packages").selectpicker();
|
|
||||||
if ($('#ml-relative-reasoning-additional-fingerprinter').length > 0) {
|
|
||||||
$("#ml-relative-reasoning-additional-fingerprinter").selectpicker();
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#build-app-domain").change(function () {
|
$("#build-app-domain").change(function () {
|
||||||
if ($(this).is(":checked")) {
|
if ($(this).is(":checked")) {
|
||||||
@ -161,29 +169,20 @@ $(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// On change hide all and show only selected
|
// On change hide all and show only selected
|
||||||
$("#model-type").change(function() {
|
$("#model-type").change(function () {
|
||||||
$("div[id$='-specific-form']").each( function() {
|
$('.ep-model-param').hide();
|
||||||
$(this).hide();
|
var modelType = $('#model-type').val();
|
||||||
});
|
if (nativeModelTypes.indexOf(modelType) !== -1) {
|
||||||
val = $('option:selected', this).val();
|
$('.' + modelType).show();
|
||||||
|
|
||||||
if (val === 'ml-relative-reasoning' || val === 'rule-based-relative-reasoning') {
|
|
||||||
$("#package-based-relative-reasoning-specific-form").show();
|
|
||||||
if (val === 'ml-relative-reasoning') {
|
|
||||||
$("#ml-relative-reasoning-specific-form").show();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$("#" + val + "-specific-form").show();
|
// do nothing
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#new_model_modal_form_submit').on('click', function(e){
|
$('#new_model_modal_form_submit').on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#new_model_form').submit();
|
$('#new_model_form').submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<div class="modal fade" tabindex="-1" id="new_pathway_modal" role="dialog" aria-labelledby="new_pathway_modal"
|
<div class="modal fade" tabindex="-1" id="new_pathway_modal" role="dialog" aria-labelledby="new_pathway_modal"
|
||||||
aria-hidden="true" style="overflow-y: auto;">
|
aria-hidden="true" style="overflow-y: auto;">
|
||||||
@ -111,7 +112,7 @@
|
|||||||
|
|
||||||
<select id="settingSelect" name="settingSelect" class="form-control">
|
<select id="settingSelect" name="settingSelect" class="form-control">
|
||||||
{% for setting in available_settings %}
|
{% for setting in available_settings %}
|
||||||
<option value="{{ setting.id }}">{{ setting.name }}</option>
|
<option value="{{ setting.id }}">{{ setting.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<p></p>
|
<p></p>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<div id="new_prediction_setting_modal" class="modal" tabindex="-1">
|
<div id="new_prediction_setting_modal" class="modal" tabindex="-1">
|
||||||
@ -40,14 +41,14 @@
|
|||||||
<option disabled>Reviewed Packages</option>
|
<option disabled>Reviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<option disabled>Unreviewed Packages</option>
|
<option disabled>Unreviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if not obj.reviewed %}
|
{% if not obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
@ -57,7 +58,7 @@
|
|||||||
<select id="model-based-prediction-setting-model" name="model-based-prediction-setting-model" class="form-control" data-width='100%'>
|
<select id="model-based-prediction-setting-model" name="model-based-prediction-setting-model" class="form-control" data-width='100%'>
|
||||||
<option disabled selected>Select the model</option>
|
<option disabled selected>Select the model</option>
|
||||||
{% for m in models %}
|
{% for m in models %}
|
||||||
<option value="{{ m.url }}">{{ m.name }}</option>
|
<option value="{{ m.url }}">{{ m.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<label for="model-based-prediction-setting-threshold">Threshold</label>
|
<label for="model-based-prediction-setting-threshold">Threshold</label>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<div class="modal fade bs-modal-lg" id="add_pathway_edge_modal" tabindex="-1" aria-labelledby="add_pathway_edge_modal"
|
<div class="modal fade bs-modal-lg" id="add_pathway_edge_modal" tabindex="-1" aria-labelledby="add_pathway_edge_modal"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
@ -36,7 +37,7 @@
|
|||||||
<select id="add_pathway_edge_substrates" name="edge-substrates"
|
<select id="add_pathway_edge_substrates" name="edge-substrates"
|
||||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
||||||
{% for n in pathway.nodes %}
|
{% for n in pathway.nodes %}
|
||||||
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name }}</option>
|
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +48,7 @@
|
|||||||
<select id="add_pathway_edge_products" name="edge-products"
|
<select id="add_pathway_edge_products" name="edge-products"
|
||||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
||||||
{% for n in pathway.nodes %}
|
{% for n in pathway.nodes %}
|
||||||
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name }}</option>
|
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Delete Edge -->
|
<!-- Delete Edge -->
|
||||||
<div id="delete_pathway_edge_modal" class="modal" tabindex="-1">
|
<div id="delete_pathway_edge_modal" class="modal" tabindex="-1">
|
||||||
@ -19,7 +20,7 @@
|
|||||||
data-actions-box='true' class="form-control" data-width='100%'>
|
data-actions-box='true' class="form-control" data-width='100%'>
|
||||||
<option value="" disabled selected>Select Reaction to delete</option>
|
<option value="" disabled selected>Select Reaction to delete</option>
|
||||||
{% for e in pathway.edges %}
|
{% for e in pathway.edges %}
|
||||||
<option value="{{ e.url }}">{{ e.edge_label.name }}</option>
|
<option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" id="hidden" name="hidden" value="delete"/>
|
<input type="hidden" id="hidden" name="hidden" value="delete"/>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<!-- Delete Node -->
|
<!-- Delete Node -->
|
||||||
<div id="delete_pathway_node_modal" class="modal" tabindex="-1">
|
<div id="delete_pathway_node_modal" class="modal" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@ -19,7 +20,7 @@
|
|||||||
data-actions-box='true' class="form-control" data-width='100%'>
|
data-actions-box='true' class="form-control" data-width='100%'>
|
||||||
<option value="" disabled selected>Select Compound to delete</option>
|
<option value="" disabled selected>Select Compound to delete</option>
|
||||||
{% for n in pathway.nodes %}
|
{% for n in pathway.nodes %}
|
||||||
<option value="{{ n.url }}">{{ n.default_node_label.name }}</option>
|
<option value="{{ n.url }}">{{ n.default_node_label.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" id="hidden" name="hidden" value="delete"/>
|
<input type="hidden" id="hidden" name="hidden" value="delete"/>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Compound -->
|
<!-- Edit Compound -->
|
||||||
<div id="edit_compound_modal" class="modal" tabindex="-1">
|
<div id="edit_compound_modal" class="modal" tabindex="-1">
|
||||||
@ -15,12 +16,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="compound-name">Name</label>
|
<label for="compound-name">Name</label>
|
||||||
<input id="compound-name" class="form-control" name="compound-name" value="{{ compound.name}}">
|
<input id="compound-name" class="form-control" name="compound-name" value="{{ compound.name|safe}}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="compound-description">Description</label>
|
<label for="compound-description">Description</label>
|
||||||
<input id="compound-description" type="text" class="form-control"
|
<input id="compound-description" type="text" class="form-control"
|
||||||
value="{{ compound.description }}"
|
value="{{ compound.description|safe }}"
|
||||||
name="compound-description">
|
name="compound-description">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Compound -->
|
<!-- Edit Compound -->
|
||||||
<div id="edit_compound_structure_modal" class="modal" tabindex="-1">
|
<div id="edit_compound_structure_modal" class="modal" tabindex="-1">
|
||||||
@ -15,12 +16,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="compound-structure-name">Name</label>
|
<label for="compound-structure-name">Name</label>
|
||||||
<input id="compound-structure-name" class="form-control" name="compound-structure-name" value="{{ compound_structure.name }}">
|
<input id="compound-structure-name" class="form-control" name="compound-structure-name" value="{{ compound_structure.name|safe }}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="compound-structure-description">Description</label>
|
<label for="compound-structure-description">Description</label>
|
||||||
<input id="compound-structure-description" type="text" class="form-control"
|
<input id="compound-structure-description" type="text" class="form-control"
|
||||||
value="{{ compound_structure.description }}" name="compound-structure-description">
|
value="{{ compound_structure.description|safe }}" name="compound-structure-description">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package Permission -->
|
<!-- Edit Package Permission -->
|
||||||
<div id="edit_group_member_modal" class="modal" tabindex="-1">
|
<div id="edit_group_member_modal" class="modal" tabindex="-1">
|
||||||
@ -39,7 +40,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<option disabled>Groups</option>
|
<option disabled>Groups</option>
|
||||||
{% for g in groups %}
|
{% for g in groups %}
|
||||||
<option value="{{ g.url }}">{{ g.name }}</option>
|
<option value="{{ g.url }}">{{ g.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" name="action" value="add">
|
<input type="hidden" name="action" value="add">
|
||||||
@ -81,7 +82,7 @@
|
|||||||
accept-charset="UTF-8" action="" data-remote="true" method="post">
|
accept-charset="UTF-8" action="" data-remote="true" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col-xs-8">
|
<div class="col-xs-8">
|
||||||
{{ g.name }}
|
{{ g.name|safe }}
|
||||||
<input type="hidden" name="member" value="{{ g.url }}"/>
|
<input type="hidden" name="member" value="{{ g.url }}"/>
|
||||||
<input type="hidden" name="action" value="remove">
|
<input type="hidden" name="action" value="remove">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Model -->
|
<!-- Edit Model -->
|
||||||
<div id="edit_model_modal" class="modal" tabindex="-1">
|
<div id="edit_model_modal" class="modal" tabindex="-1">
|
||||||
@ -16,12 +17,12 @@
|
|||||||
<p>
|
<p>
|
||||||
<label for="model-name">Name</label>
|
<label for="model-name">Name</label>
|
||||||
<input id="model-name" type="text" class="form-control" name="model-name"
|
<input id="model-name" type="text" class="form-control" name="model-name"
|
||||||
value="{{ model.name }}">
|
value="{{ model.name|safe }}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="model-description">Description</label>
|
<label for="model-description">Description</label>
|
||||||
<input id="model-description" type="text" class="form-control" name="model-description"
|
<input id="model-description" type="text" class="form-control" name="model-description"
|
||||||
value="{{ model.description }}">
|
value="{{ model.description|safe }}">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Node -->
|
<!-- Edit Node -->
|
||||||
<div id="edit_node_modal" class="modal" tabindex="-1">
|
<div id="edit_node_modal" class="modal" tabindex="-1">
|
||||||
@ -15,12 +16,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="node-name">Name</label>
|
<label for="node-name">Name</label>
|
||||||
<input id="node-name" class="form-control" name="node-name" value="{{ node.name}}">
|
<input id="node-name" class="form-control" name="node-name" value="{{ node.name|safe}}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="node-description">Description</label>
|
<label for="node-description">Description</label>
|
||||||
<input id="node-description" type="text" class="form-control"
|
<input id="node-description" type="text" class="form-control"
|
||||||
value="{{ node.description }}"
|
value="{{ node.description|safe }}"
|
||||||
name="node-description">
|
name="node-description">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package -->
|
<!-- Edit Package -->
|
||||||
<div id="edit_package_modal" class="modal" tabindex="-1">
|
<div id="edit_package_modal" class="modal" tabindex="-1">
|
||||||
@ -15,12 +16,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="package-name">Name</label>
|
<label for="package-name">Name</label>
|
||||||
<input id="package-name" class="form-control" name="package-name" value="{{ package.name}}">
|
<input id="package-name" class="form-control" name="package-name" value="{{ package.name|safe}}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="package-description">Description</label>
|
<label for="package-description">Description</label>
|
||||||
<input id="package-description" type="text" class="form-control"
|
<input id="package-description" type="text" class="form-control"
|
||||||
value="{{ package.description }}"
|
value="{{ package.description|safe }}"
|
||||||
name="package-description">
|
name="package-description">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package Permission -->
|
<!-- Edit Package Permission -->
|
||||||
<div id="edit_package_permissions_modal" class="modal" tabindex="-1">
|
<div id="edit_package_permissions_modal" class="modal" tabindex="-1">
|
||||||
@ -46,7 +47,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<option disabled>Groups</option>
|
<option disabled>Groups</option>
|
||||||
{% for g in groups %}
|
{% for g in groups %}
|
||||||
<option value="{{ g.url }}">{{ g.name }}</option>
|
<option value="{{ g.url }}">{{ g.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -100,7 +101,7 @@
|
|||||||
accept-charset="UTF-8" action="" data-remote="true" method="post">
|
accept-charset="UTF-8" action="" data-remote="true" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4">
|
||||||
{{ gp.group.name }}
|
{{ gp.group.name|safe }}
|
||||||
<input type="hidden" name="grantee" value="{{ gp.group.url }}"/>
|
<input type="hidden" name="grantee" value="{{ gp.group.url }}"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-2">
|
<div class="col-xs-2">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Pathway -->
|
<!-- Edit Pathway -->
|
||||||
<div id="edit_pathway_modal" class="modal" tabindex="-1">
|
<div id="edit_pathway_modal" class="modal" tabindex="-1">
|
||||||
@ -15,12 +16,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="pathway-name">Name</label>
|
<label for="pathway-name">Name</label>
|
||||||
<input id="pathway-name" class="form-control" name="pathway-name" value="{{ pathway.name }}">
|
<input id="pathway-name" class="form-control" name="pathway-name" value="{{ pathway.name|safe }}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="pathway-description">Description</label>
|
<label for="pathway-description">Description</label>
|
||||||
<textarea id="pathway-description" type="text" class="form-control" name="pathway-description"
|
<textarea id="pathway-description" type="text" class="form-control" name="pathway-description"
|
||||||
rows="10">{{ pathway.description }}</textarea>
|
rows="10">{{ pathway.description|safe }}</textarea>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<select id="model" name="model" class="form-control" data-width='100%'>
|
<select id="model" name="model" class="form-control" data-width='100%'>
|
||||||
{% for m in models %}
|
{% for m in models %}
|
||||||
<option value="{{ m.id }}" {% if user.prediction_settings.model.url == m.url %}selected{% endif %}>{{ m.name }}</option>
|
<option value="{{ m.id }}" {% if user.prediction_settings.model.url == m.url %}selected{% endif %}>{{ m.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Reaction -->
|
<!-- Edit Reaction -->
|
||||||
<div id="edit_reaction_modal" class="modal" tabindex="-1">
|
<div id="edit_reaction_modal" class="modal" tabindex="-1">
|
||||||
@ -14,12 +15,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="reaction-name">Name</label>
|
<label for="reaction-name">Name</label>
|
||||||
<input id="reaction-name" class="form-control" name="reaction-name" value="{{ reaction.name }}">
|
<input id="reaction-name" class="form-control" name="reaction-name" value="{{ reaction.name|safe }}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="reaction-description">Description</label>
|
<label for="reaction-description">Description</label>
|
||||||
<input id="reaction-description" type="text" class="form-control"
|
<input id="reaction-description" type="text" class="form-control"
|
||||||
value="{{ reaction.description }}" name="reaction-description">
|
value="{{ reaction.description|safe }}" name="reaction-description">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Rule -->
|
<!-- Edit Rule -->
|
||||||
<div id="edit_rule_modal" class="modal" tabindex="-1">
|
<div id="edit_rule_modal" class="modal" tabindex="-1">
|
||||||
@ -14,12 +15,12 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<label for="rule-name">Name</label>
|
<label for="rule-name">Name</label>
|
||||||
<input id="rule-name" class="form-control" name="rule-name" value="{{ rule.name }}">
|
<input id="rule-name" class="form-control" name="rule-name" value="{{ rule.name|safe }}">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="rule-description">Description</label>
|
<label for="rule-description">Description</label>
|
||||||
<input id="rule-description" type="text" class="form-control"
|
<input id="rule-description" type="text" class="form-control"
|
||||||
value="{{ rule.description }}" name="rule-description">
|
value="{{ rule.description|safe }}" name="rule-description">
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit User -->
|
<!-- Edit User -->
|
||||||
<div id="edit_user_modal" class="modal" tabindex="-1">
|
<div id="edit_user_modal" class="modal" tabindex="-1">
|
||||||
@ -18,7 +19,7 @@
|
|||||||
<select id="default-package" name="default-package" class="form-control" data-width='100%'>
|
<select id="default-package" name="default-package" class="form-control" data-width='100%'>
|
||||||
<option disabled>Select a Package</option>
|
<option disabled>Select a Package</option>
|
||||||
{% for p in meta.writeable_packages %}
|
{% for p in meta.writeable_packages %}
|
||||||
<option value="{{ p.url }}" {% if p.id == meta.user.default_package.id %}selected{% endif %}>{{ p.name }}</option>
|
<option value="{{ p.url }}" {% if p.id == meta.user.default_package.id %}selected{% endif %}>{{ p.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
@ -27,7 +28,7 @@
|
|||||||
<select id="default-group" name="default-group" class="form-control" data-width='100%'>
|
<select id="default-group" name="default-group" class="form-control" data-width='100%'>
|
||||||
<option disabled>Select a Group</option>
|
<option disabled>Select a Group</option>
|
||||||
{% for g in meta.available_groups %}
|
{% for g in meta.available_groups %}
|
||||||
<option value="{{ g.url }}" {% if g.id == meta.user.default_group.id %}selected{% endif %}>{{ g.name }}</option>
|
<option value="{{ g.url }}" {% if g.id == meta.user.default_group.id %}selected{% endif %}>{{ g.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
@ -36,7 +37,7 @@
|
|||||||
<select id="default-prediction-setting" name="default-prediction-setting" class="form-control" data-width='100%'>
|
<select id="default-prediction-setting" name="default-prediction-setting" class="form-control" data-width='100%'>
|
||||||
<option disabled>Select a Setting</option>
|
<option disabled>Select a Setting</option>
|
||||||
{% for s in meta.available_settings %}
|
{% for s in meta.available_settings %}
|
||||||
<option value="{{ s.url }}" {% if s.id == meta.user.default_setting.id %}selected{% endif %}>{{ s.name }}</option>
|
<option value="{{ s.url }}" {% if s.id == meta.user.default_setting.id %}selected{% endif %}>{{ s.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<div class="modal fade" tabindex="-1" id="evaluate_model_modal" role="dialog" aria-labelledby="evaluate_model_modal"
|
<div class="modal fade" tabindex="-1" id="evaluate_model_modal" role="dialog" aria-labelledby="evaluate_model_modal"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
@ -17,24 +18,33 @@
|
|||||||
For evaluation, you need to select the packages you want to use.
|
For evaluation, you need to select the packages you want to use.
|
||||||
While the model is evaluating, you can use the model for predictions.
|
While the model is evaluating, you can use the model for predictions.
|
||||||
</div>
|
</div>
|
||||||
<!-- Evaluation -->
|
<!-- Evaluation Packages -->
|
||||||
<label for="relative-reasoning-evaluation-packages">Evaluation Packages</label>
|
<label for="model-evaluation-packages">Evaluation Packages</label>
|
||||||
<select id="relative-reasoning-evaluation-packages" name=relative-reasoning-evaluation-packages"
|
<select id="model-evaluation-packages" name="model-evaluation-packages" data-actions-box='true'
|
||||||
data-actions-box='true' class="form-control" multiple data-width='100%'>
|
class="form-control" multiple data-width='100%'>
|
||||||
<option disabled>Reviewed Packages</option>
|
<option disabled>Reviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<option disabled>Unreviewed Packages</option>
|
<option disabled>Unreviewed Packages</option>
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if not obj.reviewed %}
|
{% if not obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- Eval Type -->
|
||||||
|
<label for="model-evaluation-type">Evaluation Type</label>
|
||||||
|
<select id="model-evaluation-type" name="model-evaluation-type" class="form-control">
|
||||||
|
<option disabled selected>Select evaluation type</option>
|
||||||
|
<option value="sg">Single Generation</option>
|
||||||
|
<option value="mg">Multiple Generations</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<input type="hidden" name="hidden" value="evaluate">
|
<input type="hidden" name="hidden" value="evaluate">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -50,7 +60,7 @@
|
|||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
|
|
||||||
$("#relative-reasoning-evaluation-packages").selectpicker();
|
$("#model-evaluation-packages").selectpicker();
|
||||||
|
|
||||||
$('#evaluate_model_form_submit').on('click', function (e) {
|
$('#evaluate_model_form_submit').on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Copy Object -->
|
<!-- Copy Object -->
|
||||||
<div id="generic_copy_object_modal" class="modal" tabindex="-1">
|
<div id="generic_copy_object_modal" class="modal" tabindex="-1">
|
||||||
@ -18,7 +19,7 @@
|
|||||||
data-width='100%'>
|
data-width='100%'>
|
||||||
<option disabled selected>Select Target Package</option>
|
<option disabled selected>Select Target Package</option>
|
||||||
{% for p in meta.writeable_packages %}
|
{% for p in meta.writeable_packages %}
|
||||||
<option value="{{ p.url }}">{{ p.name }}</option>`
|
<option value="{{ p.url }}">{{ p.name|safe }}</option>`
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" name="hidden" value="copy">
|
<input type="hidden" name="hidden" value="copy">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -55,7 +56,7 @@
|
|||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">Set Aliases for {{ current_object.name }}</h4>
|
<h4 class="modal-title">Set Aliases for {{ current_object.name|safe }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="set_aliases_modal_form" accept-charset="UTF-8" action="{{ current_object.url }}"
|
<form id="set_aliases_modal_form" accept-charset="UTF-8" action="{{ current_object.url }}"
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Delete Object -->
|
<!-- Delete Object -->
|
||||||
<div id="generic_set_external_reference_modal" class="modal" tabindex="-1">
|
<div id="generic_set_external_reference_modal" class="modal" tabindex="-1">
|
||||||
@ -23,7 +24,7 @@
|
|||||||
{% if entity == object_type %}
|
{% if entity == object_type %}
|
||||||
{% for db in databases %}
|
{% for db in databases %}
|
||||||
<option id="db-select-{{ db.database.pk }}" data-input-placeholder="{{ db.placeholder }}"
|
<option id="db-select-{{ db.database.pk }}" data-input-placeholder="{{ db.placeholder }}"
|
||||||
value="{{ db.database.id }}">{{ db.database.name }}</option>`
|
value="{{ db.database.id }}">{{ db.database.name|safe }}</option>`
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<div class="modal fade bs-modal-lg" id="set_scenario_modal" tabindex="-1" aria-labelledby="set_scenario_modal"
|
<div class="modal fade bs-modal-lg" id="set_scenario_modal" tabindex="-1" aria-labelledby="set_scenario_modal"
|
||||||
aria-modal="true" role="dialog">
|
aria-modal="true" role="dialog">
|
||||||
@ -7,7 +8,7 @@
|
|||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<h4 class="modal-title">Set Scenarios for {{ current_object.name }}</h4>
|
<h4 class="modal-title">Set Scenarios for {{ current_object.name|safe }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div id="loading_scenario_div" class="text-center"></div>
|
<div id="loading_scenario_div" class="text-center"></div>
|
||||||
|
|||||||
54
templates/modals/objects/identify_missing_rules_modal.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!-- Identify Missing Rules -->
|
||||||
|
<div id="identify_missing_rules_modal" class="modal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title">Identify Missing Rules</h3>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
By clicking on Download we'll search the Pathway for Reactions that are not backed by
|
||||||
|
a Rule or which can be assembled by chaining two rules.
|
||||||
|
<form id="identify-missing-rules-modal-form" accept-charset="UTF-8" action="{{ pathway.url }}"
|
||||||
|
data-remote="true" method="GET">
|
||||||
|
<label for="rule-package">Select the Rule Package</label>
|
||||||
|
<select id="rule-package" name="rule-package" data-actions-box='true' class="form-control"
|
||||||
|
data-width='100%'>
|
||||||
|
<option disabled>Reviewed Packages</option>
|
||||||
|
{% for obj in meta.readable_packages %}
|
||||||
|
{% if obj.reviewed %}
|
||||||
|
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<option disabled>Unreviewed Packages</option>
|
||||||
|
{% for obj in meta.readable_packages %}
|
||||||
|
{% if not obj.reviewed %}
|
||||||
|
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="hidden" name="identify-missing-rules" value="true"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="identify-missing-rules-modal-submit">Download</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
$('#identify-missing-rules-modal-submit').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#identify-missing-rules-modal-form').submit();
|
||||||
|
$('#identify_missing_rules_modal').modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
id="manage_api_token_modal"
|
id="manage_api_token_modal"
|
||||||
@ -41,7 +42,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="hidden" name="hidden" value="delete">
|
<input type="hidden" name="hidden" value="delete">
|
||||||
<input type="hidden" name="token-id" value="{{ t.pk }}">
|
<input type="hidden" name="token-id" value="{{ t.pk }}">
|
||||||
<input type="text" class="form-control" value="{{ t.name }}" disabled>
|
<input type="text" class="form-control" value="{{ t.name|safe }}" disabled>
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button type="submit" class="btn btn-danger">Delete</button>
|
<button type="submit" class="btn btn-danger">Delete</button>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
<div class="modal fade" tabindex="-1" id="retrain_model_modal" role="dialog" aria-labelledby="retrain_model_modal"
|
<div
|
||||||
aria-hidden="true">
|
class="modal fade"
|
||||||
|
tabindex="-1"
|
||||||
|
id="retrain_model_modal"
|
||||||
|
role="dialog"
|
||||||
|
aria-labelledby="retrain_model_modal"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -10,34 +16,40 @@
|
|||||||
<h4 class="modal-title">Retrain Model</h4>
|
<h4 class="modal-title">Retrain Model</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="retrain_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model"
|
<form
|
||||||
data-remote="true" method="post">
|
id="retrain_model_form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{{ meta.current_object.url }}"
|
||||||
|
data-remote="true"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
To reflect changes in the rule or data packages, you can use the "Retrain" button,
|
To reflect changes in the rule or data packages, you can use the
|
||||||
to let the model reflect the changes without creating a new model.
|
"Retrain" button, to let the model reflect the changes without
|
||||||
While the model is retraining, it will be unavailable for prediction.
|
creating a new model. While the model is retraining, it will be
|
||||||
|
unavailable for prediction.
|
||||||
</div>
|
</div>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="retrain">
|
<input type="hidden" name="hidden" value="retrain" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a id="retrain_model_form_submit" class="btn btn-primary" href="#">Retrain</a>
|
<a id="retrain_model_form_submit" class="btn btn-primary" href="#"
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
>Retrain</a
|
||||||
|
>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
|
$("#retrain_model_form_submit").on("click", function (e) {
|
||||||
$('#retrain_model_form_submit').on('click', function (e) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#retrain_model_form').submit();
|
$("#retrain_model_form").submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -52,8 +52,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="license" name="license">
|
<input type="hidden" id="license" name="license">
|
||||||
<input type="hidden" id="license-link" name="license-link">
|
|
||||||
<input type="hidden" id="license-image-link" name="license-image-link">
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -128,8 +126,6 @@ function cc() {
|
|||||||
|
|
||||||
$('#ccfig').append(img_tpl);
|
$('#ccfig').append(img_tpl);
|
||||||
$('#license').val(ccstr);
|
$('#license').val(ccstr);
|
||||||
$('#license-link').val(link);
|
|
||||||
$('#license-image-link').val(imageLink);
|
|
||||||
} else {
|
} else {
|
||||||
$('#ccfig').empty();
|
$('#ccfig').empty();
|
||||||
$('#set_license_form_submit').prop('disabled', true);
|
$('#set_license_form_submit').prop('disabled', true);
|
||||||
|
|||||||
@ -56,7 +56,7 @@
|
|||||||
<option disabled>Select a Setting</option>
|
<option disabled>Select a Setting</option>
|
||||||
{% for s in meta.available_settings %}
|
{% for s in meta.available_settings %}
|
||||||
<option value="{{ s.url }}"{% if s.id == meta.user.default_setting.id %}selected{% endif %}>
|
<option value="{{ s.url }}"{% if s.id == meta.user.default_setting.id %}selected{% endif %}>
|
||||||
{{ s.name }}{% if s.id == meta.user.default_setting.id %} <i>(User default)</i>{% endif %}
|
{{ s.name|safe }}{% if s.id == meta.user.default_setting.id %} <i>(User default)</i>{% endif %}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
535
templates/modals/search_modal.html
Normal file
@ -0,0 +1,535 @@
|
|||||||
|
{% load static %}
|
||||||
|
<dialog id="search_modal" class="modal @max-sm:modal-top justify-center">
|
||||||
|
<div class="modal-box w-lvw sm:w-[85vw] sm:max-w-5xl h-full sm:h-8/12 p-1" >
|
||||||
|
|
||||||
|
<!-- Search Input and Mode Selector -->
|
||||||
|
<div class="form-control mb-4 flex-shrink-0 w-full">
|
||||||
|
<div class="join w-full m-0 p-3 items-center">
|
||||||
|
<label class="input join-item input-ghost grow">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg>
|
||||||
|
<input type="text" autofocus
|
||||||
|
id="modal_searchbar"
|
||||||
|
placeholder="Benfuracarb"
|
||||||
|
class="grow" aria-label="Search" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Mode Dropdown -->
|
||||||
|
<div>
|
||||||
|
<button type="button"
|
||||||
|
tabindex="0"
|
||||||
|
id="modal_mode_button"
|
||||||
|
popovertarget="search_dropdown_menu" style="anchor-name:--anchor-1"
|
||||||
|
class="btn join-item btn-ghost">
|
||||||
|
Text
|
||||||
|
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<ul tabindex="0"" class="dropdown dropdown-end menu bg-base-200 rounded-box z-[100] w-64 p-2 shadow-lg" popover id="search_dropdown_menu" style="position-anchor:--anchor-1">
|
||||||
|
<li class="menu-title">Text</li>
|
||||||
|
<li>
|
||||||
|
<a id="modal_dropdown_text"
|
||||||
|
class="tooltip tooltip-left"
|
||||||
|
data-tip="Search on object names and descriptions">
|
||||||
|
Text
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-title">SMILES</li>
|
||||||
|
<li>
|
||||||
|
<a id="modal_dropdown_smiles_default"
|
||||||
|
class="tooltip tooltip-left"
|
||||||
|
data-tip="Ignores stereochemistry and charge">
|
||||||
|
Default
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a id="modal_dropdown_smiles_canonical"
|
||||||
|
class="tooltip tooltip-left"
|
||||||
|
data-tip="Ignores stereochemistry, preserves charge">
|
||||||
|
Canonical
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a id="modal_dropdown_smiles_exact"
|
||||||
|
class="tooltip tooltip-left"
|
||||||
|
data-tip="Exact match for stereochemistry and charge">
|
||||||
|
Exact
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-title">InChI</li>
|
||||||
|
<li>
|
||||||
|
<a id="modal_dropdown_inchikey"
|
||||||
|
class="tooltip tooltip-left"
|
||||||
|
data-tip="Search by InChIKey">
|
||||||
|
InChIKey
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" id="modal_search_button" class="btn btn-xs btn-ghost join-item">
|
||||||
|
<kbd class="kbd kbd-sm p-1 text-base-content/50">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-corner-down-left-icon lucide-corner-down-left"><path d="M20 4v7a4 4 0 0 1-4 4H4"/><path d="m9 10-5 5 5 5"/></svg>
|
||||||
|
</kbd>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Package Selector with Pills -->
|
||||||
|
<div class="form-control mb-4 flex-shrink-0">
|
||||||
|
<!-- Pills Container -->
|
||||||
|
<div id="modal_package_pills_container"
|
||||||
|
class="flex flex-wrap gap-2 p-3 border-2 border-dashed border-base-300 rounded-lg m-3 min-h-[3rem] items-center">
|
||||||
|
<!-- Pills will be added here dynamically -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Package Dropdown Menu -->
|
||||||
|
<ul class="dropdown dropdown-end menu bg-base-200 rounded-box z-[100] w-80 max-h-96 overflow-y-auto p-2 shadow-lg"
|
||||||
|
popover
|
||||||
|
id="package_dropdown_menu"
|
||||||
|
style="position-anchor:--anchor-packages">
|
||||||
|
{% if unreviewed_packages %}
|
||||||
|
<li class="menu-title">Reviewed Packages</li>
|
||||||
|
{% for obj in reviewed_packages %}
|
||||||
|
<li>
|
||||||
|
<a class="package-option flex justify-between items-center"
|
||||||
|
data-package-url="{{ obj.url }}"
|
||||||
|
data-package-name="{{ obj.name }}">
|
||||||
|
<span>{{ obj.name }}</span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 package-checkmark hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
<li class="menu-title">Unreviewed Packages</li>
|
||||||
|
{% for obj in unreviewed_packages %}
|
||||||
|
<li>
|
||||||
|
<a class="package-option flex justify-between items-center"
|
||||||
|
data-package-url="{{ obj.url }}"
|
||||||
|
data-package-name="{{ obj.name }}">
|
||||||
|
<span>{{ obj.name }}</span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 package-checkmark hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<li class="menu-title">Reviewed Packages</li>
|
||||||
|
{% for obj in reviewed_packages %}
|
||||||
|
<li>
|
||||||
|
<a class="package-option flex justify-between items-center"
|
||||||
|
data-package-url="{{ obj.url }}"
|
||||||
|
data-package-name="{{ obj.name }}">
|
||||||
|
<span>{{ obj.name }}</span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 package-checkmark hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading Indicator -->
|
||||||
|
<div id="search_loading" class="hidden justify-center py-8 flex-shrink-0">
|
||||||
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Container - scrollable -->
|
||||||
|
<div id="search_results" class="flex-1 overflow-y-auto min-h-0 p-2"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Backdrop to close -->
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
// Package Selector Module - Data-driven multiselect package selection
|
||||||
|
const PackageSelector = {
|
||||||
|
// Single source of truth: array of selected packages
|
||||||
|
selectedPackages: [],
|
||||||
|
|
||||||
|
elements: {
|
||||||
|
pillsContainer: null,
|
||||||
|
packageDropdown: null,
|
||||||
|
packageOptions: null
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.cacheElements();
|
||||||
|
this.loadInitialSelection();
|
||||||
|
this.attachEventListeners();
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
cacheElements() {
|
||||||
|
this.elements.pillsContainer = document.getElementById('modal_package_pills_container');
|
||||||
|
this.elements.packageDropdown = document.getElementById('package_dropdown_menu');
|
||||||
|
this.elements.packageOptions = document.querySelectorAll('.package-option');
|
||||||
|
},
|
||||||
|
|
||||||
|
loadInitialSelection() {
|
||||||
|
// Load pre-selected packages from server-rendered pills
|
||||||
|
const existingPills = this.elements.pillsContainer.querySelectorAll('.badge');
|
||||||
|
existingPills.forEach(pill => {
|
||||||
|
this.selectedPackages.push({
|
||||||
|
url: pill.dataset.packageUrl,
|
||||||
|
name: pill.dataset.packageName
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no pills found, select all reviewed packages by default
|
||||||
|
if (this.selectedPackages.length === 0) {
|
||||||
|
// Iterate through all menu items and collect reviewed packages
|
||||||
|
const menuItems = this.elements.packageDropdown.querySelectorAll('li');
|
||||||
|
|
||||||
|
for (const item of menuItems) {
|
||||||
|
// Check if this is the "Unreviewed Packages" menu title
|
||||||
|
if (item.classList.contains('menu-title') &&
|
||||||
|
item.textContent.trim() === 'Unreviewed Packages') {
|
||||||
|
break; // Stop processing after this point
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for package options (only reviewed packages reach here)
|
||||||
|
const packageOption = item.querySelector('.package-option');
|
||||||
|
if (packageOption) {
|
||||||
|
this.selectedPackages.push({
|
||||||
|
url: packageOption.dataset.packageUrl,
|
||||||
|
name: packageOption.dataset.packageName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
attachEventListeners() {
|
||||||
|
// Toggle package selection on dropdown item click
|
||||||
|
this.elements.packageOptions.forEach(option => {
|
||||||
|
option.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation(); // Prevent dropdown from closing
|
||||||
|
const packageUrl = option.dataset.packageUrl;
|
||||||
|
const packageName = option.dataset.packageName;
|
||||||
|
this.togglePackageSelection(packageUrl, packageName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove package when X is clicked (using event delegation)
|
||||||
|
this.elements.pillsContainer.addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('package-remove-btn') || e.target.closest('.package-remove-btn')) {
|
||||||
|
const pill = e.target.closest('.badge');
|
||||||
|
if (pill) {
|
||||||
|
const packageUrl = pill.dataset.packageUrl;
|
||||||
|
this.removePackage(packageUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
togglePackageSelection(packageUrl, packageName) {
|
||||||
|
const index = this.selectedPackages.findIndex(pkg => pkg.url === packageUrl);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
// Remove from selection
|
||||||
|
this.selectedPackages.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
// Add to selection
|
||||||
|
this.selectedPackages.push({ url: packageUrl, name: packageName });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
removePackage(packageUrl) {
|
||||||
|
const index = this.selectedPackages.findIndex(pkg => pkg.url === packageUrl);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.selectedPackages.splice(index, 1);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.renderPills();
|
||||||
|
this.renderAddButton();
|
||||||
|
this.renderCheckmarks();
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPills() {
|
||||||
|
// Clear existing pills and button (except placeholder)
|
||||||
|
const pills = this.elements.pillsContainer.querySelectorAll('.badge');
|
||||||
|
pills.forEach(pill => pill.remove());
|
||||||
|
|
||||||
|
const existingButton = this.elements.pillsContainer.querySelector('#modal_package_add_button');
|
||||||
|
if (existingButton) {
|
||||||
|
existingButton.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pills from data
|
||||||
|
this.selectedPackages.forEach(pkg => {
|
||||||
|
const pill = this.createPillElement(pkg.url, pkg.name);
|
||||||
|
this.elements.pillsContainer.appendChild(pill);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderAddButton() {
|
||||||
|
// Only render button if there are packages available
|
||||||
|
if (this.elements.packageOptions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.id = 'modal_package_add_button';
|
||||||
|
button.setAttribute('popovertarget', 'package_dropdown_menu');
|
||||||
|
button.style.cssText = 'anchor-name:--anchor-packages';
|
||||||
|
button.className = 'btn btn-sm btn-ghost gap-2 text-base-content/50';
|
||||||
|
|
||||||
|
button.innerHTML = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus-icon lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
|
||||||
|
Add Package
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.elements.pillsContainer.appendChild(button);
|
||||||
|
},
|
||||||
|
|
||||||
|
createPillElement(packageUrl, packageName) {
|
||||||
|
const pill = document.createElement('span');
|
||||||
|
pill.className = 'badge badge-outline gap-2 max-w-xs';
|
||||||
|
pill.dataset.packageUrl = packageUrl;
|
||||||
|
pill.dataset.packageName = packageName;
|
||||||
|
|
||||||
|
pill.innerHTML = `
|
||||||
|
<span class="truncate" title="${packageName}">${packageName}</span>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-4 w-4 cursor-pointer hover:text-error package-remove-btn flex-shrink-0 rotate-45"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M5 12h14"/><path d="M12 5v14"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return pill;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCheckmarks() {
|
||||||
|
// Update all checkmarks based on selected packages
|
||||||
|
this.elements.packageOptions.forEach(option => {
|
||||||
|
const packageUrl = option.dataset.packageUrl;
|
||||||
|
const isSelected = this.selectedPackages.some(pkg => pkg.url === packageUrl);
|
||||||
|
const checkmark = option.querySelector('.package-checkmark');
|
||||||
|
|
||||||
|
if (checkmark) {
|
||||||
|
checkmark.classList.toggle('hidden', !isSelected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getSelectedPackages() {
|
||||||
|
return this.selectedPackages.map(pkg => pkg.url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modal and Search Management
|
||||||
|
const modal = document.getElementById('search_modal');
|
||||||
|
const searchbar = document.getElementById('modal_searchbar');
|
||||||
|
const searchButton = document.getElementById('modal_search_button');
|
||||||
|
const modeButton = document.getElementById('modal_mode_button');
|
||||||
|
const resultsDiv = document.getElementById('search_results');
|
||||||
|
const loadingDiv = document.getElementById('search_loading');
|
||||||
|
|
||||||
|
// MutationObserver to detect when modal opens
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.attributeName === 'open' && modal.open) {
|
||||||
|
PackageSelector.render();
|
||||||
|
// Delay focus to allow CSS transitions to complete (modal has 0.3s transition)
|
||||||
|
setTimeout(() => {
|
||||||
|
searchbar.focus();
|
||||||
|
}, 320);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(modal, { attributes: true });
|
||||||
|
|
||||||
|
// Clear results when modal closes
|
||||||
|
modal.addEventListener('close', function() {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
loadingDiv.classList.add('hidden');
|
||||||
|
searchbar.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Mode dropdown handlers
|
||||||
|
const dropdownMenu = document.getElementById('search_dropdown_menu');
|
||||||
|
|
||||||
|
const modeButtons = [
|
||||||
|
{ id: 'modal_dropdown_text', text: 'Text' },
|
||||||
|
{ id: 'modal_dropdown_smiles_default', text: 'Default' },
|
||||||
|
{ id: 'modal_dropdown_smiles_canonical', text: 'Canonical' },
|
||||||
|
{ id: 'modal_dropdown_smiles_exact', text: 'Exact' },
|
||||||
|
{ id: 'modal_dropdown_inchikey', text: 'InChIKey' }
|
||||||
|
];
|
||||||
|
|
||||||
|
modeButtons.forEach(({ id, text }) => {
|
||||||
|
document.getElementById(id).addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
modeButton.innerHTML = text + ` <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
|
</svg>`;
|
||||||
|
// Close dropdown using popover API
|
||||||
|
if (dropdownMenu && typeof dropdownMenu.hidePopover === 'function') {
|
||||||
|
dropdownMenu.hidePopover();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize Package Selector
|
||||||
|
PackageSelector.init();
|
||||||
|
|
||||||
|
// Search Response Handler
|
||||||
|
function handleSearchResponse(data) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
|
||||||
|
function makeContent(objs) {
|
||||||
|
let links = '';
|
||||||
|
objs.forEach(obj => {
|
||||||
|
links += `<a href="${obj.url}" class="block px-4 py-2 hover:bg-base-300 rounded-lg transition-colors">${obj.name}</a>`;
|
||||||
|
});
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
let allEmpty = true;
|
||||||
|
let content = '';
|
||||||
|
|
||||||
|
// Category order for better UX
|
||||||
|
const categoryOrder = ['Compounds', 'Compound Structures', 'Rules', 'Reactions', 'Pathways'];
|
||||||
|
|
||||||
|
categoryOrder.forEach(key => {
|
||||||
|
if (!data[key] || data[key].length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allEmpty = false;
|
||||||
|
|
||||||
|
content += `
|
||||||
|
<div class="collapse collapse-arrow bg-base-200 mb-2">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title font-medium">
|
||||||
|
${key} <span class="badge badge-neutral badge-sm ml-2">${data[key].length}</span>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
${makeContent(data[key])}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allEmpty) {
|
||||||
|
resultsDiv.innerHTML = `
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||||
|
</svg>
|
||||||
|
<span>No results found</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
resultsDiv.innerHTML = `
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class="text-sm font-semibold text-base-content/70 mb-2">Results</div>
|
||||||
|
${content}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search Execution
|
||||||
|
function performSearch(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const query = searchbar.value.trim();
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
console.log('Search phrase empty, won\'t do search');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selPacks = PackageSelector.getSelectedPackages();
|
||||||
|
|
||||||
|
if (selPacks.length < 1) {
|
||||||
|
console.log('No package selected, won\'t do search');
|
||||||
|
resultsDiv.innerHTML = `
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
<span>Please select at least one package</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = modeButton.textContent.trim().toLowerCase();
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
selPacks.forEach(pack => params.append('packages', pack));
|
||||||
|
params.append('search', query);
|
||||||
|
params.append('mode', mode);
|
||||||
|
|
||||||
|
// Show loading
|
||||||
|
loadingDiv.classList.remove('hidden');
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
|
||||||
|
fetch(`{{ SERVER_BASE }}/search?${params.toString()}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Search request failed');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
loadingDiv.classList.add('hidden');
|
||||||
|
handleSearchResponse(result);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
loadingDiv.classList.add('hidden');
|
||||||
|
console.error('Search error:', error);
|
||||||
|
resultsDiv.innerHTML = `
|
||||||
|
<div class="alert alert-error">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
<span>Search failed. Please try again.</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners for search
|
||||||
|
searchButton.addEventListener('click', performSearch);
|
||||||
|
searchbar.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
performSearch(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@ -1,110 +0,0 @@
|
|||||||
<div class="modal fade bs-modal-sm" id="signupmodal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel"
|
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-sm">
|
|
||||||
<div class="modal-content">
|
|
||||||
<br>
|
|
||||||
<div class="bs-example bs-example-tabs">
|
|
||||||
<ul id="myTab" class="nav nav-tabs">
|
|
||||||
<li class="active">
|
|
||||||
<a href="#signin" data-toggle="tab">Sign In</a>
|
|
||||||
</li>
|
|
||||||
<li class="">
|
|
||||||
<a href="#signup" data-toggle="tab">Register</a>
|
|
||||||
</li>
|
|
||||||
<li class="">
|
|
||||||
<a href="#why" data-toggle="tab">Why?</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div id="myTabContent" class="tab-content">
|
|
||||||
<div class="tab-pane fade active in" id="signin">
|
|
||||||
<form class="form-horizontal" method="post" action="{% url 'login' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset>
|
|
||||||
<input type="hidden" name="login" id="login" value="true"/>
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="username">Username:</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input required id="username" name="username" type="text" class="form-control"
|
|
||||||
placeholder="username" autocomplete="username">
|
|
||||||
</div>
|
|
||||||
<label class="control-label" for="passwordinput">Password:</label>
|
|
||||||
<div class="controls">
|
|
||||||
<input required id="passwordinput" name="password" class="form-control"
|
|
||||||
type="password" placeholder="********" autocomplete="current-password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Button -->
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="signin"></label>
|
|
||||||
<div class="controls">
|
|
||||||
<button id="signin" name="signin" class="btn btn-success">Sign In</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Why tab -->
|
|
||||||
<div class="tab-pane fade in" id="why">
|
|
||||||
<p>After you register, you have more permissions on
|
|
||||||
this site, e.g., can create your own
|
|
||||||
packages, submit data for review, and set access
|
|
||||||
permissions to your data.</p>
|
|
||||||
<p></p>
|
|
||||||
<p>
|
|
||||||
<br> Please
|
|
||||||
contact <a href="mailto:admin@envipath.org">admin@envipath.org</a>
|
|
||||||
if you have any questions.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Register -->
|
|
||||||
<div class="tab-pane fade"
|
|
||||||
id="signup">
|
|
||||||
<div id="passwordGuideline" class="alert alert-info">
|
|
||||||
The password must contain 8 to 30 characters<br>
|
|
||||||
The following characters are allowed:
|
|
||||||
- Upper and lower case characters<br>
|
|
||||||
- Digits<br>
|
|
||||||
- Special characters _, -, +<br>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="signup-action" class="form-horizontal" action="{% url 'login' %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="register" id="register" value="true"/>
|
|
||||||
|
|
||||||
<label class="control-label" for="userid">Username:</label>
|
|
||||||
<input id="userid" name="username" class="form-control" type="text" placeholder="user" required autocomplete="username">
|
|
||||||
|
|
||||||
<label class="control-label" for="email">Email:</label>
|
|
||||||
<input id="email" name="email" class="form-control" type="email" placeholder="user@envipath.org" required>
|
|
||||||
|
|
||||||
<label class="control-label" for="password">Password:</label>
|
|
||||||
<input id="password" name="password" class="form-control" type="password" placeholder="********" required autocomplete="new-password">
|
|
||||||
|
|
||||||
<label class="control-label" for="rpassword">Repeat Password:</label>
|
|
||||||
<input id="rpassword" name="rpassword" class="form-control" type="password" placeholder="********" required autocomplete="new-password">
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="confirmsignup"></label>
|
|
||||||
<div class="controls">
|
|
||||||
<button id="confirmsignup" name="confirmsignup" class="btn btn-success">Sign Up
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<center>
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<div class="panel-group" id="rule-detail">
|
<div class="panel-group" id="rule-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ rule.name }}
|
{{ rule.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -60,7 +60,7 @@
|
|||||||
<div id="rule-reaction-patterns" class="panel-collapse collapse in">
|
<div id="rule-reaction-patterns" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in rule.srs %}
|
{% for r in rule.srs %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }}</a>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<p>
|
<p>
|
||||||
{{r.as_svg|safe}}
|
{{r.as_svg|safe}}
|
||||||
@ -81,7 +81,7 @@
|
|||||||
<div id="rule-scenario" class="panel-collapse collapse in">
|
<div id="rule-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in rule.scenarios.all %}
|
{% for s in rule.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
<div class="panel-group" id="compound-detail">
|
<div class="panel-group" id="compound-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ compound.name }}
|
{{ compound.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="compound-desc" class="panel-collapse collapse in">
|
<div id="compound-desc" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{{ compound.description }}
|
{{ compound.description|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -133,7 +133,7 @@
|
|||||||
<div id="compound-reaction" class="panel-collapse collapse in">
|
<div id="compound-reaction" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in compound.related_reactions %}
|
{% for r in compound.related_reactions %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }} <i>({{ r.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -150,7 +150,7 @@
|
|||||||
<div id="compound-pathway" class="panel-collapse collapse in">
|
<div id="compound-pathway" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in compound.related_pathways %}
|
{% for r in compound.related_pathways %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }} <i>({{ r.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -167,7 +167,7 @@
|
|||||||
<div id="compound-scenario" class="panel-collapse collapse in">
|
<div id="compound-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in compound.scenarios.all %}
|
{% for s in compound.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -183,7 +183,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="compound-external-identifier" class="panel-collapse collapse in">
|
<div id="compound-external-identifier" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% if compound.get_pubchem_identifiers %}
|
{% if compound.get_pubchem_compound_identifiers %}
|
||||||
<div class="panel panel-default panel-heading list-group-item"
|
<div class="panel panel-default panel-heading list-group-item"
|
||||||
style="background-color:silver">
|
style="background-color:silver">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
@ -193,12 +193,28 @@
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
|
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
|
||||||
{% for eid in compound.get_pubchem_identifiers %}
|
{% for eid in compound.get_pubchem_compound_identifiers %}
|
||||||
<a class="list-group-item"
|
<a class="list-group-item"
|
||||||
href="{{ eid.external_url }}">CID{{ eid.identifier_value }}</a>
|
href="{{ eid.external_url }}">CID{{ eid.identifier_value }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if compound.get_pubchem_substance_identifiers %}
|
||||||
|
<div class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a id="compound-pubchem-identifier-link" data-toggle="collapse"
|
||||||
|
data-parent="#compound-external-identifier"
|
||||||
|
href="#compound-pubchem-identifier">PubChem Substance Identifier</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
|
||||||
|
{% for eid in compound.get_pubchem_substance_identifiers %}
|
||||||
|
<a class="list-group-item"
|
||||||
|
href="{{ eid.external_url }}">SID{{ eid.identifier_value }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if compound.get_chebi_identifiers %}
|
{% if compound.get_chebi_identifiers %}
|
||||||
<div class="panel panel-default panel-heading list-group-item"
|
<div class="panel panel-default panel-heading list-group-item"
|
||||||
style="background-color:silver">
|
style="background-color:silver">
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<div class="panel-group" id="compound-structure-detail">
|
<div class="panel-group" id="compound-structure-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ compound_structure.name }}
|
{{ compound_structure.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p> {{ compound_structure.description }} </p>
|
<p> {{ compound_structure.description|safe }} </p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
@ -86,7 +86,7 @@
|
|||||||
<div id="compound-structure-scenario" class="panel-collapse collapse in">
|
<div id="compound-structure-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in compound_structure.scenarios.all %}
|
{% for s in compound_structure.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
<div class="panel-group" id="edge-detail">
|
<div class="panel-group" id="edge-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ edge.edge_label.name }}
|
{{ edge.edge_label.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="edge-desc" class="panel-collapse collapse in">
|
<div id="edge-desc" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{{ edge.description }}
|
{{ edge.description|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -82,12 +82,12 @@
|
|||||||
<div id="edge-description-smiles" class="panel-collapse collapse in">
|
<div id="edge-description-smiles" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for educt in edge.start_nodes.all %}
|
{% for educt in edge.start_nodes.all %}
|
||||||
<a class="btn btn-default" href="{{ educt.url }}">{{ educt.name }}</a>
|
<a class="btn btn-default" href="{{ educt.url }}">{{ educt.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<span class="glyphicon glyphicon-arrow-right" style="margin-left:5em;margin-right:5em;"
|
<span class="glyphicon glyphicon-arrow-right" style="margin-left:5em;margin-right:5em;"
|
||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
{% for product in edge.end_nodes.all %}
|
{% for product in edge.end_nodes.all %}
|
||||||
<a class="btn btn-default" href="{{ product.url }}">{{ product.name }}</a>
|
<a class="btn btn-default" href="{{ product.url }}">{{ product.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -116,7 +116,7 @@
|
|||||||
<div id="edge-rules" class="panel-collapse collapse in">
|
<div id="edge-rules" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in edge.edge_label.rules.all %}
|
{% for r in edge.edge_label.rules.all %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -132,7 +132,7 @@
|
|||||||
<div id="edge-scenario" class="panel-collapse collapse in">
|
<div id="edge-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in edge.scenarios.all %}
|
{% for s in edge.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<div class="panel-group" id="package-detail">
|
<div class="panel-group" id="package-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ group.name }}
|
{{ group.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p> {{ group.description }} </p>
|
<p> {{ group.description|safe }} </p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -39,10 +39,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{% for um in group.user_member.all %}
|
{% for um in group.user_member.all %}
|
||||||
<a class="list-group-item" href="{{ um.url }}">{{ um.username }}</a>
|
<a class="list-group-item" href="{{ um.url }}">{{ um.username|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for gm in group.group_member.all %}
|
{% for gm in group.group_member.all %}
|
||||||
<a class="list-group-item" href="{{ gm.url }}">{{ gm.name }}</a>
|
<a class="list-group-item" href="{{ gm.url }}">{{ gm.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{% for p in packages %}
|
{% for p in packages %}
|
||||||
<a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
|
<a class="list-group-item" href="{{ p.url }}">{{ p.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -13,18 +13,34 @@
|
|||||||
<!-- Include required libs -->
|
<!-- Include required libs -->
|
||||||
<script src="https://d3js.org/d3.v5.min.js"></script>
|
<script src="https://d3js.org/d3.v5.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.js"></script>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.css" rel="stylesheet">
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/c3@0.7.20/c3.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="panel-group" id="model-detail">
|
<div class="panel-group" id="model-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div
|
||||||
{{ model.name }}
|
class="panel-heading"
|
||||||
<div id="actionsButton"
|
id="headingPanel"
|
||||||
|
style="font-size:2rem;height: 46px"
|
||||||
|
>
|
||||||
|
{{ model.name|safe }}
|
||||||
|
<div
|
||||||
|
id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"
|
||||||
aria-haspopup="true" aria-expanded="false"><span
|
>
|
||||||
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
|
<a
|
||||||
style="padding-right:1em"></span></a>
|
href="#"
|
||||||
|
class="dropdown-toggle"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
role="button"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
><span class="glyphicon glyphicon-wrench"></span> Actions
|
||||||
|
<span class="caret"></span><span style="padding-right:1em"></span
|
||||||
|
></a>
|
||||||
<ul id="actionsList" class="dropdown-menu">
|
<ul id="actionsList" class="dropdown-menu">
|
||||||
{% block actions %}
|
{% block actions %}
|
||||||
{% include "actions/objects/model.html" %}
|
{% include "actions/objects/model.html" %}
|
||||||
@ -33,81 +49,135 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p> {{ model.description }} </p>
|
<p>{{ model.description|safe }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% if model|classname == 'MLRelativeReasoning' or model|classname == 'RuleBasedRelativeReasoning'%}
|
{% if model|classname == 'MLRelativeReasoning' or model|classname == 'RuleBasedRelativeReasoning' %}
|
||||||
<!-- Rule Packages -->
|
<!-- Rule Packages -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div
|
||||||
|
class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver"
|
||||||
|
>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="rule-package-link" data-toggle="collapse" data-parent="#model-detail"
|
<a
|
||||||
href="#rule-package">Rule Packages</a>
|
id="rule-package-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#model-detail"
|
||||||
|
href="#rule-package"
|
||||||
|
>Rule Packages</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="rule-package" class="panel-collapse collapse in">
|
<div id="rule-package" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for p in model.rule_packages.all %}
|
{% for p in model.rule_packages.all %}
|
||||||
<a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
|
<a class="list-group-item" href="{{ p.url }}"
|
||||||
|
>{{ p.name|safe }}</a
|
||||||
|
>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Reaction Packages -->
|
<!-- Reaction Packages -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div
|
||||||
|
class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver"
|
||||||
|
>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="reaction-package-link" data-toggle="collapse" data-parent="#model-detail"
|
<a
|
||||||
href="#reaction-package">Rule Packages</a>
|
id="reaction-package-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#model-detail"
|
||||||
|
href="#reaction-package"
|
||||||
|
>Rule Packages</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="reaction-package" class="panel-collapse collapse in">
|
<div id="reaction-package" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for p in model.data_packages.all %}
|
{% for p in model.data_packages.all %}
|
||||||
<a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
|
<a class="list-group-item" href="{{ p.url }}"
|
||||||
|
>{{ p.name|safe }}</a
|
||||||
|
>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if model.eval_packages.all|length > 0 %}
|
{% if model.eval_packages.all|length > 0 %}
|
||||||
<!-- Eval Packages -->
|
<!-- Eval Packages -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div
|
||||||
|
class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver"
|
||||||
|
>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="eval-package-link" data-toggle="collapse" data-parent="#model-detail"
|
<a
|
||||||
href="#eval-package">Rule Packages</a>
|
id="eval-package-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#model-detail"
|
||||||
|
href="#eval-package"
|
||||||
|
>Rule Packages</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="eval-package" class="panel-collapse collapse in">
|
<div id="eval-package" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for p in model.eval_packages.all %}
|
{% for p in model.eval_packages.all %}
|
||||||
<a class="list-group-item" href="{{ p.url }}">{{ p.name }}</a>
|
<a class="list-group-item" href="{{ p.url }}"
|
||||||
|
>{{ p.name|safe }}</a
|
||||||
|
>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Model Status -->
|
<!-- Model Status -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div
|
||||||
|
class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver"
|
||||||
|
>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="model-status-link" data-toggle="collapse" data-parent="#model-detail"
|
<a
|
||||||
href="#model-status">Model Status</a>
|
id="model-status-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#model-detail"
|
||||||
|
href="#model-status"
|
||||||
|
>Model Status</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="model-status" class="panel-collapse collapse in">
|
<div id="model-status" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">{{ model.status }}</div>
|
||||||
{{ model.status }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if model.ready_for_prediction %}
|
{% if model.ready_for_prediction %}
|
||||||
<!-- Predict Panel -->
|
<!-- Predict Panel -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div
|
||||||
|
class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver"
|
||||||
|
>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="predict-smiles-link" data-toggle="collapse" data-parent="#model-detail"
|
<a
|
||||||
href="#predict-smiles">Predict</a>
|
id="predict-smiles-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#model-detail"
|
||||||
|
href="#predict-smiles"
|
||||||
|
>Predict</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="predict-smiles" class="panel-collapse collapse in">
|
<div id="predict-smiles" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="smiles-to-predict" type="text" class="form-control"
|
<input
|
||||||
placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C">
|
id="smiles-to-predict"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C"
|
||||||
|
/>
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button class="btn btn-default" type="submit" id="predict-button">Predict!</button>
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
type="submit"
|
||||||
|
id="predict-button"
|
||||||
|
>
|
||||||
|
Predict!
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="predictLoading"></div>
|
<div id="predictLoading"></div>
|
||||||
@ -117,20 +187,39 @@
|
|||||||
<!-- End Predict Panel -->
|
<!-- End Predict Panel -->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if model.app_domain %}
|
{% if model.ready_for_prediction and model.app_domain %}
|
||||||
<!-- App Domain -->
|
<!-- App Domain -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div
|
||||||
|
class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver"
|
||||||
|
>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="app-domain-assessment-link" data-toggle="collapse" data-parent="#model-detail"
|
<a
|
||||||
href="#app-domain-assessment">Applicability Domain Assessment</a>
|
id="app-domain-assessment-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#model-detail"
|
||||||
|
href="#app-domain-assessment"
|
||||||
|
>Applicability Domain Assessment</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="app-domain-assessment" class="panel-collapse collapse in">
|
<div id="app-domain-assessment" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="smiles-to-assess" type="text" class="form-control" placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C">
|
<input
|
||||||
|
id="smiles-to-assess"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C"
|
||||||
|
/>
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button class="btn btn-default" type="submit" id="assess-button">Assess!</button>
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
type="submit"
|
||||||
|
id="assess-button"
|
||||||
|
>
|
||||||
|
Assess!
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="appDomainLoading"></div>
|
<div id="appDomainLoading"></div>
|
||||||
@ -142,23 +231,34 @@
|
|||||||
|
|
||||||
{% if model.model_status == 'FINISHED' %}
|
{% if model.model_status == 'FINISHED' %}
|
||||||
<!-- Single Gen Curve Panel -->
|
<!-- Single Gen Curve Panel -->
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div
|
||||||
|
class="panel panel-default panel-heading list-group-item"
|
||||||
|
style="background-color:silver"
|
||||||
|
>
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a id="sg-curve-link" data-toggle="collapse" data-parent="#model-detail"
|
<a
|
||||||
href="#sg-curve">Precision Recall Curve</a>
|
id="sg-curve-link"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-parent="#model-detail"
|
||||||
|
href="#sg-curve"
|
||||||
|
>Precision Recall Curve</a
|
||||||
|
>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="sg-curve" class="panel-collapse collapse in">
|
<div id="sg-curve" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<!-- Center container contents -->
|
<!-- Center container contents -->
|
||||||
<div class="container" style="display: flex;justify-content: center;">
|
<div
|
||||||
|
class="container"
|
||||||
|
style="display: flex;justify-content: center;"
|
||||||
|
>
|
||||||
<div id="sg-curve-plotdiv" class="chart">
|
<div id="sg-curve-plotdiv" class="chart">
|
||||||
<div id="sg-chart"></div>
|
<div id="sg-chart"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# prettier-ignore-start #}
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
if (!($('#sg-chart').length > 0)) {
|
if (!($('#sg-chart').length > 0)) {
|
||||||
@ -265,44 +365,61 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
{# prettier-ignore-end #}
|
||||||
<!-- End Single Gen Curve Panel -->
|
<!-- End Single Gen Curve Panel -->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
function handlePredictionResponse(data) {
|
function handlePredictionResponse(data) {
|
||||||
res = "<table class='table table-striped'>"
|
res = "<table class='table table-striped'>";
|
||||||
res += "<thead>"
|
res += "<thead>";
|
||||||
res += "<th scope='col'>#</th>"
|
res += "<th scope='col'>#</th>";
|
||||||
|
|
||||||
columns = ['products', 'image', 'probability', 'btrule']
|
columns = ["products", "image", "probability", "btrule"];
|
||||||
|
|
||||||
for (col in columns) {
|
for (col in columns) {
|
||||||
res += "<th scope='col'>" + columns[col] + "</th>"
|
res += "<th scope='col'>" + columns[col] + "</th>";
|
||||||
}
|
}
|
||||||
|
|
||||||
res += "</thead>"
|
res += "</thead>";
|
||||||
res += "<tbody>"
|
res += "<tbody>";
|
||||||
var cnt = 1;
|
var cnt = 1;
|
||||||
for (transformation in data) {
|
for (transformation in data) {
|
||||||
res += "<tr>"
|
res += "<tr>";
|
||||||
res += "<th scope='row'>" + cnt + "</th>"
|
res += "<th scope='row'>" + cnt + "</th>";
|
||||||
res += "<th scope='row'>" + data[transformation]['products'][0].join(', ') + "</th>"
|
res +=
|
||||||
res += "<th scope='row'>" + "<img width='400' src='{% url 'depict' %}?smiles=" + encodeURIComponent(data[transformation]['products'][0].join('.')) + "'></th>"
|
"<th scope='row'>" +
|
||||||
res += "<th scope='row'>" + data[transformation]['probability'].toFixed(3) + "</th>"
|
data[transformation]["products"][0].join(", ") +
|
||||||
if (data[transformation]['btrule'] != null) {
|
"</th>";
|
||||||
res += "<th scope='row'>" + "<a href='" + data[transformation]['btrule']['url'] + "'>" + data[transformation]['btrule']['name'] + "</a>" + "</th>"
|
res +=
|
||||||
|
"<th scope='row'>" +
|
||||||
|
"<img width='400' src='{% url 'depict' %}?smiles=" +
|
||||||
|
encodeURIComponent(data[transformation]["products"][0].join(".")) +
|
||||||
|
"'></th>";
|
||||||
|
res +=
|
||||||
|
"<th scope='row'>" +
|
||||||
|
data[transformation]["probability"].toFixed(3) +
|
||||||
|
"</th>";
|
||||||
|
if (data[transformation]["btrule"] != null) {
|
||||||
|
res +=
|
||||||
|
"<th scope='row'>" +
|
||||||
|
"<a href='" +
|
||||||
|
data[transformation]["btrule"]["url"] +
|
||||||
|
"'>" +
|
||||||
|
data[transformation]["btrule"]["name"] +
|
||||||
|
"</a>" +
|
||||||
|
"</th>";
|
||||||
} else {
|
} else {
|
||||||
res += "<th scope='row'>N/A</th>"
|
res += "<th scope='row'>N/A</th>";
|
||||||
}
|
}
|
||||||
res += "</tr>"
|
res += "</tr>";
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
res += "</tbody>"
|
res += "</tbody>";
|
||||||
res += "</table>"
|
res += "</table>";
|
||||||
$("#predictResultTable").append(res);
|
$("#predictResultTable").append(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,37 +428,40 @@
|
|||||||
$("#" + divid).empty();
|
$("#" + divid).empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($('#predict-button').length > 0) {
|
if ($("#predict-button").length > 0) {
|
||||||
$("#predict-button").on("click", function (e) {
|
$("#predict-button").on("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
clear("predictResultTable");
|
clear("predictResultTable");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"smiles": $("#smiles-to-predict").val(),
|
smiles: $("#smiles-to-predict").val(),
|
||||||
"classify": "ILikeCats!"
|
classify: "ILikeCats!",
|
||||||
}
|
};
|
||||||
|
|
||||||
if (data["smiles"].trim() === "") {
|
if (data["smiles"].trim() === "") {
|
||||||
$("#predictResultTable").addClass("alert alert-danger");
|
$("#predictResultTable").addClass("alert alert-danger");
|
||||||
$("#predictResultTable").append("Please enter a SMILES string to predict!");
|
$("#predictResultTable").append(
|
||||||
|
"Please enter a SMILES string to predict!",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
makeLoadingGif("#predictLoading", "{% static '/images/wait.gif' %}");
|
makeLoadingGif("#predictLoading", "{% static '/images/wait.gif' %}");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'get',
|
type: "get",
|
||||||
data: data,
|
data: data,
|
||||||
url: '',
|
url: "",
|
||||||
success: function (data, textStatus) {
|
success: function (data, textStatus) {
|
||||||
try {
|
try {
|
||||||
$("#predictLoading").empty();
|
$("#predictLoading").empty();
|
||||||
handlePredictionResponse(data);
|
handlePredictionResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
$("#predictLoading").empty();
|
$("#predictLoading").empty();
|
||||||
$("#predictResultTable").addClass("alert alert-danger");
|
$("#predictResultTable").addClass("alert alert-danger");
|
||||||
$("#predictResultTable").append("Error while processing response :/");
|
$("#predictResultTable").append(
|
||||||
|
"Error while processing response :/",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (jqXHR, textStatus, errorThrown, x) {
|
error: function (jqXHR, textStatus, errorThrown, x) {
|
||||||
@ -353,30 +473,30 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($('#assess-button').length > 0) {
|
if ($("#assess-button").length > 0) {
|
||||||
$("#assess-button").on("click", function (e) {
|
$("#assess-button").on("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
clear("appDomainAssessmentResultTable");
|
clear("appDomainAssessmentResultTable");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"smiles": $("#smiles-to-assess").val(),
|
smiles: $("#smiles-to-assess").val(),
|
||||||
"app-domain-assessment": "ILikeCats!"
|
"app-domain-assessment": "ILikeCats!",
|
||||||
}
|
};
|
||||||
|
|
||||||
if (data["smiles"].trim() === "") {
|
if (data["smiles"].trim() === "") {
|
||||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
||||||
$("#appDomainAssessmentResultTable").append("Please enter a SMILES string to predict!");
|
$("#appDomainAssessmentResultTable").append(
|
||||||
|
"Please enter a SMILES string to predict!",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
makeLoadingGif("#appDomainLoading", "{% static '/images/wait.gif' %}");
|
makeLoadingGif("#appDomainLoading", "{% static '/images/wait.gif' %}");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'get',
|
type: "get",
|
||||||
data: data,
|
data: data,
|
||||||
url: '',
|
url: "",
|
||||||
success: function (data, textStatus) {
|
success: function (data, textStatus) {
|
||||||
try {
|
try {
|
||||||
$("#appDomainLoading").empty();
|
$("#appDomainLoading").empty();
|
||||||
@ -384,19 +504,23 @@
|
|||||||
console.log(data);
|
console.log(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
$("#appDomainLoading").empty();
|
$("#appDomainLoading").empty();
|
||||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
$("#appDomainAssessmentResultTable").addClass(
|
||||||
$("#appDomainAssessmentResultTable").append("Error while processing response :/");
|
"alert alert-danger",
|
||||||
|
);
|
||||||
|
$("#appDomainAssessmentResultTable").append(
|
||||||
|
"Error while processing response :/",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
$("#appDomainLoading").empty();
|
$("#appDomainLoading").empty();
|
||||||
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
|
||||||
$("#appDomainAssessmentResultTable").append(jqXHR.responseJSON.error);
|
$("#appDomainAssessmentResultTable").append(
|
||||||
}
|
jqXHR.responseJSON.error,
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
<div class="panel-group" id="node-detail">
|
<div class="panel-group" id="node-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ node.name }}
|
{{ node.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="node-desc" class="panel-collapse collapse in">
|
<div id="node-desc" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{{ node.description }}
|
{{ node.description|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -98,7 +98,7 @@
|
|||||||
<div id="node-scenario" class="panel-collapse collapse in">
|
<div id="node-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in node.scenarios.all %}
|
{% for s in node.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="panel-group" id="package-detail">
|
<div class="panel-group" id="package-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ package.name }}
|
{{ package.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
|
|||||||
@ -83,6 +83,7 @@
|
|||||||
{% include "modals/objects/add_pathway_edge_modal.html" %}
|
{% include "modals/objects/add_pathway_edge_modal.html" %}
|
||||||
{% include "modals/objects/download_pathway_csv_modal.html" %}
|
{% include "modals/objects/download_pathway_csv_modal.html" %}
|
||||||
{% include "modals/objects/download_pathway_image_modal.html" %}
|
{% include "modals/objects/download_pathway_image_modal.html" %}
|
||||||
|
{% include "modals/objects/identify_missing_rules_modal.html" %}
|
||||||
{% include "modals/objects/generic_copy_object_modal.html" %}
|
{% include "modals/objects/generic_copy_object_modal.html" %}
|
||||||
{% include "modals/objects/edit_pathway_modal.html" %}
|
{% include "modals/objects/edit_pathway_modal.html" %}
|
||||||
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
{% include "modals/objects/generic_set_aliases_modal.html" %}
|
||||||
@ -97,7 +98,7 @@
|
|||||||
<div class="panel-group" id="pwAccordion">
|
<div class="panel-group" id="pwAccordion">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ pathway.name }}
|
{{ pathway.name|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
|
||||||
@ -235,7 +236,7 @@
|
|||||||
<div id="pathway-scenario" class="panel-collapse collapse in">
|
<div id="pathway-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in pathway.scenarios.all %}
|
{% for s in pathway.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -265,7 +266,7 @@
|
|||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{{ pathway.setting.model.url }}">
|
<a href="{{ pathway.setting.model.url }}">
|
||||||
{{ pathway.setting.model.name }}
|
{{ pathway.setting.model.name|safe }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</td>
|
</td>
|
||||||
@ -298,7 +299,7 @@
|
|||||||
{% for p in pathway.setting.rule_packages.all %}
|
{% for p in pathway.setting.rule_packages.all %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{{ p.url }}">
|
<a href="{{ p.url }}">
|
||||||
{{ p.name }}
|
{{ p.name|safe }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="panel-group" id="reaction-detail">
|
<div class="panel-group" id="reaction-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ reaction.name }}
|
{{ reaction.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="reaction-desc" class="panel-collapse collapse in">
|
<div id="reaction-desc" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{{ reaction.description }}
|
{{ reaction.description|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -84,12 +84,12 @@
|
|||||||
<div id="reaction-description-smiles" class="panel-collapse collapse in">
|
<div id="reaction-description-smiles" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for educt in reaction.educts.all %}
|
{% for educt in reaction.educts.all %}
|
||||||
<a class="btn btn-default" href="{{ educt.url }}">{{ educt.name }}</a>
|
<a class="btn btn-default" href="{{ educt.url }}">{{ educt.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<span class="glyphicon glyphicon-arrow-right" style="margin-left:5em;margin-right:5em;"
|
<span class="glyphicon glyphicon-arrow-right" style="margin-left:5em;margin-right:5em;"
|
||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
{% for product in reaction.products.all %}
|
{% for product in reaction.products.all %}
|
||||||
<a class="btn btn-default" href="{{ product.url }}">{{ product.name }}</a>
|
<a class="btn btn-default" href="{{ product.url }}">{{ product.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,7 +118,7 @@
|
|||||||
<div id="reaction-rules" class="panel-collapse collapse in">
|
<div id="reaction-rules" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in reaction.rules.all %}
|
{% for r in reaction.rules.all %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -152,7 +152,7 @@
|
|||||||
<div id="reaction-pathway" class="panel-collapse collapse in">
|
<div id="reaction-pathway" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in reaction.related_pathways %}
|
{% for r in reaction.related_pathways %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -168,7 +168,7 @@
|
|||||||
<div id="reaction-scenario" class="panel-collapse collapse in">
|
<div id="reaction-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in reaction.scenarios.all %}
|
{% for s in reaction.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<div class="panel-group" id="scenario-detail">
|
<div class="panel-group" id="scenario-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ scenario.name }}
|
{{ scenario.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -30,7 +30,7 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">Description</div>
|
<div class="panel-heading">Description</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{{ scenario.description }}
|
{{ scenario.description|safe }}
|
||||||
<br>
|
<br>
|
||||||
{{ scenario.scenario_type }}
|
{{ scenario.scenario_type }}
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<div class="panel-group" id="rule-detail">
|
<div class="panel-group" id="rule-detail">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
|
||||||
{{ rule.name }}
|
{{ rule.name|safe }}
|
||||||
<div id="actionsButton"
|
<div id="actionsButton"
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
||||||
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||||
@ -29,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p>
|
<p>
|
||||||
{{ rule.description }}
|
{{ rule.description|safe }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -145,7 +145,7 @@
|
|||||||
<div id="rule-composite-rule" class="panel-collapse collapse in">
|
<div id="rule-composite-rule" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for cr in rule.parallelrule_set.all %}
|
{% for cr in rule.parallelrule_set.all %}
|
||||||
<a class="list-group-item" href="{{ cr.url }}">{{ cr.name }}</a>
|
<a class="list-group-item" href="{{ cr.url }}">{{ cr.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -162,7 +162,7 @@
|
|||||||
<div id="rule-scenario" class="panel-collapse collapse in">
|
<div id="rule-scenario" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for s in rule.scenarios.all %}
|
{% for s in rule.scenarios.all %}
|
||||||
<a class="list-group-item" href="{{ s.url }}">{{ s.name }}</a>
|
<a class="list-group-item" href="{{ s.url }}">{{ s.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -179,7 +179,7 @@
|
|||||||
<div id="rule-reaction" class="panel-collapse collapse">
|
<div id="rule-reaction" class="panel-collapse collapse">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in rule.related_reactions %}
|
{% for r in rule.related_reactions %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +196,7 @@
|
|||||||
<div id="rule-pathway" class="panel-collapse collapse">
|
<div id="rule-pathway" class="panel-collapse collapse">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for r in rule.related_pathways %}
|
{% for r in rule.related_pathways %}
|
||||||
<a class="list-group-item" href="{{ r.url }}">{{ r.name }}</a>
|
<a class="list-group-item" href="{{ r.url }}">{{ r.name|safe }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
<div id="default-package" class="panel-collapse collapse in">
|
<div id="default-package" class="panel-collapse collapse in">
|
||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{{ user.default_package.url }}"> {{ user.default_package.name }}</a>
|
<a href="{{ user.default_package.url }}"> {{ user.default_package.name|safe }}</a>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<div class="panel-body list-group-item">
|
<div class="panel-body list-group-item">
|
||||||
{% for g in meta.available_groups %}
|
{% for g in meta.available_groups %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{{ g.url }}"> {{ g.name }}</a>
|
<a href="{{ g.url }}"> {{ g.name|safe }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@ -90,7 +90,7 @@
|
|||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{{user.default_setting.model.url}}">
|
<a href="{{user.default_setting.model.url}}">
|
||||||
{{ user.default_setting.model.name }}
|
{{ user.default_setting.model.name|safe }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</td>
|
</td>
|
||||||
@ -123,7 +123,7 @@
|
|||||||
{% for p in user.default_setting.rule_packages.all %}
|
{% for p in user.default_setting.rule_packages.all %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{{p.url}}">
|
<a href="{{p.url}}">
|
||||||
{{ p.name }}
|
{{ p.name|safe }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p></p>
|
<p></p>
|
||||||
{{ pathway.name }}
|
{{ pathway.name|safe }}
|
||||||
<div id="viz">
|
<div id="viz">
|
||||||
<svg width="2000" height="2000"> <!-- Sehr großes SVG für Zoom -->
|
<svg width="2000" height="2000"> <!-- Sehr großes SVG für Zoom -->
|
||||||
<defs>
|
<defs>
|
||||||
|
|||||||
188
templates/predict_pathway.html
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
{% extends "framework_modern.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="mx-auto w-full p-8">
|
||||||
|
<h1 class="h1 mb-4 text-3xl font-bold">Predict a Pathway</h1>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="predict_form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{{ meta.current_package.url }}/pathway"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-8 flex flex-col gap-8 md:flex-row md:items-end">
|
||||||
|
<fieldset class="flex flex-col gap-4 md:flex-3/4">
|
||||||
|
<label class="floating-label" for="name">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
placeholder="Name"
|
||||||
|
id="name"
|
||||||
|
class="input input-md w-full"
|
||||||
|
/>
|
||||||
|
<span>Name</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="floating-label" for="description">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="description"
|
||||||
|
placeholder="Description"
|
||||||
|
id="description"
|
||||||
|
class="input input-md w-full"
|
||||||
|
/>
|
||||||
|
<span>Description</span>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset
|
||||||
|
class="fieldset flex shrink-0 flex-row items-start gap-3 md:flex-1/4 md:flex-col"
|
||||||
|
>
|
||||||
|
<label class="fieldset-label text-base-content/50">Mode</label>
|
||||||
|
<label class="label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="predict"
|
||||||
|
id="radioPredict"
|
||||||
|
value="predict"
|
||||||
|
checked
|
||||||
|
class="radio radio-neutral"
|
||||||
|
/>
|
||||||
|
Predict
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="predict"
|
||||||
|
id="radioIncremental"
|
||||||
|
value="incremental"
|
||||||
|
class="radio radio-neutral"
|
||||||
|
/>
|
||||||
|
Incremental
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="predict"
|
||||||
|
id="radioBuild"
|
||||||
|
value="build"
|
||||||
|
class="radio radio-neutral"
|
||||||
|
/>
|
||||||
|
Build
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="floating-label" for="predict-smiles">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="smiles"
|
||||||
|
placeholder="SMILES"
|
||||||
|
id="predict-smiles"
|
||||||
|
class="input input-md w-full"
|
||||||
|
/>
|
||||||
|
<span>SMILES</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="divider text-base-content/50">OR</div>
|
||||||
|
|
||||||
|
<div class="mb-8 w-full">
|
||||||
|
<label class="text-base-content/50 mb-4 text-xs font-medium"
|
||||||
|
>Draw Structure</label
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
id="predict-ketcher"
|
||||||
|
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
|
width="100%"
|
||||||
|
height="510"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="select mb-8 w-full">
|
||||||
|
<span class="label">Predictor</span>
|
||||||
|
<select id="prediction-setting" name="prediction-setting">
|
||||||
|
<option disabled>Select a Setting</option>
|
||||||
|
{% for s in meta.available_settings %}
|
||||||
|
<option
|
||||||
|
value="{{ s.url }}"
|
||||||
|
{% if s.id == meta.user.default_setting.id %}selected{% endif %}
|
||||||
|
>
|
||||||
|
{{ s.name }}{% if s.id == meta.user.default_setting.id %}
|
||||||
|
(User default)
|
||||||
|
{% endif %}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<a href="{{ meta.current_package.url }}/pathway" class="btn btn-outline"
|
||||||
|
>Cancel</a
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
id="predict-submit-button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
>
|
||||||
|
Predict
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function predictKetcherToTextInput() {
|
||||||
|
$("#predict-smiles").val(this.ketcher.getSmiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
$("#predict-ketcher").on("load", function () {
|
||||||
|
const checkKetcherReady = () => {
|
||||||
|
const win = this.contentWindow;
|
||||||
|
if (win.ketcher && "editor" in win.ketcher) {
|
||||||
|
window.predictKetcher = win.ketcher;
|
||||||
|
win.ketcher.editor.event.change.handlers.push({
|
||||||
|
once: false,
|
||||||
|
priority: 0,
|
||||||
|
f: predictKetcherToTextInput,
|
||||||
|
ketcher: win.ketcher,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(checkKetcherReady, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkKetcherReady();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#predict-submit-button").on("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const button = $(this);
|
||||||
|
button.prop("disabled", true);
|
||||||
|
button.text("Predicting...");
|
||||||
|
|
||||||
|
// Get SMILES from either input or Ketcher
|
||||||
|
let smiles = $("#predict-smiles").val().trim();
|
||||||
|
|
||||||
|
// If SMILES input is empty, try to get from Ketcher
|
||||||
|
if (!smiles && window.predictKetcher) {
|
||||||
|
smiles = window.predictKetcher.getSmiles().trim();
|
||||||
|
if (smiles) {
|
||||||
|
$("#predict-smiles").val(smiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (!smiles) {
|
||||||
|
alert("Please enter a SMILES string or draw a structure.");
|
||||||
|
button.prop("disabled", false);
|
||||||
|
button.text("Predict");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit form
|
||||||
|
$("#predict_form").submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
@ -11,13 +11,13 @@
|
|||||||
<option disabled>Reviewed Packages</option>
|
<option disabled>Reviewed Packages</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for obj in reviewed_objects %}
|
{% for obj in reviewed_objects %}
|
||||||
<option value="{{ obj.url }}" selected>{{ obj.name }}</option>
|
<option value="{{ obj.url }}" selected>{{ obj.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if unreviewed_objects %}
|
{% if unreviewed_objects %}
|
||||||
<option disabled>Unreviewed Packages</option>
|
<option disabled>Unreviewed Packages</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for obj in unreviewed_objects %}
|
{% for obj in unreviewed_objects %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
132
templates/static/about_us.html
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
{% extends "framework_modern.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block main_content %}
|
||||||
|
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||||
|
<!-- Breadcrumbs -->
|
||||||
|
<div class="text-sm breadcrumbs mb-4">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li>About Us</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="bg-base-100 shadow-xl rounded-lg p-8">
|
||||||
|
<h1 class="text-4xl font-bold mb-6">About enviPath</h1>
|
||||||
|
|
||||||
|
<div class="prose max-w-none">
|
||||||
|
<!-- Hero Image/Graphic -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<img src="{% static '/images/ep-rule-artwork.png' %}" alt="enviPath System" class="w-full max-w-2xl mx-auto rounded-lg shadow-md" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-lg mb-6">
|
||||||
|
enviPath is a comprehensive database and prediction system for the microbial biotransformation of
|
||||||
|
organic environmental contaminants. Since 2015, we have been at the forefront of computational
|
||||||
|
environmental chemistry, helping researchers understand and predict biodegradation pathways.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">Our Mission</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
Our mission is to advance environmental science through innovative computational tools that predict
|
||||||
|
and analyze the biotransformation of chemical compounds. We strive to provide researchers, regulators,
|
||||||
|
and industry professionals with accurate, accessible tools for understanding environmental fate and behavior.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">What We Offer</h2>
|
||||||
|
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||||||
|
<div class="card bg-base-200 shadow-md">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-primary">Pathway Database</h3>
|
||||||
|
<p>Access experimentally observed biotransformation pathways and reactions from curated scientific literature.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card bg-base-200 shadow-md">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-primary">Prediction System</h3>
|
||||||
|
<p>Use our relative reasoning models to predict likely biotransformation pathways and products for new compounds.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card bg-base-200 shadow-md">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-primary">Machine Learning Models</h3>
|
||||||
|
<p>Leverage advanced ML algorithms trained on extensive biodegradation data for accurate predictions.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card bg-base-200 shadow-md">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-primary">Community Platform</h3>
|
||||||
|
<p>Join our active community of researchers to share knowledge, discuss findings, and collaborate.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">Our Technology</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
enviPath employs a unique combination of rule-based and machine learning approaches to predict
|
||||||
|
biotransformation pathways:
|
||||||
|
</p>
|
||||||
|
<ul class="list-disc list-inside mb-4 space-y-2">
|
||||||
|
<li><strong>Relative Reasoning:</strong> Uses structural similarity to known biotransformations</li>
|
||||||
|
<li><strong>Rule-Based Systems:</strong> Applies expert-curated transformation rules</li>
|
||||||
|
<li><strong>Machine Learning:</strong> Leverages neural networks for pattern recognition</li>
|
||||||
|
<li><strong>Hybrid Models:</strong> Combines multiple approaches for optimal accuracy</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">Our Partners</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
enviPath is backed by leading research institutions and collaborators:
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap justify-center items-center gap-8 my-8">
|
||||||
|
<img src="{% static '/images/uoa-logo-small.png' %}" alt="The University of Auckland" class="h-20 object-contain" />
|
||||||
|
<img src="{% static '/images/logo-eawag.svg' %}" alt="Eawag" class="h-16 object-contain" />
|
||||||
|
<img src="{% static '/images/uzh-logo.svg' %}" alt="University of Zurich" class="h-20 object-contain" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">Our Team</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
enviPath is developed and maintained by a dedicated team of computational chemists, environmental
|
||||||
|
scientists, and software engineers. Our interdisciplinary approach ensures that the platform meets
|
||||||
|
the needs of the scientific community while remaining accessible and user-friendly.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">Research Impact</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
Since its inception, enviPath has contributed to numerous scientific publications and environmental
|
||||||
|
assessments. Our tools are used by:
|
||||||
|
</p>
|
||||||
|
<ul class="list-disc list-inside mb-4 space-y-2">
|
||||||
|
<li>Academic researchers in environmental chemistry and toxicology</li>
|
||||||
|
<li>Regulatory agencies for chemical risk assessment</li>
|
||||||
|
<li>Chemical manufacturers for product development and safety evaluation</li>
|
||||||
|
<li>Environmental consultants for contamination studies</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">Open Science Commitment</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
We are committed to open science principles. enviPath provides free access to our database and
|
||||||
|
prediction tools for academic research. We actively contribute to the scientific community through
|
||||||
|
publications, open-source software, and collaboration.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="card bg-primary text-primary-content mt-8">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">Get Involved</h3>
|
||||||
|
<p>Join our community, contribute data, or collaborate on research projects.</p>
|
||||||
|
<div class="card-actions justify-end mt-4">
|
||||||
|
<a href="https://community.envipath.org/" target="_blank" class="btn btn-secondary">Visit Community</a>
|
||||||
|
<a href="/contact" class="btn btn-ghost">Contact Us</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-semibold mt-8 mb-4">Publications</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
To learn more about the science behind enviPath, please visit our
|
||||||
|
<a href="/cite" class="link link-primary">citations page</a> for key publications and how to cite our work.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock main_content %}
|
||||||