forked from enviPath/enviPy
Compare commits
11 Commits
fix/issue2
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a2c9bb543 | |||
| 7f6f209b4a | |||
| b6c35fea76 | |||
| fa8a191383 | |||
| 67b1baa5b0 | |||
| 89c194dcca | |||
| a8554c903c | |||
| d584791ee8 | |||
| e60052b05c | |||
| 3ff8d938d6 | |||
| a7f48c2cf9 |
@ -8,6 +8,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
if: ${{ !contains(gitea.event.pull_request.title, 'WIP') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@ -99,6 +100,18 @@ jobs:
|
|||||||
- name: Setup venv
|
- name: Setup venv
|
||||||
run: |
|
run: |
|
||||||
uv sync --locked --all-extras --dev
|
uv sync --locked --all-extras --dev
|
||||||
|
source .venv/bin/activate
|
||||||
|
playwright install --with-deps
|
||||||
|
|
||||||
|
- name: Run PNPM Commands
|
||||||
|
run: |
|
||||||
|
uv run python scripts/pnpm_wrapper.py install
|
||||||
|
cat << 'EOF' > pnpm-workspace.yaml
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- '@parcel/watcher'
|
||||||
|
- '@tailwindcss/oxide'
|
||||||
|
EOF
|
||||||
|
uv run python scripts/pnpm_wrapper.py run build
|
||||||
|
|
||||||
- name: Wait for services
|
- name: Wait for services
|
||||||
run: |
|
run: |
|
||||||
@ -110,7 +123,12 @@ jobs:
|
|||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
python manage.py migrate --noinput
|
python manage.py migrate --noinput
|
||||||
|
|
||||||
|
- name: Run frontend tests
|
||||||
|
run: |
|
||||||
|
source .venv/bin/activate
|
||||||
|
python manage.py test --tag frontend
|
||||||
|
|
||||||
- name: Run Django tests
|
- name: Run Django tests
|
||||||
run: |
|
run: |
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
python manage.py test tests --exclude-tag slow
|
python manage.py test tests --exclude-tag slow --exclude-tag frontend
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@ static/django_extensions/
|
|||||||
.env
|
.env
|
||||||
debug.log
|
debug.log
|
||||||
scratches/
|
scratches/
|
||||||
|
test-results/
|
||||||
|
|
||||||
data/
|
data/
|
||||||
|
|
||||||
|
|||||||
@ -49,9 +49,23 @@ INSTALLED_APPS = [
|
|||||||
"oauth2_provider",
|
"oauth2_provider",
|
||||||
# Custom
|
# Custom
|
||||||
"epdb",
|
"epdb",
|
||||||
"migration",
|
# "migration",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TENANT = os.environ.get("TENANT", "public")
|
||||||
|
|
||||||
|
if TENANT != "public":
|
||||||
|
INSTALLED_APPS.append(TENANT)
|
||||||
|
|
||||||
|
EPDB_PACKAGE_MODEL = os.environ.get("EPDB_PACKAGE_MODEL", "epdb.Package")
|
||||||
|
|
||||||
|
|
||||||
|
def GET_PACKAGE_MODEL():
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
return apps.get_model(EPDB_PACKAGE_MODEL)
|
||||||
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
"django.contrib.auth.backends.ModelBackend",
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -23,12 +23,20 @@ from .api import api_v1, api_legacy
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include("epdb.urls")),
|
path("", include("epdb.urls")),
|
||||||
path("", include("migration.urls")),
|
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/v1/", api_v1.urls),
|
path("api/v1/", api_v1.urls),
|
||||||
path("api/legacy/", api_legacy.urls),
|
path("api/legacy/", api_legacy.urls),
|
||||||
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if "migration" in s.INSTALLED_APPS:
|
||||||
|
urlpatterns.append(path("", include("migration.urls")))
|
||||||
|
|
||||||
if s.MS_ENTRA_ENABLED:
|
if s.MS_ENTRA_ENABLED:
|
||||||
urlpatterns.append(path("", include("epauth.urls")))
|
urlpatterns.append(path("", include("epauth.urls")))
|
||||||
|
|
||||||
|
# Custom error handlers
|
||||||
|
handler400 = "epdb.views.handler400"
|
||||||
|
handler403 = "epdb.views.handler403"
|
||||||
|
handler404 = "epdb.views.handler404"
|
||||||
|
handler500 = "epdb.views.handler500"
|
||||||
|
|||||||
@ -1,29 +1,31 @@
|
|||||||
|
from django.conf import settings as s
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
User,
|
|
||||||
UserPackagePermission,
|
|
||||||
Group,
|
|
||||||
GroupPackagePermission,
|
|
||||||
Package,
|
|
||||||
MLRelativeReasoning,
|
|
||||||
EnviFormer,
|
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
SimpleAmbitRule,
|
|
||||||
ParallelRule,
|
|
||||||
Reaction,
|
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
Edge,
|
Edge,
|
||||||
Scenario,
|
EnviFormer,
|
||||||
Setting,
|
|
||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
ExternalIdentifier,
|
ExternalIdentifier,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
JobLog,
|
JobLog,
|
||||||
License,
|
License,
|
||||||
|
MLRelativeReasoning,
|
||||||
|
Node,
|
||||||
|
ParallelRule,
|
||||||
|
Pathway,
|
||||||
|
Reaction,
|
||||||
|
Scenario,
|
||||||
|
Setting,
|
||||||
|
SimpleAmbitRule,
|
||||||
|
User,
|
||||||
|
UserPackagePermission,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(admin.ModelAdmin):
|
||||||
list_display = ["username", "email", "is_active"]
|
list_display = ["username", "email", "is_active"]
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EPDBConfig(AppConfig):
|
class EPDBConfig(AppConfig):
|
||||||
@ -7,3 +12,6 @@ class EPDBConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import epdb.signals # noqa: F401
|
import epdb.signals # noqa: F401
|
||||||
|
|
||||||
|
model_name = getattr(settings, "EPDB_PACKAGE_MODEL", "epdb.Package")
|
||||||
|
logger.info(f"Using Package model: {model_name}")
|
||||||
|
|||||||
@ -5,7 +5,7 @@ Context processors automatically make variables available to all templates.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .logic import PackageManager
|
from .logic import PackageManager
|
||||||
from .models import Package
|
from django.conf import settings as s
|
||||||
|
|
||||||
|
|
||||||
def package_context(request):
|
def package_context(request):
|
||||||
@ -20,7 +20,7 @@ def package_context(request):
|
|||||||
|
|
||||||
reviewed_package_qs = PackageManager.get_reviewed_packages()
|
reviewed_package_qs = PackageManager.get_reviewed_packages()
|
||||||
|
|
||||||
unreviewed_package_qs = Package.objects.none()
|
unreviewed_package_qs = s.GET_PACKAGE_MODEL().objects.none()
|
||||||
|
|
||||||
# Only get user-specific packages if user is authenticated
|
# Only get user-specific packages if user is authenticated
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
|
|||||||
@ -1,27 +1,35 @@
|
|||||||
from typing import List, Dict, Optional, Any
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
import nh3
|
||||||
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from ninja import Router, Schema, Field, Form
|
from ninja import Field, Form, Router, Schema, Query
|
||||||
|
from ninja.security import SessionAuth
|
||||||
|
|
||||||
from utilities.chem import FormatConverter
|
from utilities.chem import FormatConverter
|
||||||
from .logic import PackageManager, UserManager, SettingManager
|
from utilities.misc import PackageExporter
|
||||||
|
|
||||||
|
from .logic import GroupManager, PackageManager, SettingManager, UserManager, SearchManager
|
||||||
from .models import (
|
from .models import (
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
Package,
|
Edge,
|
||||||
|
EPModel,
|
||||||
|
Node,
|
||||||
|
Pathway,
|
||||||
|
Reaction,
|
||||||
|
Rule,
|
||||||
|
Scenario,
|
||||||
|
SimpleAmbitRule,
|
||||||
User,
|
User,
|
||||||
UserPackagePermission,
|
UserPackagePermission,
|
||||||
Rule,
|
ParallelRule,
|
||||||
Reaction,
|
|
||||||
Scenario,
|
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
Edge,
|
|
||||||
SimpleAmbitRule,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def _anonymous_or_real(request):
|
def _anonymous_or_real(request):
|
||||||
if request.user.is_authenticated and not request.user.is_anonymous:
|
if request.user.is_authenticated and not request.user.is_anonymous:
|
||||||
@ -29,8 +37,7 @@ def _anonymous_or_real(request):
|
|||||||
return get_user_model().objects.get(username="anonymous")
|
return get_user_model().objects.get(username="anonymous")
|
||||||
|
|
||||||
|
|
||||||
# router = Router(auth=SessionAuth())
|
router = Router(auth=SessionAuth(csrf=False))
|
||||||
router = Router()
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Schema):
|
class Error(Schema):
|
||||||
@ -118,13 +125,16 @@ class SimpleEdge(SimpleObject):
|
|||||||
identifier: str = "edge"
|
identifier: str = "edge"
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleModel(SimpleObject):
|
||||||
|
identifier: str = "relative-reasoning"
|
||||||
|
|
||||||
|
|
||||||
################
|
################
|
||||||
# Login/Logout #
|
# Login/Logout #
|
||||||
################
|
################
|
||||||
@router.post("/", response={200: SimpleUser, 403: Error})
|
@router.post("/", response={200: SimpleUser, 403: Error}, auth=None)
|
||||||
def login(request, loginusername: Form[str], loginpassword: Form[str]):
|
def login(request, loginusername: Form[str], loginpassword: Form[str]):
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth import login
|
|
||||||
|
|
||||||
email = User.objects.get(username=loginusername).email
|
email = User.objects.get(username=loginusername).email
|
||||||
user = authenticate(username=email, password=loginpassword)
|
user = authenticate(username=email, password=loginpassword)
|
||||||
@ -167,9 +177,13 @@ class UserSchema(Schema):
|
|||||||
return SettingManager.get_all_settings(obj)
|
return SettingManager.get_all_settings(obj)
|
||||||
|
|
||||||
|
|
||||||
|
class Me(Schema):
|
||||||
|
whoami: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@router.get("/user", response={200: UserWrapper, 403: Error})
|
@router.get("/user", response={200: UserWrapper, 403: Error})
|
||||||
def get_users(request, whoami: str = None):
|
def get_users(request, me: Query[Me]):
|
||||||
if whoami:
|
if me.whoami:
|
||||||
return {"user": [request.user]}
|
return {"user": [request.user]}
|
||||||
else:
|
else:
|
||||||
return {"user": User.objects.all()}
|
return {"user": User.objects.all()}
|
||||||
@ -186,6 +200,61 @@ def get_user(request, user_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Search(Schema):
|
||||||
|
packages: List[str] = Field(alias="packages[]")
|
||||||
|
search: str
|
||||||
|
method: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/search", response={200: Any, 403: Error})
|
||||||
|
def search(request, search: Query[Search]):
|
||||||
|
try:
|
||||||
|
packs = []
|
||||||
|
for package in search.packages:
|
||||||
|
packs.append(PackageManager.get_package_by_url(request.user, package))
|
||||||
|
|
||||||
|
method = None
|
||||||
|
|
||||||
|
if search.method == "text":
|
||||||
|
method = "text"
|
||||||
|
elif search.method == "inchikey":
|
||||||
|
method = "inchikey"
|
||||||
|
elif search.method == "defaultSmiles":
|
||||||
|
method = "default"
|
||||||
|
elif search.method == "canonicalSmiles":
|
||||||
|
method = "canonical"
|
||||||
|
elif search.method == "exactSmiles":
|
||||||
|
method = "exact"
|
||||||
|
|
||||||
|
if method is None:
|
||||||
|
raise ValueError(f"Search method {search.method} is not supported!")
|
||||||
|
|
||||||
|
search_res = SearchManager.search(packs, search.search, method)
|
||||||
|
res = {}
|
||||||
|
if "Compounds" in search_res:
|
||||||
|
res["compound"] = search_res["Compounds"]
|
||||||
|
|
||||||
|
if "Compound Structures" in search_res:
|
||||||
|
res["structure"] = search_res["Compound Structures"]
|
||||||
|
|
||||||
|
if "Reaction" in search_res:
|
||||||
|
res["reaction"] = search_res["Reaction"]
|
||||||
|
|
||||||
|
if "Pathway" in search_res:
|
||||||
|
res["pathway"] = search_res["Pathway"]
|
||||||
|
|
||||||
|
if "Rules" in search_res:
|
||||||
|
res["rule"] = search_res["Rules"]
|
||||||
|
|
||||||
|
for key in res:
|
||||||
|
for v in res[key]:
|
||||||
|
v["id"] = v["url"].replace("simple-ambit-rule", "simple-rule")
|
||||||
|
|
||||||
|
return res
|
||||||
|
except ValueError as e:
|
||||||
|
return 403, {"message": f"Search failed due to {e}"}
|
||||||
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
# Package #
|
# Package #
|
||||||
###########
|
###########
|
||||||
@ -251,67 +320,110 @@ def get_packages(request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema, 403: Error})
|
class GetPackage(Schema):
|
||||||
def get_package(request, package_uuid):
|
exportAsJson: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema | Any, 403: Error})
|
||||||
|
def get_package(request, package_uuid, gp: Query[GetPackage]):
|
||||||
try:
|
try:
|
||||||
return PackageManager.get_package_by_id(request.user, package_uuid)
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if gp.exportAsJson and gp.exportAsJson.strip() == "true":
|
||||||
|
return PackageExporter(p).do_export()
|
||||||
|
|
||||||
|
return p
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 403, {
|
return 403, {
|
||||||
"message": f"Getting Package with id {package_uuid} failed due to insufficient rights!"
|
"message": f"Getting Package with id {package_uuid} failed due to insufficient rights!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePackage(Schema):
|
||||||
|
packageName: str
|
||||||
|
packageDescription: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@router.post("/package")
|
@router.post("/package")
|
||||||
def create_packages(
|
def create_packages(
|
||||||
request, packageName: Form[str], packageDescription: Optional[str] = Form(None)
|
request,
|
||||||
|
p: Form[CreatePackage],
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if packageName.strip() == "":
|
if p.packageName.strip() == "":
|
||||||
raise ValueError("Package name cannot be empty!")
|
raise ValueError("Package name cannot be empty!")
|
||||||
|
|
||||||
new_pacakge = PackageManager.create_package(request.user, packageName, packageDescription)
|
new_pacakge = PackageManager.create_package(
|
||||||
|
request.user, p.packageName, p.packageDescription
|
||||||
|
)
|
||||||
return redirect(new_pacakge.url)
|
return redirect(new_pacakge.url)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return 400, {"message": str(e)}
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePackage(Schema):
|
||||||
|
packageDescription: str | None = None
|
||||||
|
hiddenMethod: str | None = None
|
||||||
|
permissions: str | None = None
|
||||||
|
ppsURI: str | None = None
|
||||||
|
read: str | None = None
|
||||||
|
write: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@router.post("/package/{uuid:package_uuid}", response={200: PackageSchema | Any, 400: Error})
|
@router.post("/package/{uuid:package_uuid}", response={200: PackageSchema | Any, 400: Error})
|
||||||
def update_package(
|
def update_package(request, package_uuid, pack: Form[UpdatePackage]):
|
||||||
request,
|
|
||||||
package_uuid,
|
|
||||||
packageDescription: Optional[str] = Form(None),
|
|
||||||
hiddenMethod: Optional[str] = Form(None),
|
|
||||||
exportAsJson: Optional[str] = Form(None),
|
|
||||||
permissions: Optional[str] = Form(None),
|
|
||||||
ppsURI: Optional[str] = Form(None),
|
|
||||||
read: Optional[str] = Form(None),
|
|
||||||
write: Optional[str] = Form(None),
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
if hiddenMethod:
|
if pack.hiddenMethod:
|
||||||
if hiddenMethod == "DELETE":
|
if pack.hiddenMethod == "DELETE":
|
||||||
p.delete()
|
p.delete()
|
||||||
|
|
||||||
elif packageDescription and packageDescription.strip() != "":
|
elif pack.packageDescription is not None:
|
||||||
p.description = packageDescription
|
description = nh3.clean(pack.packageDescription, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
p.save()
|
|
||||||
return
|
|
||||||
elif exportAsJson == "true":
|
|
||||||
pack_json = PackageManager.export_package(
|
|
||||||
p, include_models=False, include_external_identifiers=False
|
|
||||||
)
|
|
||||||
return pack_json
|
|
||||||
elif all([permissions, ppsURI, read]):
|
|
||||||
PackageManager.update_permissions
|
|
||||||
elif all([permissions, ppsURI, write]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
if description:
|
||||||
|
p.description = description
|
||||||
|
p.save()
|
||||||
|
return HttpResponse(status=200)
|
||||||
|
else:
|
||||||
|
raise ValueError("Package description cannot be empty!")
|
||||||
|
elif all([pack.permissions, pack.ppsURI, pack.read]):
|
||||||
|
if "group" in pack.ppsURI:
|
||||||
|
grantee = GroupManager.get_group_lp(pack.ppsURI)
|
||||||
|
else:
|
||||||
|
grantee = UserManager.get_user_lp(pack.ppsURI)
|
||||||
|
|
||||||
|
PackageManager.grant_read(request.user, p, grantee)
|
||||||
|
return HttpResponse(status=200)
|
||||||
|
elif all([pack.permissions, pack.ppsURI, pack.write]):
|
||||||
|
if "group" in pack.ppsURI:
|
||||||
|
grantee = GroupManager.get_group_lp(pack.ppsURI)
|
||||||
|
else:
|
||||||
|
grantee = UserManager.get_user_lp(pack.ppsURI)
|
||||||
|
|
||||||
|
PackageManager.grant_write(request.user, p, grantee)
|
||||||
|
return HttpResponse(status=200)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return 400, {"message": str(e)}
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}")
|
||||||
|
def delete_package(request, package_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.administrable(request.user, p):
|
||||||
|
p.delete()
|
||||||
|
return redirect(f"{s.SERVER_URL}/package")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Package!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Package with id {package_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# Compound / CompoundStructure #
|
# Compound / CompoundStructure #
|
||||||
################################
|
################################
|
||||||
@ -509,6 +621,83 @@ def get_package_compound_structure(request, package_uuid, compound_uuid, structu
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateCompound(Schema):
|
||||||
|
compoundSmiles: str
|
||||||
|
compoundName: str | None = None
|
||||||
|
compoundDescription: str | None = None
|
||||||
|
inchi: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/compound")
|
||||||
|
def create_package_compound(
|
||||||
|
request,
|
||||||
|
package_uuid,
|
||||||
|
c: Form[CreateCompound],
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
# inchi is not used atm
|
||||||
|
c = Compound.create(
|
||||||
|
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
||||||
|
)
|
||||||
|
return redirect(c.url)
|
||||||
|
except ValueError as e:
|
||||||
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}")
|
||||||
|
def delete_compound(request, package_uuid, compound_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
c = Compound.objects.get(package=p, uuid=compound_uuid)
|
||||||
|
c.delete()
|
||||||
|
return redirect(f"{p.url}/compound")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Compound!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Compound with id {compound_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}"
|
||||||
|
)
|
||||||
|
def delete_compound_structure(request, package_uuid, compound_uuid, structure_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
c = Compound.objects.get(package=p, uuid=compound_uuid)
|
||||||
|
cs = CompoundStructure.objects.get(compound=c, uuid=structure_uuid)
|
||||||
|
|
||||||
|
# Check if we have to delete the compound as no structure is left
|
||||||
|
if len(cs.compound.structures.all()) == 1:
|
||||||
|
# This will delete the structure as well
|
||||||
|
c.delete()
|
||||||
|
return redirect(p.url + "/compound")
|
||||||
|
else:
|
||||||
|
if cs.normalized_structure:
|
||||||
|
c.delete()
|
||||||
|
return redirect(p.url + "/compound")
|
||||||
|
else:
|
||||||
|
if c.default_structure == cs:
|
||||||
|
cs.delete()
|
||||||
|
c.default_structure = c.structures.all().first()
|
||||||
|
return redirect(c.url + "/structure")
|
||||||
|
else:
|
||||||
|
cs.delete()
|
||||||
|
return redirect(c.url + "/structure")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this CompoundStructure!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting CompoundStructure with id {compound_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# Rules #
|
# Rules #
|
||||||
#########
|
#########
|
||||||
@ -672,6 +861,73 @@ def _get_package_rule(request, package_uuid, rule_uuid):
|
|||||||
|
|
||||||
|
|
||||||
# POST
|
# POST
|
||||||
|
class CreateSimpleRule(Schema):
|
||||||
|
smirks: str
|
||||||
|
name: str | None = None
|
||||||
|
description: str | None = None
|
||||||
|
reactantFilterSmarts: str | None = None
|
||||||
|
productFilterSmarts: str | None = None
|
||||||
|
immediate: str | None = None
|
||||||
|
rdkitrule: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/simple-rule")
|
||||||
|
def create_package_simple_rule(
|
||||||
|
request,
|
||||||
|
package_uuid,
|
||||||
|
r: Form[CreateSimpleRule],
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if r.rdkitrule and r.rdkitrule.strip() == "true":
|
||||||
|
raise ValueError("Not yet implemented!")
|
||||||
|
else:
|
||||||
|
sr = SimpleAmbitRule.create(
|
||||||
|
p, r.name, r.description, r.smirks, r.reactantFilterSmarts, r.productFilterSmarts
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect(sr.url)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateParallelRule(Schema):
|
||||||
|
simpleRules: str
|
||||||
|
name: str | None = None
|
||||||
|
description: str | None = None
|
||||||
|
reactantFilterSmarts: str | None = None
|
||||||
|
productFilterSmarts: str | None = None
|
||||||
|
immediate: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/parallel-rule")
|
||||||
|
def create_package_parallel_rule(
|
||||||
|
request,
|
||||||
|
package_uuid,
|
||||||
|
r: Form[CreateParallelRule],
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
srs = SimpleRule.objects.filter(package=p, url__in=r.simpleRules)
|
||||||
|
|
||||||
|
if srs.count() != len(r.simpleRules):
|
||||||
|
raise ValueError(
|
||||||
|
f"Not all SimpleRules could be found in Package with id {package_uuid}!"
|
||||||
|
)
|
||||||
|
|
||||||
|
sr = ParallelRule.create(
|
||||||
|
p, list(srs), r.name, r.description, r.reactantFilterSmarts, r.productFilterSmarts
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect(sr.url)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}
|
"/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error}
|
||||||
)
|
)
|
||||||
@ -721,6 +977,41 @@ def _post_package_rule(request, package_uuid, rule_uuid, compound: Form[str]):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}")
|
||||||
|
def delete_rule(request, package_uuid, rule_uuid):
|
||||||
|
return _delete_rule(request, package_uuid, rule_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}",
|
||||||
|
)
|
||||||
|
def delete_simple_rule(request, package_uuid, rule_uuid):
|
||||||
|
return _delete_rule(request, package_uuid, rule_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}",
|
||||||
|
)
|
||||||
|
def delete_parallel_rule(request, package_uuid, rule_uuid):
|
||||||
|
return _delete_rule(request, package_uuid, rule_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_rule(request, package_uuid, rule_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
r = Rule.objects.get(package=p, uuid=rule_uuid)
|
||||||
|
r.delete()
|
||||||
|
return redirect(f"{p.url}/rule")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Rule!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Rule with id {rule_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
############
|
############
|
||||||
# Reaction #
|
# Reaction #
|
||||||
############
|
############
|
||||||
@ -809,6 +1100,82 @@ def get_package_reaction(request, package_uuid, reaction_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateReaction(Schema):
|
||||||
|
reactionName: str | None = None
|
||||||
|
reactionDescription: str | None = None
|
||||||
|
smirks: str | None = None
|
||||||
|
educt: str | None = None
|
||||||
|
product: str | None = None
|
||||||
|
rule: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/reaction")
|
||||||
|
def create_package_reaction(
|
||||||
|
request,
|
||||||
|
package_uuid,
|
||||||
|
r: Form[CreateReaction],
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if r.smirks is None and (r.educt is None or r.product is None):
|
||||||
|
raise ValueError("Either SMIRKS or educt/product must be provided")
|
||||||
|
|
||||||
|
if r.smirks is not None and (r.educt is not None and r.product is not None):
|
||||||
|
raise ValueError("SMIRKS and educt/product provided!")
|
||||||
|
|
||||||
|
rule = None
|
||||||
|
if r.rule:
|
||||||
|
try:
|
||||||
|
rule = Rule.objects.get(package=p, url=r.rule)
|
||||||
|
except Rule.DoesNotExist:
|
||||||
|
raise ValueError(f"Rule with id {r.rule} does not exist!")
|
||||||
|
|
||||||
|
if r.educt is not None:
|
||||||
|
try:
|
||||||
|
educt_cs = CompoundStructure.objects.get(compound__package=p, url=r.educt)
|
||||||
|
except CompoundStructure.DoesNotExist:
|
||||||
|
raise ValueError(f"Compound with id {r.educt} does not exist!")
|
||||||
|
|
||||||
|
try:
|
||||||
|
product_cs = CompoundStructure.objects.get(compound__package=p, url=r.product)
|
||||||
|
except CompoundStructure.DoesNotExist:
|
||||||
|
raise ValueError(f"Compound with id {r.product} does not exist!")
|
||||||
|
|
||||||
|
new_r = Reaction.create(
|
||||||
|
p, r.reactionName, r.reactionDescription, [educt_cs], [product_cs], rule
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
educts = r.smirks.split(">>")[0].split("\\.")
|
||||||
|
products = r.smirks.split(">>")[1].split("\\.")
|
||||||
|
|
||||||
|
new_r = Reaction.create(
|
||||||
|
p, r.reactionName, r.reactionDescription, educts, products, rule
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect(new_r.url)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/reaction/{uuid:reaction_uuid}")
|
||||||
|
def delete_reaction(request, package_uuid, reaction_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
r = Reaction.objects.get(package=p, uuid=reaction_uuid)
|
||||||
|
r.delete()
|
||||||
|
return redirect(f"{p.url}/reaction")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Reaction!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Reaction with id {reaction_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
############
|
############
|
||||||
# Scenario #
|
# Scenario #
|
||||||
############
|
############
|
||||||
@ -823,7 +1190,7 @@ class ScenarioSchema(Schema):
|
|||||||
description: str = Field(None, alias="description")
|
description: str = Field(None, alias="description")
|
||||||
id: str = Field(None, alias="url")
|
id: str = Field(None, alias="url")
|
||||||
identifier: str = "scenario"
|
identifier: str = "scenario"
|
||||||
linkedTo: List[Dict[str, str]] = Field({}, alias="linked_to")
|
linkedTo: List[Dict[str, str]] = Field([], alias="linked_to")
|
||||||
name: str = Field(None, alias="name")
|
name: str = Field(None, alias="name")
|
||||||
pathways: List["SimplePathway"] = Field([], alias="related_pathways")
|
pathways: List["SimplePathway"] = Field([], alias="related_pathways")
|
||||||
relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios")
|
relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios")
|
||||||
@ -874,6 +1241,38 @@ def get_package_scenario(request, package_uuid, scenario_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/scenario")
|
||||||
|
def delete_scenarios(request, package_uuid, scenario_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
scens = Scenario.objects.filter(package=p)
|
||||||
|
scens.delete()
|
||||||
|
return redirect(f"{p.url}/scenario")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete Scenarios!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {"message": "Deleting Scenarios failed due to insufficient rights!"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/scenario/{uuid:scenario_uuid}")
|
||||||
|
def delete_scenario(request, package_uuid, scenario_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
scen = Scenario.objects.get(package=p, uuid=scenario_uuid)
|
||||||
|
scen.delete()
|
||||||
|
return redirect(f"{p.url}/scenario")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Scenario!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Scenario with id {scenario_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
# Pathway #
|
# Pathway #
|
||||||
###########
|
###########
|
||||||
@ -1013,46 +1412,67 @@ def get_package_pathway(request, package_uuid, pathway_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePathway(Schema):
|
||||||
|
smilesinput: str
|
||||||
|
name: str | None = None
|
||||||
|
description: str | None = None
|
||||||
|
rootOnly: str | None = None
|
||||||
|
selectedSetting: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@router.post("/package/{uuid:package_uuid}/pathway")
|
@router.post("/package/{uuid:package_uuid}/pathway")
|
||||||
def create_pathway(
|
def create_pathway(
|
||||||
request,
|
request,
|
||||||
package_uuid,
|
package_uuid,
|
||||||
smilesinput: Form[str],
|
pw: Form[CreatePathway],
|
||||||
name: Optional[str] = Form(None),
|
|
||||||
description: Optional[str] = Form(None),
|
|
||||||
rootOnly: Optional[str] = Form(None),
|
|
||||||
selectedSetting: Optional[str] = Form(None),
|
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
stand_smiles = FormatConverter.standardize(smilesinput.strip())
|
stand_smiles = FormatConverter.standardize(pw.smilesinput.strip())
|
||||||
|
|
||||||
pw = Pathway.create(p, stand_smiles, name=name, description=description)
|
new_pw = Pathway.create(p, stand_smiles, name=pw.name, description=pw.description)
|
||||||
|
|
||||||
pw_mode = "predict"
|
pw_mode = "predict"
|
||||||
if rootOnly and rootOnly == "true":
|
if pw.rootOnly and pw.rootOnly.strip() == "true":
|
||||||
pw_mode = "build"
|
pw_mode = "build"
|
||||||
|
|
||||||
pw.kv.update({"mode": pw_mode})
|
new_pw.kv.update({"mode": pw_mode})
|
||||||
pw.save()
|
new_pw.save()
|
||||||
|
|
||||||
if pw_mode == "predict":
|
if pw_mode == "predict":
|
||||||
setting = request.user.prediction_settings()
|
setting = request.user.prediction_settings()
|
||||||
|
|
||||||
if selectedSetting:
|
if pw.selectedSetting:
|
||||||
setting = SettingManager.get_setting_by_url(request.user, selectedSetting)
|
setting = SettingManager.get_setting_by_url(request.user, pw.selectedSetting)
|
||||||
|
|
||||||
pw.setting = setting
|
new_pw.setting = setting
|
||||||
pw.save()
|
new_pw.save()
|
||||||
|
|
||||||
from .tasks import predict
|
from .tasks import dispatch, predict
|
||||||
|
|
||||||
predict.delay(pw.pk, setting.pk, limit=-1)
|
dispatch(request.user, predict, new_pw.pk, setting.pk, limit=-1)
|
||||||
|
|
||||||
return redirect(pw.url)
|
return redirect(new_pw.url)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(e)
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
||||||
|
def delete_pathway(request, package_uuid, pathway_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
pw.delete()
|
||||||
|
return redirect(f"{p.url}/pathway")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this pathway!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Pathway with id {pathway_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
########
|
########
|
||||||
@ -1143,6 +1563,52 @@ def get_package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateNode(Schema):
|
||||||
|
nodeAsSmiles: str
|
||||||
|
nodeName: str | None = None
|
||||||
|
nodeReason: str | None = None
|
||||||
|
nodeDepth: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/node",
|
||||||
|
response={200: str | Any, 403: Error},
|
||||||
|
)
|
||||||
|
def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
|
||||||
|
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
||||||
|
node_depth = int(n.nodeDepth)
|
||||||
|
else:
|
||||||
|
node_depth = -1
|
||||||
|
|
||||||
|
n = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
||||||
|
|
||||||
|
return redirect(n.url)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {"message": "Adding node failed!"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/node/{uuid:node_uuid}")
|
||||||
|
def delete_node(request, package_uuid, pathway_uuid, node_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
n = Node.objects.get(pathway=pw, uuid=node_uuid)
|
||||||
|
n.delete()
|
||||||
|
return redirect(f"{pw.url}/node")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Node!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Node with id {node_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
########
|
########
|
||||||
# Edge #
|
# Edge #
|
||||||
########
|
########
|
||||||
@ -1206,6 +1672,200 @@ def get_package_pathway_edge(request, package_uuid, pathway_uuid, edge_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateEdge(Schema):
|
||||||
|
edgeAsSmirks: str | None = None
|
||||||
|
educts: str | None = None # Node URIs comma sep
|
||||||
|
products: str | None = None # Node URIs comma sep
|
||||||
|
multistep: str | None = None
|
||||||
|
edgeReason: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/package/{uuid:package_uuid}/üathway/{uuid:pathway_uuid}/edge",
|
||||||
|
response={200: str | Any, 403: Error},
|
||||||
|
)
|
||||||
|
def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
|
||||||
|
if e.edgeAsSmirks is None and (e.educts is None or e.products is None):
|
||||||
|
raise ValueError("Either SMIRKS or educt/product must be provided")
|
||||||
|
|
||||||
|
if e.edgeAsSmirks is not None and (e.educts is not None and e.products is not None):
|
||||||
|
raise ValueError("SMIRKS and educt/product provided!")
|
||||||
|
|
||||||
|
educts = []
|
||||||
|
products = []
|
||||||
|
|
||||||
|
if e.edgeAsSmirks:
|
||||||
|
for ed in e.edgeAsSmirks.split(">>")[0].split("\\."):
|
||||||
|
educts.append(Node.objects.get(pathway=pw, default_node_label__smiles=ed))
|
||||||
|
|
||||||
|
for pr in e.edgeAsSmirks.split(">>")[1].split("\\."):
|
||||||
|
products.append(Node.objects.get(pathway=pw, default_node_label__smiles=pr))
|
||||||
|
else:
|
||||||
|
for ed in e.educts.split(","):
|
||||||
|
educts.append(Node.objects.get(pathway=pw, url=ed.strip()))
|
||||||
|
|
||||||
|
for pr in e.products.split(","):
|
||||||
|
products.append(Node.objects.get(pathway=pw, url=pr.strip()))
|
||||||
|
|
||||||
|
new_e = Edge.create(
|
||||||
|
pathway=pw,
|
||||||
|
start_nodes=educts,
|
||||||
|
end_nodes=products,
|
||||||
|
rule=None,
|
||||||
|
name=e.name,
|
||||||
|
description=e.edgeReason,
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect(new_e.url)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {"message": "Adding node failed!"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}/edge/{uuid:edge_uuid}")
|
||||||
|
def delete_edge(request, package_uuid, pathway_uuid, edge_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
e = Edge.objects.get(pathway=pw, uuid=edge_uuid)
|
||||||
|
e.delete()
|
||||||
|
return redirect(f"{pw.url}/edge")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Edge!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Edge with id {edge_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#########
|
||||||
|
# Model #
|
||||||
|
#########
|
||||||
|
class ModelWrapper(Schema):
|
||||||
|
relative_reasoning: List["SimpleModel"] = Field(..., alias="relative-reasoning")
|
||||||
|
|
||||||
|
|
||||||
|
class ModelSchema(Schema):
|
||||||
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
|
description: str = Field(None, alias="description")
|
||||||
|
evalPackages: List["SimplePackage"] = Field([])
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = "relative-reasoning"
|
||||||
|
# "info" : {
|
||||||
|
# "Accuracy (Single-Gen)" : "0.5932962678936605" ,
|
||||||
|
# "Area under PR-Curve (Single-Gen)" : "0.5654653182134282" ,
|
||||||
|
# "Area under ROC-Curve (Single-Gen)" : "0.8178302405034772" ,
|
||||||
|
# "Precision (Single-Gen)" : "0.6978730822873083" ,
|
||||||
|
# "Probability Threshold" : "0.5" ,
|
||||||
|
# "Recall/Sensitivity (Single-Gen)" : "0.4484149210261006"
|
||||||
|
# } ,
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
pathwayPackages: List["SimplePackage"] = Field([])
|
||||||
|
reviewStatus: str = Field(None, alias="review_status")
|
||||||
|
rulePackages: List["SimplePackage"] = Field([])
|
||||||
|
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
||||||
|
status: str
|
||||||
|
statusMessage: str
|
||||||
|
threshold: str
|
||||||
|
type: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/model", response={200: ModelWrapper, 403: Error})
|
||||||
|
def get_models(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/package/{uuid:package_uuid}/model", response={200: ModelWrapper, 403: Error})
|
||||||
|
def get_package_models(request, package_uuid, model_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
return EPModel.objects.filter(package=p)
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Getting Reaction with id {model_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Classify(Schema):
|
||||||
|
smiles: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/package/{uuid:package_uuid}/model/{uuid:model_uuid}",
|
||||||
|
response={200: ModelSchema | Any, 403: Error, 400: Error},
|
||||||
|
)
|
||||||
|
def get_model(request, package_uuid, model_uuid, c: Query[Classify]):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
mod = EPModel.objects.get(package=p, uuid=model_uuid)
|
||||||
|
|
||||||
|
if c.smiles:
|
||||||
|
if c.smiles == "":
|
||||||
|
return 400, {"message": "Received empty SMILES"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
stand_smiles = FormatConverter.standardize(c.smiles)
|
||||||
|
except ValueError:
|
||||||
|
return 400, {"message": f'"{c.smiles}" is not a valid SMILES'}
|
||||||
|
|
||||||
|
from epdb.tasks import dispatch_eager, predict_simple
|
||||||
|
|
||||||
|
pred_res = dispatch_eager(request.user, predict_simple, mod.pk, stand_smiles)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for pr in pred_res:
|
||||||
|
if len(pr) > 0:
|
||||||
|
products = []
|
||||||
|
for prod_set in pr.product_sets:
|
||||||
|
products.append(tuple([x for x in prod_set]))
|
||||||
|
|
||||||
|
res = {
|
||||||
|
"probability": pr.probability,
|
||||||
|
"products": list(set(products)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.rule:
|
||||||
|
res["id"] = pr.rule.url
|
||||||
|
res["identifier"] = pr.rule.get_rule_identifier()
|
||||||
|
res["name"] = pr.rule.name
|
||||||
|
res["reviewStatus"] = (
|
||||||
|
"reviewed" if pr.rule.package.reviewed else "unreviewed"
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(res)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
return mod
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Getting Reaction with id {model_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/package/{uuid:package_uuid}/model/{uuid:model_uuid}")
|
||||||
|
def delete_model(request, package_uuid, model_uuid):
|
||||||
|
try:
|
||||||
|
p = PackageManager.get_package_by_id(request.user, package_uuid)
|
||||||
|
|
||||||
|
if PackageManager.writable(request.user, p):
|
||||||
|
m = EPModel.objects.get(package=p, uuid=model_uuid)
|
||||||
|
m.delete()
|
||||||
|
return redirect(f"{p.url}/model")
|
||||||
|
else:
|
||||||
|
raise ValueError("You do not have the rights to delete this Model!")
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Model with id {model_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
# Setting #
|
# Setting #
|
||||||
###########
|
###########
|
||||||
|
|||||||
@ -1,39 +1,40 @@
|
|||||||
import re
|
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
from typing import Union, List, Optional, Set, Dict, Any
|
import logging
|
||||||
|
import re
|
||||||
|
from typing import Any, Dict, List, Optional, Set, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import nh3
|
import nh3
|
||||||
|
from django.conf import settings as s
|
||||||
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 pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from epdb.models import (
|
from epdb.models import (
|
||||||
User,
|
|
||||||
Package,
|
|
||||||
UserPackagePermission,
|
|
||||||
GroupPackagePermission,
|
|
||||||
Permission,
|
|
||||||
Group,
|
|
||||||
Setting,
|
|
||||||
EPModel,
|
|
||||||
UserSettingPermission,
|
|
||||||
Rule,
|
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
Edge,
|
|
||||||
Compound,
|
Compound,
|
||||||
Reaction,
|
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
|
Edge,
|
||||||
EnzymeLink,
|
EnzymeLink,
|
||||||
|
EPModel,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
|
Node,
|
||||||
|
Pathway,
|
||||||
|
Permission,
|
||||||
|
Reaction,
|
||||||
|
Rule,
|
||||||
|
Setting,
|
||||||
|
User,
|
||||||
|
UserPackagePermission,
|
||||||
|
UserSettingPermission,
|
||||||
)
|
)
|
||||||
from utilities.chem import FormatConverter
|
from utilities.chem import FormatConverter
|
||||||
from utilities.misc import PackageImporter, PackageExporter
|
from utilities.misc import PackageExporter, PackageImporter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class EPDBURLParser:
|
class EPDBURLParser:
|
||||||
UUID_PATTERN = r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
UUID_PATTERN = r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||||
@ -578,30 +579,39 @@ class PackageManager(object):
|
|||||||
else:
|
else:
|
||||||
_ = perm_cls.objects.update_or_create(defaults={"permission": new_perm}, **data)
|
_ = perm_cls.objects.update_or_create(defaults={"permission": new_perm}, **data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def grant_read(caller: User, package: Package, grantee: Union[User, Group]):
|
||||||
|
PackageManager.update_permissions(caller, package, grantee, Permission.READ[0])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def grant_write(caller: User, package: Package, grantee: Union[User, Group]):
|
||||||
|
PackageManager.update_permissions(caller, package, grantee, Permission.WRITE[0])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def import_legacy_package(
|
def import_legacy_package(
|
||||||
data: dict, owner: User, keep_ids=False, add_import_timestamp=True, trust_reviewed=False
|
data: dict, owner: User, keep_ids=False, add_import_timestamp=True, trust_reviewed=False
|
||||||
):
|
):
|
||||||
from uuid import UUID, uuid4
|
|
||||||
from datetime import datetime
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from datetime import datetime
|
||||||
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from envipy_additional_information import AdditionalInformationConverter
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Package,
|
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
SimpleRule,
|
Edge,
|
||||||
SimpleAmbitRule,
|
Node,
|
||||||
ParallelRule,
|
ParallelRule,
|
||||||
|
Pathway,
|
||||||
|
Reaction,
|
||||||
|
Scenario,
|
||||||
SequentialRule,
|
SequentialRule,
|
||||||
SequentialRuleOrdering,
|
SequentialRuleOrdering,
|
||||||
Reaction,
|
SimpleAmbitRule,
|
||||||
Pathway,
|
SimpleRule,
|
||||||
Node,
|
|
||||||
Edge,
|
|
||||||
Scenario,
|
|
||||||
)
|
)
|
||||||
from envipy_additional_information import AdditionalInformationConverter
|
|
||||||
|
|
||||||
pack = Package()
|
pack = Package()
|
||||||
pack.uuid = UUID(data["id"].split("/")[-1]) if keep_ids else uuid4()
|
pack.uuid = UUID(data["id"].split("/")[-1]) if keep_ids else uuid4()
|
||||||
|
|||||||
@ -2,7 +2,9 @@ from django.conf import settings as s
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from epdb.models import MLRelativeReasoning, EnviFormer, Package
|
from epdb.models import EnviFormer, MLRelativeReasoning
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -75,11 +77,13 @@ 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']}\n"
|
print(
|
||||||
f"Data packages: {options['data_packages']}\n"
|
f"Creating models: {options['model_names']}\n"
|
||||||
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
f"Data packages: {options['data_packages']}\n"
|
||||||
f"Eval Packages: {options['eval_packages']}\n"
|
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
|
||||||
f"Threshold: {options['threshold']:.2f}")
|
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"])
|
||||||
@ -90,10 +94,10 @@ class Command(BaseCommand):
|
|||||||
pack,
|
pack,
|
||||||
data_packages=data_packages,
|
data_packages=data_packages,
|
||||||
eval_packages=eval_packages,
|
eval_packages=eval_packages,
|
||||||
threshold=options['threshold'],
|
threshold=options["threshold"],
|
||||||
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
description=f"EnviFormer transformer trained on {options['data_packages']} "
|
||||||
f"evaluated on {options['eval_packages']}.",
|
f"evaluated on {options['eval_packages']}.",
|
||||||
)
|
)
|
||||||
elif model_name == "mlrr":
|
elif model_name == "mlrr":
|
||||||
model = MLRelativeReasoning.create(
|
model = MLRelativeReasoning.create(
|
||||||
@ -101,10 +105,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=options['threshold'],
|
threshold=options["threshold"],
|
||||||
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
|
||||||
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
|
||||||
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
|
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")
|
||||||
|
|||||||
@ -8,7 +8,9 @@ from django.conf import settings as s
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from epdb.models import EnviFormer, Package
|
from epdb.models import EnviFormer
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.conf import settings as s
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db.models import F, JSONField, TextField, Value
|
||||||
from django.db.models import F, Value, TextField, JSONField
|
from django.db.models.functions import Cast, Replace
|
||||||
from django.db.models.functions import Replace, Cast
|
|
||||||
|
|
||||||
from epdb.models import EnviPathModel
|
from epdb.models import EnviPathModel
|
||||||
|
|
||||||
@ -23,10 +23,13 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
print("Localizing urls for Package")
|
||||||
|
Package.objects.update(url=Replace(F("url"), Value(options["old"]), Value(options["new"])))
|
||||||
|
|
||||||
MODELS = [
|
MODELS = [
|
||||||
"User",
|
"User",
|
||||||
"Group",
|
"Group",
|
||||||
"Package",
|
|
||||||
"Compound",
|
"Compound",
|
||||||
"CompoundStructure",
|
"CompoundStructure",
|
||||||
"Pathway",
|
"Pathway",
|
||||||
|
|||||||
127
epdb/models.py
127
epdb/models.py
@ -2,40 +2,41 @@ import abc
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Union, List, Optional, Dict, Tuple, Set, Any
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import math
|
|
||||||
import joblib
|
import joblib
|
||||||
import nh3
|
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
|
||||||
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import JSONField, Count, Q, QuerySet
|
from django.db.models import Count, JSONField, Q, QuerySet
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from envipy_additional_information import EnviPyModel
|
from envipy_additional_information import EnviPyModel
|
||||||
from model_utils.models import TimeStampedModel
|
from model_utils.models import TimeStampedModel
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
from sklearn.metrics import precision_score, recall_score, jaccard_score
|
from sklearn.metrics import jaccard_score, precision_score, recall_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, IndigoUtils, PredictionResult, ProductSet
|
||||||
from utilities.ml import (
|
from utilities.ml import (
|
||||||
RuleBasedDataset,
|
|
||||||
ApplicabilityDomainPCA,
|
ApplicabilityDomainPCA,
|
||||||
EnsembleClassifierChain,
|
|
||||||
RelativeReasoning,
|
|
||||||
EnviFormerDataset,
|
|
||||||
Dataset,
|
Dataset,
|
||||||
|
EnsembleClassifierChain,
|
||||||
|
EnviFormerDataset,
|
||||||
|
RelativeReasoning,
|
||||||
|
RuleBasedDataset,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -44,8 +45,6 @@ logger = logging.getLogger(__name__)
|
|||||||
##########################
|
##########################
|
||||||
# User/Groups/Permission #
|
# User/Groups/Permission #
|
||||||
##########################
|
##########################
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
email = models.EmailField(unique=True)
|
email = models.EmailField(unique=True)
|
||||||
uuid = models.UUIDField(
|
uuid = models.UUIDField(
|
||||||
@ -53,7 +52,10 @@ class User(AbstractUser):
|
|||||||
)
|
)
|
||||||
url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True)
|
url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True)
|
||||||
default_package = models.ForeignKey(
|
default_package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Default Package", null=True, on_delete=models.SET_NULL
|
s.EPDB_PACKAGE_MODEL,
|
||||||
|
verbose_name="Default Package",
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
default_group = models.ForeignKey(
|
default_group = models.ForeignKey(
|
||||||
"Group",
|
"Group",
|
||||||
@ -243,7 +245,7 @@ class UserPackagePermission(Permission):
|
|||||||
)
|
)
|
||||||
user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE)
|
user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE)
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
|
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -259,7 +261,7 @@ class GroupPackagePermission(Permission):
|
|||||||
)
|
)
|
||||||
group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE)
|
group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE)
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
|
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -728,10 +730,13 @@ class Package(EnviPathModel):
|
|||||||
rules = sorted(rules, key=lambda x: x.url)
|
rules = sorted(rules, key=lambda x: x.url)
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
swappable = "EPDB_PACKAGE_MODEL"
|
||||||
|
|
||||||
|
|
||||||
class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin):
|
class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
default_structure = models.ForeignKey(
|
default_structure = models.ForeignKey(
|
||||||
"CompoundStructure",
|
"CompoundStructure",
|
||||||
@ -781,7 +786,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs
|
package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs
|
||||||
) -> "Compound":
|
) -> "Compound":
|
||||||
if smiles is None or smiles.strip() == "":
|
if smiles is None or smiles.strip() == "":
|
||||||
raise ValueError("SMILES is required")
|
raise ValueError("SMILES is required")
|
||||||
@ -1061,7 +1066,7 @@ class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
|
|||||||
|
|
||||||
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# # https://github.com/django-polymorphic/django-polymorphic/issues/229
|
# # https://github.com/django-polymorphic/django-polymorphic/issues/229
|
||||||
@ -1074,6 +1079,10 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
def apply(self, *args, **kwargs):
|
def apply(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cls_for_type(rule_type: str):
|
def cls_for_type(rule_type: str):
|
||||||
if rule_type == "SimpleAmbitRule":
|
if rule_type == "SimpleAmbitRule":
|
||||||
@ -1167,7 +1176,7 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
package: Package,
|
package: "Package",
|
||||||
name: str = None,
|
name: str = None,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
smirks: str = None,
|
smirks: str = None,
|
||||||
@ -1228,6 +1237,9 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
def _url(self):
|
def _url(self):
|
||||||
return "{}/simple-ambit-rule/{}".format(self.package.url, self.uuid)
|
return "{}/simple-ambit-rule/{}".format(self.package.url, self.uuid)
|
||||||
|
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
return "simple-rule"
|
||||||
|
|
||||||
def apply(self, smiles):
|
def apply(self, smiles):
|
||||||
return FormatConverter.apply(smiles, self.smirks)
|
return FormatConverter.apply(smiles, self.smirks)
|
||||||
|
|
||||||
@ -1241,7 +1253,7 @@ class SimpleAmbitRule(SimpleRule):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def related_reactions(self):
|
def related_reactions(self):
|
||||||
qs = Package.objects.filter(reviewed=True)
|
qs = s.GET_PACKAGE_MODEL().objects.filter(reviewed=True)
|
||||||
return self.reaction_rule.filter(package__in=qs).order_by("name")
|
return self.reaction_rule.filter(package__in=qs).order_by("name")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1273,6 +1285,9 @@ class ParallelRule(Rule):
|
|||||||
def _url(self):
|
def _url(self):
|
||||||
return "{}/parallel-rule/{}".format(self.package.url, self.uuid)
|
return "{}/parallel-rule/{}".format(self.package.url, self.uuid)
|
||||||
|
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
return "parallel-rule"
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def srs(self) -> QuerySet:
|
def srs(self) -> QuerySet:
|
||||||
return self.simple_rules.all()
|
return self.simple_rules.all()
|
||||||
@ -1304,6 +1319,57 @@ class ParallelRule(Rule):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@transaction.atomic
|
||||||
|
def create(
|
||||||
|
package: "Package",
|
||||||
|
simple_rules: List["SimpleRule"],
|
||||||
|
name: str = None,
|
||||||
|
description: str = None,
|
||||||
|
reactant_filter_smarts: str = None,
|
||||||
|
product_filter_smarts: str = None,
|
||||||
|
):
|
||||||
|
if len(simple_rules) == 0:
|
||||||
|
raise ValueError("At least one simple rule is required!")
|
||||||
|
|
||||||
|
for sr in simple_rules:
|
||||||
|
if sr.package != package:
|
||||||
|
raise ValueError(
|
||||||
|
f"Simple rule {sr.uuid} does not belong to package {package.uuid}!"
|
||||||
|
)
|
||||||
|
|
||||||
|
r = ParallelRule()
|
||||||
|
r.package = package
|
||||||
|
|
||||||
|
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}"
|
||||||
|
|
||||||
|
r.name = name
|
||||||
|
if description is not None and description.strip() != "":
|
||||||
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
|
||||||
|
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 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()
|
||||||
|
|
||||||
|
for sr in simple_rules:
|
||||||
|
r.simple_rules.add(sr)
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
class SequentialRule(Rule):
|
class SequentialRule(Rule):
|
||||||
simple_rules = models.ManyToManyField(
|
simple_rules = models.ManyToManyField(
|
||||||
@ -1313,6 +1379,9 @@ class SequentialRule(Rule):
|
|||||||
def _url(self):
|
def _url(self):
|
||||||
return "{}/sequential-rule/{}".format(self.compound.url, self.uuid)
|
return "{}/sequential-rule/{}".format(self.compound.url, self.uuid)
|
||||||
|
|
||||||
|
def get_rule_identifier(self) -> str:
|
||||||
|
return "sequential-rule"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def srs(self):
|
def srs(self):
|
||||||
return self.simple_rules.all()
|
return self.simple_rules.all()
|
||||||
@ -1333,7 +1402,7 @@ class SequentialRuleOrdering(models.Model):
|
|||||||
|
|
||||||
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
|
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
educts = models.ManyToManyField(
|
educts = models.ManyToManyField(
|
||||||
"epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts"
|
"epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts"
|
||||||
@ -1355,7 +1424,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
package: Package,
|
package: "Package",
|
||||||
name: str = None,
|
name: str = None,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
educts: Union[List[str], List[CompoundStructure]] = None,
|
educts: Union[List[str], List[CompoundStructure]] = None,
|
||||||
@ -1514,7 +1583,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
|||||||
|
|
||||||
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
setting = models.ForeignKey(
|
setting = models.ForeignKey(
|
||||||
"epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True
|
"epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True
|
||||||
@ -2076,7 +2145,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
|
|
||||||
class EPModel(PolymorphicModel, EnviPathModel):
|
class EPModel(PolymorphicModel, EnviPathModel):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def _url(self):
|
def _url(self):
|
||||||
@ -2085,17 +2154,17 @@ class EPModel(PolymorphicModel, EnviPathModel):
|
|||||||
|
|
||||||
class PackageBasedModel(EPModel):
|
class PackageBasedModel(EPModel):
|
||||||
rule_packages = models.ManyToManyField(
|
rule_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Rule Packages",
|
verbose_name="Rule Packages",
|
||||||
related_name="%(app_label)s_%(class)s_rule_packages",
|
related_name="%(app_label)s_%(class)s_rule_packages",
|
||||||
)
|
)
|
||||||
data_packages = models.ManyToManyField(
|
data_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Data Packages",
|
verbose_name="Data Packages",
|
||||||
related_name="%(app_label)s_%(class)s_data_packages",
|
related_name="%(app_label)s_%(class)s_data_packages",
|
||||||
)
|
)
|
||||||
eval_packages = models.ManyToManyField(
|
eval_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Evaluation Packages",
|
verbose_name="Evaluation Packages",
|
||||||
related_name="%(app_label)s_%(class)s_eval_packages",
|
related_name="%(app_label)s_%(class)s_eval_packages",
|
||||||
)
|
)
|
||||||
@ -3400,7 +3469,7 @@ class PluginModel(EPModel):
|
|||||||
|
|
||||||
class Scenario(EnviPathModel):
|
class Scenario(EnviPathModel):
|
||||||
package = models.ForeignKey(
|
package = models.ForeignKey(
|
||||||
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
scenario_date = models.CharField(max_length=256, null=False, blank=False, default="No date")
|
scenario_date = models.CharField(max_length=256, null=False, blank=False, default="No date")
|
||||||
scenario_type = models.CharField(
|
scenario_type = models.CharField(
|
||||||
@ -3555,7 +3624,7 @@ class Setting(EnviPathModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
rule_packages = models.ManyToManyField(
|
rule_packages = models.ManyToManyField(
|
||||||
"Package",
|
s.EPDB_PACKAGE_MODEL,
|
||||||
verbose_name="Setting Rule Packages",
|
verbose_name="Setting Rule Packages",
|
||||||
related_name="setting_rule_packages",
|
related_name="setting_rule_packages",
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|||||||
@ -6,14 +6,17 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from celery.utils.functional import LRUCache
|
from celery.utils.functional import LRUCache
|
||||||
|
from django.conf import settings as s
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from epdb.logic import SPathway
|
from epdb.logic import SPathway
|
||||||
from epdb.models import Edge, EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User
|
from epdb.models import Edge, EPModel, JobLog, Node, Pathway, Rule, Setting, User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def get_ml_model(model_pk: int):
|
def get_ml_model(model_pk: int):
|
||||||
if model_pk not in ML_CACHE:
|
if model_pk not in ML_CACHE:
|
||||||
|
|||||||
134
epdb/views.py
134
epdb/views.py
@ -1,58 +1,60 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Any
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
import nh3
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse
|
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
|
||||||
from utilities.misc import HTMLGenerator
|
from utilities.misc import HTMLGenerator
|
||||||
|
|
||||||
from .logic import (
|
from .logic import (
|
||||||
|
EPDBURLParser,
|
||||||
GroupManager,
|
GroupManager,
|
||||||
PackageManager,
|
PackageManager,
|
||||||
UserManager,
|
|
||||||
SettingManager,
|
|
||||||
SearchManager,
|
SearchManager,
|
||||||
EPDBURLParser,
|
SettingManager,
|
||||||
|
UserManager,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
Package,
|
APIToken,
|
||||||
GroupPackagePermission,
|
|
||||||
Group,
|
|
||||||
CompoundStructure,
|
|
||||||
Compound,
|
Compound,
|
||||||
|
CompoundStructure,
|
||||||
|
Edge,
|
||||||
|
EnviFormer,
|
||||||
|
EnzymeLink,
|
||||||
|
EPModel,
|
||||||
|
ExternalDatabase,
|
||||||
|
ExternalIdentifier,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
|
JobLog,
|
||||||
|
License,
|
||||||
|
MLRelativeReasoning,
|
||||||
|
Node,
|
||||||
|
Pathway,
|
||||||
|
Permission,
|
||||||
Reaction,
|
Reaction,
|
||||||
Rule,
|
Rule,
|
||||||
Pathway,
|
|
||||||
Node,
|
|
||||||
EPModel,
|
|
||||||
EnviFormer,
|
|
||||||
MLRelativeReasoning,
|
|
||||||
RuleBasedRelativeReasoning,
|
RuleBasedRelativeReasoning,
|
||||||
Scenario,
|
Scenario,
|
||||||
SimpleAmbitRule,
|
SimpleAmbitRule,
|
||||||
APIToken,
|
|
||||||
UserPackagePermission,
|
|
||||||
Permission,
|
|
||||||
License,
|
|
||||||
User,
|
User,
|
||||||
Edge,
|
UserPackagePermission,
|
||||||
ExternalDatabase,
|
|
||||||
ExternalIdentifier,
|
|
||||||
EnzymeLink,
|
|
||||||
JobLog,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def log_post_params(request):
|
def log_post_params(request):
|
||||||
if s.DEBUG:
|
if s.DEBUG:
|
||||||
@ -60,6 +62,26 @@ def log_post_params(request):
|
|||||||
logger.debug(f"{k}\t{v}")
|
logger.debug(f"{k}\t{v}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_error_handler_context(request, for_user=None) -> Dict[str, Any]:
|
||||||
|
current_user = _anonymous_or_real(request)
|
||||||
|
|
||||||
|
if for_user:
|
||||||
|
current_user = for_user
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
"title": "enviPath",
|
||||||
|
"meta": {
|
||||||
|
"site_id": s.MATOMO_SITE_ID,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"server_url": s.SERVER_URL,
|
||||||
|
"user": current_user,
|
||||||
|
"enabled_features": s.FLAGS,
|
||||||
|
"debug": s.DEBUG,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
def error(request, message: str, detail: str, code: int = 400):
|
def error(request, message: str, detail: str, code: int = 400):
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
error_context = {
|
error_context = {
|
||||||
@ -74,6 +96,48 @@ def error(request, message: str, detail: str, code: int = 400):
|
|||||||
return render(request, "errors/error.html", context, status=code)
|
return render(request, "errors/error.html", context, status=code)
|
||||||
|
|
||||||
|
|
||||||
|
def handler400(request, exception):
|
||||||
|
"""Custom 400 Bad Request error handler"""
|
||||||
|
context = get_error_handler_context(request)
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "errors/400_bad_request.html", context, status=400)
|
||||||
|
|
||||||
|
|
||||||
|
def handler403(request, exception):
|
||||||
|
"""Custom 403 Forbidden error handler"""
|
||||||
|
context = get_error_handler_context(request)
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "errors/403_access_denied.html", context, status=403)
|
||||||
|
|
||||||
|
|
||||||
|
def handler404(request, exception):
|
||||||
|
"""Custom 404 Not Found error handler"""
|
||||||
|
context = get_error_handler_context(request)
|
||||||
|
context["public_mode"] = True
|
||||||
|
return render(request, "errors/404_not_found.html", context, status=404)
|
||||||
|
|
||||||
|
|
||||||
|
def handler500(request):
|
||||||
|
"""Custom 500 Internal Server Error handler"""
|
||||||
|
context = get_error_handler_context(request)
|
||||||
|
|
||||||
|
error_context = {}
|
||||||
|
error_context["error_message"] = "Internal Server Error"
|
||||||
|
error_context["error_detail"] = "An unexpected error occurred. Please try again later."
|
||||||
|
|
||||||
|
if request.headers.get("Accept") == "application/json":
|
||||||
|
return JsonResponse(error_context, status=500)
|
||||||
|
|
||||||
|
context["public_mode"] = True
|
||||||
|
context["error_code"] = 500
|
||||||
|
context["error_description"] = (
|
||||||
|
"We encountered an unexpected error while processing your request. Our team has been notified and is working to resolve the issue."
|
||||||
|
)
|
||||||
|
context.update(**error_context)
|
||||||
|
|
||||||
|
return render(request, "errors/error.html", context, status=500)
|
||||||
|
|
||||||
|
|
||||||
def login(request):
|
def login(request):
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
@ -83,8 +147,7 @@ def login(request):
|
|||||||
return render(request, "static/login.html", context)
|
return render(request, "static/login.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth import login
|
|
||||||
|
|
||||||
username = request.POST.get("username").strip()
|
username = request.POST.get("username").strip()
|
||||||
if username != request.POST.get("username"):
|
if username != request.POST.get("username"):
|
||||||
@ -191,8 +254,8 @@ def register(request):
|
|||||||
|
|
||||||
|
|
||||||
def editable(request, user):
|
def editable(request, user):
|
||||||
if user.is_superuser:
|
# if user.is_superuser:
|
||||||
return True
|
# return True
|
||||||
|
|
||||||
url = request.build_absolute_uri(request.path)
|
url = request.build_absolute_uri(request.path)
|
||||||
if PackageManager.is_package_url(url):
|
if PackageManager.is_package_url(url):
|
||||||
@ -872,7 +935,7 @@ def package_models(request, package_uuid):
|
|||||||
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
|
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
|
||||||
)
|
)
|
||||||
|
|
||||||
from .tasks import dispatch, build_model
|
from .tasks import build_model, dispatch
|
||||||
|
|
||||||
dispatch(current_user, build_model, mod.pk)
|
dispatch(current_user, build_model, mod.pk)
|
||||||
|
|
||||||
@ -906,9 +969,10 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
if classify:
|
if classify:
|
||||||
from epdb.tasks import dispatch_eager, predict_simple
|
from epdb.tasks import dispatch_eager, predict_simple
|
||||||
|
|
||||||
res = dispatch_eager(current_user, predict_simple, current_model.pk, stand_smiles)
|
pred_res = dispatch_eager(
|
||||||
|
current_user, predict_simple, current_model.pk, stand_smiles
|
||||||
|
)
|
||||||
|
|
||||||
pred_res = current_model.predict(stand_smiles)
|
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
for pr in pred_res:
|
for pr in pred_res:
|
||||||
@ -1068,9 +1132,7 @@ def package(request, package_uuid):
|
|||||||
return redirect(s.SERVER_URL + "/package")
|
return redirect(s.SERVER_URL + "/package")
|
||||||
elif hidden == "publish-package":
|
elif hidden == "publish-package":
|
||||||
for g in Group.objects.filter(public=True):
|
for g in Group.objects.filter(public=True):
|
||||||
PackageManager.update_permissions(
|
PackageManager.grant_read(current_user, current_package, g)
|
||||||
current_user, current_package, g, Permission.READ[0]
|
|
||||||
)
|
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
elif hidden == "copy":
|
elif hidden == "copy":
|
||||||
object_to_copy = request.POST.get("object_to_copy")
|
object_to_copy = request.POST.get("object_to_copy")
|
||||||
@ -2409,9 +2471,9 @@ def package_scenarios(request, package_uuid):
|
|||||||
context["unreviewed_objects"] = unreviewed_scenario_qs
|
context["unreviewed_objects"] = unreviewed_scenario_qs
|
||||||
|
|
||||||
from envipy_additional_information import (
|
from envipy_additional_information import (
|
||||||
|
SEDIMENT_ADDITIONAL_INFORMATION,
|
||||||
SLUDGE_ADDITIONAL_INFORMATION,
|
SLUDGE_ADDITIONAL_INFORMATION,
|
||||||
SOIL_ADDITIONAL_INFORMATION,
|
SOIL_ADDITIONAL_INFORMATION,
|
||||||
SEDIMENT_ADDITIONAL_INFORMATION,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
context["scenario_types"] = {
|
context["scenario_types"] = {
|
||||||
|
|||||||
@ -1,24 +1,21 @@
|
|||||||
import gzip
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.http import HttpResponseNotAllowed
|
from django.http import HttpResponseNotAllowed
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from epdb.logic import PackageManager
|
|
||||||
from epdb.models import Rule, SimpleAmbitRule, Package, CompoundStructure
|
|
||||||
from epdb.views import get_base_context, _anonymous_or_real
|
|
||||||
from utilities.chem import FormatConverter
|
|
||||||
|
|
||||||
|
|
||||||
from rdkit import Chem
|
from rdkit import Chem
|
||||||
from rdkit.Chem.MolStandardize import rdMolStandardize
|
from rdkit.Chem.MolStandardize import rdMolStandardize
|
||||||
|
|
||||||
|
from epdb.models import CompoundStructure, Rule, SimpleAmbitRule
|
||||||
|
from epdb.views import get_base_context
|
||||||
|
from utilities.chem import FormatConverter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def normalize_smiles(smiles):
|
def normalize_smiles(smiles):
|
||||||
m1 = Chem.MolFromSmiles(smiles)
|
m1 = Chem.MolFromSmiles(smiles)
|
||||||
@ -59,9 +56,7 @@ def run_both_engines(SMILES, SMIRKS):
|
|||||||
set(
|
set(
|
||||||
[
|
[
|
||||||
normalize_smiles(str(x))
|
normalize_smiles(str(x))
|
||||||
for x in FormatConverter.sanitize_smiles(
|
for x in FormatConverter.sanitize_smiles([str(s) for s in all_rdkit_prods])[0]
|
||||||
[str(s) for s in all_rdkit_prods]
|
|
||||||
)[0]
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -85,8 +80,7 @@ def migration(request):
|
|||||||
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
url="http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1"
|
||||||
)
|
)
|
||||||
ALL_SMILES = [
|
ALL_SMILES = [
|
||||||
cs.smiles
|
cs.smiles for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
||||||
for cs in CompoundStructure.objects.filter(compound__package=BBD)
|
|
||||||
]
|
]
|
||||||
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
RULES = SimpleAmbitRule.objects.filter(package=BBD)
|
||||||
|
|
||||||
@ -142,9 +136,7 @@ def migration(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for r in migration_status["results"]:
|
for r in migration_status["results"]:
|
||||||
r["detail_url"] = r["detail_url"].replace(
|
r["detail_url"] = r["detail_url"].replace("http://localhost:8000", s.SERVER_URL)
|
||||||
"http://localhost:8000", s.SERVER_URL
|
|
||||||
)
|
|
||||||
|
|
||||||
context.update(**migration_status)
|
context.update(**migration_status)
|
||||||
|
|
||||||
@ -152,8 +144,6 @@ def migration(request):
|
|||||||
|
|
||||||
|
|
||||||
def migration_detail(request, package_uuid, rule_uuid):
|
def migration_detail(request, package_uuid, rule_uuid):
|
||||||
current_user = _anonymous_or_real(request)
|
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
|
|
||||||
@ -235,9 +225,7 @@ def compare(request):
|
|||||||
context["smirks"] = (
|
context["smirks"] = (
|
||||||
"[#1,#6:6][#7;X3;!$(NC1CC1)!$([N][C]=O)!$([!#8]CNC=O):1]([#1,#6:7])[#6;A;X4:2][H:3]>>[#1,#6:6][#7;X3:1]([#1,#6:7])[H:3].[#6;A:2]=O"
|
"[#1,#6:6][#7;X3;!$(NC1CC1)!$([N][C]=O)!$([!#8]CNC=O):1]([#1,#6:7])[#6;A;X4:2][H:3]>>[#1,#6:6][#7;X3:1]([#1,#6:7])[H:3].[#6;A:2]=O"
|
||||||
)
|
)
|
||||||
context["smiles"] = (
|
context["smiles"] = "C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N"
|
||||||
"C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N"
|
|
||||||
)
|
|
||||||
return render(request, "compare.html", context)
|
return render(request, "compare.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
|
|||||||
@ -34,7 +34,7 @@ dependencies = [
|
|||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.4" }
|
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" }
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
@ -45,6 +45,8 @@ dev = [
|
|||||||
"poethepoet>=0.37.0",
|
"poethepoet>=0.37.0",
|
||||||
"pre-commit>=4.3.0",
|
"pre-commit>=4.3.0",
|
||||||
"ruff>=0.13.3",
|
"ruff>=0.13.3",
|
||||||
|
"pytest-playwright>=0.7.1",
|
||||||
|
"pytest-django>=4.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
@ -66,47 +68,31 @@ 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 = [
|
||||||
dev = { shell = """
|
"db-up",
|
||||||
# Start pnpm CSS watcher in background
|
"migrate",
|
||||||
pnpm run dev &
|
"bootstrap",
|
||||||
PNPM_PID=$!
|
], help = "Complete setup: start database, run migrations, and bootstrap data" }
|
||||||
echo "Started CSS watcher (PID: $PNPM_PID)"
|
dev = { cmd = "uv run python scripts/dev_server.py", help = "Start the development server with CSS watcher", deps = [
|
||||||
|
"db-up",
|
||||||
# Cleanup function
|
"js-deps",
|
||||||
cleanup() {
|
] }
|
||||||
echo "\nShutting down..."
|
build = { sequence = [
|
||||||
if kill -0 $PNPM_PID 2>/dev/null; then
|
"build-frontend",
|
||||||
kill $PNPM_PID
|
"collectstatic",
|
||||||
echo "✓ CSS watcher stopped"
|
], help = "Build frontend assets and collect static files" }
|
||||||
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
|
# Frontend tasks
|
||||||
js-deps = { cmd = "pnpm install", help = "Install frontend dependencies" }
|
js-deps = { cmd = "uv run python scripts/pnpm_wrapper.py 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
|
||||||
@ -121,9 +107,17 @@ echo "Default admin credentials:"
|
|||||||
echo " Username: admin"
|
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 = "uv run 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"] }
|
build-frontend = { cmd = "uv run python scripts/pnpm_wrapper.py run build", help = "Build frontend assets using pnpm", deps = [
|
||||||
collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = ["build-frontend"] }
|
"js-deps",
|
||||||
|
] } # Build tasks
|
||||||
|
|
||||||
|
|
||||||
|
collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = [
|
||||||
|
"build-frontend",
|
||||||
|
] }
|
||||||
|
|
||||||
|
frontend-test-setup = { cmd = "playwright install", help = "Install the browsers required for frontend testing" }
|
||||||
|
|||||||
201
scripts/dev_server.py
Executable file
201
scripts/dev_server.py
Executable file
@ -0,0 +1,201 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Cross-platform development server script.
|
||||||
|
Starts pnpm CSS watcher and Django dev server, handling cleanup on exit.
|
||||||
|
Works on both Windows and Unix systems.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def find_pnpm():
|
||||||
|
"""
|
||||||
|
Find pnpm executable on the system.
|
||||||
|
Returns the path to pnpm or None if not found.
|
||||||
|
"""
|
||||||
|
# Try to find pnpm using shutil.which
|
||||||
|
# On Windows, this will find pnpm.cmd if it's in PATH
|
||||||
|
pnpm_path = shutil.which("pnpm")
|
||||||
|
|
||||||
|
if pnpm_path:
|
||||||
|
return pnpm_path
|
||||||
|
|
||||||
|
# On Windows, also try pnpm.cmd explicitly
|
||||||
|
if sys.platform == "win32":
|
||||||
|
pnpm_cmd = shutil.which("pnpm.cmd")
|
||||||
|
if pnpm_cmd:
|
||||||
|
return pnpm_cmd
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class DevServerManager:
|
||||||
|
"""Manages background processes for development server."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.processes = []
|
||||||
|
self._cleanup_registered = False
|
||||||
|
|
||||||
|
def start_process(self, command, description, shell=False):
|
||||||
|
"""Start a background process and return the process object."""
|
||||||
|
print(f"Starting {description}...")
|
||||||
|
try:
|
||||||
|
if shell:
|
||||||
|
# Use shell=True for commands that need shell interpretation
|
||||||
|
process = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
shell=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
bufsize=1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Split command into list for subprocess
|
||||||
|
process = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
bufsize=1,
|
||||||
|
)
|
||||||
|
self.processes.append((process, description))
|
||||||
|
print(f"✓ Started {description} (PID: {process.pid})")
|
||||||
|
return process
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Failed to start {description}: {e}", file=sys.stderr)
|
||||||
|
self.cleanup()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Terminate all running processes."""
|
||||||
|
if not self.processes:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nShutting down...")
|
||||||
|
for process, description in self.processes:
|
||||||
|
if process.poll() is None: # Process is still running
|
||||||
|
try:
|
||||||
|
# Try graceful termination first
|
||||||
|
if sys.platform == "win32":
|
||||||
|
process.terminate()
|
||||||
|
else:
|
||||||
|
process.send_signal(signal.SIGTERM)
|
||||||
|
|
||||||
|
# Wait up to 5 seconds for graceful shutdown
|
||||||
|
try:
|
||||||
|
process.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
# Force kill if graceful shutdown failed
|
||||||
|
if sys.platform == "win32":
|
||||||
|
process.kill()
|
||||||
|
else:
|
||||||
|
process.send_signal(signal.SIGKILL)
|
||||||
|
process.wait()
|
||||||
|
|
||||||
|
print(f"✓ {description} stopped")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error stopping {description}: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
self.processes.clear()
|
||||||
|
|
||||||
|
def register_cleanup(self):
|
||||||
|
"""Register cleanup handlers for various exit scenarios."""
|
||||||
|
if self._cleanup_registered:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._cleanup_registered = True
|
||||||
|
|
||||||
|
# Register atexit handler (works on all platforms)
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
|
||||||
|
# Register signal handlers (Unix only)
|
||||||
|
if sys.platform != "win32":
|
||||||
|
signal.signal(signal.SIGINT, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||||
|
|
||||||
|
def _signal_handler(self, signum, frame):
|
||||||
|
"""Handle Unix signals."""
|
||||||
|
self.cleanup()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def wait_for_process(self, process, description):
|
||||||
|
"""Wait for a process to finish and handle its output."""
|
||||||
|
try:
|
||||||
|
# Stream output from the process
|
||||||
|
for line in iter(process.stdout.readline, ""):
|
||||||
|
if line:
|
||||||
|
print(f"[{description}] {line.rstrip()}")
|
||||||
|
|
||||||
|
process.wait()
|
||||||
|
return process.returncode
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# Handle Ctrl+C
|
||||||
|
self.cleanup()
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error waiting for {description}: {e}", file=sys.stderr)
|
||||||
|
self.cleanup()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
manager = DevServerManager()
|
||||||
|
manager.register_cleanup()
|
||||||
|
|
||||||
|
# Find pnpm executable
|
||||||
|
pnpm_path = find_pnpm()
|
||||||
|
if not pnpm_path:
|
||||||
|
print("Error: pnpm not found in PATH.", file=sys.stderr)
|
||||||
|
print("\nPlease install pnpm:", file=sys.stderr)
|
||||||
|
print(" Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
|
||||||
|
print(" Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Determine shell usage based on platform
|
||||||
|
use_shell = sys.platform == "win32"
|
||||||
|
|
||||||
|
# Start pnpm CSS watcher
|
||||||
|
# Use the found pnpm path to ensure it works on Windows
|
||||||
|
pnpm_command = f'"{pnpm_path}" run dev' if use_shell else [pnpm_path, "run", "dev"]
|
||||||
|
manager.start_process(
|
||||||
|
pnpm_command,
|
||||||
|
"CSS watcher",
|
||||||
|
shell=use_shell,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Give pnpm a moment to start
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Start Django dev server
|
||||||
|
django_process = manager.start_process(
|
||||||
|
["uv", "run", "python", "manage.py", "runserver"],
|
||||||
|
"Django server",
|
||||||
|
shell=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nDevelopment servers are running. Press Ctrl+C to stop.\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Wait for Django server (main process)
|
||||||
|
# If Django exits, we should clean up everything
|
||||||
|
return_code = manager.wait_for_process(django_process, "Django")
|
||||||
|
|
||||||
|
# If Django exited unexpectedly, clean up and exit
|
||||||
|
if return_code != 0:
|
||||||
|
manager.cleanup()
|
||||||
|
sys.exit(return_code)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# Ctrl+C was pressed
|
||||||
|
manager.cleanup()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
59
scripts/pnpm_wrapper.py
Executable file
59
scripts/pnpm_wrapper.py
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Cross-platform pnpm command wrapper.
|
||||||
|
Finds pnpm correctly on Windows (handles pnpm.cmd) and Unix systems.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def find_pnpm():
|
||||||
|
"""
|
||||||
|
Find pnpm executable on the system.
|
||||||
|
Returns the path to pnpm or None if not found.
|
||||||
|
"""
|
||||||
|
# Try to find pnpm using shutil.which
|
||||||
|
# On Windows, this will find pnpm.cmd if it's in PATH
|
||||||
|
pnpm_path = shutil.which("pnpm")
|
||||||
|
|
||||||
|
if pnpm_path:
|
||||||
|
return pnpm_path
|
||||||
|
|
||||||
|
# On Windows, also try pnpm.cmd explicitly
|
||||||
|
if sys.platform == "win32":
|
||||||
|
pnpm_cmd = shutil.which("pnpm.cmd")
|
||||||
|
if pnpm_cmd:
|
||||||
|
return pnpm_cmd
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point - execute pnpm with provided arguments."""
|
||||||
|
pnpm_path = find_pnpm()
|
||||||
|
|
||||||
|
if not pnpm_path:
|
||||||
|
print("Error: pnpm not found in PATH.", file=sys.stderr)
|
||||||
|
print("\nPlease install pnpm:", file=sys.stderr)
|
||||||
|
print(" Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
|
||||||
|
print(" Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Get all arguments passed to this script
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
# Execute pnpm with the provided arguments
|
||||||
|
try:
|
||||||
|
sys.exit(subprocess.call([pnpm_path] + args))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# Handle Ctrl+C gracefully
|
||||||
|
sys.exit(130)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error executing pnpm: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -34,3 +34,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@import "./daisyui-theme.css";
|
@import "./daisyui-theme.css";
|
||||||
|
|
||||||
|
/* Loading Spinner - Benzene Ring */
|
||||||
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner svg {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner .hexagon,
|
||||||
|
.loading-spinner .double-bonds {
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 814 B |
265
static/js/alpine/index.js
Normal file
265
static/js/alpine/index.js
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
/**
|
||||||
|
* Alpine.js Components for enviPath
|
||||||
|
*
|
||||||
|
* This module provides reusable Alpine.js data components for modals,
|
||||||
|
* form validation, and form submission.
|
||||||
|
*/
|
||||||
|
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
/**
|
||||||
|
* Modal Form Component
|
||||||
|
*
|
||||||
|
* Provides form validation using HTML5 Constraint Validation API,
|
||||||
|
* loading states for submission, and error message management.
|
||||||
|
*
|
||||||
|
* Basic Usage:
|
||||||
|
* <dialog x-data="modalForm()" @close="reset()">
|
||||||
|
* <form id="my-form">
|
||||||
|
* <input name="field" required>
|
||||||
|
* </form>
|
||||||
|
* <button @click="submit('my-form')" :disabled="isSubmitting">Submit</button>
|
||||||
|
* </dialog>
|
||||||
|
*
|
||||||
|
* With Custom State:
|
||||||
|
* <dialog x-data="modalForm({ state: { selectedItem: '', imageUrl: '' } })" @close="reset()">
|
||||||
|
* <select x-model="selectedItem" @change="updateImagePreview(selectedItem + '?image=svg')">
|
||||||
|
* <img :src="imageUrl" x-show="imageUrl">
|
||||||
|
* </dialog>
|
||||||
|
*
|
||||||
|
* With AJAX:
|
||||||
|
* <button @click="submitAsync('my-form', { onSuccess: (data) => console.log(data) })">
|
||||||
|
*/
|
||||||
|
Alpine.data('modalForm', (options = {}) => ({
|
||||||
|
isSubmitting: false,
|
||||||
|
errors: {},
|
||||||
|
// Spread custom initial state from options
|
||||||
|
...(options.state || {}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a single field using HTML5 Constraint Validation API
|
||||||
|
* @param {HTMLElement} field - The input/select/textarea element
|
||||||
|
*/
|
||||||
|
validateField(field) {
|
||||||
|
const name = field.name || field.id;
|
||||||
|
if (!name) return;
|
||||||
|
|
||||||
|
if (!field.validity.valid) {
|
||||||
|
this.errors[name] = field.validationMessage;
|
||||||
|
} else {
|
||||||
|
delete this.errors[name];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear error for a field (call on input)
|
||||||
|
* @param {HTMLElement} field - The input element
|
||||||
|
*/
|
||||||
|
clearError(field) {
|
||||||
|
const name = field.name || field.id;
|
||||||
|
if (name && this.errors[name]) {
|
||||||
|
delete this.errors[name];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get error message for a field
|
||||||
|
* @param {string} name - Field name
|
||||||
|
* @returns {string|undefined} Error message or undefined
|
||||||
|
*/
|
||||||
|
getError(name) {
|
||||||
|
return this.errors[name];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if form has any errors
|
||||||
|
* @returns {boolean} True if there are errors
|
||||||
|
*/
|
||||||
|
hasErrors() {
|
||||||
|
return Object.keys(this.errors).length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate all fields in a form
|
||||||
|
* @param {string} formId - The form element ID
|
||||||
|
* @returns {boolean} True if form is valid
|
||||||
|
*/
|
||||||
|
validateAll(formId) {
|
||||||
|
const form = document.getElementById(formId);
|
||||||
|
if (!form) return false;
|
||||||
|
|
||||||
|
this.errors = {};
|
||||||
|
const fields = form.querySelectorAll('input, select, textarea');
|
||||||
|
|
||||||
|
fields.forEach(field => {
|
||||||
|
if (field.name && !field.validity.valid) {
|
||||||
|
this.errors[field.name] = field.validationMessage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return !this.hasErrors();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that two password fields match
|
||||||
|
* @param {string} password1Id - ID of first password field
|
||||||
|
* @param {string} password2Id - ID of second password field
|
||||||
|
* @returns {boolean} True if passwords match
|
||||||
|
*/
|
||||||
|
validatePasswordMatch(password1Id, password2Id) {
|
||||||
|
const pw1 = document.getElementById(password1Id);
|
||||||
|
const pw2 = document.getElementById(password2Id);
|
||||||
|
|
||||||
|
if (!pw1 || !pw2) return false;
|
||||||
|
|
||||||
|
if (pw1.value !== pw2.value) {
|
||||||
|
this.errors[pw2.name || password2Id] = 'Passwords do not match';
|
||||||
|
pw2.setCustomValidity('Passwords do not match');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.errors[pw2.name || password2Id];
|
||||||
|
pw2.setCustomValidity('');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a form with loading state
|
||||||
|
* @param {string} formId - The form element ID
|
||||||
|
*/
|
||||||
|
submit(formId) {
|
||||||
|
const form = document.getElementById(formId);
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
// Validate before submit
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set action to current URL if empty
|
||||||
|
if (!form.action || form.action === window.location.href + '#') {
|
||||||
|
form.action = window.location.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set loading state and submit
|
||||||
|
this.isSubmitting = true;
|
||||||
|
form.submit();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit form via AJAX (fetch)
|
||||||
|
* @param {string} formId - The form element ID
|
||||||
|
* @param {Object} options - Options { onSuccess, onError, closeOnSuccess }
|
||||||
|
*/
|
||||||
|
async submitAsync(formId, options = {}) {
|
||||||
|
const form = document.getElementById(formId);
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
// Validate before submit
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSubmitting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const response = await fetch(form.action || window.location.href, {
|
||||||
|
method: form.method || 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
if (options.onSuccess) {
|
||||||
|
options.onSuccess(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.redirect || data.success) {
|
||||||
|
window.location.href = data.redirect || data.success;
|
||||||
|
} else if (options.closeOnSuccess) {
|
||||||
|
this.$el.closest('dialog')?.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorMsg = data.error || data.message || `Error: ${response.status}`;
|
||||||
|
this.errors['_form'] = errorMsg;
|
||||||
|
|
||||||
|
if (options.onError) {
|
||||||
|
options.onError(errorMsg, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.errors['_form'] = error.message;
|
||||||
|
|
||||||
|
if (options.onError) {
|
||||||
|
options.onError(error.message);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set form action URL dynamically
|
||||||
|
* @param {string} formId - The form element ID
|
||||||
|
* @param {string} url - The URL to set as action
|
||||||
|
*/
|
||||||
|
setFormAction(formId, url) {
|
||||||
|
const form = document.getElementById(formId);
|
||||||
|
if (form) {
|
||||||
|
form.action = url;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update image preview
|
||||||
|
* @param {string} url - Image URL (with query params)
|
||||||
|
* @param {string} targetId - Target element ID for the image
|
||||||
|
*/
|
||||||
|
updateImagePreview(url) {
|
||||||
|
// Store URL for reactive binding with :src
|
||||||
|
this.imageUrl = url;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset form state (call on modal close)
|
||||||
|
* Resets to initial state from options
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.errors = {};
|
||||||
|
this.imageUrl = '';
|
||||||
|
|
||||||
|
// Reset custom state to initial values
|
||||||
|
if (options.state) {
|
||||||
|
Object.keys(options.state).forEach(key => {
|
||||||
|
this[key] = options.state[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call custom reset handler if provided
|
||||||
|
if (options.onReset) {
|
||||||
|
options.onReset.call(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Modal Component (no form)
|
||||||
|
*
|
||||||
|
* For modals that don't need form validation.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* <dialog x-data="modal()">
|
||||||
|
* <button @click="$el.closest('dialog').close()">Close</button>
|
||||||
|
* </dialog>
|
||||||
|
*/
|
||||||
|
Alpine.data('modal', () => ({
|
||||||
|
// Placeholder for simple modals that may need state later
|
||||||
|
}));
|
||||||
|
});
|
||||||
133
static/js/alpine/pagination.js
Normal file
133
static/js/alpine/pagination.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Alpine.js Pagination Component
|
||||||
|
*
|
||||||
|
* Provides client-side pagination for large lists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('paginatedList', (initialItems = [], options = {}) => ({
|
||||||
|
allItems: initialItems,
|
||||||
|
filteredItems: [],
|
||||||
|
currentPage: 1,
|
||||||
|
perPage: options.perPage || 50,
|
||||||
|
searchQuery: '',
|
||||||
|
isReviewed: options.isReviewed || false,
|
||||||
|
instanceId: options.instanceId || Math.random().toString(36).substring(2, 9),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.filteredItems = this.allItems;
|
||||||
|
},
|
||||||
|
|
||||||
|
get totalPages() {
|
||||||
|
return Math.ceil(this.filteredItems.length / this.perPage);
|
||||||
|
},
|
||||||
|
|
||||||
|
get paginatedItems() {
|
||||||
|
const start = (this.currentPage - 1) * this.perPage;
|
||||||
|
const end = start + this.perPage;
|
||||||
|
return this.filteredItems.slice(start, end);
|
||||||
|
},
|
||||||
|
|
||||||
|
get totalItems() {
|
||||||
|
return this.filteredItems.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
get showingStart() {
|
||||||
|
if (this.totalItems === 0) return 0;
|
||||||
|
return (this.currentPage - 1) * this.perPage + 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
get showingEnd() {
|
||||||
|
return Math.min(this.currentPage * this.perPage, this.totalItems);
|
||||||
|
},
|
||||||
|
|
||||||
|
search(query) {
|
||||||
|
this.searchQuery = query.toLowerCase();
|
||||||
|
if (this.searchQuery === '') {
|
||||||
|
this.filteredItems = this.allItems;
|
||||||
|
} else {
|
||||||
|
this.filteredItems = this.allItems.filter(item =>
|
||||||
|
item.name.toLowerCase().includes(this.searchQuery)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.currentPage = 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
nextPage() {
|
||||||
|
if (this.currentPage < this.totalPages) {
|
||||||
|
this.currentPage++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
prevPage() {
|
||||||
|
if (this.currentPage > 1) {
|
||||||
|
this.currentPage--;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goToPage(page) {
|
||||||
|
if (page >= 1 && page <= this.totalPages) {
|
||||||
|
this.currentPage = page;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get pageNumbers() {
|
||||||
|
const pages = [];
|
||||||
|
const total = this.totalPages;
|
||||||
|
const current = this.currentPage;
|
||||||
|
|
||||||
|
// Handle empty case
|
||||||
|
if (total === 0) {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total <= 7) {
|
||||||
|
// Show all pages if 7 or fewer
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// More than 7 pages - show first, last, and sliding window around current
|
||||||
|
// Always show first page
|
||||||
|
pages.push({ page: 1, isEllipsis: false, key: `${this.instanceId}-page-1` });
|
||||||
|
|
||||||
|
// Determine the start and end of the middle range
|
||||||
|
let rangeStart, rangeEnd;
|
||||||
|
|
||||||
|
if (current <= 4) {
|
||||||
|
// Near the beginning: show pages 2-5
|
||||||
|
rangeStart = 2;
|
||||||
|
rangeEnd = 5;
|
||||||
|
} else if (current >= total - 3) {
|
||||||
|
// Near the end: show last 4 pages before the last page
|
||||||
|
rangeStart = total - 4;
|
||||||
|
rangeEnd = total - 1;
|
||||||
|
} else {
|
||||||
|
// In the middle: show current page and one on each side
|
||||||
|
rangeStart = current - 1;
|
||||||
|
rangeEnd = current + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ellipsis before range if there's a gap
|
||||||
|
if (rangeStart > 2) {
|
||||||
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-start` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add pages in the range
|
||||||
|
for (let i = rangeStart; i <= rangeEnd; i++) {
|
||||||
|
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ellipsis after range if there's a gap
|
||||||
|
if (rangeEnd < total - 1) {
|
||||||
|
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-end` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always show last page
|
||||||
|
pages.push({ page: total, isEllipsis: false, key: `${this.instanceId}-page-${total}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
145
static/js/alpine/search.js
Normal file
145
static/js/alpine/search.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* Search Modal Alpine.js Component
|
||||||
|
*
|
||||||
|
* Provides package selection, search mode switching, and results display
|
||||||
|
* for the search modal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
/**
|
||||||
|
* Search Modal Component
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* <dialog x-data="searchModal()" @close="reset()">
|
||||||
|
* ...
|
||||||
|
* </dialog>
|
||||||
|
*/
|
||||||
|
Alpine.data('searchModal', () => ({
|
||||||
|
// Package selector state
|
||||||
|
selectedPackages: [],
|
||||||
|
|
||||||
|
// Search state
|
||||||
|
searchMode: 'text',
|
||||||
|
searchModeLabel: 'Text',
|
||||||
|
query: '',
|
||||||
|
|
||||||
|
// Results state
|
||||||
|
results: null,
|
||||||
|
isSearching: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
// Initialize on modal open
|
||||||
|
init() {
|
||||||
|
// Load reviewed packages by default
|
||||||
|
this.loadInitialSelection();
|
||||||
|
|
||||||
|
// Watch for modal open to focus searchbar
|
||||||
|
this.$watch('$el.open', (open) => {
|
||||||
|
if (open) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.searchbar.focus();
|
||||||
|
}, 320);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
loadInitialSelection() {
|
||||||
|
// Select all reviewed packages by default
|
||||||
|
const menuItems = this.$refs.packageDropdown.querySelectorAll('li');
|
||||||
|
|
||||||
|
for (const item of menuItems) {
|
||||||
|
// Stop at 'Unreviewed Packages' section
|
||||||
|
if (item.classList.contains('menu-title') &&
|
||||||
|
item.textContent.trim() === 'Unreviewed Packages') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageOption = item.querySelector('.package-option');
|
||||||
|
if (packageOption) {
|
||||||
|
this.selectedPackages.push({
|
||||||
|
url: packageOption.dataset.packageUrl,
|
||||||
|
name: packageOption.dataset.packageName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
togglePackage(url, name) {
|
||||||
|
const index = this.selectedPackages.findIndex(pkg => pkg.url === url);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
this.selectedPackages.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.selectedPackages.push({ url, name });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removePackage(url) {
|
||||||
|
const index = this.selectedPackages.findIndex(pkg => pkg.url === url);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.selectedPackages.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isPackageSelected(url) {
|
||||||
|
return this.selectedPackages.some(pkg => pkg.url === url);
|
||||||
|
},
|
||||||
|
|
||||||
|
setSearchMode(mode, label) {
|
||||||
|
this.searchMode = mode;
|
||||||
|
this.searchModeLabel = label;
|
||||||
|
this.$refs.modeDropdown.hidePopover();
|
||||||
|
},
|
||||||
|
|
||||||
|
async performSearch(serverBase) {
|
||||||
|
if (!this.query.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedPackages.length < 1) {
|
||||||
|
this.results = { error: 'no_packages' };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
this.selectedPackages.forEach(pkg => params.append('packages', pkg.url));
|
||||||
|
params.append('search', this.query.trim());
|
||||||
|
params.append('mode', this.searchModeLabel.toLowerCase());
|
||||||
|
|
||||||
|
this.isSearching = true;
|
||||||
|
this.results = null;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${serverBase}/search?${params.toString()}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Accept': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Search request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.results = await response.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Search error:', err);
|
||||||
|
this.error = 'Search failed. Please try again.';
|
||||||
|
} finally {
|
||||||
|
this.isSearching = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hasResults() {
|
||||||
|
if (!this.results || this.results.error) return false;
|
||||||
|
const categories = ['Compounds', 'Compound Structures', 'Rules', 'Reactions', 'Pathways'];
|
||||||
|
return categories.some(cat => this.results[cat] && this.results[cat].length > 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.query = '';
|
||||||
|
this.results = null;
|
||||||
|
this.error = null;
|
||||||
|
this.isSearching = false;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
@ -63,17 +63,20 @@ class DiscourseAPI {
|
|||||||
* @returns {string} Cleaned excerpt
|
* @returns {string} Cleaned excerpt
|
||||||
*/
|
*/
|
||||||
extractExcerpt(excerpt) {
|
extractExcerpt(excerpt) {
|
||||||
if (!excerpt) return 'Click to read more';
|
if (!excerpt) return 'No preview available yet';
|
||||||
|
|
||||||
// Remove HTML tags and clean up; collapse whitespace; do not add manual ellipsis
|
// Remove HTML tags and clean up; collapse whitespace; do not add manual ellipsis
|
||||||
return excerpt
|
const cleaned = excerpt
|
||||||
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
||||||
.replace(/ /g, ' ') // Replace with spaces
|
.replace(/ /g, ' ') // Replace with spaces
|
||||||
.replace(/&/g, '&') // Replace & with &
|
.replace(/&/g, '&') // Replace & with &
|
||||||
.replace(/</g, '<') // Replace < with <
|
.replace(/</g, '<') // Replace < with <
|
||||||
.replace(/>/g, '>') // Replace > with >
|
.replace(/>/g, '>') // Replace > with >
|
||||||
.replace(/\s+/g, ' ') // Collapse all whitespace/newlines
|
.replace(/\s+/g, ' ') // Collapse all whitespace/newlines
|
||||||
.trim()
|
.trim();
|
||||||
|
|
||||||
|
// Check if excerpt is empty after cleaning
|
||||||
|
return cleaned || 'No preview available yet';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
1171
static/js/pps.js
1171
static/js/pps.js
File diff suppressed because it is too large
Load Diff
239
static/js/pw.js
239
static/js/pw.js
@ -1,15 +1,22 @@
|
|||||||
console.log("loaded pw.js")
|
console.log("loaded pw.js")
|
||||||
|
|
||||||
function predictFromNode(url) {
|
function predictFromNode(url) {
|
||||||
$.post("", {node: url})
|
fetch("", {
|
||||||
.done(function (data) {
|
method: "POST",
|
||||||
console.log("Success:", data);
|
headers: {
|
||||||
window.location.href = data.success;
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
})
|
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
|
||||||
.fail(function (xhr, status, error) {
|
},
|
||||||
console.error("Error:", xhr.status, xhr.responseText);
|
body: new URLSearchParams({node: url})
|
||||||
// show user-friendly message or log error
|
})
|
||||||
});
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log("Success:", data);
|
||||||
|
window.location.href = data.success;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error:", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// data = {{ pathway.d3_json | safe }};
|
// data = {{ pathway.d3_json | safe }};
|
||||||
@ -103,6 +110,9 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dragstarted(event, d) {
|
function dragstarted(event, d) {
|
||||||
|
// Prevent zoom pan when dragging nodes
|
||||||
|
event.sourceEvent.stopPropagation();
|
||||||
|
|
||||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||||
d.fx = d.x;
|
d.fx = d.x;
|
||||||
d.fy = d.y;
|
d.fy = d.y;
|
||||||
@ -117,6 +127,9 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dragged(event, d) {
|
function dragged(event, d) {
|
||||||
|
// Prevent zoom pan when dragging nodes
|
||||||
|
event.sourceEvent.stopPropagation();
|
||||||
|
|
||||||
d.fx = event.x;
|
d.fx = event.x;
|
||||||
d.fy = event.y;
|
d.fy = event.y;
|
||||||
|
|
||||||
@ -127,6 +140,9 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dragended(event, d) {
|
function dragended(event, d) {
|
||||||
|
// Prevent zoom pan when dragging nodes
|
||||||
|
event.sourceEvent.stopPropagation();
|
||||||
|
|
||||||
if (!event.active) simulation.alphaTarget(0);
|
if (!event.active) simulation.alphaTarget(0);
|
||||||
|
|
||||||
// Mark that dragging has ended
|
// Mark that dragging has ended
|
||||||
@ -192,52 +208,153 @@ function draw(pathway, elem) {
|
|||||||
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
|
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait one second before showing popup
|
// Wait before showing popup (ms)
|
||||||
var popupWaitBeforeShow = 1000;
|
var popupWaitBeforeShow = 1000;
|
||||||
// Keep Popup at least for one second
|
|
||||||
var popushowAtLeast = 1000;
|
|
||||||
|
|
||||||
function pop_show_e(element) {
|
// Custom popover element
|
||||||
var e = element;
|
let popoverTimeout = null;
|
||||||
setTimeout(function () {
|
|
||||||
if ($(e).is(':hover')) { // if element is still hovered
|
|
||||||
$(e).popover("show");
|
|
||||||
|
|
||||||
// workaround to set fixed positions
|
function createPopover() {
|
||||||
pop = $(e).attr("aria-describedby")
|
const popover = document.createElement('div');
|
||||||
h = $('#' + pop).height();
|
popover.id = 'custom-popover';
|
||||||
$('#' + pop).attr("style", `position: fixed; top: ${clientY - (h / 2.0)}px; left: ${clientX + 10}px; margin: 0px; max-width: 1000px; display: block;`)
|
popover.className = 'fixed z-50';
|
||||||
setTimeout(function () {
|
popover.style.cssText = `
|
||||||
var close = setInterval(function () {
|
background: #ffffff;
|
||||||
if (!$(".popover:hover").length // mouse outside popover
|
border: 1px solid #d1d5db;
|
||||||
&& !$(e).is(':hover')) { // mouse outside element
|
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
$(e).popover('hide');
|
max-width: 320px;
|
||||||
clearInterval(close);
|
padding: 0.75rem;
|
||||||
}
|
border-radius: 0.5rem;
|
||||||
}, 100);
|
opacity: 0;
|
||||||
}, popushowAtLeast);
|
visibility: hidden;
|
||||||
|
transition: opacity 150ms ease-in-out, visibility 150ms ease-in-out;
|
||||||
|
pointer-events: auto;
|
||||||
|
`;
|
||||||
|
popover.setAttribute('role', 'tooltip');
|
||||||
|
popover.innerHTML = `
|
||||||
|
<div class="font-semibold mb-2 popover-title" style="font-weight: 600; margin-bottom: 0.5rem;"></div>
|
||||||
|
<div class="text-sm popover-content" style="font-size: 0.875rem;"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add styles for content images
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
#custom-popover img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
}, popupWaitBeforeShow);
|
#custom-popover a {
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#custom-popover a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
if (!document.getElementById('popover-styles')) {
|
||||||
|
style.id = 'popover-styles';
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep popover open when hovering over it
|
||||||
|
popover.addEventListener('mouseenter', () => {
|
||||||
|
if (popoverTimeout) {
|
||||||
|
clearTimeout(popoverTimeout);
|
||||||
|
popoverTimeout = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
popover.addEventListener('mouseleave', () => {
|
||||||
|
hidePopover();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(popover);
|
||||||
|
return popover;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPopover() {
|
||||||
|
return document.getElementById('custom-popover') || createPopover();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPopover(element, title, content) {
|
||||||
|
const popover = getPopover();
|
||||||
|
popover.querySelector('.popover-title').textContent = title;
|
||||||
|
popover.querySelector('.popover-content').innerHTML = content;
|
||||||
|
|
||||||
|
// Make visible to measure
|
||||||
|
popover.style.visibility = 'hidden';
|
||||||
|
popover.style.opacity = '0';
|
||||||
|
popover.style.display = 'block';
|
||||||
|
|
||||||
|
// Smart positioning - avoid viewport overflow
|
||||||
|
const padding = 10;
|
||||||
|
const popoverRect = popover.getBoundingClientRect();
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
let left = clientX + 15;
|
||||||
|
let top = clientY - (popoverRect.height / 2);
|
||||||
|
|
||||||
|
// Prevent right overflow
|
||||||
|
if (left + popoverRect.width > viewportWidth - padding) {
|
||||||
|
left = clientX - popoverRect.width - 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent bottom overflow
|
||||||
|
if (top + popoverRect.height > viewportHeight - padding) {
|
||||||
|
top = viewportHeight - popoverRect.height - padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent top overflow
|
||||||
|
if (top < padding) {
|
||||||
|
top = padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
popover.style.top = `${top}px`;
|
||||||
|
popover.style.left = `${left}px`;
|
||||||
|
popover.style.visibility = 'visible';
|
||||||
|
popover.style.opacity = '1';
|
||||||
|
|
||||||
|
currentElement = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePopover() {
|
||||||
|
const popover = getPopover();
|
||||||
|
popover.style.opacity = '0';
|
||||||
|
popover.style.visibility = 'hidden';
|
||||||
|
currentElement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pop_add(objects, title, contentFunction) {
|
function pop_add(objects, title, contentFunction) {
|
||||||
objects.attr("id", "pop")
|
objects.each(function (d) {
|
||||||
.attr("data-container", "body")
|
const element = this;
|
||||||
.attr("data-toggle", "popover")
|
|
||||||
.attr("data-placement", "right")
|
|
||||||
.attr("title", title);
|
|
||||||
|
|
||||||
objects.each(function (d, i) {
|
element.addEventListener('mouseenter', () => {
|
||||||
options = {trigger: "manual", html: true, animation: false};
|
if (popoverTimeout) clearTimeout(popoverTimeout);
|
||||||
this_ = this;
|
|
||||||
var p = $(this).popover(options).on("mouseenter", function () {
|
popoverTimeout = setTimeout(() => {
|
||||||
pop_show_e(this);
|
if (element.matches(':hover')) {
|
||||||
|
const content = contentFunction(d);
|
||||||
|
showPopover(element, title, content);
|
||||||
|
}
|
||||||
|
}, popupWaitBeforeShow);
|
||||||
});
|
});
|
||||||
p.on("show.bs.popover", function (e) {
|
|
||||||
// this is to dynamically ajdust the content and bounds of the popup
|
element.addEventListener('mouseleave', () => {
|
||||||
p.attr('data-content', contentFunction(d));
|
if (popoverTimeout) {
|
||||||
p.data("bs.popover").setContent();
|
clearTimeout(popoverTimeout);
|
||||||
p.data("bs.popover").tip().css({"max-width": "1000px"});
|
popoverTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay hide to allow moving to popover
|
||||||
|
setTimeout(() => {
|
||||||
|
const popover = getPopover();
|
||||||
|
if (!popover.matches(':hover') && !element.matches(':hover')) {
|
||||||
|
hidePopover();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -255,7 +372,7 @@ function draw(pathway, elem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
popupContent += "<img src='" + n.image + "' width='" + 20 * nodeRadius + "'><br>"
|
popupContent += "<img src='" + n.image + "'><br>"
|
||||||
if (n.scenarios.length > 0) {
|
if (n.scenarios.length > 0) {
|
||||||
popupContent += '<b>Half-lives and related scenarios:</b><br>'
|
popupContent += '<b>Half-lives and related scenarios:</b><br>'
|
||||||
for (var s of n.scenarios) {
|
for (var s of n.scenarios) {
|
||||||
@ -265,7 +382,7 @@ function draw(pathway, elem) {
|
|||||||
|
|
||||||
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
|
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
|
||||||
if (pathway.isIncremental && isLeaf) {
|
if (pathway.isIncremental && isLeaf) {
|
||||||
popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
|
popupContent += '<br><a class="btn btn-primary btn-sm mt-2" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return popupContent;
|
return popupContent;
|
||||||
@ -285,7 +402,7 @@ function draw(pathway, elem) {
|
|||||||
popupContent += adcontent;
|
popupContent += adcontent;
|
||||||
}
|
}
|
||||||
|
|
||||||
popupContent += "<img src='" + e.image + "' width='" + 20 * nodeRadius + "'><br>"
|
popupContent += "<img src='" + e.image + "'><br>"
|
||||||
if (e.reaction_probability) {
|
if (e.reaction_probability) {
|
||||||
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
|
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
|
||||||
}
|
}
|
||||||
@ -308,6 +425,23 @@ function draw(pathway, elem) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const zoomable = d3.select("#zoomable");
|
const zoomable = d3.select("#zoomable");
|
||||||
|
const svg = d3.select("#pwsvg");
|
||||||
|
const container = d3.select("#vizdiv");
|
||||||
|
|
||||||
|
// Set explicit SVG dimensions for proper zoom behavior
|
||||||
|
svg.attr("width", width)
|
||||||
|
.attr("height", height);
|
||||||
|
|
||||||
|
// Add background rectangle FIRST to enable pan/zoom on empty space
|
||||||
|
// This must be inserted before zoomable group so it's behind everything
|
||||||
|
svg.insert("rect", "#zoomable")
|
||||||
|
.attr("x", 0)
|
||||||
|
.attr("y", 0)
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.attr("fill", "transparent")
|
||||||
|
.attr("pointer-events", "all")
|
||||||
|
.style("cursor", "grab");
|
||||||
|
|
||||||
// Zoom Funktion aktivieren
|
// Zoom Funktion aktivieren
|
||||||
const zoom = d3.zoom()
|
const zoom = d3.zoom()
|
||||||
@ -316,7 +450,12 @@ function draw(pathway, elem) {
|
|||||||
zoomable.attr("transform", event.transform);
|
zoomable.attr("transform", event.transform);
|
||||||
});
|
});
|
||||||
|
|
||||||
d3.select("svg").call(zoom);
|
// Apply zoom to the SVG element - this enables wheel zoom
|
||||||
|
svg.call(zoom);
|
||||||
|
|
||||||
|
// Also apply zoom to container to catch events that might not reach SVG
|
||||||
|
// This ensures drag-to-pan works even when clicking on empty space
|
||||||
|
container.call(zoom);
|
||||||
|
|
||||||
nodes = pathway['nodes'];
|
nodes = pathway['nodes'];
|
||||||
links = pathway['links'];
|
links = pathway['links'];
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_compound_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_compound_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Compound</a
|
<span class="glyphicon glyphicon-plus"></span> New Compound</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('new_compound_structure_modal').showModal(); return false;"
|
||||||
data-target="#new_compound_structure_modal"
|
|
||||||
>
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a
|
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_edge_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_edge_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Edge</a
|
<span class="glyphicon glyphicon-plus"></span> New Edge</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_group_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_group_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Group</a
|
<span class="glyphicon glyphicon-plus"></span> New Group</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_model_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Model</a
|
<span class="glyphicon glyphicon-plus"></span> New Model</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_node_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Node</a
|
<span class="glyphicon glyphicon-plus"></span> New Node</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,18 +1,23 @@
|
|||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_package_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Package</a
|
<span class="glyphicon glyphicon-plus"></span> New Package</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#import_package_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('import_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a
|
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;"
|
||||||
data-target="#import_legacy_package_modal"
|
|
||||||
>
|
>
|
||||||
<span class="glyphicon glyphicon-import"></span> Import Package from legacy
|
<span class="glyphicon glyphicon-import"></span> Import Package from legacy
|
||||||
JSON</a
|
JSON</a
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_reaction_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_reaction_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Reaction</a
|
<span class="glyphicon glyphicon-plus"></span> New Reaction</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_rule_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_rule_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Rule</a
|
<span class="glyphicon glyphicon-plus"></span> New Rule</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span> New Scenario</a
|
<span class="glyphicon glyphicon-plus"></span> New Scenario</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#new_setting_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('new_setting_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<span class="glyphicon glyphicon-plus"></span>New Setting</a
|
<span class="glyphicon glyphicon-plus"></span>New Setting</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,42 +1,59 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_compound_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_compound_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#add_structure_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('add_structure_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add Structure</a
|
<i class="glyphicon glyphicon-plus"></i> Add Structure</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||||
data-target="#generic_set_external_reference_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -2,33 +2,40 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('edit_compound_structure_modal').showModal(); return false;"
|
||||||
data-target="#edit_compound_structure_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||||
data-target="#generic_set_external_reference_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_group_member_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a
|
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Group</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Group</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,21 +1,33 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_model_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Model</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Model</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#evaluate_model_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('evaluate_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a
|
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#retrain_model_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('retrain_model_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a
|
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Model</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Model</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,21 +1,33 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_node_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Node</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Node</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Node</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Node</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,35 +1,49 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_package_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Package</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Package</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('edit_package_permissions_modal').showModal(); return false;"
|
||||||
data-target="#edit_package_permissions_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a
|
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#publish_package_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('publish_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a
|
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#export_package_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('export_package_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a
|
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_license_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_license_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> License</a
|
<i class="glyphicon glyphicon-duplicate"></i> License</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Package</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Package</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,26 +1,34 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#add_pathway_node_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('add_pathway_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add Compound</a
|
<i class="glyphicon glyphicon-plus"></i> Add Compound</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('add_pathway_edge_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a
|
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('download_pathway_csv_modal').showModal(); return false;"
|
||||||
data-target="#download_pathway_csv_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a
|
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a
|
||||||
>
|
>
|
||||||
@ -28,8 +36,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('download_pathway_image_modal').showModal(); return false;"
|
||||||
data-target="#download_pathway_image_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a
|
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a
|
||||||
>
|
>
|
||||||
@ -38,8 +45,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('identify_missing_rules_modal').showModal(); return false;"
|
||||||
data-target="#identify_missing_rules_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing
|
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing
|
||||||
Rules</a
|
Rules</a
|
||||||
@ -47,30 +53,34 @@
|
|||||||
</li>
|
</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"
|
||||||
|
onclick="document.getElementById('edit_pathway_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{# <li>#}
|
|
||||||
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
|
|
||||||
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
|
|
||||||
{# </li>#}
|
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
|
||||||
data-target="#delete_pathway_node_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||||
>
|
>
|
||||||
@ -78,14 +88,16 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
|
||||||
data-target="#delete_pathway_edge_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,37 +1,51 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_reaction_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
|
||||||
data-target="#generic_set_external_reference_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,28 +1,43 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_rule_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_rule_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a
|
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_aliases_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_aliases_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('add_additional_information_modal').showModal(); return false;"
|
||||||
data-target="#add_additional_information_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a
|
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a
|
||||||
>
|
>
|
||||||
@ -11,14 +10,16 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('update_scenario_additional_information_modal').showModal(); return false;"
|
||||||
data-target="#update_scenario_additional_information_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a
|
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,19 +1,24 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_user_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_user_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-edit"></i> Update</a
|
<i class="glyphicon glyphicon-edit"></i> Update</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#edit_password_modal">
|
<a
|
||||||
|
role="button"
|
||||||
|
onclick="document.getElementById('edit_password_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-lock"></i> Update Password</a
|
<i class="glyphicon glyphicon-lock"></i> Update Password</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
data-toggle="modal"
|
onclick="document.getElementById('new_prediction_setting_modal').showModal(); return false;"
|
||||||
data-target="#new_prediction_setting_modal"
|
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a
|
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a
|
||||||
>
|
>
|
||||||
@ -23,7 +28,10 @@
|
|||||||
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
|
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
|
||||||
{# </li>#}
|
{# </li>#}
|
||||||
<li>
|
<li>
|
||||||
<a role="button" data-toggle="modal" data-target="#generic_delete_modal">
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Account</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Account</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,56 +1,52 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load envipytags %}
|
{% load envipytags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="panel-group" id="reviewListAccordion">
|
<div class="space-y-2 p-4">
|
||||||
<div class="panel panel-default">
|
<!-- Header Section -->
|
||||||
<div
|
<div class="card bg-base-100">
|
||||||
class="panel-heading"
|
<div class="card-body">
|
||||||
id="headingPanel"
|
<h2 class="card-title text-2xl">User Prediction Jobs</h2>
|
||||||
style="font-size:2rem;height: 46px"
|
<p class="mt-2">Job Logs Desc</p>
|
||||||
>
|
|
||||||
Jobs
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>Job Logs Desc</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<!-- Jobs -->
|
||||||
class="panel panel-default panel-heading list-group-item"
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
style="background-color:silver"
|
<input type="checkbox" checked />
|
||||||
>
|
<div class="collapse-title text-xl font-medium">Recent Jobs</div>
|
||||||
<h4 class="panel-title">
|
<div class="collapse-content" id="job-content">
|
||||||
<a
|
<div class="overflow-x-auto">
|
||||||
id="job-accordion-link"
|
<table class="table-zebra table">
|
||||||
data-toggle="collapse"
|
<thead>
|
||||||
data-parent="#job-accordion"
|
<tr>
|
||||||
href="#jobs"
|
<th>ID</th>
|
||||||
>
|
<th>Name</th>
|
||||||
Jobs
|
<th>Status</th>
|
||||||
</a>
|
<th>Queued</th>
|
||||||
</h4>
|
<th>Done</th>
|
||||||
</div>
|
<th>Result</th>
|
||||||
<div id="jobs" class="panel-collapse in collapse">
|
</tr>
|
||||||
<div class="panel-body list-group-item" id="job-content">
|
</thead>
|
||||||
<table class="table-bordered table-hover table">
|
|
||||||
<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>
|
<tbody>
|
||||||
{% for job in jobs %}
|
{% for job in jobs %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if meta.user.is_superuser %}
|
||||||
|
<td>
|
||||||
|
<a href="{{ job.user.url }}">{{ job.user.username }}</a>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>{{ job.task_id }}</td>
|
<td>{{ job.task_id }}</td>
|
||||||
<td>{{ job.job_name }}</td>
|
<td>{{ job.job_name }}</td>
|
||||||
<td>{{ job.status }}</td>
|
<td>{{ job.status }}</td>
|
||||||
<td>{{ job.created }}</td>
|
<td>{{ job.created }}</td>
|
||||||
<td>{{ job.done_at }}</td>
|
<td>{{ job.done_at }}</td>
|
||||||
{% if job.task_result and job.task_result|is_url == True %}
|
{% if job.task_result and job.task_result|is_url == True %}
|
||||||
<td><a href="{{ job.task_result }}">Result</a></td>
|
<td>
|
||||||
|
<a href="{{ job.task_result }}" class="link link-primary"
|
||||||
|
>Result</a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
{% elif job.task_result %}
|
{% elif job.task_result %}
|
||||||
<td>{{ job.task_result|slice:"40" }}...</td>
|
<td>{{ job.task_result|slice:"40" }}...</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -62,19 +58,31 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
|
{% if objects %}
|
||||||
|
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="menu bg-base-200 rounded-box">
|
||||||
|
{% for obj in objects %}
|
||||||
|
{% if object_type == 'user' %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
|
>{{ obj.username }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
|
>{{ obj.name }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,28 +1,32 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if object_type != 'package' %}
|
{# Serialize objects data for Alpine pagination #}
|
||||||
<div>
|
{# prettier-ignore-start #}
|
||||||
<div id="load-all-error" style="display: none;">
|
{# FIXME: This is a hack to get the objects data into the JavaScript code. #}
|
||||||
<div class="alert alert-danger" role="alert">
|
<script>
|
||||||
<span
|
window.reviewedObjects = [
|
||||||
class="glyphicon glyphicon-exclamation-sign"
|
{% for obj in reviewed_objects %}
|
||||||
aria-hidden="true"
|
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
||||||
></span>
|
{% endfor %}
|
||||||
<span class="sr-only">Error:</span>
|
];
|
||||||
Getting objects failed!
|
window.unreviewedObjects = [
|
||||||
</div>
|
{% for obj in unreviewed_objects %}
|
||||||
</div>
|
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
{# prettier-ignore-end #}
|
||||||
|
|
||||||
|
{% if object_type != 'package' %}
|
||||||
|
<div class="px-8 py-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="object-search"
|
id="object-search"
|
||||||
class="form-control"
|
class="input input-bordered hidden w-full max-w-xs"
|
||||||
placeholder="Search by name"
|
placeholder="Search by name"
|
||||||
style="display: none;"
|
|
||||||
/>
|
/>
|
||||||
<p></p>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -56,423 +60,474 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
|
|
||||||
<div class="panel-group" id="reviewListAccordion">
|
<div class="px-8 py-4">
|
||||||
<div class="panel panel-default">
|
<!-- Header Section -->
|
||||||
<div
|
<div class="card bg-base-100">
|
||||||
class="panel-heading"
|
<div class="card-body px-0 py-4">
|
||||||
id="headingPanel"
|
<div class="flex items-center justify-between">
|
||||||
style="font-size:2rem;height: 46px"
|
<h2 class="card-title text-2xl">
|
||||||
>
|
{% if object_type == 'package' %}
|
||||||
{% if object_type == 'package' %}
|
Packages
|
||||||
Packages
|
{% elif object_type == 'compound' %}
|
||||||
{% elif object_type == 'compound' %}
|
Compounds
|
||||||
Compounds
|
{% elif object_type == 'structure' %}
|
||||||
{% elif object_type == 'structure' %}
|
Compound structures
|
||||||
Compound structures
|
{% elif object_type == 'rule' %}
|
||||||
{% elif object_type == 'rule' %}
|
Rules
|
||||||
Rules
|
{% elif object_type == 'reaction' %}
|
||||||
{% elif object_type == 'reaction' %}
|
Reactions
|
||||||
Reactions
|
{% elif object_type == 'pathway' %}
|
||||||
{% elif object_type == 'pathway' %}
|
Pathways
|
||||||
Pathways
|
{% elif object_type == 'node' %}
|
||||||
{% elif object_type == 'node' %}
|
Nodes
|
||||||
Nodes
|
{% elif object_type == 'edge' %}
|
||||||
{% elif object_type == 'edge' %}
|
Edges
|
||||||
Edges
|
{% elif object_type == 'scenario' %}
|
||||||
{% elif object_type == 'scenario' %}
|
Scenarios
|
||||||
Scenarios
|
{% elif object_type == 'model' %}
|
||||||
{% elif object_type == 'model' %}
|
Model
|
||||||
Model
|
{% elif object_type == 'setting' %}
|
||||||
{% elif object_type == 'setting' %}
|
Settings
|
||||||
Settings
|
{% elif object_type == 'user' %}
|
||||||
{% elif object_type == 'user' %}
|
Users
|
||||||
Users
|
{% elif object_type == 'group' %}
|
||||||
{% elif object_type == 'group' %}
|
Groups
|
||||||
Groups
|
{% endif %}
|
||||||
{% endif %}
|
</h2>
|
||||||
<div
|
<div id="actionsButton" class="dropdown dropdown-end hidden">
|
||||||
id="actionsButton"
|
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
|
||||||
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
|
<svg
|
||||||
class="dropdown"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
width="16"
|
||||||
<a
|
height="16"
|
||||||
href="#"
|
viewBox="0 0 24 24"
|
||||||
class="dropdown-toggle"
|
fill="none"
|
||||||
data-toggle="dropdown"
|
stroke="currentColor"
|
||||||
role="button"
|
stroke-width="2"
|
||||||
aria-haspopup="true"
|
stroke-linecap="round"
|
||||||
aria-expanded="false"
|
stroke-linejoin="round"
|
||||||
><span class="glyphicon glyphicon-wrench"></span> Actions
|
class="lucide lucide-wrench"
|
||||||
<span class="caret"></span><span style="padding-right:1em"></span
|
>
|
||||||
></a>
|
<path
|
||||||
<ul id="actionsList" class="dropdown-menu">
|
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
||||||
{% block actions %}
|
/>
|
||||||
{% if object_type == 'package' %}
|
</svg>
|
||||||
{% include "actions/collections/package.html" %}
|
Actions
|
||||||
{% elif object_type == 'compound' %}
|
</div>
|
||||||
{% include "actions/collections/compound.html" %}
|
<ul
|
||||||
{% elif object_type == 'structure' %}
|
tabindex="-1"
|
||||||
{% include "actions/collections/compound_structure.html" %}
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
||||||
{% elif object_type == 'rule' %}
|
>
|
||||||
{% include "actions/collections/rule.html" %}
|
{% block actions %}
|
||||||
{% elif object_type == 'reaction' %}
|
{% if object_type == 'package' %}
|
||||||
{% include "actions/collections/reaction.html" %}
|
{% include "actions/collections/package.html" %}
|
||||||
{% elif object_type == 'setting' %}
|
{% elif object_type == 'compound' %}
|
||||||
{% include "actions/collections/setting.html" %}
|
{% include "actions/collections/compound.html" %}
|
||||||
{% elif object_type == 'scenario' %}
|
{% elif object_type == 'structure' %}
|
||||||
{% include "actions/collections/scenario.html" %}
|
{% include "actions/collections/compound_structure.html" %}
|
||||||
{% elif object_type == 'model' %}
|
{% elif object_type == 'rule' %}
|
||||||
{% include "actions/collections/model.html" %}
|
{% include "actions/collections/rule.html" %}
|
||||||
{% elif object_type == 'pathway' %}
|
{% elif object_type == 'reaction' %}
|
||||||
{% include "actions/collections/pathway.html" %}
|
{% include "actions/collections/reaction.html" %}
|
||||||
{% elif object_type == 'node' %}
|
{% elif object_type == 'setting' %}
|
||||||
{% include "actions/collections/node.html" %}
|
{% include "actions/collections/setting.html" %}
|
||||||
{% elif object_type == 'edge' %}
|
{% elif object_type == 'scenario' %}
|
||||||
{% include "actions/collections/edge.html" %}
|
{% include "actions/collections/scenario.html" %}
|
||||||
{% elif object_type == 'group' %}
|
{% elif object_type == 'model' %}
|
||||||
{% include "actions/collections/group.html" %}
|
{% include "actions/collections/model.html" %}
|
||||||
{% endif %}
|
{% elif object_type == 'pathway' %}
|
||||||
{% endblock %}
|
{% include "actions/collections/pathway.html" %}
|
||||||
</ul>
|
{% elif object_type == 'node' %}
|
||||||
|
{% include "actions/collections/node.html" %}
|
||||||
|
{% elif object_type == 'edge' %}
|
||||||
|
{% include "actions/collections/edge.html" %}
|
||||||
|
{% elif object_type == 'group' %}
|
||||||
|
{% include "actions/collections/group.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="mt-2">
|
||||||
<div class="panel-body">
|
<!-- Set Text above links -->
|
||||||
<!-- Set Text above links -->
|
{% if object_type == 'package' %}
|
||||||
{% if object_type == 'package' %}
|
|
||||||
<p>
|
|
||||||
A package contains pathways, rules, etc. and can reflect specific
|
|
||||||
experimental conditions.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/packages"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'compound' %}
|
|
||||||
<p>
|
|
||||||
A compound stores the structure of a molecule and can include
|
|
||||||
meta-information.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/compounds"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'structure' %}
|
|
||||||
<p>
|
|
||||||
The structures stored in this compound
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/compounds"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'rule' %}
|
|
||||||
<p>
|
|
||||||
A rule describes a biotransformation reaction template that is
|
|
||||||
defined as SMIRKS.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/Rules"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'reaction' %}
|
|
||||||
<p>
|
|
||||||
A reaction is a specific biotransformation from educt compounds to
|
|
||||||
product compounds.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/reactions"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'pathway' %}
|
|
||||||
<p>
|
|
||||||
A pathway displays the (predicted) biodegradation of a compound as
|
|
||||||
graph.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/pathways"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'node' %}
|
|
||||||
<p>
|
|
||||||
Nodes represent the (predicted) compounds in a graph.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/nodes"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'edge' %}
|
|
||||||
<p>
|
|
||||||
Edges represent the links between Nodes in a graph
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/edges"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'scenario' %}
|
|
||||||
<p>
|
|
||||||
A scenario contains meta-information that can be attached to other
|
|
||||||
data (compounds, rules, ..).
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/scenarios"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'model' %}
|
|
||||||
<p>
|
|
||||||
A model applies machine learning to limit the combinatorial
|
|
||||||
explosion.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/relative_reasoning"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'setting' %}
|
|
||||||
<p>
|
|
||||||
A setting includes configuration parameters for pathway predictions.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/settings"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'user' %}
|
|
||||||
<p>
|
|
||||||
Register now to create own packages and to submit and manage your
|
|
||||||
data.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/users"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% elif object_type == 'group' %}
|
|
||||||
<p>
|
|
||||||
Users can team up in groups to share packages.
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://wiki.envipath.org/index.php/groups"
|
|
||||||
role="button"
|
|
||||||
>Learn more >></a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- If theres nothing to show extend the text above -->
|
|
||||||
{% if reviewed_objects and unreviewed_objects %}
|
|
||||||
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
|
|
||||||
<p>
|
<p>
|
||||||
Nothing found. There are two possible reasons: <br /><br />1.
|
A package contains pathways, rules, etc. and can reflect specific
|
||||||
There is no content yet.<br />2. You have no reading
|
experimental conditions.
|
||||||
permissions.<br /><br />Please be sure you have at least reading
|
<a
|
||||||
permissions.
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/packages"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'compound' %}
|
||||||
|
<p>
|
||||||
|
A compound stores the structure of a molecule and can include
|
||||||
|
meta-information.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/compounds"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'structure' %}
|
||||||
|
<p>
|
||||||
|
The structures stored in this compound
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/compounds"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'rule' %}
|
||||||
|
<p>
|
||||||
|
A rule describes a biotransformation reaction template that is
|
||||||
|
defined as SMIRKS.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/Rules"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'reaction' %}
|
||||||
|
<p>
|
||||||
|
A reaction is a specific biotransformation from educt compounds to
|
||||||
|
product compounds.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/reactions"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'pathway' %}
|
||||||
|
<p>
|
||||||
|
A pathway displays the (predicted) biodegradation of a compound as
|
||||||
|
graph.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/pathways"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'node' %}
|
||||||
|
<p>
|
||||||
|
Nodes represent the (predicted) compounds in a graph.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/nodes"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'edge' %}
|
||||||
|
<p>
|
||||||
|
Edges represent the links between Nodes in a graph
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/edges"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'scenario' %}
|
||||||
|
<p>
|
||||||
|
A scenario contains meta-information that can be attached to other
|
||||||
|
data (compounds, rules, ..).
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/scenarios"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'model' %}
|
||||||
|
<p>
|
||||||
|
A model applies machine learning to limit the combinatorial
|
||||||
|
explosion.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/relative_reasoning"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'setting' %}
|
||||||
|
<p>
|
||||||
|
A setting includes configuration parameters for pathway
|
||||||
|
predictions.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/settings"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'user' %}
|
||||||
|
<p>
|
||||||
|
Register now to create own packages and to submit and manage your
|
||||||
|
data.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/users"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
{% elif object_type == 'group' %}
|
||||||
|
<p>
|
||||||
|
Users can team up in groups to share packages.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/groups"
|
||||||
|
class="link link-primary"
|
||||||
|
>Learn more >></a
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
<!-- If theres nothing to show extend the text above -->
|
||||||
|
{% if reviewed_objects and unreviewed_objects %}
|
||||||
|
{% if reviewed_objects|length == 0 and unreviewed_objects|length == 0 %}
|
||||||
|
<p class="mt-4">
|
||||||
|
Nothing found. There are two possible reasons: <br /><br />1.
|
||||||
|
There is no content yet.<br />2. You have no reading
|
||||||
|
permissions.<br /><br />Please be sure you have at least reading
|
||||||
|
permissions.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lists Container - Full Width with Reviewed on Right -->
|
||||||
|
<div class="w-full">
|
||||||
{% if reviewed_objects %}
|
{% if reviewed_objects %}
|
||||||
{% if reviewed_objects|length > 0 %}
|
{% if reviewed_objects|length > 0 %}
|
||||||
|
<!-- Reviewed -->
|
||||||
<div
|
<div
|
||||||
class="panel panel-default panel-heading list-group-item"
|
class="collapse-arrow bg-base-200 collapse order-2 w-full"
|
||||||
style="background-color:silver"
|
x-data="paginatedList(window.reviewedObjects || [], { isReviewed: true, instanceId: 'reviewed' })"
|
||||||
>
|
>
|
||||||
<h4 class="panel-title">
|
<input type="checkbox" checked />
|
||||||
<a
|
<div class="collapse-title text-xl font-medium">
|
||||||
id="ReviewedLink"
|
Reviewed
|
||||||
data-toggle="collapse"
|
<span
|
||||||
data-parent="#reviewListAccordion"
|
class="badge badge-sm badge-neutral ml-2"
|
||||||
href="#Reviewed"
|
x-text="totalItems"
|
||||||
>Reviewed</a
|
></span>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content w-full">
|
||||||
|
<ul class="menu bg-base-100 rounded-box w-full">
|
||||||
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||||
|
<li>
|
||||||
|
<a :href="obj.url" class="hover:bg-base-200">
|
||||||
|
<span x-text="obj.name"></span>
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left ml-auto"
|
||||||
|
data-tip="Reviewed"
|
||||||
|
>
|
||||||
|
<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-star"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div
|
||||||
|
x-show="totalPages > 1"
|
||||||
|
class="mt-4 flex items-center justify-between px-2"
|
||||||
>
|
>
|
||||||
</h4>
|
<span class="text-base-content/70 text-sm">
|
||||||
</div>
|
Showing <span x-text="showingStart"></span>-<span
|
||||||
<div id="Reviewed" class="panel-collapse in collapse">
|
x-text="showingEnd"
|
||||||
<div class="panel-body list-group-item" id="ReviewedContent">
|
></span>
|
||||||
{% if object_type == 'package' %}
|
of <span x-text="totalItems"></span>
|
||||||
{% for obj in reviewed_objects %}
|
</span>
|
||||||
<a class="list-group-item" href="{{ obj.url }}"
|
<div class="join">
|
||||||
>{{ obj.name|safe }}
|
<button
|
||||||
<span
|
class="join-item btn btn-sm"
|
||||||
class="glyphicon glyphicon-star"
|
:disabled="currentPage === 1"
|
||||||
aria-hidden="true"
|
@click="prevPage()"
|
||||||
style="float:right"
|
>
|
||||||
data-toggle="tooltip"
|
«
|
||||||
data-placement="top"
|
</button>
|
||||||
title=""
|
<template x-for="item in pageNumbers" :key="item.key">
|
||||||
data-original-title="Reviewed"
|
<button
|
||||||
>
|
class="join-item btn btn-sm"
|
||||||
</span>
|
:class="{ 'btn-active': item.page === currentPage }"
|
||||||
</a>
|
:disabled="item.isEllipsis"
|
||||||
{% endfor %}
|
@click="!item.isEllipsis && goToPage(item.page)"
|
||||||
{% else %}
|
x-text="item.page"
|
||||||
{% for obj in reviewed_objects|slice:":50" %}
|
></button>
|
||||||
<a class="list-group-item" href="{{ obj.url }}"
|
</template>
|
||||||
>{{ obj.name|safe }}{# <i>({{ obj.package.name }})</i> #}
|
<button
|
||||||
<span
|
class="join-item btn btn-sm"
|
||||||
class="glyphicon glyphicon-star"
|
:disabled="currentPage === totalPages"
|
||||||
aria-hidden="true"
|
@click="nextPage()"
|
||||||
style="float:right"
|
>
|
||||||
data-toggle="tooltip"
|
»
|
||||||
data-placement="top"
|
</button>
|
||||||
title=""
|
</div>
|
||||||
data-original-title="Reviewed"
|
</div>
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if unreviewed_objects %}
|
{% if unreviewed_objects %}
|
||||||
|
<!-- Unreviewed -->
|
||||||
<div
|
<div
|
||||||
class="panel panel-default panel-heading list-group-item"
|
class="collapse-arrow bg-base-200 collapse order-1 w-full"
|
||||||
style="background-color:silver"
|
x-data="paginatedList(window.unreviewedObjects || [], { isReviewed: false, instanceId: 'unreviewed' })"
|
||||||
>
|
>
|
||||||
<h4 class="panel-title">
|
<input
|
||||||
<a
|
type="checkbox"
|
||||||
id="UnreviewedLink"
|
{% if reviewed_objects|length == 0 or object_type == 'package' %}checked{% endif %}
|
||||||
data-toggle="collapse"
|
/>
|
||||||
data-parent="#unReviewListAccordion"
|
<div class="collapse-title text-xl font-medium">
|
||||||
href="#Unreviewed"
|
Unreviewed
|
||||||
>Unreviewed</a
|
<span
|
||||||
|
class="badge badge-sm badge-neutral ml-2"
|
||||||
|
x-text="totalItems"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content w-full">
|
||||||
|
<ul class="menu bg-base-100 rounded-box w-full">
|
||||||
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
:href="obj.url"
|
||||||
|
class="hover:bg-base-200"
|
||||||
|
x-text="obj.name"
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div
|
||||||
|
x-show="totalPages > 1"
|
||||||
|
class="mt-4 flex items-center justify-between px-2"
|
||||||
>
|
>
|
||||||
</h4>
|
<span class="text-base-content/70 text-sm">
|
||||||
</div>
|
Showing <span x-text="showingStart"></span>-<span
|
||||||
<div
|
x-text="showingEnd"
|
||||||
id="Unreviewed"
|
></span>
|
||||||
class="panel-collapse {% if reviewed_objects|length == 0 or object_type == 'package' %}in{% endif %} collapse"
|
of <span x-text="totalItems"></span>
|
||||||
>
|
</span>
|
||||||
<div class="panel-body list-group-item" id="UnreviewedContent">
|
<div class="join">
|
||||||
{% if object_type == 'package' %}
|
<button
|
||||||
{% for obj in unreviewed_objects %}
|
class="join-item btn btn-sm"
|
||||||
<a class="list-group-item" href="{{ obj.url }}"
|
:disabled="currentPage === 1"
|
||||||
>{{ obj.name|safe }}</a
|
@click="prevPage()"
|
||||||
>
|
>
|
||||||
{% endfor %}
|
«
|
||||||
{% else %}
|
</button>
|
||||||
{% for obj in unreviewed_objects|slice:":50" %}
|
<template x-for="item in pageNumbers" :key="item.key">
|
||||||
<a class="list-group-item" href="{{ obj.url }}"
|
<button
|
||||||
>{{ obj.name|safe }}</a
|
class="join-item btn btn-sm"
|
||||||
|
:class="{ 'btn-active': item.page === currentPage }"
|
||||||
|
:disabled="item.isEllipsis"
|
||||||
|
@click="!item.isEllipsis && goToPage(item.page)"
|
||||||
|
x-text="item.page"
|
||||||
|
></button>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === totalPages"
|
||||||
|
@click="nextPage()"
|
||||||
>
|
>
|
||||||
{% endfor %}
|
»
|
||||||
{% endif %}
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if objects %}
|
|
||||||
<!-- 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|safe }}</a
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<a class="list-group-item" href="{{ obj.url }}"
|
|
||||||
>{{ obj.name|safe }}</a
|
|
||||||
>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<style>
|
|
||||||
.spinner-widget {
|
|
||||||
position: fixed; /* stays in place on scroll */
|
|
||||||
bottom: 20px; /* distance from bottom */
|
|
||||||
right: 20px; /* distance from right */
|
|
||||||
z-index: 9999; /* above most elements */
|
|
||||||
width: 60px; /* adjust to gif size */
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner-widget img {
|
{% if objects %}
|
||||||
width: 100%;
|
<!-- Unreviewable objects such as User / Group / Setting -->
|
||||||
height: auto;
|
<div class="card bg-base-100">
|
||||||
}
|
<div class="card-body">
|
||||||
</style>
|
<ul class="menu bg-base-200 rounded-box">
|
||||||
|
{% for obj in objects %}
|
||||||
<div id="load-all-loading" class="spinner-widget" style="display: none">
|
{% if object_type == 'user' %}
|
||||||
<img
|
<li>
|
||||||
id="loading-gif"
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
src="{% static '/images/wait.gif' %}"
|
>{{ obj.username }}</a
|
||||||
alt="Loading..."
|
>
|
||||||
/>
|
</li>
|
||||||
</div>
|
{% else %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ obj.url }}" class="hover:bg-base-300"
|
||||||
|
>{{ obj.name }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{# prettier-ignore-start #}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
$('#object-search').show();
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
// Show actions button if there are actions
|
||||||
|
const actionsButton = document.getElementById("actionsButton");
|
||||||
|
const actionsList = actionsButton?.querySelector("ul");
|
||||||
|
if (actionsList && actionsList.children.length > 0) {
|
||||||
|
actionsButton?.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
{% if object_type != 'package' and object_type != 'user' and object_type != 'group' %}
|
// Show search input and connect to Alpine pagination
|
||||||
{% if reviewed_objects|length > 50 or unreviewed_objects|length > 50 %}
|
const objectSearch = document.getElementById("object-search");
|
||||||
$('#load-all-loading').show()
|
if (objectSearch) {
|
||||||
|
objectSearch.classList.remove("hidden");
|
||||||
setTimeout(function () {
|
objectSearch.addEventListener("input", function () {
|
||||||
$('#load-all-error').hide();
|
const query = this.value;
|
||||||
|
// Dispatch search to all paginatedList components
|
||||||
$.getJSON('?all=true', function (resp) {
|
document
|
||||||
$('#ReviewedContent').empty();
|
.querySelectorAll('[x-data*="paginatedList"]')
|
||||||
$('#UnreviewedContent').empty();
|
.forEach((el) => {
|
||||||
|
if (el._x_dataStack && el._x_dataStack[0]) {
|
||||||
for (o in resp.objects) {
|
el._x_dataStack[0].search(query);
|
||||||
obj = resp.objects[o];
|
}
|
||||||
if (obj.reviewed) {
|
|
||||||
$('#ReviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + ' <span class="glyphicon glyphicon-star" aria-hidden="true" style="float:right" data-toggle="tooltip" data-placement="top" title="" data-original-title="Reviewed"></span></a>');
|
|
||||||
} else {
|
|
||||||
$('#UnreviewedContent').append('<a class="list-group-item" href="' + obj.url + '">' + obj.name + '</a>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#load-all-loading').hide();
|
|
||||||
$('#load-remaining').hide();
|
|
||||||
}).fail(function (resp) {
|
|
||||||
$('#load-all-loading').hide();
|
|
||||||
$('#load-all-error').show();
|
|
||||||
});
|
|
||||||
|
|
||||||
}, 2500);
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
$('#modal-form-delete-submit').on('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$('#modal-form-delete').submit();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#object-search').on('keyup', function () {
|
|
||||||
let query = $(this).val().toLowerCase();
|
|
||||||
$('a.list-group-item').each(function () {
|
|
||||||
let text = $(this).text().toLowerCase();
|
|
||||||
$(this).toggle(text.indexOf(query) !== -1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
}
|
||||||
{# prettier-ignore-end #}
|
|
||||||
|
// Delete form submit handler
|
||||||
|
const deleteSubmit = document.getElementById("modal-form-delete-submit");
|
||||||
|
const deleteForm = document.getElementById("modal-form-delete");
|
||||||
|
if (deleteSubmit && deleteForm) {
|
||||||
|
deleteSubmit.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
deleteForm.submit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,18 +1,77 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="alert alert-error" role="alert">
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<h4 class="alert-heading">Bad Request!</h4>
|
<div class="w-full max-w-2xl">
|
||||||
<p>Lorem</p>
|
<div class="alert alert-error mb-6 shadow-lg">
|
||||||
<hr />
|
<svg
|
||||||
<p class="mb-0">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
You can find out more about permissions in our
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
<a
|
fill="none"
|
||||||
target="_blank"
|
viewBox="0 0 24 24"
|
||||||
href="https://wiki.envipath.org/index.php/packages"
|
>
|
||||||
role="button"
|
<path
|
||||||
>Wiki >></a
|
stroke-linecap="round"
|
||||||
>
|
stroke-linejoin="round"
|
||||||
</p>
|
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>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3 class="text-lg font-bold">Bad Request</h3>
|
||||||
|
<p class="text-sm">The request you sent was invalid or malformed.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title mb-4 text-2xl">What happened?</h2>
|
||||||
|
<p class="text-base-content/70 mb-4">
|
||||||
|
The server couldn't process your request because it contains invalid
|
||||||
|
data or parameters.
|
||||||
|
</p>
|
||||||
|
<div class="card-actions mt-6 justify-end">
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Go Home
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://wiki.envipath.org/index.php/packages"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-outline"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="ml-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,18 +1,80 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="alert alert-error" role="alert">
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<h4 class="alert-heading">Access Denied!</h4>
|
<div class="w-full max-w-2xl">
|
||||||
<p>Access to X denied.</p>
|
<div class="alert alert-warning mb-6 shadow-lg">
|
||||||
<hr />
|
<svg
|
||||||
<p class="mb-0">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
You can find out more about permissions in our
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
<a
|
fill="none"
|
||||||
target="_blank"
|
viewBox="0 0 24 24"
|
||||||
href="https://wiki.envipath.org/index.php/packages"
|
>
|
||||||
role="button"
|
<path
|
||||||
>Wiki >></a
|
stroke-linecap="round"
|
||||||
>
|
stroke-linejoin="round"
|
||||||
</p>
|
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>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3 class="text-lg font-bold">Access Denied</h3>
|
||||||
|
<p class="text-sm">
|
||||||
|
You don't have permission to access this resource.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title mb-4 text-2xl">Permission Required</h2>
|
||||||
|
<p class="text-base-content/70 mb-4">
|
||||||
|
You need the appropriate permissions to access this content. If you
|
||||||
|
believe this is an error, please contact the package owner or
|
||||||
|
administrator.
|
||||||
|
</p>
|
||||||
|
<div class="card-actions mt-6 justify-end">
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Go Home
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://wiki.envipath.org/index.php/packages"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-outline"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="ml-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,18 +1,77 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="alert alert-error" role="alert">
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<h4 class="alert-heading">Not Found!</h4>
|
<div class="w-full max-w-2xl">
|
||||||
<p>Does not exist</p>
|
<div class="alert alert-info mb-6 shadow-lg">
|
||||||
<hr />
|
<svg
|
||||||
<p class="mb-0">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
You can find out more about permissions in our
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
<a
|
fill="none"
|
||||||
target="_blank"
|
viewBox="0 0 24 24"
|
||||||
href="https://wiki.envipath.org/index.php/packages"
|
>
|
||||||
role="button"
|
<path
|
||||||
>Wiki >></a
|
stroke-linecap="round"
|
||||||
>
|
stroke-linejoin="round"
|
||||||
</p>
|
stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3 class="text-lg font-bold">Page Not Found</h3>
|
||||||
|
<p class="text-sm">The page you're looking for doesn't exist.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title mb-4 text-2xl">404 Error</h2>
|
||||||
|
<p class="text-base-content/70 mb-4">
|
||||||
|
The page or resource you requested could not be found. It may have
|
||||||
|
been moved, deleted, or the URL might be incorrect.
|
||||||
|
</p>
|
||||||
|
<div class="card-actions mt-6 justify-end">
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Go Home
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://wiki.envipath.org/index.php/packages"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-outline"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="ml-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,9 +1,76 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<h4 class="alert-heading">{{ error_message }}</h4>
|
<div class="w-full max-w-2xl">
|
||||||
<hr />
|
<div class="alert alert-error mb-6 shadow-lg">
|
||||||
<p class="mb-0">{{ error_detail }}</p>
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3 class="text-lg font-bold">
|
||||||
|
{{ error_message|default:"An Error Occurred" }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm">
|
||||||
|
{{ error_detail|default:"Something went wrong. Please try again later." }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title mb-4 text-2xl">Oops! Something went wrong</h2>
|
||||||
|
<p class="text-base-content/70 mb-4">
|
||||||
|
{{ error_description|default:"We encountered an unexpected error while processing your request. Our team has been notified and is working to resolve the issue." }}
|
||||||
|
</p>
|
||||||
|
<div class="card-actions mt-6 justify-end">
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Go Home
|
||||||
|
</a>
|
||||||
|
<button onclick="window.history.back()" class="btn btn-outline">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Go Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -1,11 +1,81 @@
|
|||||||
{% extends "framework.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
|
||||||
<h4 class="alert-heading">Your account has not been activated yet!</h4>
|
<div class="w-full max-w-2xl">
|
||||||
<p>
|
<div class="alert alert-warning mb-6 shadow-lg">
|
||||||
Your account has not been activated yet. If you have questions
|
<svg
|
||||||
<a href="mailto:admin@envipath.org">contact us.</a>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</p>
|
class="h-8 w-8 shrink-0 stroke-current"
|
||||||
|
fill="none"
|
||||||
|
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>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3 class="text-lg font-bold">Account Not Activated</h3>
|
||||||
|
<p class="text-sm">Your account is pending activation.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title mb-4 text-2xl">Account Activation Required</h2>
|
||||||
|
<p class="text-base-content/70 mb-4">
|
||||||
|
Your account has not been activated yet. An administrator needs to
|
||||||
|
approve your account before you can access all features. This
|
||||||
|
process typically takes 24-48 hours.
|
||||||
|
</p>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<p class="text-base-content/70 mb-4">
|
||||||
|
If you have questions or believe this is an error, please
|
||||||
|
<a href="mailto:admin@envipath.org" class="link link-primary"
|
||||||
|
>contact us</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<div class="card-actions mt-6 justify-end">
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Go Home
|
||||||
|
</a>
|
||||||
|
<a href="mailto:admin@envipath.org" class="btn btn-outline">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Contact Admin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -228,15 +228,7 @@
|
|||||||
>Documentation Wiki</a
|
>Documentation Wiki</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>
|
||||||
@ -408,10 +400,5 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% block modals %}
|
|
||||||
{% include "modals/cite_modal.html" %}
|
|
||||||
{% include "modals/predict_modal.html" %}
|
|
||||||
{% include "modals/batch_predict_modal.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -21,8 +21,14 @@
|
|||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{# jQuery - Keep for compatibility with existing JS #}
|
{# Alpine.js - For reactive components #}
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
<script
|
||||||
|
defer
|
||||||
|
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
||||||
|
></script>
|
||||||
|
<script src="{% static 'js/alpine/index.js' %}"></script>
|
||||||
|
<script src="{% static 'js/alpine/search.js' %}"></script>
|
||||||
|
<script src="{% static 'js/alpine/pagination.js' %}"></script>
|
||||||
|
|
||||||
{# Font Awesome #}
|
{# Font Awesome #}
|
||||||
<link
|
<link
|
||||||
@ -35,21 +41,10 @@
|
|||||||
|
|
||||||
<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
|
|
||||||
$.ajaxSetup({
|
|
||||||
beforeSend: function (xhr, settings) {
|
|
||||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
|
|
||||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{# 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 #}
|
|
||||||
<script src="{% static 'js/jquery-bootstrap-modal-steps.js' %}"></script>
|
|
||||||
|
|
||||||
{% if not debug %}
|
{% if not debug %}
|
||||||
<!-- Matomo -->
|
<!-- Matomo -->
|
||||||
@ -171,10 +166,11 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Hide actionsbutton if there's no action defined
|
// Show actions button if there are actions defined
|
||||||
if ($("#actionsButton ul").children().length > 0) {
|
const actionsButtonUl = document.querySelector("#actionsButton ul");
|
||||||
$("#actionsButton").show();
|
if (actionsButtonUl && actionsButtonUl.children.length > 0) {
|
||||||
|
document.getElementById("actionsButton").style.display = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,140 +1,267 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{# Modern DaisyUI Navbar #}
|
{# Modern DaisyUI Navbar with Mobile Drawer Menu #}
|
||||||
<div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg">
|
<div class="drawer drawer-mobile">
|
||||||
<div class="navbar-start">
|
<input id="drawer-toggle" type="checkbox" class="drawer-toggle" />
|
||||||
<a href="{{ meta.server_url }}" class="btn btn-ghost text-xl normal-case">
|
<div class="drawer-content flex flex-col">
|
||||||
<svg class="fill-base-content h-8" viewBox="0 0 104 26" role="img">
|
{# Navbar #}
|
||||||
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
|
<div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg">
|
||||||
</svg>
|
<div class="navbar-start">
|
||||||
</a>
|
{# Hamburger menu button - visible on mobile, hidden on desktop #}
|
||||||
</div>
|
{% if not public_mode %}
|
||||||
|
<label
|
||||||
{% if not public_mode %}
|
for="drawer-toggle"
|
||||||
<div class="navbar-center hidden lg:flex">
|
class="btn btn-square btn-ghost drawer-button lg:hidden"
|
||||||
<a
|
>
|
||||||
href="{{ meta.server_url }}/predict"
|
<svg
|
||||||
role="button"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="btn btn-ghost"
|
fill="none"
|
||||||
id="predictLink"
|
viewBox="0 0 24 24"
|
||||||
>Predict</a
|
class="inline-block h-5 w-5 stroke-current"
|
||||||
>
|
>
|
||||||
<!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> -->
|
<path
|
||||||
<!--<li><a href="{{ meta.server_url }}/browse" id="browseLink">Browse</a></li>-->
|
stroke-linecap="round"
|
||||||
<div class="dropdown dropdown-center">
|
stroke-linejoin="round"
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost">Browse</div>
|
stroke-width="2"
|
||||||
<ul
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
tabindex="-1"
|
></path>
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"
|
</svg>
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
<a
|
||||||
|
href="{{ meta.server_url }}"
|
||||||
|
class="btn btn-ghost text-xl normal-case"
|
||||||
>
|
>
|
||||||
<li>
|
<svg class="fill-base-content h-8" viewBox="0 0 104 26" role="img">
|
||||||
<a href="{{ meta.server_url }}/package" id="packageLink">Package</a>
|
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
|
||||||
</li>
|
</svg>
|
||||||
<li>
|
</a>
|
||||||
<a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a>
|
</div>
|
||||||
</li>
|
|
||||||
<li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li>
|
{% if not public_mode %}
|
||||||
<li>
|
{# Desktop menu - hidden on mobile, visible on desktop #}
|
||||||
<a href="{{ meta.server_url }}/compound" id="compoundLink"
|
<div class="navbar-center hidden lg:flex">
|
||||||
>Compound</a
|
<a
|
||||||
|
href="{{ meta.server_url }}/predict"
|
||||||
|
role="button"
|
||||||
|
class="btn btn-ghost"
|
||||||
|
id="predictLink"
|
||||||
|
>Predict</a
|
||||||
|
>
|
||||||
|
<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>
|
<li>
|
||||||
<li>
|
<a href="{{ meta.server_url }}/package" id="packageLink"
|
||||||
<a href="{{ meta.server_url }}/reaction" id="reactionLink"
|
>Package</a
|
||||||
>Reaction</a
|
>
|
||||||
|
</li>
|
||||||
|
<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 id="search-trigger" role="button" class="cursor-pointer">
|
||||||
|
<div
|
||||||
|
class="badge badge-dash bg-base-200 text-base-content/50 m-1 flex items-center space-x-1 p-2"
|
||||||
>
|
>
|
||||||
</li>
|
<svg
|
||||||
<li>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<a href="{{ meta.server_url }}/model" id="relative-reasoningLink"
|
width="16"
|
||||||
>Model</a
|
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 btn-circle m-1"
|
||||||
|
id="loggedInButton"
|
||||||
>
|
>
|
||||||
</li>
|
<svg
|
||||||
<li>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<a href="{{ meta.server_url }}/scenario" id="scenarioLink"
|
width="24"
|
||||||
>Scenario</a
|
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>
|
<li>
|
||||||
</ul>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
|
{# Mobile drawer menu - slides in from the left #}
|
||||||
<div class="navbar-end">
|
<div class="drawer-side">
|
||||||
{% if not public_mode %}
|
<label for="drawer-toggle" class="drawer-overlay"></label>
|
||||||
<a id="search-trigger" role="button" class="cursor-pointer">
|
<ul class="menu min-h-full w-80 bg-base-200 p-4 text-base-content">
|
||||||
<div
|
{# Drawer header with close button #}
|
||||||
class="badge badge-dash bg-base-200 text-base-content/50 m-1 flex items-center space-x-1 p-2"
|
<li class="mb-4">
|
||||||
>
|
<div class="flex items-center justify-between">
|
||||||
<svg
|
<span class="font-bold text-lg">Menu</span>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<label
|
||||||
width="16"
|
for="drawer-toggle"
|
||||||
height="16"
|
class="btn btn-sm btn-circle btn-ghost"
|
||||||
viewBox="0 0 24 24"
|
aria-label="Close menu"
|
||||||
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" />
|
<svg
|
||||||
<circle cx="11" cy="11" r="8" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</svg>
|
class="h-6 w-6"
|
||||||
<span id="search-shortcut">⌘K</span>
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</li>
|
||||||
{% endif %}
|
{% if not public_mode %}
|
||||||
{% if meta.user.username == 'anonymous' or public_mode %}
|
{# Predict link #}
|
||||||
<a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a>
|
<li>
|
||||||
{% else %}
|
<a
|
||||||
<div class="dropdown dropdown-end">
|
href="{{ meta.server_url }}/predict"
|
||||||
<div
|
class="text-lg"
|
||||||
tabindex="0"
|
id="predictLinkMobile"
|
||||||
role="button"
|
>Predict</a
|
||||||
class="btn btn-ghost btn-circle m-1"
|
|
||||||
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" />
|
</li>
|
||||||
<circle cx="12" cy="10" r="3" />
|
{# Browse menu with submenu #}
|
||||||
<path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662" />
|
<li>
|
||||||
</svg>
|
<details>
|
||||||
</div>
|
<summary class="text-lg">Browse</summary>
|
||||||
<ul
|
<ul>
|
||||||
tabindex="-1"
|
<li>
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-sm"
|
<a href="{{ meta.server_url }}/package" id="packageLinkMobile"
|
||||||
>
|
>Package</a
|
||||||
<li><a href="{{ meta.user.url }}" id="accountbutton">Settings</a></li>
|
>
|
||||||
<li>
|
</li>
|
||||||
<form
|
<li>
|
||||||
id="logoutForm"
|
<a href="{{ meta.server_url }}/pathway" id="pathwayLinkMobile"
|
||||||
action="{% url 'logout' %}"
|
>Pathway</a
|
||||||
method="post"
|
>
|
||||||
style="display: none;"
|
</li>
|
||||||
>
|
<li>
|
||||||
{% csrf_token %}
|
<a href="{{ meta.server_url }}/rule" id="ruleLinkMobile"
|
||||||
<input type="hidden" name="logout" value="true" />
|
>Rule</a
|
||||||
</form>
|
>
|
||||||
<a
|
</li>
|
||||||
href="#"
|
<li>
|
||||||
id="logoutButton"
|
<a href="{{ meta.server_url }}/compound" id="compoundLinkMobile"
|
||||||
onclick="event.preventDefault(); document.getElementById('logoutForm').submit();"
|
>Compound</a
|
||||||
>Logout</a
|
>
|
||||||
>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
</ul>
|
<a href="{{ meta.server_url }}/reaction" id="reactionLinkMobile"
|
||||||
</div>
|
>Reaction</a
|
||||||
{% endif %}
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="{{ meta.server_url }}/model"
|
||||||
|
id="relative-reasoningLinkMobile"
|
||||||
|
>Model</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ meta.server_url }}/scenario" id="scenarioLinkMobile"
|
||||||
|
>Scenario</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
>
|
>
|
||||||
<div class="hero-overlay"></div>
|
<div class="hero-overlay"></div>
|
||||||
<!-- Predict Pathway text over the image -->
|
<!-- Predict Pathway text over the image -->
|
||||||
<div class="absolute bottom-40 left-1/8 z-10 -translate-x-8">
|
<div class="absolute bottom-40 left-1/8 -translate-x-8">
|
||||||
<h2 class="text-base-100 text-left text-3xl text-shadow-lg">
|
<h2 class="text-base-100 text-left text-3xl text-shadow-lg">
|
||||||
Predict Your Pathway
|
Predict Your Pathway
|
||||||
</h2>
|
</h2>
|
||||||
@ -20,16 +20,68 @@
|
|||||||
<div class="bg-base-200 mx-auto max-w-5xl shadow-md">
|
<div class="bg-base-200 mx-auto max-w-5xl shadow-md">
|
||||||
<!-- Predict Pathway Section -->
|
<!-- Predict Pathway Section -->
|
||||||
<div
|
<div
|
||||||
class="relative z-20 mx-auto -mt-32 mb-10 w-full flex-col lg:flex-row-reverse"
|
class="relative mx-auto -mt-32 mb-10 w-full flex-col lg:flex-row-reverse"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="card bg-base-100 mx-auto w-3/4 shrink-0 shadow-xl transition-all duration-300 ease-in-out"
|
class="card bg-base-100 mx-auto w-3/4 shrink-0 shadow-xl transition-all duration-300 ease-in-out"
|
||||||
|
x-data="{
|
||||||
|
drawMode: false,
|
||||||
|
smiles: '',
|
||||||
|
loadExample(smilesStr, linkEl) {
|
||||||
|
if (this.drawMode && window.indexKetcher && window.indexKetcher.setMolecule) {
|
||||||
|
window.indexKetcher.setMolecule(smilesStr);
|
||||||
|
} else {
|
||||||
|
this.smiles = smilesStr;
|
||||||
|
}
|
||||||
|
const original = linkEl.textContent;
|
||||||
|
linkEl.textContent = 'loaded!';
|
||||||
|
setTimeout(() => linkEl.textContent = original, 1000);
|
||||||
|
},
|
||||||
|
syncFromKetcher() {
|
||||||
|
const ketcher = getKetcherInstance('index-ketcher');
|
||||||
|
if (ketcher && ketcher.getSmiles) {
|
||||||
|
try {
|
||||||
|
const s = ketcher.getSmiles();
|
||||||
|
if (s && s.trim()) this.smiles = s;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to sync from Ketcher:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
let finalSmiles = '';
|
||||||
|
if (this.drawMode) {
|
||||||
|
const ketcher = getKetcherInstance('index-ketcher');
|
||||||
|
if (ketcher && ketcher.getSmiles) {
|
||||||
|
try {
|
||||||
|
finalSmiles = ketcher.getSmiles().trim();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to get SMILES from Ketcher:', err);
|
||||||
|
alert('Unable to extract structure. Please try again or switch to SMILES input.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('The drawing editor is still loading. Please wait and try again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalSmiles = this.smiles.trim();
|
||||||
|
}
|
||||||
|
if (!finalSmiles) {
|
||||||
|
alert('Please enter a SMILES string or draw a structure.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('index-form-smiles').value = finalSmiles;
|
||||||
|
document.getElementById('index-form').submit();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
x-init="$watch('drawMode', value => { if (!value) syncFromKetcher(); })"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="my-4 ml-8 flex h-fit flex-row items-center justify-start">
|
<div class="my-4 ml-8 flex h-fit flex-row items-center justify-start">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<label class="swap btn btn-ghost btn-sm p-1" title="Input Mode">
|
<label class="swap btn btn-ghost btn-sm p-1" title="Input Mode">
|
||||||
<input type="checkbox" />
|
<input type="checkbox" x-model="drawMode" />
|
||||||
<span class="swap-on flex items-center gap-1">
|
<span class="swap-on flex items-center gap-1">
|
||||||
<div
|
<div
|
||||||
class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1"
|
class="bg-neutral/50 text-neutral-content flex items-center justify-center rounded-full p-1"
|
||||||
@ -82,16 +134,24 @@
|
|||||||
|
|
||||||
<fieldset
|
<fieldset
|
||||||
class="fieldset overflow-hidden transition-all duration-300 ease-in-out"
|
class="fieldset overflow-hidden transition-all duration-300 ease-in-out"
|
||||||
|
:class="drawMode ? 'p-4' : 'p-8'"
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
id="index-form"
|
id="index-form"
|
||||||
action="{{ meta.current_package.url }}/pathway"
|
action="{{ meta.current_package.url }}/pathway"
|
||||||
method="POST"
|
method="POST"
|
||||||
|
@submit.prevent="submitForm()"
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div
|
<div
|
||||||
id="text-input-container"
|
id="text-input-container"
|
||||||
class="scale-100 transform opacity-100 transition-all duration-300 ease-in-out"
|
x-show="!drawMode"
|
||||||
|
x-transition:enter="transition ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-300"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<div class="join mx-auto w-full">
|
<div class="join mx-auto w-full">
|
||||||
<input
|
<input
|
||||||
@ -99,6 +159,7 @@
|
|||||||
id="index-form-text-input"
|
id="index-form-text-input"
|
||||||
placeholder="canonical SMILES string"
|
placeholder="canonical SMILES string"
|
||||||
class="input input-md join-item grow"
|
class="input input-md join-item grow"
|
||||||
|
x-model="smiles"
|
||||||
/>
|
/>
|
||||||
<button class="btn btn-neutral join-item">Predict!</button>
|
<button class="btn btn-neutral join-item">Predict!</button>
|
||||||
</div>
|
</div>
|
||||||
@ -107,26 +168,35 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="example-link hover:text-primary cursor-pointer"
|
class="example-link hover:text-primary cursor-pointer"
|
||||||
data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
|
|
||||||
title="load example"
|
title="load example"
|
||||||
|
@click.prevent="loadExample('CN1C=NC2=C1C(=O)N(C(=O)N2C)C', $el)"
|
||||||
>Caffeine</a
|
>Caffeine</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="example-link hover:text-primary cursor-pointer"
|
class="example-link hover:text-primary cursor-pointer"
|
||||||
data-smiles="CC(C)CC1=CC=C(C=C1)C(C)C(=O)O"
|
|
||||||
title="load example"
|
title="load example"
|
||||||
|
@click.prevent="loadExample('CC(C)CC1=CC=C(C=C1)C(C)C(=O)O', $el)"
|
||||||
>Ibuprofen</a
|
>Ibuprofen</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#"
|
<a
|
||||||
|
class="absolute top-0 left-[calc(100%-5.4rem)]"
|
||||||
|
href="/predict"
|
||||||
>Advanced</a
|
>Advanced</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="ketcher-container"
|
id="ketcher-container"
|
||||||
class="hidden w-full scale-95 transform opacity-0 transition-all duration-300 ease-in-out"
|
x-show="drawMode"
|
||||||
|
x-transition:enter="transition ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-300"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
class="w-full"
|
||||||
>
|
>
|
||||||
<iframe
|
<iframe
|
||||||
id="index-ketcher"
|
id="index-ketcher"
|
||||||
@ -256,6 +326,31 @@
|
|||||||
<script language="javascript">
|
<script language="javascript">
|
||||||
var currentPackage = "{{ meta.current_package.url }}";
|
var currentPackage = "{{ meta.current_package.url }}";
|
||||||
|
|
||||||
|
// Helper function to safely get Ketcher instance from iframe
|
||||||
|
function getKetcherInstance(iframeId) {
|
||||||
|
const ketcherFrame = document.getElementById(iframeId);
|
||||||
|
if (!ketcherFrame) {
|
||||||
|
console.error("Ketcher iframe not found:", iframeId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
"contentWindow" in ketcherFrame &&
|
||||||
|
ketcherFrame.contentWindow.ketcher
|
||||||
|
) {
|
||||||
|
return ketcherFrame.contentWindow.ketcher;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
"Cannot access Ketcher iframe - possible CORS issue:",
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Discourse API integration is now handled by discourse-api.js
|
// Discourse API integration is now handled by discourse-api.js
|
||||||
|
|
||||||
// Function to render Discourse topics into cards
|
// Function to render Discourse topics into cards
|
||||||
@ -278,16 +373,13 @@
|
|||||||
const date = new Date(topic.created_at).toLocaleDateString();
|
const date = new Date(topic.created_at).toLocaleDateString();
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="card bg-white shadow-xs hover:shadow-lg transition-shadow duration-300 h-64 w-75 flex-shrink-0">
|
<div class="card bg-white shadow-sm hover:shadow-lg transition-shadow duration-300 h-52 w-75 flex-shrink-0">
|
||||||
<div class="card-body flex flex-col h-full">
|
<div class="card-body flex flex-col h-full justify-between">
|
||||||
<h3 class="card-title leading-tight font-normal tracking-tight h-12 mb-2 line-clamp-2 text-ellipsis wrap-break-word overflow-hidden">
|
<h3 class="card-title leading-tight font-normal tracking-tight mb-2 line-clamp-5 overflow-hidden">
|
||||||
<a href="${topic.url}" target="_blank" class="hover:text-primary">
|
<a href="${topic.url}" target="_blank" class="hover:text-primary">
|
||||||
${topic.title}
|
${topic.title}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</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 flex-row items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@ -313,141 +405,20 @@
|
|||||||
// Make render function globally available
|
// Make render function globally available
|
||||||
window.renderDiscourseTopics = renderDiscourseTopics;
|
window.renderDiscourseTopics = renderDiscourseTopics;
|
||||||
|
|
||||||
// Toggle functionality with smooth animations
|
// Ketcher iframe load handler - set up change event to sync SMILES
|
||||||
function toggleInputMode() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const toggle = $('input[type="checkbox"]');
|
const indexKetcher = document.getElementById("index-ketcher");
|
||||||
const textContainer = $("#text-input-container");
|
indexKetcher.addEventListener("load", function () {
|
||||||
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 {
|
|
||||||
// SMILES mode - show text input, hide Ketcher
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ketcher integration
|
|
||||||
function indexKetcherToTextInput() {
|
|
||||||
$("#index-form-smiles").val(this.ketcher.getSmiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
// Initialize fieldset with proper padding
|
|
||||||
$(".fieldset").addClass("p-8");
|
|
||||||
|
|
||||||
// Toggle event listener
|
|
||||||
$('input[type="checkbox"]').on("change", toggleInputMode);
|
|
||||||
|
|
||||||
// Ketcher iframe load handler
|
|
||||||
$("#index-ketcher").on("load", function () {
|
|
||||||
const checkKetcherReady = () => {
|
const checkKetcherReady = () => {
|
||||||
const 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;
|
window.indexKetcher = win.ketcher;
|
||||||
win.ketcher.editor.event.change.handlers.push({
|
|
||||||
once: false,
|
|
||||||
priority: 0,
|
|
||||||
f: indexKetcherToTextInput,
|
|
||||||
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 main_content %}
|
{% endblock main_content %}
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
<div
|
|
||||||
class="modal fade bs-modal-lg"
|
|
||||||
id="citemodal"
|
|
||||||
tabindex="-1"
|
|
||||||
role="dialog"
|
|
||||||
aria-labelledby="myLargeModalLabel"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<div class="modal-dialog modal-lg">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>How to cite enviPath</h3>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<ol class="list-group list-group-numbered">
|
|
||||||
<li class="list-group-item">
|
|
||||||
Hafner, J., Lorsbach, T., Schmidt, S. <em>et al.</em>
|
|
||||||
<cite
|
|
||||||
>Advancements in biotransformation pathway prediction:
|
|
||||||
enhancements, datasets, and novel functionalities in
|
|
||||||
enviPath.</cite
|
|
||||||
>
|
|
||||||
<a href="https://doi.org/10.1186/s13321-024-00881-6" target="_blank"
|
|
||||||
>J Cheminform 16, 93 (2024)</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item">
|
|
||||||
Wicker, J., Lorsbach, T., Gütlein, M., Schmid, E., Latino, D.,
|
|
||||||
Kramer, S., Fenner, K.
|
|
||||||
<cite
|
|
||||||
>enviPath - The environmental contaminant biotransformation
|
|
||||||
pathway resource</cite
|
|
||||||
>
|
|
||||||
<a href="https://doi.org/10.1093/nar/gkv1229" target="_blank">
|
|
||||||
Nucleic Acids Research, Volume 44, Issue D1, 4 January 2016, Pages
|
|
||||||
D502-D508
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,70 +1,85 @@
|
|||||||
<div
|
<dialog
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
|
||||||
id="import_legacy_package_modal"
|
id="import_legacy_package_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="import_legacy_package_modal"
|
x-data="modalForm()"
|
||||||
aria-hidden="true"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog">
|
<div class="modal-box">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">Import Package from Legacy System</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
<span class="sr-only">Close</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
<h4 class="modal-title">Import Package from legacy System</h4>
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-body">
|
>
|
||||||
<p>Create a Package based on the JSON Export of the legacy system.</p>
|
✕
|
||||||
<form
|
</button>
|
||||||
id="import-legacy-package-modal-form"
|
</form>
|
||||||
accept-charset="UTF-8"
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
enctype="multipart/form-data"
|
<p class="mb-4">
|
||||||
>
|
Create a Package based on the JSON Export of the legacy system.
|
||||||
{% csrf_token %}
|
</p>
|
||||||
<p>
|
<form
|
||||||
<label class="btn btn-primary" for="legacyJsonFile">
|
id="import-legacy-package-modal-form"
|
||||||
<input
|
accept-charset="UTF-8"
|
||||||
id="legacyJsonFile"
|
method="post"
|
||||||
name="file"
|
enctype="multipart/form-data"
|
||||||
type="file"
|
>
|
||||||
style="display:none;"
|
{% csrf_token %}
|
||||||
onchange="$('#upload-legacy-file-info').html(this.files[0].name)"
|
<div class="form-control">
|
||||||
/>
|
<label class="label">
|
||||||
Choose JSON File
|
<span class="label-text">Legacy JSON File</span>
|
||||||
</label>
|
</label>
|
||||||
<span class="label label-info" id="upload-legacy-file-info"></span>
|
<input
|
||||||
<input
|
type="file"
|
||||||
type="hidden"
|
id="legacyJsonFile"
|
||||||
value="import-legacy-package-json"
|
name="file"
|
||||||
name="hidden"
|
class="file-input file-input-bordered w-full"
|
||||||
readonly=""
|
accept=".json"
|
||||||
/>
|
required
|
||||||
</p>
|
/>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
<input
|
||||||
<div class="modal-footer">
|
type="hidden"
|
||||||
<a
|
value="import-legacy-package-json"
|
||||||
id="import-legacy-package-modal-form-submit"
|
name="hidden"
|
||||||
class="btn btn-primary"
|
readonly
|
||||||
href="#"
|
/>
|
||||||
>Submit</a
|
</form>
|
||||||
>
|
</div>
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
|
||||||
Cancel
|
<!-- Footer -->
|
||||||
</button>
|
<div class="modal-action">
|
||||||
</div>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('import-legacy-package-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Importing...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#import-legacy-package-modal-form-submit").on("click", function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#import-legacy-package-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,70 +1,83 @@
|
|||||||
<div
|
<dialog
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
|
||||||
id="import_package_modal"
|
id="import_package_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="import_package_modal"
|
x-data="modalForm()"
|
||||||
aria-hidden="true"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog">
|
<div class="modal-box">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">Import Package</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
<span class="sr-only">Close</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
<h4 class="modal-title">Import Package</h4>
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-body">
|
>
|
||||||
<p>Create a Package based on a JSON Export.</p>
|
✕
|
||||||
<form
|
</button>
|
||||||
id="import-package-modal-form"
|
</form>
|
||||||
accept-charset="UTF-8"
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
enctype="multipart/form-data"
|
<p class="mb-4">Create a Package based on a JSON Export.</p>
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="import-package-modal-form"
|
||||||
<p>
|
accept-charset="UTF-8"
|
||||||
<label class="btn btn-primary" for="jsonFile">
|
method="post"
|
||||||
<input
|
enctype="multipart/form-data"
|
||||||
id="jsonFile"
|
>
|
||||||
name="file"
|
{% csrf_token %}
|
||||||
type="file"
|
<div class="form-control">
|
||||||
style="display:none;"
|
<label class="label">
|
||||||
onchange="$('#upload-file-info').html(this.files[0].name)"
|
<span class="label-text">JSON File</span>
|
||||||
/>
|
</label>
|
||||||
Choose JSON File
|
<input
|
||||||
</label>
|
type="file"
|
||||||
<span class="label label-info" id="upload-file-info"></span>
|
id="jsonFile"
|
||||||
<input
|
name="file"
|
||||||
type="hidden"
|
class="file-input file-input-bordered w-full"
|
||||||
value="import-package-json"
|
accept=".json"
|
||||||
name="hidden"
|
required
|
||||||
readonly=""
|
/>
|
||||||
/>
|
</div>
|
||||||
</p>
|
<input
|
||||||
</form>
|
type="hidden"
|
||||||
</div>
|
value="import-package-json"
|
||||||
<div class="modal-footer">
|
name="hidden"
|
||||||
<a
|
readonly
|
||||||
id="import-package-modal-form-submit"
|
/>
|
||||||
class="btn btn-primary"
|
</form>
|
||||||
href="#"
|
</div>
|
||||||
>Submit</a
|
|
||||||
>
|
<!-- Footer -->
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
<div class="modal-action">
|
||||||
Cancel
|
<button
|
||||||
</button>
|
type="button"
|
||||||
</div>
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('import-package-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Importing...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#import-package-modal-form-submit").on("click", function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#import-package-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,119 +1,137 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div
|
|
||||||
class="modal fade bs-modal-lg"
|
<dialog
|
||||||
id="new_compound_modal"
|
id="new_compound_modal"
|
||||||
tabindex="-1"
|
class="modal"
|
||||||
aria-labelledby="new_compound_modal"
|
x-data="modalForm()"
|
||||||
aria-modal="true"
|
@close="reset()"
|
||||||
role="dialog"
|
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-3xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">Create a new Compound</h3>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Close button (X) -->
|
||||||
class="close"
|
<form method="dialog">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
aria-label="Close"
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
</button>
|
✕
|
||||||
<h4 class="modal-title">Create a new Compound</h4>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-body">
|
|
||||||
<form
|
<!-- Body -->
|
||||||
id="new_compound_modal_form"
|
<div class="py-4">
|
||||||
accept-charset="UTF-8"
|
<form
|
||||||
action="{% url 'package compound list' meta.current_package.uuid %}"
|
id="new-compound-modal-form"
|
||||||
data-remote="true"
|
accept-charset="UTF-8"
|
||||||
method="post"
|
action="{% url 'package compound list' meta.current_package.uuid %}"
|
||||||
>
|
method="post"
|
||||||
{% csrf_token %}
|
>
|
||||||
<label for="compound-name">Name</label>
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="compound-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="compound-name"
|
id="compound-name"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="compound-name"
|
name="compound-name"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<label for="compound-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="compound-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="compound-description"
|
id="compound-description"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="compound-description"
|
name="compound-description"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
<label for="compound-smiles">SMILES</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="compound-smiles">
|
||||||
|
<span class="label-text">SMILES</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="compound-smiles"
|
name="compound-smiles"
|
||||||
placeholder="SMILES"
|
placeholder="SMILES"
|
||||||
id="compound-smiles"
|
id="compound-smiles"
|
||||||
/>
|
/>
|
||||||
<p></p>
|
</div>
|
||||||
<div>
|
|
||||||
<iframe
|
<div class="mb-3">
|
||||||
id="new_compound_ketcher"
|
<iframe
|
||||||
src="{% static '/js/ketcher2/ketcher.html' %}"
|
id="new_compound_ketcher"
|
||||||
width="100%"
|
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
height="510"
|
width="100%"
|
||||||
></iframe>
|
height="510"
|
||||||
</div>
|
></iframe>
|
||||||
<p></p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
<!-- Footer -->
|
||||||
type="button"
|
<div class="modal-action">
|
||||||
class="btn btn-secondary pull-left"
|
<button
|
||||||
data-dismiss="modal"
|
type="button"
|
||||||
>
|
class="btn"
|
||||||
Close
|
onclick="this.closest('dialog').close()"
|
||||||
</button>
|
:disabled="isSubmitting"
|
||||||
<button
|
>
|
||||||
type="button"
|
Close
|
||||||
class="btn btn-primary"
|
</button>
|
||||||
id="new_compound_modal_form_submit"
|
<button
|
||||||
>
|
type="button"
|
||||||
Submit
|
class="btn btn-primary"
|
||||||
</button>
|
@click="submit('new-compound-modal-form')"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function newCompoundModalketcherToNewCompoundModalTextInput() {
|
|
||||||
$("#compound-smiles").val(this.ketcher.getSmiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
<!-- Backdrop -->
|
||||||
$("#new_compound_ketcher").on("load", function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document
|
||||||
|
.getElementById("new_compound_ketcher")
|
||||||
|
.addEventListener("load", function () {
|
||||||
|
const iframe = this;
|
||||||
const checkKetcherReady = () => {
|
const checkKetcherReady = () => {
|
||||||
win = this.contentWindow;
|
const win = iframe.contentWindow;
|
||||||
if (win.ketcher && "editor" in win.ketcher) {
|
if (win.ketcher && "editor" in 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: newCompoundModalketcherToNewCompoundModalTextInput,
|
f: function () {
|
||||||
|
document.getElementById("compound-smiles").value =
|
||||||
|
this.ketcher.getSmiles();
|
||||||
|
},
|
||||||
ketcher: win.ketcher,
|
ketcher: win.ketcher,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkKetcherReady, 100);
|
setTimeout(checkKetcherReady, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkKetcherReady();
|
checkKetcherReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$("#new_compound_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).prop("disabled", true);
|
|
||||||
|
|
||||||
// submit form
|
|
||||||
$("#new_compound_modal_form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,70 +1,96 @@
|
|||||||
<div
|
{% load static %}
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
<dialog
|
||||||
id="new_group_modal"
|
id="new_group_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="new_group_modal"
|
x-data="modalForm()"
|
||||||
aria-hidden="true"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog">
|
<div class="modal-box">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="font-bold text-lg">New Group</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
<span class="sr-only">Close</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
<h4 class="modal-title">New Group</h4>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-body">
|
>
|
||||||
<p>
|
✕
|
||||||
Create new Group. You can assign users to the group once it is
|
</button>
|
||||||
created. Description can be changed after creation.
|
</form>
|
||||||
</p>
|
|
||||||
<form
|
<!-- Body -->
|
||||||
id="new_group_modal_form"
|
<div class="py-4">
|
||||||
accept-charset="UTF-8"
|
<p class="mb-4">
|
||||||
action="{{ SERVER_BASE }}/group"
|
Create new Group. You can assign users to the group once it is created.
|
||||||
data-remote="true"
|
Description can be changed after creation.
|
||||||
method="post"
|
</p>
|
||||||
>
|
|
||||||
{% csrf_token %}
|
<form
|
||||||
<p>
|
id="new-group-modal-form"
|
||||||
<label for="name">Name</label>
|
accept-charset="UTF-8"
|
||||||
<input
|
action="{{ SERVER_BASE }}/group"
|
||||||
id="name"
|
method="post"
|
||||||
type="text"
|
>
|
||||||
name="group-name"
|
{% csrf_token %}
|
||||||
class="form-control"
|
|
||||||
placeholder="Name"
|
<div class="form-control mb-3">
|
||||||
/>
|
<label class="label" for="group-name">
|
||||||
</p>
|
<span class="label-text">Name</span>
|
||||||
<p>
|
</label>
|
||||||
<label for="description">Description</label>
|
<input
|
||||||
<input
|
id="group-name"
|
||||||
id="description"
|
class="input input-bordered w-full"
|
||||||
type="text"
|
name="group-name"
|
||||||
class="form-control"
|
placeholder="Name"
|
||||||
placeholder="Description..."
|
required
|
||||||
name="group-description"
|
/>
|
||||||
/>
|
</div>
|
||||||
</p>
|
|
||||||
</form>
|
<div class="form-control mb-3">
|
||||||
</div>
|
<label class="label" for="group-description">
|
||||||
<div class="modal-footer">
|
<span class="label-text">Description</span>
|
||||||
<a id="new_group_modal_form_submit" class="btn btn-primary" href="#"
|
</label>
|
||||||
>Submit</a
|
<input
|
||||||
>
|
id="group-description"
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
type="text"
|
||||||
Cancel
|
class="input input-bordered w-full"
|
||||||
</button>
|
placeholder="Description..."
|
||||||
</div>
|
name="group-description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('new-group-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#new_group_modal_form_submit").on("click", function () {
|
<button :disabled="isSubmitting">close</button>
|
||||||
$("#new_group_modal_form").submit();
|
</form>
|
||||||
});
|
</dialog>
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,30 +1,66 @@
|
|||||||
<div
|
<dialog
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
|
||||||
id="new_model_modal"
|
id="new_model_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="new_model_modal"
|
x-data="{
|
||||||
aria-hidden="true"
|
isSubmitting: false,
|
||||||
|
modelType: '',
|
||||||
|
buildAppDomain: false,
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.modelType = '';
|
||||||
|
this.buildAppDomain = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
get showMlrr() {
|
||||||
|
return this.modelType === 'mlrr';
|
||||||
|
},
|
||||||
|
|
||||||
|
get showRbrr() {
|
||||||
|
return this.modelType === 'rbrr';
|
||||||
|
},
|
||||||
|
|
||||||
|
get showEnviformer() {
|
||||||
|
return this.modelType === 'enviformer';
|
||||||
|
},
|
||||||
|
|
||||||
|
submit(formId) {
|
||||||
|
const form = document.getElementById(formId);
|
||||||
|
if (form && form.checkValidity()) {
|
||||||
|
this.isSubmitting = true;
|
||||||
|
form.submit();
|
||||||
|
} else if (form) {
|
||||||
|
form.reportValidity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-3xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">New Model</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
<span class="sr-only">Close</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
<h4 class="modal-title">New Model</h4>
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-body">
|
>
|
||||||
<form
|
✕
|
||||||
id="new_model_form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action="{{ meta.current_package.url }}/model"
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="new_model_form"
|
||||||
<div class="jumbotron">
|
accept-charset="UTF-8"
|
||||||
|
action="{{ meta.current_package.url }}/model"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<span>
|
||||||
Create a new Model to limit the number of degradation products in
|
Create a new Model to limit the number of degradation products in
|
||||||
the prediction. You just need to set a name and the packages you
|
the 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
|
want the object to be based on. There are multiple types of models
|
||||||
@ -32,239 +68,270 @@
|
|||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://wiki.envipath.org/index.php/relative-reasoning"
|
href="https://wiki.envipath.org/index.php/relative-reasoning"
|
||||||
role="button"
|
class="link"
|
||||||
>wiki >></a
|
>wiki >></a
|
||||||
>
|
>
|
||||||
</div>
|
</span>
|
||||||
<!-- Name -->
|
</div>
|
||||||
<label for="model-name">Name</label>
|
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="model-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="model-name"
|
id="model-name"
|
||||||
name="model-name"
|
name="model-name"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<label for="model-description">Description</label>
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="model-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="model-description"
|
id="model-description"
|
||||||
name="model-description"
|
name="model-description"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Model Type -->
|
<!-- Model Type -->
|
||||||
<label for="model-type">Model Type</label>
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="model-type">
|
||||||
|
<span class="label-text">Model Type</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="model-type"
|
id="model-type"
|
||||||
name="model-type"
|
name="model-type"
|
||||||
class="form-control"
|
class="select select-bordered w-full"
|
||||||
data-width="100%"
|
x-model="modelType"
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<option disabled selected>Select Model Type</option>
|
<option value="" disabled selected>Select Model Type</option>
|
||||||
{% for k, v in model_types.items %}
|
{% for k, v in model_types.items %}
|
||||||
<option value="{{ v }}">{{ k }}</option>
|
<option value="{{ v }}">{{ k }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Rule Packages -->
|
<!-- Rule Packages (MLRR, RBRR) -->
|
||||||
<div id="rule-packages" class="ep-model-param mlrr rbrr">
|
<div class="form-control mb-3" x-show="showMlrr || showRbrr" x-cloak>
|
||||||
<label for="model-rule-packages">Rule Packages</label>
|
<label class="label" for="model-rule-packages">
|
||||||
<select
|
<span class="label-text">Rule Packages</span>
|
||||||
id="model-rule-packages"
|
</label>
|
||||||
name="model-rule-packages"
|
<select
|
||||||
data-actions-box="true"
|
id="model-rule-packages"
|
||||||
class="form-control"
|
name="model-rule-packages"
|
||||||
multiple
|
class="select select-bordered w-full h-32"
|
||||||
data-width="100%"
|
multiple
|
||||||
>
|
>
|
||||||
<option disabled>Reviewed Packages</option>
|
<optgroup label="Reviewed Packages">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
<option disabled>Unreviewed Packages</option>
|
<optgroup label="Unreviewed Packages">
|
||||||
{% 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|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</optgroup>
|
||||||
</div>
|
</select>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Data Packages -->
|
<!-- Data Packages (MLRR, RBRR, Enviformer) -->
|
||||||
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
|
<div
|
||||||
<label for="model-data-packages">Data Packages</label>
|
class="form-control mb-3"
|
||||||
<select
|
x-show="showMlrr || showRbrr || showEnviformer"
|
||||||
id="model-data-packages"
|
x-cloak
|
||||||
name="model-data-packages"
|
>
|
||||||
data-actions-box="true"
|
<label class="label" for="model-data-packages">
|
||||||
class="form-control"
|
<span class="label-text">Data Packages</span>
|
||||||
multiple
|
</label>
|
||||||
data-width="100%"
|
<select
|
||||||
>
|
id="model-data-packages"
|
||||||
<option disabled>Reviewed Packages</option>
|
name="model-data-packages"
|
||||||
|
class="select select-bordered w-full h-32"
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<optgroup label="Reviewed Packages">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
<option disabled>Unreviewed Packages</option>
|
<optgroup label="Unreviewed Packages">
|
||||||
{% 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|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</optgroup>
|
||||||
</div>
|
</select>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Fingerprinter -->
|
<!-- Fingerprinter (MLRR) -->
|
||||||
<div id="fingerprinter" class="ep-model-param mlrr">
|
<div class="form-control mb-3" x-show="showMlrr" x-cloak>
|
||||||
<label for="model-fingerprinter">Fingerprinter</label>
|
<label class="label" for="model-fingerprinter">
|
||||||
<select
|
<span class="label-text">Fingerprinter</span>
|
||||||
id="model-fingerprinter"
|
</label>
|
||||||
name="model-fingerprinter"
|
<select
|
||||||
data-actions-box="true"
|
id="model-fingerprinter"
|
||||||
class="form-control"
|
name="model-fingerprinter"
|
||||||
multiple
|
class="select select-bordered w-full h-32"
|
||||||
data-width="100%"
|
multiple
|
||||||
>
|
>
|
||||||
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
<option value="MACCS" selected>MACCS Fingerprinter</option>
|
||||||
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
|
||||||
<option disabled selected>
|
<optgroup label="Additional Fingerprinter / Descriptor">
|
||||||
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 %}
|
||||||
{% endif %}
|
</optgroup>
|
||||||
</select>
|
{% endif %}
|
||||||
</div>
|
</select>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Threshold -->
|
<!-- Threshold (MLRR, Enviformer) -->
|
||||||
<div id="threshold" class="ep-model-param mlrr enviformer">
|
<div
|
||||||
<label for="model-threshold">Threshold</label>
|
class="form-control mb-3"
|
||||||
<input
|
x-show="showMlrr || showEnviformer"
|
||||||
type="number"
|
x-cloak
|
||||||
min="0"
|
>
|
||||||
max="1"
|
<label class="label" for="model-threshold">
|
||||||
step="0.05"
|
<span class="label-text">Threshold</span>
|
||||||
value="0.5"
|
</label>
|
||||||
id="model-threshold"
|
<input
|
||||||
name="model-threshold"
|
type="number"
|
||||||
class="form-control"
|
min="0"
|
||||||
/>
|
max="1"
|
||||||
</div>
|
step="0.05"
|
||||||
|
value="0.5"
|
||||||
|
id="model-threshold"
|
||||||
|
name="model-threshold"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="appdomain" class="ep-model-param mlrr">
|
<!-- Applicability Domain (MLRR) -->
|
||||||
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
|
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
|
||||||
<!-- Build AD? -->
|
<div x-show="showMlrr" x-cloak>
|
||||||
<div class="checkbox">
|
<div class="form-control mb-3">
|
||||||
<label>
|
<label class="label cursor-pointer justify-start gap-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="build-app-domain"
|
id="build-app-domain"
|
||||||
name="build-app-domain"
|
name="build-app-domain"
|
||||||
/>Also build an Applicability Domain?
|
class="checkbox"
|
||||||
|
x-model="buildAppDomain"
|
||||||
|
/>
|
||||||
|
<span class="label-text"
|
||||||
|
>Also build an Applicability Domain?</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="buildAppDomain" x-cloak class="ml-4 space-y-3">
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label" for="num-neighbors">
|
||||||
|
<span class="label-text">Number of Neighbors</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
|
||||||
<div id="ad-params" style="display:none">
|
|
||||||
<!-- Num Neighbors -->
|
|
||||||
<label for="num-neighbors">Number of Neighbors</label>
|
|
||||||
<input
|
<input
|
||||||
id="num-neighbors"
|
id="num-neighbors"
|
||||||
name="num-neighbors"
|
name="num-neighbors"
|
||||||
type="number"
|
type="number"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
value="5"
|
value="5"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max="10"
|
||||||
/>
|
/>
|
||||||
<!-- Local Compatibility -->
|
</div>
|
||||||
<label for="local-compatibility-threshold"
|
|
||||||
>Local Compatibility Threshold</label
|
<div class="form-control">
|
||||||
>
|
<label class="label" for="local-compatibility-threshold">
|
||||||
|
<span class="label-text">Local Compatibility Threshold</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="local-compatibility-threshold"
|
id="local-compatibility-threshold"
|
||||||
name="local-compatibility-threshold"
|
name="local-compatibility-threshold"
|
||||||
type="number"
|
type="number"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
value="0.5"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
max="1"
|
|
||||||
/>
|
|
||||||
<!-- Reliability -->
|
|
||||||
<label for="reliability-threshold">Reliability Threshold</label>
|
|
||||||
<input
|
|
||||||
id="reliability-threshold"
|
|
||||||
name="reliability-threshold"
|
|
||||||
type="number"
|
|
||||||
class="form-control"
|
|
||||||
value="0.5"
|
value="0.5"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
min="0"
|
min="0"
|
||||||
max="1"
|
max="1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label" for="reliability-threshold">
|
||||||
|
<span class="label-text">Reliability Threshold</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="reliability-threshold"
|
||||||
|
name="reliability-threshold"
|
||||||
|
type="number"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
value="0.5"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
{% endif %}
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<a id="new_model_modal_form_submit" class="btn btn-primary" href="#"
|
|
||||||
>Submit</a
|
<!-- Footer -->
|
||||||
>
|
<div class="modal-action">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
<button
|
||||||
Cancel
|
type="button"
|
||||||
</button>
|
class="btn"
|
||||||
</div>
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('new_model_form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
// Built in Model Types
|
<button :disabled="isSubmitting">close</button>
|
||||||
var nativeModelTypes = ["mlrr", "rbrr", "enviformer"];
|
</form>
|
||||||
|
</dialog>
|
||||||
// Initially hide all "specific" forms
|
|
||||||
$(".ep-model-param").each(function () {
|
|
||||||
$(this).hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#model-type").selectpicker();
|
|
||||||
$("#model-fingerprinter").selectpicker();
|
|
||||||
$("#model-rule-packages").selectpicker();
|
|
||||||
$("#model-data-packages").selectpicker();
|
|
||||||
|
|
||||||
$("#build-app-domain").change(function () {
|
|
||||||
if ($(this).is(":checked")) {
|
|
||||||
$("#ad-params").show();
|
|
||||||
} else {
|
|
||||||
$("#ad-params").hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// On change hide all and show only selected
|
|
||||||
$("#model-type").change(function () {
|
|
||||||
$(".ep-model-param").hide();
|
|
||||||
var modelType = $("#model-type").val();
|
|
||||||
if (nativeModelTypes.indexOf(modelType) !== -1) {
|
|
||||||
$("." + modelType).show();
|
|
||||||
} else {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#new_model_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$("#new_model_form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,68 +1,93 @@
|
|||||||
<div
|
{% load static %}
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
<dialog
|
||||||
id="new_package_modal"
|
id="new_package_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="new_package_modal"
|
x-data="modalForm()"
|
||||||
aria-hidden="true"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog">
|
<div class="modal-box">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="font-bold text-lg">New Package</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
<span class="sr-only">Close</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
<h4 class="modal-title">New Package</h4>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-body">
|
>
|
||||||
<p>Create new package. Description can be changed later.</p>
|
✕
|
||||||
<form
|
</button>
|
||||||
id="new_package_modal_form"
|
</form>
|
||||||
accept-charset="UTF-8"
|
|
||||||
action=""
|
<!-- Body -->
|
||||||
data-remote="true"
|
<div class="py-4">
|
||||||
method="post"
|
<p class="mb-4">Create new package. Description can be changed later.</p>
|
||||||
>
|
|
||||||
{% csrf_token %}
|
<form
|
||||||
<p>
|
id="new-package-modal-form"
|
||||||
<label for="name">Name</label>
|
accept-charset="UTF-8"
|
||||||
<input
|
action=""
|
||||||
id="name"
|
method="post"
|
||||||
class="form-control"
|
>
|
||||||
name="package-name"
|
{% csrf_token %}
|
||||||
placeholder="Name"
|
|
||||||
/>
|
<div class="form-control mb-3">
|
||||||
</p>
|
<label class="label" for="package-name">
|
||||||
<p>
|
<span class="label-text">Name</span>
|
||||||
<label for="description">Description</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="description"
|
id="package-name"
|
||||||
type="text"
|
class="input input-bordered w-full"
|
||||||
rows="3"
|
name="package-name"
|
||||||
class="form-control"
|
placeholder="Name"
|
||||||
placeholder="Description..."
|
required
|
||||||
name="package-description"
|
/>
|
||||||
/>
|
</div>
|
||||||
</p>
|
|
||||||
</form>
|
<div class="form-control mb-3">
|
||||||
</div>
|
<label class="label" for="package-description">
|
||||||
<div class="modal-footer">
|
<span class="label-text">Description</span>
|
||||||
<a id="new_package_modal_form_submit" class="btn btn-primary" href="#"
|
</label>
|
||||||
>Submit</a
|
<input
|
||||||
>
|
id="package-description"
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
type="text"
|
||||||
Cancel
|
class="input input-bordered w-full"
|
||||||
</button>
|
placeholder="Description..."
|
||||||
</div>
|
name="package-description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('new-package-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#new_package_modal_form_submit").on("click", function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#new_package_modal_form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,376 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
<div
|
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
|
||||||
id="new_pathway_modal"
|
|
||||||
role="dialog"
|
|
||||||
aria-labelledby="new_pathway_modal"
|
|
||||||
aria-hidden="true"
|
|
||||||
style="overflow-y: auto;"
|
|
||||||
>
|
|
||||||
<!-- FIXME: make width dynamic-->
|
|
||||||
<div class="modal-dialog" id="new_pathway_modal_dialog" style="width:900px">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
<span class="sr-only">Close</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="js-title-step"></h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body hide" data-step="1" data-title="New Pathway">
|
|
||||||
<div class="jumbotron">
|
|
||||||
Create a new pathway by entering the root compound and a name. Then
|
|
||||||
select if you want to use the prediction engine to generate a
|
|
||||||
predicted pathway or create an empty pathway that you fill in by
|
|
||||||
yourself. If you choose to predict a pathway, you can modify the
|
|
||||||
settings for the prediction, or use the default settings and just
|
|
||||||
click Submit.
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
{% if current_user.name == 'anonymous' %}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
You are currently logged in as Anonymous. Please note: Pathways
|
|
||||||
entered or predicted as anonymous user will be deleted after 30
|
|
||||||
days. Please log in to save your results.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input
|
|
||||||
id="name"
|
|
||||||
class="form-control"
|
|
||||||
name="name"
|
|
||||||
placeholder="Name"
|
|
||||||
/>
|
|
||||||
<label for="description">Description</label>
|
|
||||||
<input
|
|
||||||
id="description"
|
|
||||||
class="form-control"
|
|
||||||
name="description"
|
|
||||||
placeholder="no description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="predict">Predict pathway or build yourself?</label>
|
|
||||||
<div class="radio" id="predict">
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="predict"
|
|
||||||
id="radioPredict"
|
|
||||||
value="predict"
|
|
||||||
checked
|
|
||||||
/>Predict pathway
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="predict"
|
|
||||||
id="radioIncremental"
|
|
||||||
value="incremental"
|
|
||||||
/>Incremental prediction
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="predict"
|
|
||||||
id="radioBuild"
|
|
||||||
value="build"
|
|
||||||
/>Build pathway
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label for="smilesinput">SMILES</label>
|
|
||||||
<table style="width: 100%">
|
|
||||||
<colgroup>
|
|
||||||
<col span="1" style="width: 90%;" />
|
|
||||||
<col span="1" style="width: 10%;" />
|
|
||||||
</colgroup>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
id="smilesinput"
|
|
||||||
class="form-control"
|
|
||||||
name="smilesinput"
|
|
||||||
placeholder="C1CCCCC1"
|
|
||||||
autocapitalize="none"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-default" id="render-button">
|
|
||||||
Render
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<p id="ketcher_container"></p>
|
|
||||||
<div>
|
|
||||||
<iframe
|
|
||||||
id="ifKetcher"
|
|
||||||
src="{% static '/js/ketcher/ketcher.html' %}"
|
|
||||||
width="850"
|
|
||||||
height="510"
|
|
||||||
></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="modal-body hide"
|
|
||||||
data-step="2"
|
|
||||||
data-title="New Pathway - Advanced Settings"
|
|
||||||
>
|
|
||||||
<div class="jumbotron">
|
|
||||||
Choose if you want to use an existing setting, or create a new one for
|
|
||||||
this pathway prediction. Then click Submit to use the specified
|
|
||||||
setting, or click next to set the parameters.
|
|
||||||
</div>
|
|
||||||
<div id="settings">
|
|
||||||
<div class="radio" id="settingRadio">
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="existing"
|
|
||||||
id="radioDefault"
|
|
||||||
value="exisiting"
|
|
||||||
checked
|
|
||||||
/>
|
|
||||||
Use Default
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="existing"
|
|
||||||
id="radioExists"
|
|
||||||
value="exisiting"
|
|
||||||
/>
|
|
||||||
Select Existing
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="existing"
|
|
||||||
id="radioNew"
|
|
||||||
value="temporary"
|
|
||||||
/>
|
|
||||||
Create New
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<select id="settingSelect" name="settingSelect" class="form-control">
|
|
||||||
{% for setting in available_settings %}
|
|
||||||
<option value="{{ setting.id }}">{{ setting.name|safe }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<p></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% with step_offset=1 %}
|
|
||||||
{% include "templates/modals/collections/new_setting_modal_body.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-default js-btn-step pull-left"
|
|
||||||
data-orientation="cancel"
|
|
||||||
onclick="reset()"
|
|
||||||
data-dismiss="modal"
|
|
||||||
></button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-default js-btn-step"
|
|
||||||
data-orientation="previous"
|
|
||||||
id="backbutton"
|
|
||||||
></button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-default js-btn-step"
|
|
||||||
data-orientation="next"
|
|
||||||
id="nextbutton"
|
|
||||||
></button>
|
|
||||||
<a id="modal-form-submit" class="btn btn-primary" href="#">Submit</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
s = new Setting(
|
|
||||||
"settingName",
|
|
||||||
"package_multi_select",
|
|
||||||
"modelSelect",
|
|
||||||
"cutoff",
|
|
||||||
"evalType",
|
|
||||||
"availableTS",
|
|
||||||
"forms",
|
|
||||||
"truncatorTable",
|
|
||||||
"summaryTable",
|
|
||||||
);
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
// hide all forms
|
|
||||||
$("#forms").children().hide();
|
|
||||||
|
|
||||||
$("#render-button").on("click", function () {
|
|
||||||
syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
|
|
||||||
});
|
|
||||||
|
|
||||||
// If theres a change in the in '#smilesinput' sync the value to ketcher
|
|
||||||
$("#smilesinput").on("input", function () {
|
|
||||||
syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
|
|
||||||
});
|
|
||||||
|
|
||||||
// If theres an update in ketcher sync it to textinput
|
|
||||||
setInterval(function () {
|
|
||||||
syncKetcherAndTextInput("ketcher", "ifKetcher", "smilesinput");
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
$("#smilesinput").on("blur", function () {
|
|
||||||
syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#smilesinput").on("keypress", function (event) {
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
syncKetcherAndTextInput("text", "ifKetcher", "smilesinput");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show forms depending on the selected TS
|
|
||||||
$("#availableTS").on("change", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var type = $(this).val();
|
|
||||||
// hide current content
|
|
||||||
$("#forms").children().hide();
|
|
||||||
|
|
||||||
if (type === "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#" + type + "_form").show();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#modelSelect").on("change", function () {
|
|
||||||
setCutoff = function (thresh) {
|
|
||||||
$("#cutoff").val(thresh);
|
|
||||||
};
|
|
||||||
|
|
||||||
var modelUri = $("#modelSelect :selected").val();
|
|
||||||
fillPRCurve(modelUri, setCutoff);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add a TS to the setting
|
|
||||||
$("#add-ts-button").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
s.addTruncator();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("input[type=radio][name=predict]").change(function () {
|
|
||||||
if (this.id == "radioBuild") {
|
|
||||||
$("#nextbutton").prop("disabled", true);
|
|
||||||
} else {
|
|
||||||
$("#nextbutton").prop("disabled", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("input[type=radio][name=existing]").change(function () {
|
|
||||||
if (this.id == "radioDefault" || this.id == "radioExists") {
|
|
||||||
if (this.id == "radioDefault") {
|
|
||||||
$("#settingSelect").prop("disabled", true);
|
|
||||||
} else {
|
|
||||||
$("#settingSelect").prop("disabled", false);
|
|
||||||
}
|
|
||||||
$("#nextbutton").prop("disabled", true);
|
|
||||||
} else {
|
|
||||||
// build...
|
|
||||||
$("#settingSelect").prop("disabled", true);
|
|
||||||
$("#nextbutton").prop("disabled", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var pwStep1 = function () {
|
|
||||||
console.log("pw step 1");
|
|
||||||
// Make "Next" to "Advanced"
|
|
||||||
$("#nextbutton").val("Advanced");
|
|
||||||
};
|
|
||||||
|
|
||||||
var pwStep2 = function () {
|
|
||||||
console.log("pw step 2");
|
|
||||||
// Make "Advanced" to "Next"
|
|
||||||
$("#nextbutton").val("Next");
|
|
||||||
// As "Use default is preselected" disable "Next" button
|
|
||||||
$("#nextbutton").prop("disabled", true);
|
|
||||||
// Disable setting dropdown as long as the correspndonding radio isnt checked
|
|
||||||
$("#settingSelect").prop("disabled", true);
|
|
||||||
// Show submit button
|
|
||||||
$("#modal-form-submit").show();
|
|
||||||
};
|
|
||||||
|
|
||||||
var settingStep1 = function () {
|
|
||||||
// First step sets name and packages
|
|
||||||
s.extractName();
|
|
||||||
s.extractSelectedPackages();
|
|
||||||
};
|
|
||||||
|
|
||||||
var settingStep2 = function () {
|
|
||||||
// Seconds step gathers relative reasoning params
|
|
||||||
s.extractRelativeReasoning();
|
|
||||||
s.extractCutoff();
|
|
||||||
s.extractEvaluationType();
|
|
||||||
};
|
|
||||||
|
|
||||||
var settingStep3 = function () {
|
|
||||||
s.updateTable();
|
|
||||||
s.updateSummaryTable();
|
|
||||||
// hide duplicate submit...
|
|
||||||
$("#nextbutton").hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
var postPathway = function () {
|
|
||||||
console.log("Complete!");
|
|
||||||
console.log(s.tsParams);
|
|
||||||
console.log("Getting SMILES");
|
|
||||||
};
|
|
||||||
|
|
||||||
function dummy() {
|
|
||||||
console.log("dummy");
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#new_pathway_modal").modalSteps({
|
|
||||||
btnCancelHtml: "Cancel",
|
|
||||||
btnPreviousHtml: "Back",
|
|
||||||
btnNextHtml: "Next",
|
|
||||||
btnLastStepHtml: "Submit",
|
|
||||||
disableNextButton: false,
|
|
||||||
completeCallback: postPathway,
|
|
||||||
callbacks: {
|
|
||||||
1: pwStep1,
|
|
||||||
2: pwStep2,
|
|
||||||
3: dummy,
|
|
||||||
4: settingStep1,
|
|
||||||
5: settingStep2,
|
|
||||||
6: settingStep3,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#modal-form-submit").on("click", function () {
|
|
||||||
e.preventDefault();
|
|
||||||
postPathway();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,185 +1,260 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<div id="new_prediction_setting_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="new_prediction_setting_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="{
|
||||||
<h5 class="modal-title">Create a Prediction Setting</h5>
|
isSubmitting: false,
|
||||||
<button
|
tpMethod: '',
|
||||||
type="button"
|
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>
|
|
||||||
To create a Prediction Setting fill the form below and click "Create"
|
|
||||||
</p>
|
|
||||||
<form
|
|
||||||
id="new-prediction-setting-modal-form"
|
|
||||||
accept-charset="UTF-8"
|
|
||||||
action=""
|
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<label for="prediction-setting-name">Name</label>
|
reset() {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.tpMethod = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
const form = document.getElementById('new-prediction-setting-modal-form');
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSubmitting = true;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/setting', {
|
||||||
|
method: 'POST',
|
||||||
|
body: new URLSearchParams(formData)
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating setting:', error);
|
||||||
|
} finally {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset()"
|
||||||
|
>
|
||||||
|
<div class="modal-box max-w-2xl">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="text-lg font-bold">Create a Prediction Setting</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="mb-4">
|
||||||
|
To create a Prediction Setting fill the form below and click "Create"
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
id="new-prediction-setting-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action=""
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="prediction-setting-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="prediction-setting-name"
|
id="prediction-setting-name"
|
||||||
name="prediction-setting-name"
|
name="prediction-setting-name"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<label for="prediction-setting-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="prediction-setting-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="prediction-setting-description"
|
id="prediction-setting-description"
|
||||||
name="prediction-setting-description"
|
name="prediction-setting-description"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="prediction-setting-max-nodes">Max #Nodes</label>
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="prediction-setting-max-nodes">
|
||||||
|
<span class="label-text">Max #Nodes</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="prediction-setting-max-nodes"
|
id="prediction-setting-max-nodes"
|
||||||
type="number"
|
type="number"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="prediction-setting-max-nodes"
|
name="prediction-setting-max-nodes"
|
||||||
value="30"
|
value="30"
|
||||||
min="1"
|
min="1"
|
||||||
max="50"
|
max="50"
|
||||||
step="1"
|
step="1"
|
||||||
/>
|
/>
|
||||||
<label for="prediction-setting-max-depth">Max Depth</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="prediction-setting-max-depth">
|
||||||
|
<span class="label-text">Max Depth</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="prediction-setting-max-depth"
|
id="prediction-setting-max-depth"
|
||||||
type="number"
|
type="number"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="prediction-setting-max-depth"
|
name="prediction-setting-max-depth"
|
||||||
value="5"
|
value="5"
|
||||||
min="1"
|
min="1"
|
||||||
max="8"
|
max="8"
|
||||||
step="1"
|
step="1"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="tp-generation-method">TP Generation Method</label>
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="tp-generation-method">
|
||||||
|
<span class="label-text">TP Generation Method</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="tp-generation-method"
|
id="tp-generation-method"
|
||||||
name="tp-generation-method"
|
name="tp-generation-method"
|
||||||
class="form-control"
|
class="select select-bordered w-full"
|
||||||
data-width="100%"
|
x-model="tpMethod"
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<option disabled selected>Select how TPs are generated</option>
|
<option value="" disabled selected>
|
||||||
|
Select how TPs are generated
|
||||||
|
</option>
|
||||||
<option value="rule-based-prediction-setting">Rule Based</option>
|
<option value="rule-based-prediction-setting">Rule Based</option>
|
||||||
<option value="model-based-prediction-setting">Model Based</option>
|
<option value="model-based-prediction-setting">Model Based</option>
|
||||||
</select>
|
</select>
|
||||||
<div id="rule-based-prediction-setting-specific-form">
|
</div>
|
||||||
<!-- Rule Packages -->
|
|
||||||
<label>Rule Packages</label><br />
|
<!-- Rule Based Settings -->
|
||||||
|
<div x-show="tpMethod === 'rule-based-prediction-setting'" x-cloak>
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Rule Packages</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="rule-based-prediction-setting-packages"
|
id="rule-based-prediction-setting-packages"
|
||||||
name="rule-based-prediction-setting-packages"
|
name="rule-based-prediction-setting-packages"
|
||||||
data-actions-box="true"
|
class="select select-bordered w-full h-32"
|
||||||
class="form-control"
|
|
||||||
multiple
|
multiple
|
||||||
data-width="100%"
|
|
||||||
>
|
>
|
||||||
<option disabled>Reviewed Packages</option>
|
<optgroup label="Reviewed Packages">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
<option disabled>Unreviewed Packages</option>
|
<optgroup label="Unreviewed Packages">
|
||||||
{% 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|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt"
|
||||||
|
>Hold Ctrl/Cmd to select multiple</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="model-based-prediction-setting-specific-form">
|
</div>
|
||||||
<label>Select Model</label><br />
|
|
||||||
|
<!-- Model Based Settings -->
|
||||||
|
<div x-show="tpMethod === 'model-based-prediction-setting'" x-cloak>
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="model-based-prediction-setting-model">
|
||||||
|
<span class="label-text">Select Model</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="model-based-prediction-setting-model"
|
id="model-based-prediction-setting-model"
|
||||||
name="model-based-prediction-setting-model"
|
name="model-based-prediction-setting-model"
|
||||||
class="form-control"
|
class="select select-bordered w-full"
|
||||||
data-width="100%"
|
|
||||||
>
|
>
|
||||||
<option disabled selected>Select the model</option>
|
<option value="" disabled selected>Select the model</option>
|
||||||
{% for m in models %}
|
{% for m in models %}
|
||||||
<option value="{{ m.url }}">{{ m.name|safe }}</option>
|
<option value="{{ m.url }}">{{ m.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<label for="model-based-prediction-setting-threshold"
|
</div>
|
||||||
>Threshold</label
|
|
||||||
>
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="model-based-prediction-setting-threshold">
|
||||||
|
<span class="label-text">Threshold</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="model-based-prediction-setting-threshold"
|
id="model-based-prediction-setting-threshold"
|
||||||
name="model-based-prediction-setting-threshold"
|
name="model-based-prediction-setting-threshold"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
placeholder="0.25"
|
placeholder="0.25"
|
||||||
type="number"
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.05"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<div class="form-control">
|
||||||
class="form-check-input"
|
<label class="label cursor-pointer justify-start gap-3">
|
||||||
type="checkbox"
|
<input
|
||||||
value="on"
|
type="checkbox"
|
||||||
id="prediction-setting-new-default"
|
class="checkbox"
|
||||||
name="prediction-setting-new-default"
|
value="on"
|
||||||
/>
|
id="prediction-setting-new-default"
|
||||||
<label class="form-check-label" for="prediction-setting-new-default"
|
name="prediction-setting-new-default"
|
||||||
>Set this setting as new default</label
|
/>
|
||||||
>
|
<span class="label-text">Set this setting as new default</span>
|
||||||
</form>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
</form>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
</div>
|
||||||
Close
|
|
||||||
</button>
|
<!-- Footer -->
|
||||||
<button
|
<div class="modal-action">
|
||||||
type="button"
|
<button
|
||||||
class="btn btn-primary"
|
type="button"
|
||||||
id="new-prediction-setting-modal-submit"
|
class="btn"
|
||||||
>
|
onclick="this.closest('dialog').close()"
|
||||||
Create
|
:disabled="isSubmitting"
|
||||||
</button>
|
>
|
||||||
</div>
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Create</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
// Initially hide all "specific" forms
|
|
||||||
$("div[id$='-specific-form']").each(function () {
|
|
||||||
$(this).hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#rule-based-prediction-setting-packages").selectpicker();
|
<!-- Backdrop -->
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
// On change hide all and show only selected
|
<button :disabled="isSubmitting">close</button>
|
||||||
$("#tp-generation-method").change(function () {
|
</form>
|
||||||
$("div[id$='-specific-form']").each(function () {
|
</dialog>
|
||||||
$(this).hide();
|
|
||||||
});
|
|
||||||
val = $("option:selected", this).val();
|
|
||||||
$("#" + val + "-specific-form").show();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#new-prediction-setting-modal-submit").click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
// $('#new-prediction-setting-modal-form').submit();
|
|
||||||
|
|
||||||
const formData = $("#new-prediction-setting-modal-form").serialize();
|
|
||||||
$.post("/setting", formData, function (response) {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,91 +1,105 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div
|
|
||||||
class="modal fade bs-modal-lg"
|
<dialog
|
||||||
id="new_reaction_modal"
|
id="new_reaction_modal"
|
||||||
tabindex="-1"
|
class="modal"
|
||||||
aria-labelledby="new_reaction_modal"
|
x-data="modalForm()"
|
||||||
aria-modal="true"
|
@close="reset()"
|
||||||
role="dialog"
|
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-3xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="font-bold text-lg">Create a new Reaction</h3>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Close button (X) -->
|
||||||
class="close"
|
<form method="dialog">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
aria-label="Close"
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
</button>
|
✕
|
||||||
<h4 class="modal-title">Create a new Reaction</h4>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-body">
|
|
||||||
<form
|
<!-- Body -->
|
||||||
id="new_reaction_modal_form"
|
<div class="py-4">
|
||||||
accept-charset="UTF-8"
|
<form
|
||||||
action="{% url 'package reaction list' meta.current_package.uuid %}"
|
id="new-reaction-modal-form"
|
||||||
data-remote="true"
|
accept-charset="UTF-8"
|
||||||
method="post"
|
action="{% url 'package reaction list' meta.current_package.uuid %}"
|
||||||
>
|
method="post"
|
||||||
{% csrf_token %}
|
>
|
||||||
<label for="reaction-name">Name</label>
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="reaction-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="reaction-name"
|
id="reaction-name"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="reaction-name"
|
name="reaction-name"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<label for="reaction-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="reaction-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="reaction-description"
|
id="reaction-description"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="reaction-description"
|
name="reaction-description"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
<p></p>
|
</div>
|
||||||
<div>
|
|
||||||
<iframe
|
<div class="mb-3">
|
||||||
id="new_reaction_ketcher"
|
<iframe
|
||||||
src="{% static '/js/ketcher2/ketcher.html' %}"
|
id="new_reaction_ketcher"
|
||||||
width="100%"
|
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
height="510"
|
width="100%"
|
||||||
></iframe>
|
height="510"
|
||||||
</div>
|
></iframe>
|
||||||
<input type="hidden" name="reaction-smirks" id="reaction-smirks" />
|
</div>
|
||||||
<p></p>
|
|
||||||
</form>
|
<input type="hidden" name="reaction-smirks" id="reaction-smirks" />
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Footer -->
|
||||||
class="btn btn-secondary pull-left"
|
<div class="modal-action">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
>
|
type="button"
|
||||||
Close
|
class="btn"
|
||||||
</button>
|
onclick="this.closest('dialog').close()"
|
||||||
<button
|
:disabled="isSubmitting"
|
||||||
type="button"
|
>
|
||||||
class="btn btn-primary"
|
Close
|
||||||
id="new_reaction_modal_form_submit"
|
</button>
|
||||||
>
|
<button
|
||||||
Submit
|
type="button"
|
||||||
</button>
|
class="btn btn-primary"
|
||||||
</div>
|
@click="
|
||||||
|
const k = getKetcher('new_reaction_ketcher');
|
||||||
|
document.getElementById('reaction-smirks').value = k.getSmiles();
|
||||||
|
submit('new-reaction-modal-form');
|
||||||
|
"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#new_reaction_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).prop("disabled", true);
|
|
||||||
|
|
||||||
k = getKetcher("new_reaction_ketcher");
|
<!-- Backdrop -->
|
||||||
$("#reaction-smirks").val(k.getSmiles());
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
// submit form
|
</form>
|
||||||
$("#new_reaction_modal_form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,120 +1,140 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div
|
|
||||||
class="modal fade bs-modal-lg"
|
<dialog
|
||||||
id="new_rule_modal"
|
id="new_rule_modal"
|
||||||
tabindex="-1"
|
class="modal"
|
||||||
aria-labelledby="new_rule_modal"
|
x-data="{
|
||||||
aria-modal="true"
|
...modalForm(),
|
||||||
role="dialog"
|
smirksVizHtml: '',
|
||||||
|
updateSmirksViz() {
|
||||||
|
const smirks = document.getElementById('rule-smirks').value;
|
||||||
|
if (!smirks) {
|
||||||
|
this.smirksVizHtml = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.src = '{% url 'depict' %}?is_query_smirks=true&smirks=' + encodeURIComponent(smirks);
|
||||||
|
img.style.width = '100%';
|
||||||
|
img.style.height = '100%';
|
||||||
|
img.style.objectFit = 'cover';
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
this.smirksVizHtml = img.outerHTML;
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
this.smirksVizHtml = `
|
||||||
|
<div class='alert alert-error' role='alert'>
|
||||||
|
<h4 class='alert-heading'>Could not render SMIRKS!</h4>
|
||||||
|
<p>Could not render SMIRKS - Have you entered a valid SMIRKS?</p>
|
||||||
|
</div>`;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset(); smirksVizHtml = ''"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-3xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="font-bold text-lg">Create a new Rule</h3>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Close button (X) -->
|
||||||
class="close"
|
<form method="dialog">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
aria-label="Close"
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
</button>
|
✕
|
||||||
<h4 class="modal-title">Create a new Rule</h4>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-body">
|
|
||||||
<form
|
<!-- Body -->
|
||||||
id="new_rule_modal_form"
|
<div class="py-4">
|
||||||
accept-charset="UTF-8"
|
<form
|
||||||
action="{% url 'package rule list' meta.current_package.uuid %}"
|
id="new-rule-modal-form"
|
||||||
data-remote="true"
|
accept-charset="UTF-8"
|
||||||
method="post"
|
action="{% url 'package rule list' meta.current_package.uuid %}"
|
||||||
>
|
method="post"
|
||||||
{% csrf_token %}
|
>
|
||||||
<label for="rule-name">Name</label>
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="rule-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="rule-name"
|
id="rule-name"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="rule-name"
|
name="rule-name"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<label for="rule-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="rule-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="rule-description"
|
id="rule-description"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="rule-description"
|
name="rule-description"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
<label for="rule-smirks">SMIRKS</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="rule-smirks">
|
||||||
|
<span class="label-text">SMIRKS</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="rule-smirks"
|
id="rule-smirks"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="rule-smirks"
|
name="rule-smirks"
|
||||||
placeholder="SMIRKS"
|
placeholder="SMIRKS"
|
||||||
|
@input="updateSmirksViz()"
|
||||||
/>
|
/>
|
||||||
<p></p>
|
</div>
|
||||||
<div id="rule-smirks-viz"></div>
|
|
||||||
<input
|
<div id="rule-smirks-viz" class="mb-3" x-html="smirksVizHtml"></div>
|
||||||
type="hidden"
|
|
||||||
name="rule-type"
|
<input
|
||||||
id="rule-type"
|
type="hidden"
|
||||||
value="SimpleAmbitRule"
|
name="rule-type"
|
||||||
/>
|
id="rule-type"
|
||||||
<p></p>
|
value="SimpleAmbitRule"
|
||||||
</form>
|
/>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Footer -->
|
||||||
class="btn btn-secondary pull-left"
|
<div class="modal-action">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
>
|
type="button"
|
||||||
Close
|
class="btn"
|
||||||
</button>
|
onclick="this.closest('dialog').close()"
|
||||||
<button
|
:disabled="isSubmitting"
|
||||||
type="button"
|
>
|
||||||
class="btn btn-primary"
|
Close
|
||||||
id="new_rule_modal_form_submit"
|
</button>
|
||||||
>
|
<button
|
||||||
Submit
|
type="button"
|
||||||
</button>
|
class="btn btn-primary"
|
||||||
</div>
|
@click="submit('new-rule-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#rule-smirks").on("input", function (e) {
|
|
||||||
$("#rule-smirks-viz").empty();
|
|
||||||
|
|
||||||
smirks = $("#rule-smirks").val();
|
<!-- Backdrop -->
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
const img = new Image();
|
<button :disabled="isSubmitting">close</button>
|
||||||
img.src =
|
</form>
|
||||||
"{% url 'depict' %}?is_query_smirks=true&smirks=" +
|
</dialog>
|
||||||
encodeURIComponent(smirks);
|
|
||||||
img.style.width = "100%";
|
|
||||||
img.style.height = "100%";
|
|
||||||
img.style.objectFit = "cover";
|
|
||||||
|
|
||||||
img.onload = function () {
|
|
||||||
$("#rule-smirks-viz").append(img);
|
|
||||||
};
|
|
||||||
|
|
||||||
img.onerror = function () {
|
|
||||||
error_tpl = `
|
|
||||||
<div class="alert alert-error" role="alert">
|
|
||||||
<h4 class="alert-heading">Could not render SMIRKS!</h4>
|
|
||||||
<p>Could not render SMIRKS - Have you entered a valid SMIRKS?</a>
|
|
||||||
</p>
|
|
||||||
</div>`;
|
|
||||||
$("#rule-smirks-viz").append(error_tpl);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#new_rule_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).prop("disabled", true);
|
|
||||||
// submit form
|
|
||||||
$("#new_rule_modal_form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,30 +1,45 @@
|
|||||||
<div
|
{% load static %}
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
<dialog
|
||||||
id="new_scenario_modal"
|
id="new_scenario_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="new_scenario_modal"
|
x-data="{
|
||||||
aria-hidden="true"
|
...modalForm(),
|
||||||
|
scenarioType: 'empty',
|
||||||
|
validateYear(el) {
|
||||||
|
if (el.value && el.value.length < 4) {
|
||||||
|
el.value = new Date().getFullYear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-3xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="font-bold text-lg">New Scenario</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
<span class="sr-only">Close</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
<h4 class="modal-title">New Scenario</h4>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-body">
|
>
|
||||||
<form
|
✕
|
||||||
id="new_scenario_form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action="{{ meta.current_package.url }}/scenario"
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="new-scenario-modal-form"
|
||||||
<div class="jumbotron">
|
accept-charset="UTF-8"
|
||||||
|
action="{{ meta.current_package.url }}/scenario"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<span>
|
||||||
Please enter name, description, and date of scenario. Date should be
|
Please enter name, description, and date of scenario. Date should be
|
||||||
associated to the data, not the current date. For example, this
|
associated to the data, not the current date. For example, this
|
||||||
could reflect the publishing date of a study. You can leave all
|
could reflect the publishing date of a study. You can leave all
|
||||||
@ -32,122 +47,131 @@
|
|||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://wiki.envipath.org/index.php/scenario"
|
href="https://wiki.envipath.org/index.php/scenario"
|
||||||
role="button"
|
class="link"
|
||||||
>wiki >></a
|
>wiki >></a
|
||||||
>
|
>
|
||||||
</div>
|
</span>
|
||||||
<label for="scenario-name">Name</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="scenario-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="scenario-name"
|
id="scenario-name"
|
||||||
name="scenario-name"
|
name="scenario-name"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<label for="scenario-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="scenario-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="scenario-description"
|
id="scenario-description"
|
||||||
name="scenario-description"
|
name="scenario-description"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
<label id="dateField" for="dateYear">Date</label>
|
</div>
|
||||||
<table>
|
|
||||||
<tr>
|
<div class="form-control mb-3">
|
||||||
<th>
|
<label class="label">
|
||||||
<input
|
<span class="label-text">Date</span>
|
||||||
type="number"
|
</label>
|
||||||
id="dateYear"
|
<div class="flex gap-2">
|
||||||
name="scenario-date-year"
|
<input
|
||||||
class="form-control"
|
type="number"
|
||||||
placeholder="YYYY"
|
id="dateYear"
|
||||||
max="{% now "Y" %}"
|
name="scenario-date-year"
|
||||||
/>
|
class="input input-bordered w-24"
|
||||||
</th>
|
placeholder="YYYY"
|
||||||
<th>
|
max="{% now 'Y' %}"
|
||||||
<input
|
@blur="validateYear($el)"
|
||||||
type="number"
|
/>
|
||||||
id="dateMonth"
|
<input
|
||||||
name="scenario-date-month"
|
type="number"
|
||||||
min="1"
|
id="dateMonth"
|
||||||
max="12"
|
name="scenario-date-month"
|
||||||
class="form-control"
|
min="1"
|
||||||
placeholder="MM"
|
max="12"
|
||||||
/>
|
class="input input-bordered w-20"
|
||||||
</th>
|
placeholder="MM"
|
||||||
<th>
|
/>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
id="dateDay"
|
id="dateDay"
|
||||||
name="scenario-date-day"
|
name="scenario-date-day"
|
||||||
min="1"
|
min="1"
|
||||||
max="31"
|
max="31"
|
||||||
class="form-control"
|
class="input input-bordered w-20"
|
||||||
placeholder="DD"
|
placeholder="DD"
|
||||||
/>
|
/>
|
||||||
</th>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
|
||||||
<label for="scenario-type">Scenario Type</label>
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="scenario-type">
|
||||||
|
<span class="label-text">Scenario Type</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="scenario-type"
|
id="scenario-type"
|
||||||
name="scenario-type"
|
name="scenario-type"
|
||||||
class="form-control"
|
class="select select-bordered w-full"
|
||||||
data-width="100%"
|
x-model="scenarioType"
|
||||||
>
|
>
|
||||||
<option value="empty" selected>Empty Scenario</option>
|
<option value="empty" selected>Empty Scenario</option>
|
||||||
{% for k, v in scenario_types.items %}
|
{% for k, v in scenario_types.items %}
|
||||||
<option value="{{ v.name }}">{{ k }}</option>
|
<option value="{{ v.name }}">{{ k }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% for type in scenario_types.values %}
|
{% for type in scenario_types.values %}
|
||||||
<div id="{{ type.name }}-specific-inputs">
|
<div
|
||||||
{% for widget in type.widgets %}
|
id="{{ type.name }}-specific-inputs"
|
||||||
{{ widget|safe }}
|
x-show="scenarioType === '{{ type.name }}'"
|
||||||
{% endfor %}
|
x-cloak
|
||||||
</div>
|
>
|
||||||
{% endfor %}
|
{% for widget in type.widgets %}
|
||||||
</form>
|
{{ widget|safe }}
|
||||||
</div>
|
{% endfor %}
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<a id="new_scenario_modal_form_submit" class="btn btn-primary" href="#"
|
{% endfor %}
|
||||||
>Submit</a
|
</form>
|
||||||
>
|
</div>
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
|
||||||
Cancel
|
<!-- Footer -->
|
||||||
</button>
|
<div class="modal-action">
|
||||||
</div>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('new-scenario-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Creating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
// Initially hide all "specific" forms
|
<button :disabled="isSubmitting">close</button>
|
||||||
$("div[id$='-specific-inputs']").each(function () {
|
</form>
|
||||||
$(this).hide();
|
</dialog>
|
||||||
});
|
|
||||||
|
|
||||||
// On change hide all and show only selected
|
|
||||||
$("#scenario-type").change(function () {
|
|
||||||
$("div[id$='-specific-inputs']").each(function () {
|
|
||||||
$(this).hide();
|
|
||||||
});
|
|
||||||
val = $("option:selected", this).val();
|
|
||||||
$("#" + val + "-specific-inputs").show();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#new_scenario_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$("#new_scenario_form").submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
var dateYear = document.getElementById("dateYear");
|
|
||||||
dateYear.addEventListener("change", () => {
|
|
||||||
console.log("Final value after editing:", dateYear.value);
|
|
||||||
if (dateYear.value.length < 4) {
|
|
||||||
dateYear.value = new Date().getFullYear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,92 +1,117 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Add Additional Information-->
|
<!-- Add Additional Information -->
|
||||||
<div id="add_additional_information_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="add_additional_information_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="{
|
||||||
<button
|
isSubmitting: false,
|
||||||
type="button"
|
selectedType: '',
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
reset() {
|
||||||
aria-label="Close"
|
this.isSubmitting = false;
|
||||||
>
|
this.selectedType = '';
|
||||||
<span aria-hidden="true">×</span>
|
},
|
||||||
</button>
|
|
||||||
<h3 class="modal-title">Add Additional Information</h3>
|
submit() {
|
||||||
</div>
|
if (!this.selectedType) return;
|
||||||
<div class="modal-body">
|
|
||||||
|
const form = document.getElementById('add_' + this.selectedType + '_add-additional-information-modal-form');
|
||||||
|
if (form && form.checkValidity()) {
|
||||||
|
this.isSubmitting = true;
|
||||||
|
form.submit();
|
||||||
|
} else if (form) {
|
||||||
|
form.reportValidity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset()"
|
||||||
|
>
|
||||||
|
<div class="modal-box">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="text-lg font-bold">Add Additional Information</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label" for="select-additional-information-type">
|
||||||
|
<span class="label-text">Select the type to add</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="select-additional-information-type"
|
id="select-additional-information-type"
|
||||||
data-actions-box="true"
|
class="select select-bordered w-full"
|
||||||
class="form-control"
|
x-model="selectedType"
|
||||||
data-width="100%"
|
|
||||||
>
|
>
|
||||||
<option selected disabled>Select the type to add</option>
|
<option value="" selected disabled>Select the type to add</option>
|
||||||
{% for add_inf in available_additional_information %}
|
{% for add_inf in available_additional_information %}
|
||||||
<option value="{{ add_inf.name }}">
|
<option value="{{ add_inf.name }}">
|
||||||
{{ add_inf.display_name }}
|
{{ add_inf.display_name }}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% for add_inf in available_additional_information %}
|
|
||||||
<div class="aiform {{ add_inf.name }}" style="display: none;">
|
|
||||||
<form
|
|
||||||
id="add_{{ add_inf.name }}_add-additional-information-modal-form"
|
|
||||||
accept-charset="UTF-8"
|
|
||||||
action=""
|
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ add_inf.widget|safe }}
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="hidden"
|
|
||||||
value="add-additional-information"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
{% for add_inf in available_additional_information %}
|
||||||
Close
|
<div
|
||||||
</button>
|
class="mt-4"
|
||||||
<button
|
x-show="selectedType === '{{ add_inf.name }}'"
|
||||||
type="button"
|
x-cloak
|
||||||
class="btn btn-primary"
|
|
||||||
id="add-additional-information-modal-submit"
|
|
||||||
>
|
>
|
||||||
Add
|
<form
|
||||||
</button>
|
id="add_{{ add_inf.name }}_add-additional-information-modal-form"
|
||||||
</div>
|
accept-charset="UTF-8"
|
||||||
|
action=""
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ add_inf.widget|safe }}
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="hidden"
|
||||||
|
value="add-additional-information"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit()"
|
||||||
|
:disabled="isSubmitting || !selectedType"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Add</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Adding...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#select-additional-information-type").change(function (e) {
|
|
||||||
var selectedType = $(
|
|
||||||
"#select-additional-information-type :selected",
|
|
||||||
).val();
|
|
||||||
$(".aiform").hide();
|
|
||||||
$("." + selectedType).show();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#add-additional-information-modal-submit").click(function (e) {
|
<!-- Backdrop -->
|
||||||
e.preventDefault();
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
var selectedType = $(
|
</form>
|
||||||
"#select-additional-information-type :selected",
|
</dialog>
|
||||||
).val();
|
|
||||||
console.log(selectedType);
|
|
||||||
if (
|
|
||||||
selectedType !== null &&
|
|
||||||
selectedType !== undefined &&
|
|
||||||
selectedType !== ""
|
|
||||||
) {
|
|
||||||
$("." + selectedType + " >form").submit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,67 +1,107 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div
|
<dialog
|
||||||
class="modal fade bs-modal-lg"
|
|
||||||
id="add_pathway_edge_modal"
|
id="add_pathway_edge_modal"
|
||||||
tabindex="-1"
|
class="modal"
|
||||||
aria-labelledby="add_pathway_edge_modal"
|
x-data="{
|
||||||
aria-modal="true"
|
isSubmitting: false,
|
||||||
role="dialog"
|
reactionImageUrl: '',
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.reactionImageUrl = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
updateReactionImage() {
|
||||||
|
const substratesSelect = document.getElementById('add_pathway_edge_substrates');
|
||||||
|
const productsSelect = document.getElementById('add_pathway_edge_products');
|
||||||
|
|
||||||
|
const substrates = [];
|
||||||
|
for (const option of substratesSelect.selectedOptions) {
|
||||||
|
substrates.push(option.dataset.smiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = [];
|
||||||
|
for (const option of productsSelect.selectedOptions) {
|
||||||
|
products.push(option.dataset.smiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substrates.length > 0 && products.length > 0) {
|
||||||
|
const reaction = substrates.join('.') + '>>' + products.join('.');
|
||||||
|
this.reactionImageUrl = '{% url "depict" %}?smirks=' + encodeURIComponent(reaction);
|
||||||
|
} else {
|
||||||
|
this.reactionImageUrl = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
this.isSubmitting = true;
|
||||||
|
document.getElementById('add_pathway_edge_modal_form').submit();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-4xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">Add a Reaction</h3>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Close button (X) -->
|
||||||
class="close"
|
<form method="dialog">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
aria-label="Close"
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
</button>
|
✕
|
||||||
<h4 class="modal-title">Add a Reaction</h4>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-body">
|
|
||||||
<form
|
<!-- Body -->
|
||||||
id="add_pathway_edge_modal_form"
|
<div class="py-4">
|
||||||
accept-charset="UTF-8"
|
<form
|
||||||
action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}"
|
id="add_pathway_edge_modal_form"
|
||||||
data-remote="true"
|
accept-charset="UTF-8"
|
||||||
method="post"
|
action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}"
|
||||||
>
|
data-remote="true"
|
||||||
{% csrf_token %}
|
method="post"
|
||||||
<label for="edge-name">Name</label>
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="edge-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="edge-name"
|
id="edge-name"
|
||||||
class="form-control"
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
name="edge-name"
|
name="edge-name"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
/>
|
/>
|
||||||
<label for="edge-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="edge-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="edge-description"
|
id="edge-description"
|
||||||
class="form-control"
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
name="edge-description"
|
name="edge-description"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
<p></p>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-5">
|
<div class="mb-3 grid grid-cols-11 gap-2">
|
||||||
<legend>Substrate(s)</legend>
|
<div class="col-span-5">
|
||||||
</div>
|
<div class="form-control">
|
||||||
<div class="col-xs-2"></div>
|
<label class="label">
|
||||||
<div class="col-xs-5">
|
<span class="label-text font-semibold">Substrate(s)</span>
|
||||||
<legend>Product(s)</legend>
|
</label>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-5">
|
|
||||||
<select
|
<select
|
||||||
id="add_pathway_edge_substrates"
|
id="add_pathway_edge_substrates"
|
||||||
name="edge-substrates"
|
name="edge-substrates"
|
||||||
data-actions-box="true"
|
class="select select-bordered h-32 w-full"
|
||||||
class="form-control"
|
|
||||||
multiple
|
multiple
|
||||||
data-width="100%"
|
@change="updateReactionImage()"
|
||||||
>
|
>
|
||||||
{% for n in pathway.nodes %}
|
{% for n in pathway.nodes %}
|
||||||
<option
|
<option
|
||||||
@ -73,20 +113,21 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
class="col-xs-2"
|
<div class="col-span-1 flex items-center justify-center">
|
||||||
style="display: flex; justify-content: center; align-items: center;"
|
<span class="text-2xl">→</span>
|
||||||
>
|
</div>
|
||||||
<i class="glyphicon glyphicon-arrow-right"></i>
|
<div class="col-span-5">
|
||||||
</div>
|
<div class="form-control">
|
||||||
<div class="col-xs-5">
|
<label class="label">
|
||||||
|
<span class="label-text font-semibold">Product(s)</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="add_pathway_edge_products"
|
id="add_pathway_edge_products"
|
||||||
name="edge-products"
|
name="edge-products"
|
||||||
data-actions-box="true"
|
class="select select-bordered h-32 w-full"
|
||||||
class="form-control"
|
|
||||||
multiple
|
multiple
|
||||||
data-width="100%"
|
@change="updateReactionImage()"
|
||||||
>
|
>
|
||||||
{% for n in pathway.nodes %}
|
{% for n in pathway.nodes %}
|
||||||
<option
|
<option
|
||||||
@ -99,76 +140,42 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
</div>
|
||||||
<p></p>
|
|
||||||
<div class="col-xs-12" id="reaction_image"></div>
|
<div class="mb-3" x-show="reactionImageUrl" x-cloak>
|
||||||
</div>
|
<img :src="reactionImageUrl" class="w-full" alt="Reaction preview" />
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Footer -->
|
||||||
class="btn btn-secondary pull-left"
|
<div class="modal-action">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
>
|
type="button"
|
||||||
Close
|
class="btn"
|
||||||
</button>
|
onclick="this.closest('dialog').close()"
|
||||||
<button
|
:disabled="isSubmitting"
|
||||||
type="button"
|
>
|
||||||
class="btn btn-primary"
|
Close
|
||||||
id="add_pathway_edge_modal_form_submit"
|
</button>
|
||||||
>
|
<button
|
||||||
Submit
|
type="button"
|
||||||
</button>
|
class="btn btn-primary"
|
||||||
</div>
|
@click="submit()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Submitting...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function reactionImage() {
|
|
||||||
var substrates = [];
|
|
||||||
$("#add_pathway_edge_substrates option:selected").each(function () {
|
|
||||||
var smiles = $(this).data("smiles"); // read data-smiles attribute
|
|
||||||
substrates.push(smiles);
|
|
||||||
});
|
|
||||||
|
|
||||||
var products = [];
|
<!-- Backdrop -->
|
||||||
$("#add_pathway_edge_products option:selected").each(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
var smiles = $(this).data("smiles"); // read data-smiles attribute
|
<button :disabled="isSubmitting">close</button>
|
||||||
products.push(smiles);
|
</form>
|
||||||
});
|
</dialog>
|
||||||
|
|
||||||
if (substrates.length > 0 && products.length > 0) {
|
|
||||||
reaction = substrates.join(".") + ">>" + products.join(".");
|
|
||||||
$("#reaction_image").empty();
|
|
||||||
$("#reaction_image").append(
|
|
||||||
"<img width='100%' src='{% url 'depict' %}?smirks=" +
|
|
||||||
encodeURIComponent(reaction) +
|
|
||||||
"'>",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$("#add_pathway_edge_substrates").selectpicker();
|
|
||||||
$("#add_pathway_edge_products").selectpicker();
|
|
||||||
|
|
||||||
$("#add_pathway_edge_substrates").on("change", function (e) {
|
|
||||||
reactionImage();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#add_pathway_edge_products").on("change", function (e) {
|
|
||||||
reactionImage();
|
|
||||||
});
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$("#add_pathway_edge_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).prop("disabled", true);
|
|
||||||
|
|
||||||
// submit form
|
|
||||||
$("#add_pathway_edge_modal_form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,119 +1,137 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div
|
<dialog
|
||||||
class="modal fade bs-modal-lg"
|
|
||||||
id="add_pathway_node_modal"
|
id="add_pathway_node_modal"
|
||||||
tabindex="-1"
|
class="modal"
|
||||||
aria-labelledby="add_pathway_node_modal"
|
x-data="modalForm()"
|
||||||
aria-modal="true"
|
@close="reset()"
|
||||||
role="dialog"
|
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-4xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">Add a Node</h3>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Close button (X) -->
|
||||||
class="close"
|
<form method="dialog">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
aria-label="Close"
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
</button>
|
✕
|
||||||
<h4 class="modal-title">Add a Node</h4>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-body">
|
|
||||||
<form
|
<!-- Body -->
|
||||||
id="add_pathway_node_modal_form"
|
<div class="py-4">
|
||||||
accept-charset="UTF-8"
|
<form
|
||||||
action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}"
|
id="add_pathway_node_modal_form"
|
||||||
data-remote="true"
|
accept-charset="UTF-8"
|
||||||
method="post"
|
action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}"
|
||||||
>
|
data-remote="true"
|
||||||
{% csrf_token %}
|
method="post"
|
||||||
<label for="node-name">Name</label>
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="node-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="node-name"
|
id="node-name"
|
||||||
class="form-control"
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
name="node-name"
|
name="node-name"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
/>
|
/>
|
||||||
<label for="node-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="node-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="node-description"
|
id="node-description"
|
||||||
class="form-control"
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
name="node-description"
|
name="node-description"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
<label for="node-smiles">SMILES</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="node-smiles">
|
||||||
|
<span class="label-text">SMILES</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="node-smiles"
|
name="node-smiles"
|
||||||
placeholder="SMILES"
|
placeholder="SMILES"
|
||||||
id="node-smiles"
|
id="node-smiles"
|
||||||
/>
|
/>
|
||||||
<p></p>
|
</div>
|
||||||
<div>
|
|
||||||
<iframe
|
<div class="mb-3">
|
||||||
id="add_node_ketcher"
|
<iframe
|
||||||
src="{% static '/js/ketcher2/ketcher.html' %}"
|
id="add_node_ketcher"
|
||||||
width="100%"
|
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
height="510"
|
width="100%"
|
||||||
></iframe>
|
height="510"
|
||||||
</div>
|
></iframe>
|
||||||
<p></p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
<!-- Footer -->
|
||||||
type="button"
|
<div class="modal-action">
|
||||||
class="btn btn-secondary pull-left"
|
<button
|
||||||
data-dismiss="modal"
|
type="button"
|
||||||
>
|
class="btn"
|
||||||
Close
|
onclick="this.closest('dialog').close()"
|
||||||
</button>
|
:disabled="isSubmitting"
|
||||||
<button
|
>
|
||||||
type="button"
|
Close
|
||||||
class="btn btn-primary"
|
</button>
|
||||||
id="add_pathway_node_modal_form_submit"
|
<button
|
||||||
>
|
type="button"
|
||||||
Submit
|
class="btn btn-primary"
|
||||||
</button>
|
@click="submit('add_pathway_node_modal_form')"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Submitting...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function newStructureModalketcherToNewStructureModalTextInput() {
|
|
||||||
$("#node-smiles").val(this.ketcher.getSmiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
<!-- Backdrop -->
|
||||||
$("#add_node_ketcher").on("load", function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document
|
||||||
|
.getElementById("add_node_ketcher")
|
||||||
|
.addEventListener("load", function () {
|
||||||
|
const iframe = this;
|
||||||
const checkKetcherReady = () => {
|
const checkKetcherReady = () => {
|
||||||
win = this.contentWindow;
|
const win = iframe.contentWindow;
|
||||||
if (win.ketcher && "editor" in win.ketcher) {
|
if (win.ketcher && "editor" in 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: newStructureModalketcherToNewStructureModalTextInput,
|
f: function () {
|
||||||
|
document.getElementById("node-smiles").value =
|
||||||
|
this.ketcher.getSmiles();
|
||||||
|
},
|
||||||
ketcher: win.ketcher,
|
ketcher: win.ketcher,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkKetcherReady, 100);
|
setTimeout(checkKetcherReady, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkKetcherReady();
|
checkKetcherReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$("#add_pathway_node_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).prop("disabled", true);
|
|
||||||
|
|
||||||
// submit form
|
|
||||||
$("#add_pathway_node_modal_form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,119 +1,137 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div
|
<dialog
|
||||||
class="modal fade bs-modal-lg"
|
|
||||||
id="add_structure_modal"
|
id="add_structure_modal"
|
||||||
tabindex="-1"
|
class="modal"
|
||||||
aria-labelledby="add_structure_modal"
|
x-data="modalForm()"
|
||||||
aria-modal="true"
|
@close="reset()"
|
||||||
role="dialog"
|
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-4xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">Create a new Structure</h3>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Close button (X) -->
|
||||||
class="close"
|
<form method="dialog">
|
||||||
data-dismiss="modal"
|
<button
|
||||||
aria-label="Close"
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
<span aria-hidden="true">×</span>
|
>
|
||||||
</button>
|
✕
|
||||||
<h4 class="modal-title">Create a new Structure</h4>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-body">
|
|
||||||
<form
|
<!-- Body -->
|
||||||
id="add_structure_modal_form"
|
<div class="py-4">
|
||||||
accept-charset="UTF-8"
|
<form
|
||||||
action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}"
|
id="add_structure_modal_form"
|
||||||
data-remote="true"
|
accept-charset="UTF-8"
|
||||||
method="post"
|
action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}"
|
||||||
>
|
data-remote="true"
|
||||||
{% csrf_token %}
|
method="post"
|
||||||
<label for="structure-name">Name</label>
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="structure-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="structure-name"
|
id="structure-name"
|
||||||
class="form-control"
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
name="structure-name"
|
name="structure-name"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
/>
|
/>
|
||||||
<label for="structure-description">Description</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="structure-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
id="structure-description"
|
id="structure-description"
|
||||||
class="form-control"
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
name="structure-description"
|
name="structure-description"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
/>
|
/>
|
||||||
<label for="structure-smiles">SMILES</label>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="structure-smiles">
|
||||||
|
<span class="label-text">SMILES</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="structure-smiles"
|
name="structure-smiles"
|
||||||
placeholder="SMILES"
|
placeholder="SMILES"
|
||||||
id="structure-smiles"
|
id="structure-smiles"
|
||||||
/>
|
/>
|
||||||
<p></p>
|
</div>
|
||||||
<div>
|
|
||||||
<iframe
|
<div class="mb-3">
|
||||||
id="add_structure_ketcher"
|
<iframe
|
||||||
src="{% static '/js/ketcher2/ketcher.html' %}"
|
id="add_structure_ketcher"
|
||||||
width="100%"
|
src="{% static '/js/ketcher2/ketcher.html' %}"
|
||||||
height="510"
|
width="100%"
|
||||||
></iframe>
|
height="510"
|
||||||
</div>
|
></iframe>
|
||||||
<p></p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
<!-- Footer -->
|
||||||
type="button"
|
<div class="modal-action">
|
||||||
class="btn btn-secondary pull-left"
|
<button
|
||||||
data-dismiss="modal"
|
type="button"
|
||||||
>
|
class="btn"
|
||||||
Close
|
onclick="this.closest('dialog').close()"
|
||||||
</button>
|
:disabled="isSubmitting"
|
||||||
<button
|
>
|
||||||
type="button"
|
Close
|
||||||
class="btn btn-primary"
|
</button>
|
||||||
id="add_structure_modal_form_submit"
|
<button
|
||||||
>
|
type="button"
|
||||||
Submit
|
class="btn btn-primary"
|
||||||
</button>
|
@click="submit('add_structure_modal_form')"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Submitting...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function newStructureModalketcherToNewStructureModalTextInput() {
|
|
||||||
$("#structure-smiles").val(this.ketcher.getSmiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
<!-- Backdrop -->
|
||||||
$("#add_structure_ketcher").on("load", function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document
|
||||||
|
.getElementById("add_structure_ketcher")
|
||||||
|
.addEventListener("load", function () {
|
||||||
|
const iframe = this;
|
||||||
const checkKetcherReady = () => {
|
const checkKetcherReady = () => {
|
||||||
win = this.contentWindow;
|
const win = iframe.contentWindow;
|
||||||
if (win.ketcher && "editor" in win.ketcher) {
|
if (win.ketcher && "editor" in 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: newStructureModalketcherToNewStructureModalTextInput,
|
f: function () {
|
||||||
|
document.getElementById("structure-smiles").value =
|
||||||
|
this.ketcher.getSmiles();
|
||||||
|
},
|
||||||
ketcher: win.ketcher,
|
ketcher: win.ketcher,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkKetcherReady, 100);
|
setTimeout(checkKetcherReady, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkKetcherReady();
|
checkKetcherReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$("#add_structure_modal_form_submit").on("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).prop("disabled", true);
|
|
||||||
|
|
||||||
// submit form
|
|
||||||
$("#add_structure_modal_form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,36 +1,48 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Delete Edge -->
|
<!-- Delete Edge -->
|
||||||
<div id="delete_pathway_edge_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="delete_pathway_edge_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })"
|
||||||
<h3 class="modal-title">Delete Edge</h3>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="text-lg font-bold">Delete Edge</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="mb-4">
|
||||||
Deletes the Edge. Nodes referenced by this edge will remain.
|
Deletes the Edge. Nodes referenced by this edge will remain.
|
||||||
<p></p>
|
</p>
|
||||||
<form
|
<form
|
||||||
id="delete-pathway-edge-modal-form"
|
id="delete-pathway-edge-modal-form"
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
action=""
|
action=""
|
||||||
data-remote="true"
|
method="post"
|
||||||
method="post"
|
>
|
||||||
>
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
<div class="form-control">
|
||||||
|
<label class="label" for="delete_pathway_edge_edges">
|
||||||
|
<span class="label-text">Select Reaction to delete</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="delete_pathway_edge_edges"
|
id="delete_pathway_edge_edges"
|
||||||
name="edge-url"
|
name="edge-url"
|
||||||
data-actions-box="true"
|
class="select select-bordered w-full"
|
||||||
class="form-control"
|
x-model="selectedEdge"
|
||||||
data-width="100%"
|
@change="imageUrl = selectedEdge ? selectedEdge + '?image=svg' : ''"
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<option value="" disabled selected>
|
<option value="" disabled selected>
|
||||||
Select Reaction to delete
|
Select Reaction to delete
|
||||||
@ -39,51 +51,44 @@
|
|||||||
<option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option>
|
<option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" id="hidden" name="hidden" value="delete" />
|
</div>
|
||||||
</form>
|
<input type="hidden" id="hidden" name="hidden" value="delete" />
|
||||||
<p></p>
|
</form>
|
||||||
<div id="delete_pathway_edge_image"></div>
|
|
||||||
</div>
|
<!-- Image Preview -->
|
||||||
<div class="modal-footer">
|
<div class="mt-4" x-show="imageUrl" x-cloak>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
<img :src="imageUrl" class="w-full" alt="Edge preview" />
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
id="delete-pathway-edge-modal-submit"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-error"
|
||||||
|
@click="setFormAction('delete-pathway-edge-modal-form', selectedEdge); submit('delete-pathway-edge-modal-form')"
|
||||||
|
:disabled="isSubmitting || !selectedEdge"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Delete</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Deleting...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#delete_pathway_edge_edges").selectpicker();
|
|
||||||
|
|
||||||
$("#delete_pathway_edge_edges").on("change", function (e) {
|
<!-- Backdrop -->
|
||||||
edge_url = $("#delete_pathway_edge_edges option:selected").val();
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
if (edge_url !== "") {
|
</form>
|
||||||
$("#delete_pathway_edge_image").empty();
|
</dialog>
|
||||||
$("#delete_pathway_edge_image").append(
|
|
||||||
"<img width='100%' src='" + edge_url + "?image=svg'>",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#delete-pathway-edge-modal-submit").click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
edge_url = $("#delete_pathway_edge_edges option:selected").val();
|
|
||||||
|
|
||||||
if (edge_url === "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#delete-pathway-edge-modal-form").attr("action", edge_url);
|
|
||||||
$("#delete-pathway-edge-modal-form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,38 +1,49 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<!-- Delete Node -->
|
<!-- Delete Node -->
|
||||||
<div id="delete_pathway_node_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="delete_pathway_node_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })"
|
||||||
<h3 class="modal-title">Delete Node</h3>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="text-lg font-bold">Delete Node</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="mb-4">
|
||||||
Deletes the Node. Edges having this Node as Substrate or Product will be
|
Deletes the Node. Edges having this Node as Substrate or Product will be
|
||||||
removed as well.
|
removed as well.
|
||||||
<p></p>
|
</p>
|
||||||
<form
|
<form
|
||||||
id="delete-pathway-node-modal-form"
|
id="delete-pathway-node-modal-form"
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
action=""
|
action=""
|
||||||
data-remote="true"
|
method="post"
|
||||||
method="post"
|
>
|
||||||
>
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
<div class="form-control">
|
||||||
|
<label class="label" for="delete_pathway_node_nodes">
|
||||||
|
<span class="label-text">Select Compound to delete</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="delete_pathway_node_nodes"
|
id="delete_pathway_node_nodes"
|
||||||
name="node-url"
|
name="node-url"
|
||||||
data-actions-box="true"
|
class="select select-bordered w-full"
|
||||||
class="form-control"
|
x-model="selectedNode"
|
||||||
data-width="100%"
|
@change="imageUrl = selectedNode ? selectedNode + '?image=svg' : ''"
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<option value="" disabled selected>
|
<option value="" disabled selected>
|
||||||
Select Compound to delete
|
Select Compound to delete
|
||||||
@ -43,51 +54,44 @@
|
|||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" id="hidden" name="hidden" value="delete" />
|
</div>
|
||||||
</form>
|
<input type="hidden" id="hidden" name="hidden" value="delete" />
|
||||||
<p></p>
|
</form>
|
||||||
<div id="delete_pathway_node_image"></div>
|
|
||||||
</div>
|
<!-- Image Preview -->
|
||||||
<div class="modal-footer">
|
<div class="mt-4" x-show="imageUrl" x-cloak>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
<img :src="imageUrl" class="w-full" alt="Node preview" />
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
id="delete-pathway-node-modal-submit"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-error"
|
||||||
|
@click="setFormAction('delete-pathway-node-modal-form', selectedNode); submit('delete-pathway-node-modal-form')"
|
||||||
|
:disabled="isSubmitting || !selectedNode"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Delete</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Deleting...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#delete_pathway_node_nodes").selectpicker();
|
|
||||||
|
|
||||||
$("#delete_pathway_node_nodes").on("change", function (e) {
|
<!-- Backdrop -->
|
||||||
node_url = $("#delete_pathway_node_nodes option:selected").val();
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
if (node_url !== "") {
|
</form>
|
||||||
$("#delete_pathway_node_image").empty();
|
</dialog>
|
||||||
$("#delete_pathway_node_image").append(
|
|
||||||
"<img width='100%' src='" + node_url + "?image=svg'>",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#delete-pathway-node-modal-submit").click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
node_url = $("#delete_pathway_node_nodes option:selected").val();
|
|
||||||
|
|
||||||
if (node_url === "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#delete-pathway-node-modal-form").attr("action", node_url);
|
|
||||||
$("#delete-pathway-node-modal-form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,53 +1,69 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Download Pathway -->
|
|
||||||
<div id="download_pathway_csv_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="download_pathway_csv_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h3 class="modal-title">Download Pathway as CSV</h3>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Download Pathway as CSV</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<p>
|
||||||
By clicking on Download the Pathway will be converted into a CSV and
|
By clicking on Download the Pathway will be converted into a CSV and
|
||||||
directly downloaded.
|
directly downloaded.
|
||||||
<form
|
</p>
|
||||||
id="download-pathway-csv-modal-form"
|
|
||||||
accept-charset="UTF-8"
|
<form
|
||||||
action="{{ pathway.url }}"
|
id="download-pathway-csv-modal-form"
|
||||||
data-remote="true"
|
accept-charset="UTF-8"
|
||||||
method="GET"
|
action="{{ pathway.url }}"
|
||||||
>
|
method="GET"
|
||||||
<input type="hidden" name="download" value="true" />
|
>
|
||||||
</form>
|
<input type="hidden" name="download" value="true" />
|
||||||
</div>
|
</form>
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
|
||||||
Close
|
<!-- Footer -->
|
||||||
</button>
|
<div class="modal-action">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary"
|
class="btn"
|
||||||
id="download-pathway-csv-modal-submit"
|
onclick="this.closest('dialog').close()"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
Download
|
>
|
||||||
</button>
|
Close
|
||||||
</div>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('download-pathway-csv-modal-form'); $el.closest('dialog').close();"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Download</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#download-pathway-csv-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#download-pathway-csv-modal-form").submit();
|
</dialog>
|
||||||
$("#download_pathway_csv_modal").modal("hide");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,43 +1,57 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Download Pathway -->
|
|
||||||
<div id="download_pathway_image_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="download_pathway_image_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h3 class="modal-title">Download Pathway as Image</h3>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Download Pathway as Image</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
By clicking on Download the Pathway will be saved as SVG.
|
>
|
||||||
</div>
|
✕
|
||||||
<div class="modal-footer">
|
</button>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
</form>
|
||||||
Close
|
|
||||||
</button>
|
<!-- Body -->
|
||||||
<button
|
<div class="py-4">
|
||||||
type="button"
|
<p>By clicking on Download the Pathway will be saved as SVG.</p>
|
||||||
class="btn btn-primary"
|
</div>
|
||||||
id="download-pathway-image-modal-submit"
|
|
||||||
>
|
<!-- Footer -->
|
||||||
Download
|
<div class="modal-action">
|
||||||
</button>
|
<button
|
||||||
</div>
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="isSubmitting = true; downloadSVG(document.getElementById('pwsvg'), '{{ pathway.name.split|join:'_' }}.svg'); $el.closest('dialog').close();"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Download</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#download-pathway-image-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
downloadSVG($("#pwsvg")[0], '{{ pathway.name.split|join:"_" }}.svg');
|
</dialog>
|
||||||
$("#download_pathway_image_modal").modal("hide");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,70 +1,91 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Compound -->
|
|
||||||
<div id="edit_compound_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_compound_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Edit Compound</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Edit Compound</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>Edit Compound.</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-compound-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="edit-compound-modal-form"
|
||||||
<p>
|
accept-charset="UTF-8"
|
||||||
<label for="compound-name">Name</label>
|
action=""
|
||||||
<input
|
method="post"
|
||||||
id="compound-name"
|
>
|
||||||
class="form-control"
|
{% csrf_token %}
|
||||||
name="compound-name"
|
|
||||||
value="{{ compound.name|safe }}"
|
<div class="form-control mb-3">
|
||||||
/>
|
<label class="label" for="compound-name">
|
||||||
</p>
|
<span class="label-text">Name</span>
|
||||||
<p>
|
</label>
|
||||||
<label for="compound-description">Description</label>
|
<input
|
||||||
<input
|
id="compound-name"
|
||||||
id="compound-description"
|
class="input input-bordered w-full"
|
||||||
type="text"
|
name="compound-name"
|
||||||
class="form-control"
|
value="{{ compound.name|safe }}"
|
||||||
value="{{ compound.description|safe }}"
|
required
|
||||||
name="compound-description"
|
/>
|
||||||
/>
|
</div>
|
||||||
</p>
|
|
||||||
</form>
|
<div class="form-control mb-3">
|
||||||
</div>
|
<label class="label" for="compound-description">
|
||||||
<div class="modal-footer">
|
<span class="label-text">Description</span>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
</label>
|
||||||
Close
|
<input
|
||||||
</button>
|
id="compound-description"
|
||||||
<button
|
type="text"
|
||||||
type="button"
|
class="input input-bordered w-full"
|
||||||
class="btn btn-primary"
|
value="{{ compound.description|safe }}"
|
||||||
id="edit-compound-modal-submit"
|
name="compound-description"
|
||||||
>
|
/>
|
||||||
Update
|
</div>
|
||||||
</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-compound-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-compound-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-compound-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,70 +1,91 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Compound -->
|
|
||||||
<div id="edit_compound_structure_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_compound_structure_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Create a Compound</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Edit Compound Structure</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>Edit a Compound Structure.</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-compound-structure-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="edit-compound-structure-modal-form"
|
||||||
<p>
|
accept-charset="UTF-8"
|
||||||
<label for="compound-structure-name">Name</label>
|
action=""
|
||||||
<input
|
method="post"
|
||||||
id="compound-structure-name"
|
>
|
||||||
class="form-control"
|
{% csrf_token %}
|
||||||
name="compound-structure-name"
|
|
||||||
value="{{ compound_structure.name|safe }}"
|
<div class="form-control mb-3">
|
||||||
/>
|
<label class="label" for="compound-structure-name">
|
||||||
</p>
|
<span class="label-text">Name</span>
|
||||||
<p>
|
</label>
|
||||||
<label for="compound-structure-description">Description</label>
|
<input
|
||||||
<input
|
id="compound-structure-name"
|
||||||
id="compound-structure-description"
|
class="input input-bordered w-full"
|
||||||
type="text"
|
name="compound-structure-name"
|
||||||
class="form-control"
|
value="{{ compound_structure.name|safe }}"
|
||||||
value="{{ compound_structure.description|safe }}"
|
required
|
||||||
name="compound-structure-description"
|
/>
|
||||||
/>
|
</div>
|
||||||
</p>
|
|
||||||
</form>
|
<div class="form-control mb-3">
|
||||||
</div>
|
<label class="label" for="compound-structure-description">
|
||||||
<div class="modal-footer">
|
<span class="label-text">Description</span>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
</label>
|
||||||
Close
|
<input
|
||||||
</button>
|
id="compound-structure-description"
|
||||||
<button
|
type="text"
|
||||||
type="button"
|
class="input input-bordered w-full"
|
||||||
class="btn btn-primary"
|
value="{{ compound_structure.description|safe }}"
|
||||||
id="edit-compound-structure-modal-submit"
|
name="compound-structure-description"
|
||||||
>
|
/>
|
||||||
Create
|
</div>
|
||||||
</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-compound-structure-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-compound-structure-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-compound-structure-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,151 +1,150 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package Permission -->
|
<!-- Edit Group Member -->
|
||||||
<div id="edit_group_member_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_group_member_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="{
|
||||||
<h5 class="modal-title">Add or Remove Group Member</h5>
|
isSubmitting: false,
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>
|
|
||||||
To add member (either User or entire Groups) to this group select the
|
|
||||||
entity you want to add below and click the check mark.
|
|
||||||
<br />
|
|
||||||
To remove member simply click the <code>X</code> next to the member.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="row">
|
reset() {
|
||||||
<div class="col-xs-8">
|
this.isSubmitting = false;
|
||||||
<legend>User or Group</legend>
|
},
|
||||||
</div>
|
|
||||||
<div class="col-xs-4">
|
|
||||||
<legend>Add/Remove</legend>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
submitForm(form) {
|
||||||
<form
|
if (form && form.checkValidity()) {
|
||||||
id="modal-form-group-member"
|
form.submit();
|
||||||
class="form-inline"
|
} else if (form) {
|
||||||
role="form"
|
form.reportValidity();
|
||||||
accept-charset="UTF-8"
|
}
|
||||||
action=""
|
}
|
||||||
data-remote="true"
|
}"
|
||||||
method="post"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
<div class="modal-box">
|
||||||
<div class="col-xs-8">
|
<!-- Header -->
|
||||||
<select
|
<h3 class="text-lg font-bold">Add or Remove Group Member</h3>
|
||||||
id="select_member"
|
|
||||||
name="member"
|
<!-- Close button (X) -->
|
||||||
data-actions-box="true"
|
<form method="dialog">
|
||||||
class="selPackages"
|
<button
|
||||||
data-width="100%"
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
>
|
:disabled="isSubmitting"
|
||||||
<option disabled selected>User</option>
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="mb-4">
|
||||||
|
To add member (either User or entire Groups) to this group select the
|
||||||
|
entity you want to add below and click the check mark.
|
||||||
|
<br />
|
||||||
|
To remove member simply click the X button next to the member.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Add Member Form -->
|
||||||
|
<form
|
||||||
|
id="modal-form-group-member"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action=""
|
||||||
|
method="post"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="flex gap-2 items-end">
|
||||||
|
<div class="form-control flex-1">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">User or Group</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="select_member"
|
||||||
|
name="member"
|
||||||
|
class="select select-bordered w-full"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<optgroup label="Users">
|
||||||
{% for u in users %}
|
{% for u in users %}
|
||||||
<option value="{{ u.url }}">{{ u.username }}</option>
|
<option value="{{ u.url }}">{{ u.username }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<option disabled>Groups</option>
|
</optgroup>
|
||||||
|
<optgroup label="Groups">
|
||||||
{% for g in groups %}
|
{% for g in groups %}
|
||||||
<option value="{{ g.url }}">{{ g.name|safe }}</option>
|
<option value="{{ g.url }}">{{ g.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</optgroup>
|
||||||
<input type="hidden" name="action" value="add" />
|
</select>
|
||||||
</div>
|
<input type="hidden" name="action" value="add" />
|
||||||
<div class="col-xs-2"></div>
|
</div>
|
||||||
<div class="col-xs-2">
|
<button type="submit" class="btn btn-primary">Add</button>
|
||||||
<button type="submit" style="width:60%;" class="btn col-xs-2">
|
|
||||||
<span class="glyphicon glyphicon-ok"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
</form>
|
||||||
{% for u in group.user_member.all %}
|
|
||||||
<div class="row">
|
<!-- User Members -->
|
||||||
|
{% if group.user_member.all %}
|
||||||
|
<div class="divider">User Members</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{% for u in group.user_member.all %}
|
||||||
<form
|
<form
|
||||||
id="modal-form-group-member_{{ u.uuid }}"
|
id="modal-form-group-member_{{ u.uuid }}"
|
||||||
class="form-inline"
|
|
||||||
role="form"
|
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
action=""
|
action=""
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
method="post"
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col-xs-8">
|
<div class="flex items-center gap-2">
|
||||||
{{ u.username }}
|
<span class="flex-1">{{ u.username }}</span>
|
||||||
<input type="hidden" name="member" value="{{ u.url }}" />
|
<input type="hidden" name="member" value="{{ u.url }}" />
|
||||||
<input type="hidden" name="action" value="remove" />
|
<input type="hidden" name="action" value="remove" />
|
||||||
</div>
|
<button type="submit" class="btn btn-error btn-sm">
|
||||||
<div class="col-xs-2"></div>
|
Remove
|
||||||
<div class="col-xs-2">
|
|
||||||
<button type="submit" style="width:60%;" class="btn col-xs-2">
|
|
||||||
<span class="glyphicon glyphicon-trash"></span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
</div>
|
||||||
<p></p>
|
{% endif %}
|
||||||
{% for g in group.group_member.all %}
|
|
||||||
<div class="row">
|
<!-- Group Members -->
|
||||||
|
{% if group.group_member.all %}
|
||||||
|
<div class="divider">Group Members</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{% for g in group.group_member.all %}
|
||||||
<form
|
<form
|
||||||
id="modal-form-group-member_{{ g.uuid }}"
|
id="modal-form-group-member_{{ g.uuid }}"
|
||||||
class="form-inline"
|
|
||||||
role="form"
|
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
action=""
|
action=""
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
method="post"
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col-xs-8">
|
<div class="flex items-center gap-2">
|
||||||
{{ g.name|safe }}
|
<span class="flex-1">{{ g.name|safe }}</span>
|
||||||
<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>
|
<button type="submit" class="btn btn-error btn-sm">
|
||||||
<div class="col-xs-2"></div>
|
Remove
|
||||||
<div class="col-xs-2">
|
|
||||||
<button type="submit" style="width:60%;" class="btn col-xs-2">
|
|
||||||
<span class="glyphicon glyphicon-trash"></span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
|
||||||
Close
|
<!-- Footer -->
|
||||||
</button>
|
<div class="modal-action">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary"
|
class="btn"
|
||||||
id="edit-package-modal-submit"
|
onclick="this.closest('dialog').close()"
|
||||||
>
|
>
|
||||||
Update
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#edit-package-modal-submit").click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$("#edit-package-modal-form").submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#select_member").selectpicker();
|
<!-- Backdrop -->
|
||||||
});
|
<form method="dialog" class="modal-backdrop">
|
||||||
</script>
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|||||||
@ -1,71 +1,94 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Model -->
|
|
||||||
<div id="edit_model_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_model_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<button
|
@close="reset()"
|
||||||
type="button"
|
>
|
||||||
class="close"
|
<div class="modal-box">
|
||||||
data-dismiss="modal"
|
<!-- Header -->
|
||||||
aria-label="Close"
|
<h3 class="font-bold text-lg">Update Model</h3>
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
</button>
|
<form method="dialog">
|
||||||
<h3 class="modal-title">Update Model</h3>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>Alter Name and Description of the Model.</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-model-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<p class="mb-4">Alter Name and Description of the Model.</p>
|
||||||
{% csrf_token %}
|
|
||||||
<p>
|
<form
|
||||||
<label for="model-name">Name</label>
|
id="edit-model-modal-form"
|
||||||
<input
|
accept-charset="UTF-8"
|
||||||
id="model-name"
|
action=""
|
||||||
type="text"
|
method="post"
|
||||||
class="form-control"
|
>
|
||||||
name="model-name"
|
{% csrf_token %}
|
||||||
value="{{ model.name|safe }}"
|
|
||||||
/>
|
<div class="form-control mb-3">
|
||||||
</p>
|
<label class="label" for="model-name">
|
||||||
<p>
|
<span class="label-text">Name</span>
|
||||||
<label for="model-description">Description</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="model-description"
|
id="model-name"
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="input input-bordered w-full"
|
||||||
name="model-description"
|
name="model-name"
|
||||||
value="{{ model.description|safe }}"
|
value="{{ model.name|safe }}"
|
||||||
/>
|
required
|
||||||
</p>
|
/>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
<div class="form-control mb-3">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
<label class="label" for="model-description">
|
||||||
Close
|
<span class="label-text">Description</span>
|
||||||
</button>
|
</label>
|
||||||
<button
|
<input
|
||||||
type="button"
|
id="model-description"
|
||||||
class="btn btn-primary"
|
type="text"
|
||||||
id="edit-model-modal-submit"
|
class="input input-bordered w-full"
|
||||||
>
|
name="model-description"
|
||||||
Update
|
value="{{ model.description|safe }}"
|
||||||
</button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-model-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-model-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-model-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,70 +1,91 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Node -->
|
|
||||||
<div id="edit_node_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_node_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Edit Node</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Edit Node</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>Edit Node.</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-node-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="edit-node-modal-form"
|
||||||
<p>
|
accept-charset="UTF-8"
|
||||||
<label for="node-name">Name</label>
|
action=""
|
||||||
<input
|
method="post"
|
||||||
id="node-name"
|
>
|
||||||
class="form-control"
|
{% csrf_token %}
|
||||||
name="node-name"
|
|
||||||
value="{{ node.name|safe }}"
|
<div class="form-control mb-3">
|
||||||
/>
|
<label class="label" for="node-name">
|
||||||
</p>
|
<span class="label-text">Name</span>
|
||||||
<p>
|
</label>
|
||||||
<label for="node-description">Description</label>
|
<input
|
||||||
<input
|
id="node-name"
|
||||||
id="node-description"
|
class="input input-bordered w-full"
|
||||||
type="text"
|
name="node-name"
|
||||||
class="form-control"
|
value="{{ node.name|safe }}"
|
||||||
value="{{ node.description|safe }}"
|
required
|
||||||
name="node-description"
|
/>
|
||||||
/>
|
</div>
|
||||||
</p>
|
|
||||||
</form>
|
<div class="form-control mb-3">
|
||||||
</div>
|
<label class="label" for="node-description">
|
||||||
<div class="modal-footer">
|
<span class="label-text">Description</span>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
</label>
|
||||||
Close
|
<input
|
||||||
</button>
|
id="node-description"
|
||||||
<button
|
type="text"
|
||||||
type="button"
|
class="input input-bordered w-full"
|
||||||
class="btn btn-primary"
|
value="{{ node.description|safe }}"
|
||||||
id="edit-node-modal-submit"
|
name="node-description"
|
||||||
>
|
/>
|
||||||
Create
|
</div>
|
||||||
</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-node-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-node-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-node-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,70 +1,91 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package -->
|
|
||||||
<div id="edit_package_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_package_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Update Package</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Update Package</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>Edit a Package.</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-package-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="edit-package-modal-form"
|
||||||
<p>
|
accept-charset="UTF-8"
|
||||||
<label for="package-name">Name</label>
|
action=""
|
||||||
<input
|
method="post"
|
||||||
id="package-name"
|
>
|
||||||
class="form-control"
|
{% csrf_token %}
|
||||||
name="package-name"
|
|
||||||
value="{{ package.name|safe }}"
|
<div class="form-control mb-3">
|
||||||
/>
|
<label class="label" for="package-name">
|
||||||
</p>
|
<span class="label-text">Name</span>
|
||||||
<p>
|
</label>
|
||||||
<label for="package-description">Description</label>
|
<input
|
||||||
<input
|
id="package-name"
|
||||||
id="package-description"
|
class="input input-bordered w-full"
|
||||||
type="text"
|
name="package-name"
|
||||||
class="form-control"
|
value="{{ package.name|safe }}"
|
||||||
value="{{ package.description|safe }}"
|
required
|
||||||
name="package-description"
|
/>
|
||||||
/>
|
</div>
|
||||||
</p>
|
|
||||||
</form>
|
<div class="form-control mb-3">
|
||||||
</div>
|
<label class="label" for="package-description">
|
||||||
<div class="modal-footer">
|
<span class="label-text">Description</span>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
</label>
|
||||||
Close
|
<input
|
||||||
</button>
|
id="package-description"
|
||||||
<button
|
type="text"
|
||||||
type="button"
|
class="input input-bordered w-full"
|
||||||
class="btn btn-primary"
|
value="{{ package.description|safe }}"
|
||||||
id="edit-package-modal-submit"
|
name="package-description"
|
||||||
>
|
/>
|
||||||
Update
|
</div>
|
||||||
</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-package-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-package-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-package-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,264 +1,271 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package Permission -->
|
<!-- Edit Package Permissions -->
|
||||||
<div id="edit_package_permissions_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_package_permissions_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="{
|
||||||
<h5 class="modal-title">Grant or Revoke Permissions</h5>
|
updatePermissions(checkbox) {
|
||||||
<button
|
const parts = checkbox.id.split('_');
|
||||||
type="button"
|
const perm = parts[0];
|
||||||
class="close"
|
const id = parts[1];
|
||||||
data-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>
|
|
||||||
Modify permissions for this package. Note that if you give
|
|
||||||
<code>write</code> permissions to a user or group,
|
|
||||||
<code>read</code> permissions will be granted automatically.
|
|
||||||
<br />
|
|
||||||
To allow users to perform destructive actions, such as deleting the
|
|
||||||
package, <code>owner</code>
|
|
||||||
permissions must be granted.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="row">
|
const readBox = document.getElementById('read_' + id);
|
||||||
<div class="col-xs-4">
|
const writeBox = document.getElementById('write_' + id);
|
||||||
<legend>User or Group</legend>
|
const ownerBox = document.getElementById('owner_' + id);
|
||||||
</div>
|
|
||||||
<div class="col-xs-2">
|
|
||||||
<legend>Read</legend>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-2">
|
|
||||||
<legend>Write</legend>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-2">
|
|
||||||
<legend>Owner</legend>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
if (perm === 'read' && !readBox.checked) {
|
||||||
<form
|
writeBox.checked = false;
|
||||||
id="modal-form-permissions"
|
ownerBox.checked = false;
|
||||||
class="form-inline"
|
}
|
||||||
role="form"
|
|
||||||
accept-charset="UTF-8"
|
if (perm === 'write') {
|
||||||
action=""
|
if (writeBox.checked) {
|
||||||
data-remote="true"
|
readBox.checked = true;
|
||||||
method="post"
|
} else {
|
||||||
>
|
ownerBox.checked = false;
|
||||||
{% csrf_token %}
|
}
|
||||||
<div class="col-xs-4">
|
}
|
||||||
<select
|
|
||||||
id="select_grantee"
|
if (perm === 'owner' && ownerBox.checked) {
|
||||||
name="grantee"
|
readBox.checked = true;
|
||||||
data-actions-box="true"
|
writeBox.checked = true;
|
||||||
class="selPackages"
|
}
|
||||||
data-width="100%"
|
}
|
||||||
>
|
}"
|
||||||
<option disabled selected>User</option>
|
>
|
||||||
|
<div class="modal-box max-w-2xl">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="text-lg font-bold">Grant or Revoke Permissions</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2">
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="mb-4">
|
||||||
|
Modify permissions for this package. Note that if you give
|
||||||
|
<code class="badge badge-ghost">write</code> permissions to a user or
|
||||||
|
group, <code class="badge badge-ghost">read</code> permissions will be
|
||||||
|
granted automatically.
|
||||||
|
<br />
|
||||||
|
To allow users to perform destructive actions, such as deleting the
|
||||||
|
package, <code class="badge badge-ghost">owner</code> permissions must
|
||||||
|
be granted.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Add New Permission -->
|
||||||
|
<form
|
||||||
|
id="modal-form-permissions"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action=""
|
||||||
|
method="post"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="grid grid-cols-12 gap-2 items-end">
|
||||||
|
<div class="col-span-5">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">User or Group</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="select_grantee"
|
||||||
|
name="grantee"
|
||||||
|
class="select select-bordered w-full select-sm"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<optgroup label="Users">
|
||||||
{% for u in users %}
|
{% for u in users %}
|
||||||
<option value="{{ u.url }}">{{ u.username }}</option>
|
<option value="{{ u.url }}">{{ u.username }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<option disabled>Groups</option>
|
</optgroup>
|
||||||
|
<optgroup label="Groups">
|
||||||
{% for g in groups %}
|
{% for g in groups %}
|
||||||
<option value="{{ g.url }}">{{ g.name|safe }}</option>
|
<option value="{{ g.url }}">{{ g.name|safe }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</optgroup>
|
||||||
</div>
|
</select>
|
||||||
<div class="col-xs-2">
|
</div>
|
||||||
<input type="checkbox" name="read" id="read_new" />
|
<div class="col-span-2 text-center">
|
||||||
</div>
|
<label class="label justify-center">
|
||||||
<div class="col-xs-2">
|
<span class="label-text">Read</span>
|
||||||
<input type="checkbox" name="write" id="write_new" />
|
</label>
|
||||||
</div>
|
<input
|
||||||
<div class="col-xs-2">
|
type="checkbox"
|
||||||
<input type="checkbox" name="owner" id="owner_new" />
|
name="read"
|
||||||
</div>
|
id="read_new"
|
||||||
<div class="col-xs-2">
|
class="checkbox"
|
||||||
<button
|
@click="updatePermissions($el)"
|
||||||
type="submit"
|
/>
|
||||||
style="width:60%;"
|
</div>
|
||||||
class="btn col-xs-2 modify-perm-button"
|
<div class="col-span-2 text-center">
|
||||||
>
|
<label class="label justify-center">
|
||||||
<span class="glyphicon glyphicon-plus"></span>
|
<span class="label-text">Write</span>
|
||||||
</button>
|
</label>
|
||||||
</div>
|
<input
|
||||||
</form>
|
type="checkbox"
|
||||||
|
name="write"
|
||||||
|
id="write_new"
|
||||||
|
class="checkbox"
|
||||||
|
@click="updatePermissions($el)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2 text-center">
|
||||||
|
<label class="label justify-center">
|
||||||
|
<span class="label-text">Owner</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="owner"
|
||||||
|
id="owner_new"
|
||||||
|
class="checkbox"
|
||||||
|
@click="updatePermissions($el)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-1">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm">+</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
</form>
|
||||||
{% for up in user_permissions %}
|
|
||||||
<div class="row">
|
<!-- User Permissions -->
|
||||||
|
{% if user_permissions %}
|
||||||
|
<div class="divider">User Permissions</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{% for up in user_permissions %}
|
||||||
<form
|
<form
|
||||||
id="modal-form-permissions_{{ up.user.uuid }}"
|
id="modal-form-permissions_{{ up.user.uuid }}"
|
||||||
class="form-inline"
|
|
||||||
role="form"
|
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
action=""
|
action=""
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
method="post"
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col-xs-4">
|
<div class="grid grid-cols-12 gap-2 items-center">
|
||||||
{{ up.user.username }}
|
<div class="col-span-5 truncate">
|
||||||
<input type="hidden" name="grantee" value="{{ up.user.url }}" />
|
{{ up.user.username }}
|
||||||
</div>
|
<input
|
||||||
<div class="col-xs-2">
|
type="hidden"
|
||||||
<input
|
name="grantee"
|
||||||
type="checkbox"
|
value="{{ up.user.url }}"
|
||||||
name="read"
|
/>
|
||||||
id="read_{{ up.user.uuid }}"
|
</div>
|
||||||
{% if up.has_read %}checked{% endif %}
|
<div class="col-span-2 text-center">
|
||||||
/>
|
<input
|
||||||
</div>
|
type="checkbox"
|
||||||
<div class="col-xs-2">
|
name="read"
|
||||||
<input
|
id="read_{{ up.user.uuid }}"
|
||||||
type="checkbox"
|
class="checkbox"
|
||||||
name="write"
|
{% if up.has_read %}checked{% endif %}
|
||||||
id="write_{{ up.user.uuid }}"
|
@click="updatePermissions($el)"
|
||||||
{% if up.has_write %}checked{% endif %}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="col-span-2 text-center">
|
||||||
<div class="col-xs-2">
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
name="write"
|
||||||
name="owner"
|
id="write_{{ up.user.uuid }}"
|
||||||
id="owner_{{ up.user.uuid }}"
|
class="checkbox"
|
||||||
{% if up.has_all %}checked{% endif %}
|
{% if up.has_write %}checked{% endif %}
|
||||||
/>
|
@click="updatePermissions($el)"
|
||||||
</div>
|
/>
|
||||||
<div class="col-xs-2">
|
</div>
|
||||||
<button
|
<div class="col-span-2 text-center">
|
||||||
type="submit"
|
<input
|
||||||
style="width:60%;"
|
type="checkbox"
|
||||||
class="btn col-xs-2 modify-perm-button"
|
name="owner"
|
||||||
>
|
id="owner_{{ up.user.uuid }}"
|
||||||
<span class="glyphicon glyphicon-ok"></span>
|
class="checkbox"
|
||||||
</button>
|
{% if up.has_all %}checked{% endif %}
|
||||||
|
@click="updatePermissions($el)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-1">
|
||||||
|
<button type="submit" class="btn btn-sm btn-ghost">✓</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
</div>
|
||||||
<p></p>
|
{% endif %}
|
||||||
{% for gp in group_permissions %}
|
|
||||||
<div class="row">
|
<!-- Group Permissions -->
|
||||||
|
{% if group_permissions %}
|
||||||
|
<div class="divider">Group Permissions</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{% for gp in group_permissions %}
|
||||||
<form
|
<form
|
||||||
id="modal-form-permissions_{{ gp.user.uuid }}"
|
id="modal-form-permissions_{{ gp.group.uuid }}"
|
||||||
class="form-inline"
|
|
||||||
role="form"
|
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
action=""
|
action=""
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
method="post"
|
||||||
>
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col-xs-4">
|
<div class="grid grid-cols-12 gap-2 items-center">
|
||||||
{{ gp.group.name|safe }}
|
<div class="col-span-5 truncate">
|
||||||
<input
|
{{ gp.group.name|safe }}
|
||||||
type="hidden"
|
<input
|
||||||
name="grantee"
|
type="hidden"
|
||||||
value="{{ gp.group.url }}"
|
name="grantee"
|
||||||
/>
|
value="{{ gp.group.url }}"
|
||||||
</div>
|
/>
|
||||||
<div class="col-xs-2">
|
</div>
|
||||||
<input
|
<div class="col-span-2 text-center">
|
||||||
type="checkbox"
|
<input
|
||||||
name="read"
|
type="checkbox"
|
||||||
id="read_{{ gp.group.uuid }}"
|
name="read"
|
||||||
{% if gp.has_read %}checked{% endif %}
|
id="read_{{ gp.group.uuid }}"
|
||||||
/>
|
class="checkbox"
|
||||||
</div>
|
{% if gp.has_read %}checked{% endif %}
|
||||||
<div class="col-xs-2">
|
@click="updatePermissions($el)"
|
||||||
<input
|
/>
|
||||||
type="checkbox"
|
</div>
|
||||||
name="write"
|
<div class="col-span-2 text-center">
|
||||||
id="write_{{ gp.group.uuid }}"
|
<input
|
||||||
{% if gp.has_write %}checked{% endif %}
|
type="checkbox"
|
||||||
/>
|
name="write"
|
||||||
</div>
|
id="write_{{ gp.group.uuid }}"
|
||||||
<div class="col-xs-2">
|
class="checkbox"
|
||||||
<input
|
{% if gp.has_write %}checked{% endif %}
|
||||||
type="checkbox"
|
@click="updatePermissions($el)"
|
||||||
name="owner"
|
/>
|
||||||
id="owner_{{ gp.group.uuid }}"
|
</div>
|
||||||
{% if gp.has_all %}checked{% endif %}
|
<div class="col-span-2 text-center">
|
||||||
/>
|
<input
|
||||||
</div>
|
type="checkbox"
|
||||||
<div class="col-xs-2">
|
name="owner"
|
||||||
<button
|
id="owner_{{ gp.group.uuid }}"
|
||||||
type="submit"
|
class="checkbox"
|
||||||
style="width:60%;"
|
{% if gp.has_all %}checked{% endif %}
|
||||||
class="btn col-xs-2 modify-perm-button"
|
@click="updatePermissions($el)"
|
||||||
>
|
/>
|
||||||
<span class="glyphicon glyphicon-ok"></span>
|
</div>
|
||||||
</button>
|
<div class="col-span-1">
|
||||||
|
<button type="submit" class="btn btn-sm btn-ghost">✓</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
|
||||||
Close
|
<!-- Footer -->
|
||||||
</button>
|
<div class="modal-action">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary"
|
class="btn"
|
||||||
id="edit-package-modal-submit"
|
onclick="this.closest('dialog').close()"
|
||||||
>
|
>
|
||||||
Update
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function checkboxClick() {
|
|
||||||
// id looks like read_3cadef24-220e-4587-9fa5-0e9a17aca2da
|
|
||||||
parts = this.id.split("_");
|
|
||||||
perm = parts[0];
|
|
||||||
id = parts[1];
|
|
||||||
|
|
||||||
readbox = "#read_" + id;
|
<!-- Backdrop -->
|
||||||
writebox = "#write_" + id;
|
<form method="dialog" class="modal-backdrop">
|
||||||
ownerbox = "#owner_" + id;
|
<button>close</button>
|
||||||
|
</form>
|
||||||
if (perm == "read" && !$(readbox).prop("checked")) {
|
</dialog>
|
||||||
$(writebox).prop("checked", false);
|
|
||||||
$(ownerbox).prop("checked", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (perm == "write") {
|
|
||||||
if ($(writebox).prop("checked")) {
|
|
||||||
$(readbox).prop("checked", true);
|
|
||||||
}
|
|
||||||
if (!$(writebox).prop("checked")) {
|
|
||||||
$(ownerbox).prop("checked", false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (perm == "owner") {
|
|
||||||
if ($(ownerbox).prop("checked")) {
|
|
||||||
$(readbox).prop("checked", true);
|
|
||||||
$(writebox).prop("checked", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$("#edit-package-modal-submit").click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$("#edit-package-modal-form").submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#select_grantee").selectpicker();
|
|
||||||
|
|
||||||
// Add click functions to permission checkboxes
|
|
||||||
$('[id^="read_"]').on("click", checkboxClick);
|
|
||||||
$('[id^="write_"]').on("click", checkboxClick);
|
|
||||||
$('[id^="owner_"]').on("click", checkboxClick);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,82 +1,119 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package -->
|
|
||||||
<div id="edit_password_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_password_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Update your Password</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Update your Password</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>To change your password please fill out the following inputs</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-password-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<p class="mb-4">
|
||||||
{% csrf_token %}
|
To change your password please fill out the following inputs
|
||||||
<p>
|
</p>
|
||||||
<label for="old-password">Old Password</label>
|
|
||||||
<input
|
<form
|
||||||
id="old-password"
|
id="edit-password-modal-form"
|
||||||
class="form-control"
|
accept-charset="UTF-8"
|
||||||
name="old-password"
|
action=""
|
||||||
type="password"
|
method="post"
|
||||||
autocomplete="current-password"
|
>
|
||||||
/>
|
{% csrf_token %}
|
||||||
</p>
|
<input type="hidden" name="hidden" value="update-password" />
|
||||||
<p>
|
|
||||||
<label for="new-password">New Password</label>
|
<div class="form-control mb-3">
|
||||||
<input
|
<label class="label" for="old-password">
|
||||||
id="new-password"
|
<span class="label-text">Old Password</span>
|
||||||
class="form-control"
|
</label>
|
||||||
name="new-password"
|
<input
|
||||||
type="password"
|
id="old-password"
|
||||||
,
|
class="input input-bordered w-full"
|
||||||
autocomplete="new-password"
|
name="old-password"
|
||||||
/>
|
type="password"
|
||||||
</p>
|
autocomplete="current-password"
|
||||||
<p>
|
required
|
||||||
<label for="new-password-repeat">Repeat New Password</label>
|
/>
|
||||||
<input
|
</div>
|
||||||
id="new-password-repeat"
|
|
||||||
class="form-control"
|
<div class="form-control mb-3">
|
||||||
name="new-password-repeat"
|
<label class="label" for="new-password">
|
||||||
type="password"
|
<span class="label-text">New Password</span>
|
||||||
autocomplete="new-password"
|
</label>
|
||||||
/>
|
<input
|
||||||
</p>
|
id="new-password"
|
||||||
</form>
|
class="input input-bordered w-full"
|
||||||
</div>
|
name="new-password"
|
||||||
<div class="modal-footer">
|
type="password"
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
autocomplete="new-password"
|
||||||
Close
|
required
|
||||||
</button>
|
/>
|
||||||
<button
|
</div>
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
<div class="form-control mb-3">
|
||||||
id="edit-password-modal-submit"
|
<label class="label" for="new-password-repeat">
|
||||||
>
|
<span class="label-text">Repeat New Password</span>
|
||||||
Update
|
</label>
|
||||||
</button>
|
<input
|
||||||
</div>
|
id="new-password-repeat"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
name="new-password-repeat"
|
||||||
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
required
|
||||||
|
@input="validatePasswordMatch('new-password', 'new-password-repeat')"
|
||||||
|
/>
|
||||||
|
<label class="label" x-show="errors['new-password-repeat']">
|
||||||
|
<span
|
||||||
|
class="label-text-alt text-error"
|
||||||
|
x-text="errors['new-password-repeat']"
|
||||||
|
></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="if (validatePasswordMatch('new-password', 'new-password-repeat')) submit('edit-password-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-password-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-password-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,72 +1,92 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Pathway -->
|
|
||||||
<div id="edit_pathway_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_pathway_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Edit Pathway</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Edit Pathway</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>Edit Pathway.</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-pathway-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="edit-pathway-modal-form"
|
||||||
<p>
|
accept-charset="UTF-8"
|
||||||
<label for="pathway-name">Name</label>
|
action=""
|
||||||
<input
|
method="post"
|
||||||
id="pathway-name"
|
>
|
||||||
class="form-control"
|
{% csrf_token %}
|
||||||
name="pathway-name"
|
|
||||||
value="{{ pathway.name|safe }}"
|
<div class="form-control mb-3">
|
||||||
/>
|
<label class="label" for="pathway-name">
|
||||||
</p>
|
<span class="label-text">Name</span>
|
||||||
<p>
|
</label>
|
||||||
<label for="pathway-description">Description</label>
|
<input
|
||||||
<textarea
|
id="pathway-name"
|
||||||
id="pathway-description"
|
class="input input-bordered w-full"
|
||||||
type="text"
|
name="pathway-name"
|
||||||
class="form-control"
|
value="{{ pathway.name|safe }}"
|
||||||
name="pathway-description"
|
required
|
||||||
rows="10"
|
/>
|
||||||
>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="pathway-description">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="pathway-description"
|
||||||
|
class="textarea textarea-bordered w-full"
|
||||||
|
name="pathway-description"
|
||||||
|
rows="10"
|
||||||
|
>
|
||||||
{{ pathway.description|safe }}</textarea
|
{{ pathway.description|safe }}</textarea
|
||||||
>
|
>
|
||||||
</p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
<!-- Footer -->
|
||||||
Close
|
<div class="modal-action">
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn"
|
||||||
class="btn btn-primary"
|
onclick="this.closest('dialog').close()"
|
||||||
id="edit-pathway-modal-submit"
|
:disabled="isSubmitting"
|
||||||
>
|
>
|
||||||
Update
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-pathway-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-pathway-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-pathway-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,163 +1,156 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Package -->
|
<!-- Edit Prediction Setting -->
|
||||||
<div id="update_prediction_settings_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="update_prediction_settings_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Update Prediction Setting</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box max-w-3xl">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="text-lg font-bold">Update Prediction Setting</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>
|
>
|
||||||
To update your prediction setting modify parameters in the form below
|
✕
|
||||||
und click "Update"
|
</button>
|
||||||
</p>
|
</form>
|
||||||
<form
|
|
||||||
id="edit-prediction-setting-modal-form"
|
<!-- Body -->
|
||||||
accept-charset="UTF-8"
|
<div class="py-4">
|
||||||
action=""
|
<p class="mb-4">
|
||||||
data-remote="true"
|
To update your prediction setting modify parameters in the form below
|
||||||
method="post"
|
and click "Update"
|
||||||
>
|
</p>
|
||||||
{% csrf_token %}
|
<form
|
||||||
<div id="prediction-setting" class="panel-collapse in collapse">
|
id="edit-prediction-setting-modal-form"
|
||||||
<div class="panel-body list-group-item">
|
accept-charset="UTF-8"
|
||||||
<table class="table-bordered table-hover table">
|
action=""
|
||||||
<tr style="background-color: rgba(0, 0, 0, 0.08);">
|
method="post"
|
||||||
<th scope="col" width="20%">Parameter</th>
|
>
|
||||||
<th scope="col" width="80%">Value</th>
|
{% csrf_token %}
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table table-zebra w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-1/5">Parameter</th>
|
||||||
|
<th class="w-4/5">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if 'model' in user.prediction_settings %}
|
||||||
|
<tr>
|
||||||
|
<td>Model</td>
|
||||||
|
<td>
|
||||||
|
<div class="form-control">
|
||||||
|
<select
|
||||||
|
id="model"
|
||||||
|
name="model"
|
||||||
|
class="select select-bordered w-full"
|
||||||
|
>
|
||||||
|
{% for m in models %}
|
||||||
|
<option
|
||||||
|
value="{{ m.id }}"
|
||||||
|
{% if user.prediction_settings.model.url == m.url %}selected{% endif %}
|
||||||
|
>
|
||||||
|
{{ m.name|safe }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{% for k, v in user.prediction_settings.model_parameters.items %}
|
||||||
|
{% if k == 'threshold' %}
|
||||||
|
<div class="form-control mt-2">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Threshold</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
name="{{ k }}"
|
||||||
|
value="{{ v }}"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.05"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tbody>
|
{% endif %}
|
||||||
{% if 'model' in user.prediction_settings %}
|
{% for k, v in user.prediction_settings.truncator.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td width="20%">Model</td>
|
<td>
|
||||||
<td width="80%">
|
{% if k == 'max_nodes' %}
|
||||||
<table
|
Max Nodes
|
||||||
width="100%"
|
{% elif k == 'max_depth' %}
|
||||||
class="table-bordered table-hover table"
|
Max Depth
|
||||||
>
|
{% endif %}
|
||||||
<tbody>
|
</td>
|
||||||
<tr>
|
<td>
|
||||||
<td colspan="2">
|
{% if k == 'max_nodes' %}
|
||||||
<select
|
<input
|
||||||
id="model"
|
type="number"
|
||||||
name="model"
|
class="input input-bordered w-full"
|
||||||
class="form-control"
|
name="{{ k }}"
|
||||||
data-width="100%"
|
value="{{ v }}"
|
||||||
>
|
min="1"
|
||||||
{% for m in models %}
|
max="50"
|
||||||
<option
|
step="1"
|
||||||
value="{{ m.id }}"
|
/>
|
||||||
{% if user.prediction_settings.model.url == m.url %}selected{% endif %}
|
{% elif k == 'max_depth' %}
|
||||||
>
|
<input
|
||||||
{{ m.name|safe }}
|
type="number"
|
||||||
</option>
|
class="input input-bordered w-full"
|
||||||
{% endfor %}
|
name="{{ k }}"
|
||||||
</select>
|
value="{{ v }}"
|
||||||
</td>
|
min="1"
|
||||||
</tr>
|
max="8"
|
||||||
{% for k, v in user.prediction_settings.model_parameters.items %}
|
step="1"
|
||||||
<tr>
|
/>
|
||||||
<th width="20%">Model Parameter</th>
|
{% endif %}
|
||||||
<th width="80%">Parameter Value</th>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
{% endfor %}
|
||||||
<td width="20%">
|
</tbody>
|
||||||
{% if k == 'threshold' %}
|
</table>
|
||||||
Threshold
|
</div>
|
||||||
{% endif %}
|
</form>
|
||||||
</td>
|
</div>
|
||||||
<td width="80%">
|
|
||||||
{% if k == 'threshold' %}
|
<!-- Footer -->
|
||||||
<input
|
<div class="modal-action">
|
||||||
type="number"
|
<button
|
||||||
class="form-control"
|
type="button"
|
||||||
name="{{ k }}"
|
class="btn"
|
||||||
value="{{ v }}"
|
onclick="this.closest('dialog').close()"
|
||||||
min="0"
|
:disabled="isSubmitting"
|
||||||
max="1"
|
>
|
||||||
step="0.05"
|
Close
|
||||||
/>
|
</button>
|
||||||
{% endif %}
|
<button
|
||||||
</td>
|
type="button"
|
||||||
</tr>
|
class="btn btn-primary"
|
||||||
{% endfor %}
|
@click="submit('edit-prediction-setting-modal-form')"
|
||||||
</tbody>
|
:disabled="isSubmitting"
|
||||||
</table>
|
>
|
||||||
</td>
|
<span x-show="!isSubmitting">Update</span>
|
||||||
</tr>
|
<span
|
||||||
{% endif %}
|
x-show="isSubmitting"
|
||||||
{% for k, v in user.prediction_settings.truncator.items %}
|
class="loading loading-spinner loading-sm"
|
||||||
<tr>
|
></span>
|
||||||
<td>
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
<p>
|
</button>
|
||||||
{% if k == 'max_nodes' %}
|
|
||||||
Max Nodes
|
|
||||||
{% elif k == 'max_depth' %}
|
|
||||||
Max Depth
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<p>
|
|
||||||
{% if k == 'max_nodes' %}
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="form-control"
|
|
||||||
name="{{ k }}"
|
|
||||||
value="{{ v }}"
|
|
||||||
min="1"
|
|
||||||
max="50"
|
|
||||||
step="1"
|
|
||||||
/>
|
|
||||||
{% elif k == 'max_depth' %}
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="form-control"
|
|
||||||
name="{{ k }}"
|
|
||||||
value="{{ v }}"
|
|
||||||
min="1"
|
|
||||||
max="8"
|
|
||||||
step="1"
|
|
||||||
/>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</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="edit-prediction-setting-modal-submit"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-prediction-setting-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-prediction-setting-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,69 +1,91 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Reaction -->
|
|
||||||
<div id="edit_reaction_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_reaction_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<button
|
@close="reset()"
|
||||||
type="button"
|
>
|
||||||
class="close"
|
<div class="modal-box">
|
||||||
data-dismiss="modal"
|
<!-- Header -->
|
||||||
aria-label="Close"
|
<h3 class="font-bold text-lg">Update Reaction</h3>
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
</button>
|
<form method="dialog">
|
||||||
<h3 class="modal-title">Update Reaction</h3>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<form
|
>
|
||||||
id="edit-reaction-modal-form"
|
✕
|
||||||
accept-charset="UTF-8"
|
</button>
|
||||||
action=""
|
</form>
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
<!-- Body -->
|
||||||
>
|
<div class="py-4">
|
||||||
{% csrf_token %}
|
<form
|
||||||
<p>
|
id="edit-reaction-modal-form"
|
||||||
<label for="reaction-name">Name</label>
|
accept-charset="UTF-8"
|
||||||
<input
|
action=""
|
||||||
id="reaction-name"
|
method="post"
|
||||||
class="form-control"
|
>
|
||||||
name="reaction-name"
|
{% csrf_token %}
|
||||||
value="{{ reaction.name|safe }}"
|
|
||||||
/>
|
<div class="form-control mb-3">
|
||||||
</p>
|
<label class="label" for="reaction-name">
|
||||||
<p>
|
<span class="label-text">Name</span>
|
||||||
<label for="reaction-description">Description</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="reaction-description"
|
id="reaction-name"
|
||||||
type="text"
|
class="input input-bordered w-full"
|
||||||
class="form-control"
|
name="reaction-name"
|
||||||
value="{{ reaction.description|safe }}"
|
value="{{ reaction.name|safe }}"
|
||||||
name="reaction-description"
|
required
|
||||||
/>
|
/>
|
||||||
</p>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
<div class="form-control mb-3">
|
||||||
<div class="modal-footer">
|
<label class="label" for="reaction-description">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
<span class="label-text">Description</span>
|
||||||
Close
|
</label>
|
||||||
</button>
|
<input
|
||||||
<button
|
id="reaction-description"
|
||||||
type="button"
|
type="text"
|
||||||
class="btn btn-primary"
|
class="input input-bordered w-full"
|
||||||
id="edit-reaction-modal-submit"
|
value="{{ reaction.description|safe }}"
|
||||||
>
|
name="reaction-description"
|
||||||
Update
|
/>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-reaction-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-reaction-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-reaction-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,69 +1,91 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit Rule -->
|
|
||||||
<div id="edit_rule_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_rule_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<button
|
@close="reset()"
|
||||||
type="button"
|
>
|
||||||
class="close"
|
<div class="modal-box">
|
||||||
data-dismiss="modal"
|
<!-- Header -->
|
||||||
aria-label="Close"
|
<h3 class="font-bold text-lg">Update Rule</h3>
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
</button>
|
<form method="dialog">
|
||||||
<h3 class="modal-title">Update Rule</h3>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<form
|
>
|
||||||
id="edit-rule-modal-form"
|
✕
|
||||||
accept-charset="UTF-8"
|
</button>
|
||||||
action=""
|
</form>
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
<!-- Body -->
|
||||||
>
|
<div class="py-4">
|
||||||
{% csrf_token %}
|
<form
|
||||||
<p>
|
id="edit-rule-modal-form"
|
||||||
<label for="rule-name">Name</label>
|
accept-charset="UTF-8"
|
||||||
<input
|
action=""
|
||||||
id="rule-name"
|
method="post"
|
||||||
class="form-control"
|
>
|
||||||
name="rule-name"
|
{% csrf_token %}
|
||||||
value="{{ rule.name|safe }}"
|
|
||||||
/>
|
<div class="form-control mb-3">
|
||||||
</p>
|
<label class="label" for="rule-name">
|
||||||
<p>
|
<span class="label-text">Name</span>
|
||||||
<label for="rule-description">Description</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="rule-description"
|
id="rule-name"
|
||||||
type="text"
|
class="input input-bordered w-full"
|
||||||
class="form-control"
|
name="rule-name"
|
||||||
value="{{ rule.description|safe }}"
|
value="{{ rule.name|safe }}"
|
||||||
name="rule-description"
|
required
|
||||||
/>
|
/>
|
||||||
</p>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
<div class="form-control mb-3">
|
||||||
<div class="modal-footer">
|
<label class="label" for="rule-description">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
<span class="label-text">Description</span>
|
||||||
Close
|
</label>
|
||||||
</button>
|
<input
|
||||||
<button
|
id="rule-description"
|
||||||
type="button"
|
type="text"
|
||||||
class="btn btn-primary"
|
class="input input-bordered w-full"
|
||||||
id="edit-rule-modal-submit"
|
value="{{ rule.description|safe }}"
|
||||||
>
|
name="rule-description"
|
||||||
Update
|
/>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-rule-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-rule-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-rule-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,110 +1,128 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Edit User -->
|
|
||||||
<div id="edit_user_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="edit_user_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h5 class="modal-title">Update User Defaults</h5>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Update User Defaults</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
<p>Edit User Defaults.</p>
|
>
|
||||||
<form
|
✕
|
||||||
id="edit-user-modal-form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action=""
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="edit-user-modal-form"
|
||||||
<p>
|
accept-charset="UTF-8"
|
||||||
<label for="default-package">Default Package</label>
|
action=""
|
||||||
<select
|
method="post"
|
||||||
id="default-package"
|
>
|
||||||
name="default-package"
|
{% csrf_token %}
|
||||||
class="form-control"
|
|
||||||
data-width="100%"
|
<div class="form-control mb-3">
|
||||||
>
|
<label class="label" for="default-package">
|
||||||
<option disabled>Select a Package</option>
|
<span class="label-text">Default Package</span>
|
||||||
{% for p in meta.writeable_packages %}
|
</label>
|
||||||
<option
|
<select
|
||||||
value="{{ p.url }}"
|
id="default-package"
|
||||||
{% if p.id == meta.user.default_package.id %}selected{% endif %}
|
name="default-package"
|
||||||
>
|
class="select select-bordered w-full"
|
||||||
{{ p.name|safe }}
|
>
|
||||||
</option>
|
<option disabled>Select a Package</option>
|
||||||
{% endfor %}
|
{% for p in meta.writeable_packages %}
|
||||||
</select>
|
<option
|
||||||
</p>
|
value="{{ p.url }}"
|
||||||
<p>
|
{% if p.id == meta.user.default_package.id %}selected{% endif %}
|
||||||
<label for="default-group">Default Group</label>
|
>
|
||||||
<select
|
{{ p.name|safe }}
|
||||||
id="default-group"
|
</option>
|
||||||
name="default-group"
|
{% endfor %}
|
||||||
class="form-control"
|
</select>
|
||||||
data-width="100%"
|
</div>
|
||||||
>
|
|
||||||
<option disabled>Select a Group</option>
|
<div class="form-control mb-3">
|
||||||
{% for g in meta.available_groups %}
|
<label class="label" for="default-group">
|
||||||
<option
|
<span class="label-text">Default Group</span>
|
||||||
value="{{ g.url }}"
|
</label>
|
||||||
{% if g.id == meta.user.default_group.id %}selected{% endif %}
|
<select
|
||||||
>
|
id="default-group"
|
||||||
{{ g.name|safe }}
|
name="default-group"
|
||||||
</option>
|
class="select select-bordered w-full"
|
||||||
{% endfor %}
|
>
|
||||||
</select>
|
<option disabled>Select a Group</option>
|
||||||
</p>
|
{% for g in meta.available_groups %}
|
||||||
<p>
|
<option
|
||||||
<label for="default-prediction-setting"
|
value="{{ g.url }}"
|
||||||
>Default Prediction Setting</label
|
{% if g.id == meta.user.default_group.id %}selected{% endif %}
|
||||||
>
|
>
|
||||||
<select
|
{{ g.name|safe }}
|
||||||
id="default-prediction-setting"
|
</option>
|
||||||
name="default-prediction-setting"
|
{% endfor %}
|
||||||
class="form-control"
|
</select>
|
||||||
data-width="100%"
|
</div>
|
||||||
>
|
|
||||||
<option disabled>Select a Setting</option>
|
<div class="form-control mb-3">
|
||||||
{% for s in meta.available_settings %}
|
<label class="label" for="default-prediction-setting">
|
||||||
<option
|
<span class="label-text">Default Prediction Setting</span>
|
||||||
value="{{ s.url }}"
|
</label>
|
||||||
{% if s.id == meta.user.default_setting.id %}selected{% endif %}
|
<select
|
||||||
>
|
id="default-prediction-setting"
|
||||||
{{ s.name|safe }}
|
name="default-prediction-setting"
|
||||||
</option>
|
class="select select-bordered w-full"
|
||||||
{% endfor %}
|
>
|
||||||
</select>
|
<option disabled>Select a Setting</option>
|
||||||
</p>
|
{% for s in meta.available_settings %}
|
||||||
</form>
|
<option
|
||||||
</div>
|
value="{{ s.url }}"
|
||||||
<div class="modal-footer">
|
{% if s.id == meta.user.default_setting.id %}selected{% endif %}
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
>
|
||||||
Close
|
{{ s.name|safe }}
|
||||||
</button>
|
</option>
|
||||||
<button
|
{% endfor %}
|
||||||
type="button"
|
</select>
|
||||||
class="btn btn-primary"
|
</div>
|
||||||
id="edit-user-modal-submit"
|
</form>
|
||||||
>
|
</div>
|
||||||
Update
|
|
||||||
</button>
|
<!-- Footer -->
|
||||||
</div>
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-user-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Update</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Updating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#edit-user-modal-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#edit-user-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,93 +1,123 @@
|
|||||||
<div
|
<dialog
|
||||||
class="modal fade"
|
|
||||||
tabindex="-1"
|
|
||||||
id="evaluate_model_modal"
|
id="evaluate_model_modal"
|
||||||
role="dialog"
|
class="modal"
|
||||||
aria-labelledby="evaluate_model_modal"
|
x-data="modalForm()"
|
||||||
aria-hidden="true"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-box max-w-3xl">
|
||||||
<div class="modal-content">
|
<!-- Header -->
|
||||||
<div class="modal-header">
|
<h3 class="text-lg font-bold">Evaluate Model</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
<!-- Close button (X) -->
|
||||||
<span class="sr-only">Close</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
<h4 class="modal-title">Evaluate Model</h4>
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-body">
|
>
|
||||||
<form
|
✕
|
||||||
id="evaluate_model_form"
|
</button>
|
||||||
accept-charset="UTF-8"
|
</form>
|
||||||
action="{{ current_object.url }}"
|
|
||||||
data-remote="true"
|
<!-- Body -->
|
||||||
method="post"
|
<div class="py-4">
|
||||||
>
|
<form
|
||||||
{% csrf_token %}
|
id="evaluate_model_form"
|
||||||
<div class="jumbotron">
|
accept-charset="UTF-8"
|
||||||
|
action="{{ current_object.url }}"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<span>
|
||||||
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
|
While the model is evaluating, you can use the model for
|
||||||
predictions.
|
predictions.
|
||||||
</div>
|
</span>
|
||||||
<!-- Evaluation Packages -->
|
</div>
|
||||||
<label for="model-evaluation-packages">Evaluation Packages</label>
|
|
||||||
|
<!-- Evaluation Packages -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label" for="model-evaluation-packages">
|
||||||
|
<span class="label-text">Evaluation Packages</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="model-evaluation-packages"
|
id="model-evaluation-packages"
|
||||||
name="model-evaluation-packages"
|
name="model-evaluation-packages"
|
||||||
data-actions-box="true"
|
class="select select-bordered w-full h-48"
|
||||||
class="form-control"
|
|
||||||
multiple
|
multiple
|
||||||
data-width="100%"
|
required
|
||||||
>
|
>
|
||||||
<option disabled>Reviewed Packages</option>
|
<optgroup label="Reviewed Packages">
|
||||||
{% for obj in meta.readable_packages %}
|
{% for obj in meta.readable_packages %}
|
||||||
{% if obj.reviewed %}
|
{% if obj.reviewed %}
|
||||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
<option disabled>Unreviewed Packages</option>
|
<optgroup label="Unreviewed Packages">
|
||||||
{% 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|safe }}</option>
|
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt"
|
||||||
|
>Hold Ctrl/Cmd to select multiple packages</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Eval Type -->
|
<!-- Eval Type -->
|
||||||
<label for="model-evaluation-type">Evaluation Type</label>
|
<div class="form-control mt-4">
|
||||||
|
<label class="label" for="model-evaluation-type">
|
||||||
|
<span class="label-text">Evaluation Type</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="model-evaluation-type"
|
id="model-evaluation-type"
|
||||||
name="model-evaluation-type"
|
name="model-evaluation-type"
|
||||||
class="form-control"
|
class="select select-bordered w-full"
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<option disabled selected>Select evaluation type</option>
|
<option value="" disabled selected>Select evaluation type</option>
|
||||||
<option value="sg">Single Generation</option>
|
<option value="sg">Single Generation</option>
|
||||||
<option value="mg">Multiple Generations</option>
|
<option value="mg">Multiple Generations</option>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="hidden" value="evaluate" />
|
<input type="hidden" name="hidden" value="evaluate" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<a id="evaluate_model_form_submit" class="btn btn-primary" href="#"
|
<!-- Footer -->
|
||||||
>Evaluate</a
|
<div class="modal-action">
|
||||||
>
|
<button
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
type="button"
|
||||||
Cancel
|
class="btn"
|
||||||
</button>
|
onclick="this.closest('dialog').close()"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('evaluate_model_form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Evaluate</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Evaluating...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#model-evaluation-packages").selectpicker();
|
<button :disabled="isSubmitting">close</button>
|
||||||
|
</form>
|
||||||
$("#evaluate_model_form_submit").on("click", function (e) {
|
</dialog>
|
||||||
e.preventDefault();
|
|
||||||
$("#evaluate_model_form").submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,53 +1,56 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Export Package -->
|
|
||||||
<div id="export_package_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="export_package_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="modalForm()"
|
||||||
<h3 class="modal-title">Export Package as JSON</h3>
|
@close="reset()"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div class="modal-box">
|
||||||
class="close"
|
<!-- Header -->
|
||||||
data-dismiss="modal"
|
<h3 class="font-bold text-lg">Export Package as JSON</h3>
|
||||||
aria-label="Close"
|
|
||||||
>
|
<!-- Close button (X) -->
|
||||||
<span aria-hidden="true">×</span>
|
<form method="dialog">
|
||||||
</button>
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
<div class="modal-body">
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<p>
|
||||||
By clicking on Export the Package will be serialized into a JSON and
|
By clicking on Export the Package will be serialized into a JSON and
|
||||||
directly downloaded.
|
opened in a new tab.
|
||||||
<form
|
</p>
|
||||||
id="export-package-modal-form"
|
</div>
|
||||||
accept-charset="UTF-8"
|
|
||||||
action="{{ package.url }}"
|
<!-- Footer -->
|
||||||
data-remote="true"
|
<div class="modal-action">
|
||||||
method="GET"
|
<button
|
||||||
>
|
type="button"
|
||||||
<input type="hidden" name="export" value="true" />
|
class="btn"
|
||||||
</form>
|
onclick="this.closest('dialog').close()"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
<div class="modal-footer">
|
>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
Close
|
||||||
Close
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn btn-primary"
|
||||||
class="btn btn-primary"
|
@click="window.open('{{ package.url }}?export=true', '_blank'); $el.closest('dialog').close();"
|
||||||
id="export-package-modal-form-submit"
|
:disabled="isSubmitting"
|
||||||
>
|
>
|
||||||
Export
|
Export
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#export-package-modal-form-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#export-package-modal-form").submit();
|
</dialog>
|
||||||
$("#export_package_modal").modal("hide");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,109 +1,142 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Copy Object -->
|
<!-- Copy Object -->
|
||||||
<div id="generic_copy_object_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="generic_copy_object_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="{
|
||||||
<h3 class="modal-title">Copy {{ object_type|capfirst }}</h3>
|
isSubmitting: false,
|
||||||
<button
|
errorMessage: '',
|
||||||
type="button"
|
targetPackage: '',
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
reset() {
|
||||||
aria-label="Close"
|
this.isSubmitting = false;
|
||||||
>
|
this.errorMessage = '';
|
||||||
<span aria-hidden="true">×</span>
|
this.targetPackage = '';
|
||||||
</button>
|
},
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
async submit() {
|
||||||
<form
|
if (!this.targetPackage) return;
|
||||||
id="generic-copy-object-modal-form"
|
|
||||||
accept-charset="UTF-8"
|
this.isSubmitting = true;
|
||||||
data-remote="true"
|
this.errorMessage = '';
|
||||||
method="post"
|
|
||||||
>
|
try {
|
||||||
{% csrf_token %}
|
const response = await fetch(this.targetPackage, {
|
||||||
<label for="target-package"
|
method: 'POST',
|
||||||
>Select the Target Package you want to copy this {{ object_type }}
|
headers: {
|
||||||
into</label
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
>
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
hidden: 'copy',
|
||||||
|
object_to_copy: '{{ current_object.url }}'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.href = data.success;
|
||||||
|
} else {
|
||||||
|
if (data.error && data.error.indexOf('to the same package') > -1) {
|
||||||
|
this.errorMessage = 'The target Package is the same as the source Package. Please select another target!';
|
||||||
|
} else {
|
||||||
|
this.errorMessage = data.error || 'An error occurred';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.errorMessage = 'An error occurred while copying';
|
||||||
|
} finally {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset()"
|
||||||
|
>
|
||||||
|
<div class="modal-box">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="text-lg font-bold">Copy {{ object_type|capfirst }}</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<form
|
||||||
|
id="generic-copy-object-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label" for="target-package">
|
||||||
|
<span class="label-text">
|
||||||
|
Select the Target Package you want to copy this {{ object_type }}
|
||||||
|
into
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="target-package"
|
id="target-package"
|
||||||
name="target-package"
|
name="target-package"
|
||||||
data-actions-box="true"
|
class="select select-bordered w-full"
|
||||||
class="form-control"
|
x-model="targetPackage"
|
||||||
data-width="100%"
|
required
|
||||||
>
|
>
|
||||||
<option disabled selected>Select Target Package</option>
|
<option value="" disabled selected>Select Target Package</option>
|
||||||
{% for p in meta.writeable_packages %}
|
{% for p in meta.writeable_packages %}
|
||||||
<option value="{{ p.url }}">{{ p.name|safe }}</option>
|
<option value="{{ p.url }}">{{ p.name|safe }}</option>
|
||||||
`
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" name="hidden" value="copy" />
|
</div>
|
||||||
</form>
|
<input type="hidden" name="hidden" value="copy" />
|
||||||
<div
|
</form>
|
||||||
id="copy-object-error-message"
|
|
||||||
class="alert alert-danger"
|
<!-- Error Message -->
|
||||||
role="alert"
|
<div
|
||||||
style="display: none"
|
x-show="errorMessage"
|
||||||
></div>
|
x-cloak
|
||||||
</div>
|
class="alert alert-error mt-4"
|
||||||
<div class="modal-footer">
|
role="alert"
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
>
|
||||||
Close
|
<span x-text="errorMessage"></span>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
id="generic-copy-object-modal-form-submit"
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit()"
|
||||||
|
:disabled="isSubmitting || !targetPackage"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Copy</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Copying...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#generic-copy-object-modal-form-submit").click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$("#copy-object-error-message").hide();
|
|
||||||
|
|
||||||
const packageUrl = $("#target-package").find(":selected").val();
|
<!-- Backdrop -->
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
if (
|
<button :disabled="isSubmitting">close</button>
|
||||||
packageUrl === "Select Target Package" ||
|
</form>
|
||||||
packageUrl === "" ||
|
</dialog>
|
||||||
packageUrl === null ||
|
|
||||||
packageUrl === undefined
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const formData = {
|
|
||||||
hidden: "copy",
|
|
||||||
object_to_copy: "{{ current_object.url }}",
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: "post",
|
|
||||||
data: formData,
|
|
||||||
url: packageUrl,
|
|
||||||
success: function (data, textStatus) {
|
|
||||||
window.location.href = data.success;
|
|
||||||
},
|
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
|
||||||
if (jqXHR.responseJSON.error.indexOf("to the same package") > -1) {
|
|
||||||
$("#copy-object-error-message").append(
|
|
||||||
"<p>The target Package is the same as the source Package. Please select another target!</p>",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$("#copy-object-error-message").append(
|
|
||||||
"<p>" + jqXHR.responseJSON.error + "</p>",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$("#copy-object-error-message").show();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,58 +1,99 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Delete Object -->
|
<!--
|
||||||
<div id="generic_delete_modal" class="modal" tabindex="-1">
|
Generic Delete Modal - Delete object with confirmation
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
Migrated from Bootstrap + jQuery to DaisyUI + Alpine.js
|
||||||
<div class="modal-header">
|
Uses native <dialog> element with .showModal() API
|
||||||
<h3 class="modal-title">Delete {{ object_type|capfirst }}</h3>
|
-->
|
||||||
<button
|
|
||||||
type="button"
|
<dialog
|
||||||
class="close"
|
id="generic_delete_modal"
|
||||||
data-dismiss="modal"
|
class="modal"
|
||||||
aria-label="Close"
|
x-data="modalForm()"
|
||||||
|
@close="reset()"
|
||||||
|
>
|
||||||
|
<div class="modal-box">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="font-bold text-lg">Delete {{ object_type|capfirst }}</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<!-- Warning message -->
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="stroke-current shrink-0 h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">×</span>
|
<path
|
||||||
</button>
|
stroke-linecap="round"
|
||||||
</div>
|
stroke-linejoin="round"
|
||||||
<div class="modal-body">
|
stroke-width="2"
|
||||||
{% if object_type == 'user' %}
|
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"
|
||||||
Clicking "Delete" will <strong>permanently</strong> delete the User
|
/>
|
||||||
and associated data. This action can't be undone!
|
</svg>
|
||||||
{% else %}
|
<span>
|
||||||
Deletes the {{ object_type|capfirst }}. Related objects that depend on
|
{% if object_type == 'user' %}
|
||||||
this {{ object_type|capfirst }} will be deleted as well.
|
Clicking "Delete" will <strong>permanently</strong> delete the User
|
||||||
{% endif %}
|
and associated data. This action can't be undone!
|
||||||
<form
|
{% else %}
|
||||||
id="generic-delete-modal-form"
|
Deletes the {{ object_type|capfirst }}. Related objects that depend
|
||||||
accept-charset="UTF-8"
|
on this {{ object_type|capfirst }} will be deleted as well.
|
||||||
action="{{ current_object.url }}"
|
{% endif %}
|
||||||
data-remote="true"
|
</span>
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" id="hidden" name="hidden" value="delete" />
|
|
||||||
</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="generic-delete-modal-form-submit"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hidden form -->
|
||||||
|
<form
|
||||||
|
id="generic-delete-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{{ current_object.url }}"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="hidden" value="delete" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-error"
|
||||||
|
@click="submit('generic-delete-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Delete</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Deleting...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
<!-- Backdrop (click to close) -->
|
||||||
$(function () {
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#generic-delete-modal-form-submit").click(function (e) {
|
<button :disabled="isSubmitting">close</button>
|
||||||
e.preventDefault();
|
</form>
|
||||||
$("#generic-delete-modal-form").submit();
|
</dialog>
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,213 +1,173 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<style>
|
<dialog
|
||||||
.alias-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 6px;
|
|
||||||
cursor: text;
|
|
||||||
min-height: 38px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alias {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #5bc0de;
|
|
||||||
color: white;
|
|
||||||
padding: 4px 8px;
|
|
||||||
margin: 3px 3px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alias .remove {
|
|
||||||
margin-left: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alias-input {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 120px;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
margin: 3px 3px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control.alias-container {
|
|
||||||
height: auto;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="modal fade bs-modal-lg"
|
|
||||||
id="set_aliases_modal"
|
id="set_aliases_modal"
|
||||||
tabindex="-1"
|
class="modal"
|
||||||
aria-labelledby="set_aliases_modal"
|
x-data="{
|
||||||
aria-modal="true"
|
isSubmitting: false,
|
||||||
role="dialog"
|
aliases: [{% for alias in current_object.aliases %}'{{ alias|escapejs }}'{% if not forloop.last %},{% endif %}{% endfor %}],
|
||||||
>
|
newAlias: '',
|
||||||
<div class="modal-dialog modal-lg">
|
errorMessage: '',
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="modal-title">
|
|
||||||
Set Aliases for {{ current_object.name|safe }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form
|
|
||||||
id="set_aliases_modal_form"
|
|
||||||
accept-charset="UTF-8"
|
|
||||||
action="{{ current_object.url }}"
|
|
||||||
data-remote="true"
|
|
||||||
method="post"
|
|
||||||
>
|
|
||||||
{% csrf_token %}
|
|
||||||
<label for="alias-input">Aliases:</label>
|
|
||||||
<div class="form-control alias-container" id="alias-box">
|
|
||||||
{% for alias in current_object.aliases %}
|
|
||||||
<span class="alias"
|
|
||||||
>{{ alias|escape }}<span class="remove">×</span></span
|
|
||||||
>
|
|
||||||
{% endfor %}
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="alias-input"
|
|
||||||
class="alias-input"
|
|
||||||
placeholder="Add Alias..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div
|
|
||||||
id="add-alias-error-message"
|
|
||||||
class="alert alert-danger"
|
|
||||||
role="alert"
|
|
||||||
style="display: none"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-secondary pull-left"
|
|
||||||
data-dismiss="modal"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
id="set_aliases_modal_form_submit"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
function addAlias(aliasText) {
|
|
||||||
aliasText = aliasText.trim();
|
|
||||||
if (aliasText === "") return;
|
|
||||||
|
|
||||||
// Avoid duplicate aliass
|
reset() {
|
||||||
var exists = false;
|
this.isSubmitting = false;
|
||||||
$("#alias-box .alias").each(function () {
|
this.errorMessage = '';
|
||||||
if (
|
},
|
||||||
$(this).text().replace("×", "").trim().toLowerCase() ===
|
|
||||||
aliasText.toLowerCase()
|
addAlias() {
|
||||||
) {
|
const aliasText = this.newAlias.trim();
|
||||||
exists = true;
|
if (aliasText === '') return;
|
||||||
return false;
|
|
||||||
}
|
// Check for duplicates (case-insensitive)
|
||||||
});
|
const exists = this.aliases.some(
|
||||||
|
a => a.toLowerCase() === aliasText.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
var aliasHtml =
|
this.aliases.push(aliasText);
|
||||||
'<span class="alias">' +
|
|
||||||
$("<div>").text(aliasText).html() +
|
|
||||||
'<span class="remove">×</span></span>';
|
|
||||||
$(aliasHtml).insertBefore("#alias-input");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#alias-input").val("");
|
this.newAlias = '';
|
||||||
}
|
},
|
||||||
|
|
||||||
// Add alias when Enter is pressed
|
removeAlias(index) {
|
||||||
$("#alias-input").on("keypress", function (e) {
|
this.aliases.splice(index, 1);
|
||||||
if (e.which === 13) {
|
},
|
||||||
|
|
||||||
|
handleKeypress(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addAlias($(this).val());
|
this.addAlias();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
|
||||||
// Add alias when input loses focus
|
handleBlur() {
|
||||||
$("#alias-input").on("blur", function () {
|
if (this.newAlias.trim() !== '') {
|
||||||
var val = $(this).val();
|
this.addAlias();
|
||||||
if (val.trim() !== "") {
|
|
||||||
addAlias(val);
|
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
|
||||||
// Remove alias when clicking ×
|
async submit() {
|
||||||
$("#alias-box").on("click", ".remove", function () {
|
this.isSubmitting = true;
|
||||||
$(this).closest(".alias").remove();
|
this.errorMessage = '';
|
||||||
});
|
|
||||||
|
|
||||||
// Focus input when clicking the container
|
const formData = new URLSearchParams();
|
||||||
$("#alias-box").on("click", function () {
|
if (this.aliases.length === 0) {
|
||||||
$("#alias-input").focus();
|
formData.append('aliases', '');
|
||||||
});
|
} else {
|
||||||
|
this.aliases.forEach(alias => {
|
||||||
$("#set_aliases_modal_form_submit").on("click", function (e) {
|
formData.append('aliases', alias);
|
||||||
e.preventDefault();
|
});
|
||||||
|
|
||||||
let aliases = [];
|
|
||||||
$("#alias-box .alias").each(function () {
|
|
||||||
aliases.push($(this).text().replace("×", "").trim());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (aliases.length === 0) {
|
|
||||||
// Set empty string for deletion of all aliases
|
|
||||||
// If empty list is sent, its gets removed entirely from post data
|
|
||||||
aliases = [""];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formData = {
|
try {
|
||||||
aliases: aliases,
|
const response = await fetch('{{ current_object.url }}', {
|
||||||
};
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
$.ajax({
|
if (response.ok) {
|
||||||
type: "post",
|
const data = await response.json();
|
||||||
data: formData,
|
|
||||||
url: "{{ current_object.url }}",
|
|
||||||
traditional: true,
|
|
||||||
success: function (data, textStatus) {
|
|
||||||
window.location.href = data.success;
|
window.location.href = data.success;
|
||||||
},
|
} else {
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
this.errorMessage = 'Setting aliases failed!';
|
||||||
$("#add-alias-error-message").append(
|
}
|
||||||
"<p>Setting aliases failed!</p>",
|
} catch (error) {
|
||||||
);
|
this.errorMessage = 'Setting aliases failed!';
|
||||||
$("#add-alias-error-message").show();
|
} finally {
|
||||||
},
|
this.isSubmitting = false;
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
});
|
}"
|
||||||
</script>
|
@close="reset()"
|
||||||
|
>
|
||||||
|
<div class="modal-box max-w-4xl">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="text-lg font-bold">
|
||||||
|
Set Aliases for {{ current_object.name|safe }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Aliases:</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap items-center gap-1 p-2 border border-base-300 rounded-lg bg-base-100 min-h-[38px] cursor-text"
|
||||||
|
@click="$refs.aliasInput.focus()"
|
||||||
|
>
|
||||||
|
<template x-for="(alias, index) in aliases" :key="index">
|
||||||
|
<span class="badge badge-info gap-1 py-3">
|
||||||
|
<span x-text="alias"></span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-ghost btn-xs p-0 h-auto min-h-0"
|
||||||
|
@click.stop="removeAlias(index)"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-ref="aliasInput"
|
||||||
|
x-model="newAlias"
|
||||||
|
class="flex-1 min-w-[120px] border-none outline-none bg-transparent text-sm"
|
||||||
|
placeholder="Add Alias..."
|
||||||
|
@keypress="handleKeypress($event)"
|
||||||
|
@blur="handleBlur()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Message -->
|
||||||
|
<div x-show="errorMessage" x-cloak class="alert alert-error mt-4">
|
||||||
|
<span x-text="errorMessage"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Submitting...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button :disabled="isSubmitting">close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|||||||
@ -1,97 +1,137 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!-- Delete Object -->
|
<!-- Set External Reference -->
|
||||||
<div id="generic_set_external_reference_modal" class="modal" tabindex="-1">
|
<dialog
|
||||||
<div class="modal-dialog">
|
id="generic_set_external_reference_modal"
|
||||||
<div class="modal-content">
|
class="modal"
|
||||||
<div class="modal-header">
|
x-data="{
|
||||||
<h3 class="modal-title">Add External References</h3>
|
isSubmitting: false,
|
||||||
<button
|
selectedDatabase: '',
|
||||||
type="button"
|
placeholder: '',
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
reset() {
|
||||||
aria-label="Close"
|
this.isSubmitting = false;
|
||||||
>
|
this.selectedDatabase = '';
|
||||||
<span aria-hidden="true">×</span>
|
this.placeholder = '';
|
||||||
</button>
|
},
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
updatePlaceholder() {
|
||||||
<form
|
if (this.selectedDatabase) {
|
||||||
id="generic-set-external-reference-modal-form"
|
const option = document.querySelector('#database-select option[value=\'' + this.selectedDatabase + '\']');
|
||||||
accept-charset="UTF-8"
|
if (option) {
|
||||||
action="{{ current_object.url }}"
|
this.placeholder = option.dataset.inputPlaceholder || '';
|
||||||
data-remote="true"
|
}
|
||||||
method="post"
|
}
|
||||||
>
|
},
|
||||||
{% csrf_token %}
|
|
||||||
<label for="database-select"
|
submit(formId) {
|
||||||
>Select the Database you want to attach an External Reference
|
const form = document.getElementById(formId);
|
||||||
for</label
|
if (form && form.checkValidity()) {
|
||||||
>
|
this.isSubmitting = true;
|
||||||
|
form.submit();
|
||||||
|
} else if (form) {
|
||||||
|
form.reportValidity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
@close="reset()"
|
||||||
|
>
|
||||||
|
<div class="modal-box">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="text-lg font-bold">Add External References</h3>
|
||||||
|
|
||||||
|
<!-- Close button (X) -->
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="py-4">
|
||||||
|
<form
|
||||||
|
id="generic-set-external-reference-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action="{{ current_object.url }}"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label" for="database-select">
|
||||||
|
<span class="label-text">
|
||||||
|
Select the Database you want to attach an External Reference for
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
id="database-select"
|
id="database-select"
|
||||||
name="selected-database"
|
name="selected-database"
|
||||||
data-actions-box="true"
|
class="select select-bordered w-full"
|
||||||
class="form-control"
|
x-model="selectedDatabase"
|
||||||
data-width="100%"
|
@change="updatePlaceholder()"
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<option disabled selected>Select Database</option>
|
<option value="" disabled selected>Select Database</option>
|
||||||
{% for entity, databases in meta.external_databases.items %}
|
{% for entity, databases in meta.external_databases.items %}
|
||||||
{% if entity == object_type %}
|
{% if entity == object_type %}
|
||||||
{% for db in databases %}
|
{% for db in databases %}
|
||||||
<option
|
<option
|
||||||
id="db-select-{{ db.database.pk }}"
|
|
||||||
data-input-placeholder="{{ db.placeholder }}"
|
|
||||||
value="{{ db.database.id }}"
|
value="{{ db.database.id }}"
|
||||||
|
data-input-placeholder="{{ db.placeholder }}"
|
||||||
>
|
>
|
||||||
{{ db.database.name|safe }}
|
{{ db.database.name|safe }}
|
||||||
</option>
|
</option>
|
||||||
`
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<p></p>
|
</div>
|
||||||
<div id="input-div" style="display: none">
|
|
||||||
<label for="identifier">The reference</label>
|
<div class="form-control mt-4" x-show="selectedDatabase" x-cloak>
|
||||||
<input
|
<label class="label" for="identifier">
|
||||||
type="text"
|
<span class="label-text">The reference</span>
|
||||||
id="identifier"
|
</label>
|
||||||
name="identifier"
|
<input
|
||||||
class="form-control"
|
type="text"
|
||||||
placeholder=""
|
id="identifier"
|
||||||
/>
|
name="identifier"
|
||||||
</div>
|
class="input input-bordered w-full"
|
||||||
</form>
|
:placeholder="placeholder"
|
||||||
</div>
|
required
|
||||||
<div class="modal-footer">
|
/>
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
</div>
|
||||||
Close
|
</form>
|
||||||
</button>
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
<!-- Footer -->
|
||||||
class="btn btn-primary"
|
<div class="modal-action">
|
||||||
id="generic-set-external-reference-modal-form-submit"
|
<button
|
||||||
>
|
type="button"
|
||||||
Submit
|
class="btn"
|
||||||
</button>
|
onclick="this.closest('dialog').close()"
|
||||||
</div>
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('generic-set-external-reference-modal-form')"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span x-show="!isSubmitting">Submit</span>
|
||||||
|
<span
|
||||||
|
x-show="isSubmitting"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
<span x-show="isSubmitting">Submitting...</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
$("#database-select").on("change", function () {
|
|
||||||
let selected = $(this).val();
|
|
||||||
$("#identifier").attr(
|
|
||||||
"placeholder",
|
|
||||||
$("#db-select-" + selected).data("input-placeholder"),
|
|
||||||
);
|
|
||||||
$("#input-div").show();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#generic-set-external-reference-modal-form-submit").click(function (e) {
|
<!-- Backdrop -->
|
||||||
e.preventDefault();
|
<form method="dialog" class="modal-backdrop">
|
||||||
$("#generic-set-external-reference-modal-form").submit();
|
<button :disabled="isSubmitting">close</button>
|
||||||
});
|
</form>
|
||||||
});
|
</dialog>
|
||||||
</script>
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user