2 Commits

Author SHA1 Message Date
db286d14ec Merge branch 'develop' into fix/missing_nav_links 2025-11-12 22:01:46 +13:00
999dc15189 fix: add/update missing nav links 2025-11-12 18:15:33 +13:00
157 changed files with 9595 additions and 15783 deletions

View File

@ -8,7 +8,6 @@ on:
jobs: jobs:
test: test:
if: ${{ !contains(gitea.event.pull_request.title, 'WIP') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
@ -100,18 +99,6 @@ 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: |
@ -123,12 +110,7 @@ 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 --exclude-tag frontend python manage.py test tests --exclude-tag slow

1
.gitignore vendored
View File

@ -6,7 +6,6 @@ static/django_extensions/
.env .env
debug.log debug.log
scratches/ scratches/
test-results/
data/ data/

View File

@ -49,23 +49,9 @@ 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",
] ]

View File

@ -23,20 +23,12 @@ 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"

View File

@ -1,31 +1,29 @@
from django.conf import settings as s
from django.contrib import admin from django.contrib import admin
from .models import ( from .models import (
Compound,
CompoundStructure,
Edge,
EnviFormer,
ExternalDatabase,
ExternalIdentifier,
Group,
GroupPackagePermission,
JobLog,
License,
MLRelativeReasoning,
Node,
ParallelRule,
Pathway,
Reaction,
Scenario,
Setting,
SimpleAmbitRule,
User, User,
UserPackagePermission, UserPackagePermission,
Group,
GroupPackagePermission,
Package,
MLRelativeReasoning,
EnviFormer,
Compound,
CompoundStructure,
SimpleAmbitRule,
ParallelRule,
Reaction,
Pathway,
Node,
Edge,
Scenario,
Setting,
ExternalDatabase,
ExternalIdentifier,
JobLog,
License,
) )
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"]

View File

@ -1,9 +1,4 @@
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):
@ -12,6 +7,3 @@ 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}")

View File

@ -5,7 +5,7 @@ Context processors automatically make variables available to all templates.
""" """
from .logic import PackageManager from .logic import PackageManager
from django.conf import settings as s from .models import Package
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 = s.GET_PACKAGE_MODEL().objects.none() unreviewed_package_qs = Package.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:

View File

@ -1,35 +1,27 @@
from typing import Any, Dict, List, Optional from typing import List, Dict, Optional, Any
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 Field, Form, Router, Schema, Query from ninja import Router, Schema, Field, Form
from ninja.security import SessionAuth
from utilities.chem import FormatConverter from utilities.chem import FormatConverter
from utilities.misc import PackageExporter from .logic import PackageManager, UserManager, SettingManager
from .logic import GroupManager, PackageManager, SettingManager, UserManager, SearchManager
from .models import ( from .models import (
Compound, Compound,
CompoundStructure, CompoundStructure,
Edge, Package,
EPModel,
Node,
Pathway,
Reaction,
Rule,
Scenario,
SimpleAmbitRule,
User, User,
UserPackagePermission, UserPackagePermission,
ParallelRule, Rule,
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:
@ -37,7 +29,8 @@ 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(csrf=False)) # router = Router(auth=SessionAuth())
router = Router()
class Error(Schema): class Error(Schema):
@ -125,16 +118,13 @@ 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}, auth=None) @router.post("/", response={200: SimpleUser, 403: Error})
def login(request, loginusername: Form[str], loginpassword: Form[str]): def login(request, loginusername: Form[str], loginpassword: Form[str]):
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate
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)
@ -177,13 +167,9 @@ 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, me: Query[Me]): def get_users(request, whoami: str = None):
if me.whoami: if whoami:
return {"user": [request.user]} return {"user": [request.user]}
else: else:
return {"user": User.objects.all()} return {"user": User.objects.all()}
@ -200,61 +186,6 @@ 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 #
########### ###########
@ -320,110 +251,67 @@ def get_packages(request):
} }
class GetPackage(Schema): @router.get("/package/{uuid:package_uuid}", response={200: PackageSchema, 403: Error})
exportAsJson: str | None = None def get_package(request, package_uuid):
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema | Any, 403: Error})
def get_package(request, package_uuid, gp: Query[GetPackage]):
try: try:
p = PackageManager.get_package_by_id(request.user, package_uuid) return 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, request, packageName: Form[str], packageDescription: Optional[str] = Form(None)
p: Form[CreatePackage],
): ):
try: try:
if p.packageName.strip() == "": if packageName.strip() == "":
raise ValueError("Package name cannot be empty!") raise ValueError("Package name cannot be empty!")
new_pacakge = PackageManager.create_package( new_pacakge = PackageManager.create_package(request.user, packageName, packageDescription)
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(request, package_uuid, pack: Form[UpdatePackage]): def update_package(
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 pack.hiddenMethod: if hiddenMethod:
if pack.hiddenMethod == "DELETE": if hiddenMethod == "DELETE":
p.delete() p.delete()
elif pack.packageDescription is not None: elif packageDescription and packageDescription.strip() != "":
description = nh3.clean(pack.packageDescription, tags=s.ALLOWED_HTML_TAGS).strip() p.description = packageDescription
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 #
################################ ################################
@ -621,83 +509,6 @@ 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 #
######### #########
@ -861,73 +672,6 @@ 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}
) )
@ -977,41 +721,6 @@ 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 #
############ ############
@ -1100,82 +809,6 @@ 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 #
############ ############
@ -1190,7 +823,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")
@ -1241,38 +874,6 @@ 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 #
########### ###########
@ -1412,67 +1013,46 @@ 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,
pw: Form[CreatePathway], smilesinput: Form[str],
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(pw.smilesinput.strip()) stand_smiles = FormatConverter.standardize(smilesinput.strip())
new_pw = Pathway.create(p, stand_smiles, name=pw.name, description=pw.description) pw = Pathway.create(p, stand_smiles, name=name, description=description)
pw_mode = "predict" pw_mode = "predict"
if pw.rootOnly and pw.rootOnly.strip() == "true": if rootOnly and rootOnly == "true":
pw_mode = "build" pw_mode = "build"
new_pw.kv.update({"mode": pw_mode}) pw.kv.update({"mode": pw_mode})
new_pw.save() pw.save()
if pw_mode == "predict": if pw_mode == "predict":
setting = request.user.prediction_settings() setting = request.user.prediction_settings()
if pw.selectedSetting: if selectedSetting:
setting = SettingManager.get_setting_by_url(request.user, pw.selectedSetting) setting = SettingManager.get_setting_by_url(request.user, selectedSetting)
new_pw.setting = setting pw.setting = setting
new_pw.save() pw.save()
from .tasks import dispatch, predict from .tasks import predict
dispatch(request.user, predict, new_pw.pk, setting.pk, limit=-1) predict.delay(pw.pk, setting.pk, limit=-1)
return redirect(new_pw.url) return redirect(pw.url)
except ValueError as e: except ValueError as e:
return 400, {"message": str(e)} print(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!"
}
######## ########
@ -1563,52 +1143,6 @@ 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 #
######## ########
@ -1672,200 +1206,6 @@ 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 #
########### ###########

View File

@ -1,40 +1,39 @@
import json
import logging
import re import re
from typing import Any, Dict, List, Optional, Set, Union import logging
import json
from typing import Union, List, Optional, Set, Dict, Any
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 (
Compound,
CompoundStructure,
Edge,
EnzymeLink,
EPModel,
Group,
GroupPackagePermission,
Node,
Pathway,
Permission,
Reaction,
Rule,
Setting,
User, User,
Package,
UserPackagePermission, UserPackagePermission,
GroupPackagePermission,
Permission,
Group,
Setting,
EPModel,
UserSettingPermission, UserSettingPermission,
Rule,
Pathway,
Node,
Edge,
Compound,
Reaction,
CompoundStructure,
EnzymeLink,
) )
from utilities.chem import FormatConverter from utilities.chem import FormatConverter
from utilities.misc import PackageExporter, PackageImporter from utilities.misc import PackageImporter, PackageExporter
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}"
@ -579,39 +578,30 @@ 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 collections import defaultdict
from datetime import datetime
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from datetime import datetime
from envipy_additional_information import AdditionalInformationConverter from collections import defaultdict
from .models import ( from .models import (
Package,
Compound, Compound,
CompoundStructure, CompoundStructure,
Edge, SimpleRule,
Node, SimpleAmbitRule,
ParallelRule, ParallelRule,
Pathway,
Reaction,
Scenario,
SequentialRule, SequentialRule,
SequentialRuleOrdering, SequentialRuleOrdering,
SimpleAmbitRule, Reaction,
SimpleRule, Pathway,
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()

View File

@ -2,9 +2,7 @@ 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, MLRelativeReasoning from epdb.models import MLRelativeReasoning, EnviFormer, Package
Package = s.GET_PACKAGE_MODEL()
class Command(BaseCommand): class Command(BaseCommand):
@ -77,13 +75,11 @@ class Command(BaseCommand):
return packages return packages
# Iteratively create models in options["model_names"] # Iteratively create models in options["model_names"]
print( print(f"Creating models: {options['model_names']}\n"
f"Creating models: {options['model_names']}\n" f"Data packages: {options['data_packages']}\n"
f"Data packages: {options['data_packages']}\n" f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
f"Rule Packages (only for MLRR): {options['rule_packages']}\n" f"Eval Packages: {options['eval_packages']}\n"
f"Eval Packages: {options['eval_packages']}\n" f"Threshold: {options['threshold']:.2f}")
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"])
@ -94,10 +90,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(
@ -105,10 +101,10 @@ class Command(BaseCommand):
rule_packages=rule_packages, rule_packages=rule_packages,
data_packages=data_packages, data_packages=data_packages,
eval_packages=eval_packages, eval_packages=eval_packages,
threshold=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")

View File

@ -8,9 +8,7 @@ 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 from epdb.models import EnviFormer, Package
Package = s.GET_PACKAGE_MODEL()
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -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.functions import Cast, Replace from django.db.models import F, Value, TextField, JSONField
from django.db.models.functions import Replace, Cast
from epdb.models import EnviPathModel from epdb.models import EnviPathModel
@ -23,13 +23,10 @@ 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",

View File

@ -2,41 +2,40 @@ 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 Any, Dict, List, Optional, Set, Tuple, Union from typing import Union, List, Optional, Dict, Tuple, Set, Any
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 GenericForeignKey, GenericRelation from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
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 Count, JSONField, Q, QuerySet from django.db.models import JSONField, Count, 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 jaccard_score, precision_score, recall_score from sklearn.metrics import precision_score, recall_score, jaccard_score
from sklearn.model_selection import ShuffleSplit from sklearn.model_selection import ShuffleSplit
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet from utilities.chem import FormatConverter, ProductSet, PredictionResult, IndigoUtils
from utilities.ml import ( from utilities.ml import (
ApplicabilityDomainPCA,
Dataset,
EnsembleClassifierChain,
EnviFormerDataset,
RelativeReasoning,
RuleBasedDataset, RuleBasedDataset,
ApplicabilityDomainPCA,
EnsembleClassifierChain,
RelativeReasoning,
EnviFormerDataset,
Dataset,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -45,6 +44,8 @@ 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(
@ -52,10 +53,7 @@ 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(
s.EPDB_PACKAGE_MODEL, "epdb.Package", verbose_name="Default Package", null=True, on_delete=models.SET_NULL
verbose_name="Default Package",
null=True,
on_delete=models.SET_NULL,
) )
default_group = models.ForeignKey( default_group = models.ForeignKey(
"Group", "Group",
@ -245,7 +243,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(
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE "epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
) )
class Meta: class Meta:
@ -261,7 +259,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(
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE "epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
) )
class Meta: class Meta:
@ -730,13 +728,10 @@ 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(
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
default_structure = models.ForeignKey( default_structure = models.ForeignKey(
"CompoundStructure", "CompoundStructure",
@ -786,7 +781,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")
@ -1066,7 +1061,7 @@ class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin): class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey( package = models.ForeignKey(
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", 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
@ -1079,10 +1074,6 @@ 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":
@ -1176,7 +1167,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,
@ -1237,9 +1228,6 @@ 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)
@ -1253,7 +1241,7 @@ class SimpleAmbitRule(SimpleRule):
@property @property
def related_reactions(self): def related_reactions(self):
qs = s.GET_PACKAGE_MODEL().objects.filter(reviewed=True) qs = Package.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
@ -1285,9 +1273,6 @@ 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()
@ -1319,57 +1304,6 @@ 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(
@ -1379,9 +1313,6 @@ 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()
@ -1402,7 +1333,7 @@ class SequentialRuleOrdering(models.Model):
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin): class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
package = models.ForeignKey( package = models.ForeignKey(
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", 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"
@ -1424,7 +1355,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,
@ -1583,7 +1514,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey( package = models.ForeignKey(
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", 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
@ -2145,7 +2076,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
class EPModel(PolymorphicModel, EnviPathModel): class EPModel(PolymorphicModel, EnviPathModel):
package = models.ForeignKey( package = models.ForeignKey(
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
) )
def _url(self): def _url(self):
@ -2154,17 +2085,17 @@ class EPModel(PolymorphicModel, EnviPathModel):
class PackageBasedModel(EPModel): class PackageBasedModel(EPModel):
rule_packages = models.ManyToManyField( rule_packages = models.ManyToManyField(
s.EPDB_PACKAGE_MODEL, "Package",
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(
s.EPDB_PACKAGE_MODEL, "Package",
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(
s.EPDB_PACKAGE_MODEL, "Package",
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",
) )
@ -3469,7 +3400,7 @@ class PluginModel(EPModel):
class Scenario(EnviPathModel): class Scenario(EnviPathModel):
package = models.ForeignKey( package = models.ForeignKey(
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True "epdb.Package", 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(
@ -3624,7 +3555,7 @@ class Setting(EnviPathModel):
) )
rule_packages = models.ManyToManyField( rule_packages = models.ManyToManyField(
s.EPDB_PACKAGE_MODEL, "Package",
verbose_name="Setting Rule Packages", verbose_name="Setting Rule Packages",
related_name="setting_rule_packages", related_name="setting_rule_packages",
blank=True, blank=True,

View File

@ -6,17 +6,14 @@ 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, Pathway, Rule, Setting, User from epdb.models import Edge, EPModel, JobLog, Node, Package, 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:

View File

@ -1,60 +1,58 @@
import json import json
import logging import logging
from typing import Any, Dict, List from typing import List, Dict, Any
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 HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
from django.shortcuts import redirect, render from django.shortcuts import render, redirect
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,
SearchManager,
SettingManager,
UserManager, UserManager,
SettingManager,
SearchManager,
EPDBURLParser,
) )
from .models import ( from .models import (
APIToken, Package,
Compound,
CompoundStructure,
Edge,
EnviFormer,
EnzymeLink,
EPModel,
ExternalDatabase,
ExternalIdentifier,
Group,
GroupPackagePermission, GroupPackagePermission,
JobLog, Group,
License, CompoundStructure,
MLRelativeReasoning, Compound,
Node,
Pathway,
Permission,
Reaction, Reaction,
Rule, Rule,
Pathway,
Node,
EPModel,
EnviFormer,
MLRelativeReasoning,
RuleBasedRelativeReasoning, RuleBasedRelativeReasoning,
Scenario, Scenario,
SimpleAmbitRule, SimpleAmbitRule,
User, APIToken,
UserPackagePermission, UserPackagePermission,
Permission,
License,
User,
Edge,
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:
@ -62,26 +60,6 @@ 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 = {
@ -96,48 +74,6 @@ 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)
@ -147,7 +83,8 @@ 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, login from django.contrib.auth import authenticate
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"):
@ -254,8 +191,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):
@ -935,7 +872,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 build_model, dispatch from .tasks import dispatch, build_model
dispatch(current_user, build_model, mod.pk) dispatch(current_user, build_model, mod.pk)
@ -969,10 +906,9 @@ 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
pred_res = dispatch_eager( res = dispatch_eager(current_user, predict_simple, current_model.pk, stand_smiles)
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:
@ -1132,7 +1068,9 @@ 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.grant_read(current_user, current_package, g) PackageManager.update_permissions(
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")
@ -2471,9 +2409,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"] = {

View File

@ -1,21 +1,24 @@
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)
@ -56,7 +59,9 @@ def run_both_engines(SMILES, SMIRKS):
set( set(
[ [
normalize_smiles(str(x)) normalize_smiles(str(x))
for x in FormatConverter.sanitize_smiles([str(s) for s in all_rdkit_prods])[0] for x in FormatConverter.sanitize_smiles(
[str(s) for s in all_rdkit_prods]
)[0]
] ]
) )
) )
@ -80,7 +85,8 @@ 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 for cs in CompoundStructure.objects.filter(compound__package=BBD) cs.smiles
for cs in CompoundStructure.objects.filter(compound__package=BBD)
] ]
RULES = SimpleAmbitRule.objects.filter(package=BBD) RULES = SimpleAmbitRule.objects.filter(package=BBD)
@ -136,7 +142,9 @@ def migration(request):
) )
for r in migration_status["results"]: for r in migration_status["results"]:
r["detail_url"] = r["detail_url"].replace("http://localhost:8000", s.SERVER_URL) r["detail_url"] = r["detail_url"].replace(
"http://localhost:8000", s.SERVER_URL
)
context.update(**migration_status) context.update(**migration_status)
@ -144,6 +152,8 @@ 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)
@ -225,7 +235,9 @@ 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"] = "C(CC(=O)N[C@@H](CS[Se-])C(=O)NCC(=O)[O-])[C@@H](C(=O)[O-])N" context["smiles"] = (
"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":

View File

@ -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,8 +45,6 @@ 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]
@ -68,31 +66,47 @@ docstring-code-format = true
[tool.poe.tasks] [tool.poe.tasks]
# Main tasks # Main tasks
setup = { sequence = [ setup = { sequence = ["db-up", "migrate", "bootstrap"], help = "Complete setup: start database, run migrations, and bootstrap data" }
"db-up", dev = { shell = """
"migrate", # Start pnpm CSS watcher in background
"bootstrap", pnpm run dev &
], help = "Complete setup: start database, run migrations, and bootstrap data" } PNPM_PID=$!
dev = { cmd = "uv run python scripts/dev_server.py", help = "Start the development server with CSS watcher", deps = [ echo "Started CSS watcher (PID: $PNPM_PID)"
"db-up",
"js-deps", # Cleanup function
] } cleanup() {
build = { sequence = [ echo "\nShutting down..."
"build-frontend", if kill -0 $PNPM_PID 2>/dev/null; then
"collectstatic", kill $PNPM_PID
], help = "Build frontend assets and collect static files" } echo " CSS watcher stopped"
fi
if [ ! -z "${DJ_PID:-}" ] && kill -0 $DJ_PID 2>/dev/null; then
kill $DJ_PID
echo " Django server stopped"
fi
}
# Set trap for cleanup
trap cleanup EXIT INT TERM
# Start Django dev server in background
uv run python manage.py runserver &
DJ_PID=$!
# Wait for Django to finish
wait $DJ_PID
""", help = "Start the development server with CSS watcher", deps = ["db-up", "js-deps"] }
build = { sequence = ["build-frontend", "collectstatic"], help = "Build frontend assets and collect static files" }
# Database tasks # Database tasks
db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" } db-up = { cmd = "docker compose -f docker-compose.dev.yml up -d", help = "Start PostgreSQL database using Docker Compose" }
db-down = { cmd = "docker compose -f docker-compose.dev.yml down", help = "Stop PostgreSQL database" } db-down = { cmd = "docker compose -f docker-compose.dev.yml down", help = "Stop PostgreSQL database" }
# Frontend tasks # Frontend tasks
js-deps = { cmd = "uv run python scripts/pnpm_wrapper.py install", help = "Install frontend dependencies" } js-deps = { cmd = "pnpm install", help = "Install frontend dependencies" }
# Full cleanup tasks # Full cleanup tasks
clean = { sequence = [ clean = { sequence = ["clean-db"], help = "Remove model files and database volumes (WARNING: destroys all data!)" }
"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
@ -107,17 +121,9 @@ 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 = "uv run python scripts/pnpm_wrapper.py run build", help = "Build frontend assets using pnpm", deps = [ build-frontend = { cmd = "pnpm run build", help = "Build frontend assets using pnpm", deps = ["js-deps"] }
"js-deps", collectstatic = { cmd = "uv run python manage.py collectstatic --noinput", help = "Collect static files for production", deps = ["build-frontend"] }
] } # 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" }

View File

@ -1,201 +0,0 @@
#!/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()

View File

@ -1,59 +0,0 @@
#!/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()

View File

@ -34,30 +34,3 @@
} }
@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);
}
}

BIN
static/images/wait.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

View File

@ -1,265 +0,0 @@
/**
* 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
}));
});

View File

@ -1,133 +0,0 @@
/**
* 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;
}
}));
});

View File

@ -1,145 +0,0 @@
/**
* 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;
}
}));
});

View File

@ -63,20 +63,17 @@ class DiscourseAPI {
* @returns {string} Cleaned excerpt * @returns {string} Cleaned excerpt
*/ */
extractExcerpt(excerpt) { extractExcerpt(excerpt) {
if (!excerpt) return 'No preview available yet'; if (!excerpt) return 'Click to read more';
// 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
const cleaned = excerpt return excerpt
.replace(/<[^>]*>/g, '') // Remove HTML tags .replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/&nbsp;/g, ' ') // Replace &nbsp; with spaces .replace(/&nbsp;/g, ' ') // Replace &nbsp; with spaces
.replace(/&amp;/g, '&') // Replace &amp; with & .replace(/&amp;/g, '&') // Replace &amp; with &
.replace(/&lt;/g, '<') // Replace &lt; with < .replace(/&lt;/g, '<') // Replace &lt; with <
.replace(/&gt;/g, '>') // Replace &gt; with > .replace(/&gt;/g, '>') // Replace &gt; 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';
} }
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,15 @@
console.log("loaded pw.js") console.log("loaded pw.js")
function predictFromNode(url) { function predictFromNode(url) {
fetch("", { $.post("", {node: url})
method: "POST", .done(function (data) {
headers: { console.log("Success:", data);
"Content-Type": "application/x-www-form-urlencoded", window.location.href = data.success;
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]')?.value || '' })
}, .fail(function (xhr, status, error) {
body: new URLSearchParams({node: url}) console.error("Error:", xhr.status, xhr.responseText);
}) // 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 }};
@ -110,9 +103,6 @@ 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;
@ -127,9 +117,6 @@ 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;
@ -140,9 +127,6 @@ 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
@ -208,153 +192,52 @@ 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 before showing popup (ms) // Wait one second before showing popup
var popupWaitBeforeShow = 1000; var popupWaitBeforeShow = 1000;
// Keep Popup at least for one second
var popushowAtLeast = 1000;
// Custom popover element function pop_show_e(element) {
let popoverTimeout = null; var e = element;
setTimeout(function () {
if ($(e).is(':hover')) { // if element is still hovered
$(e).popover("show");
function createPopover() { // workaround to set fixed positions
const popover = document.createElement('div'); pop = $(e).attr("aria-describedby")
popover.id = 'custom-popover'; h = $('#' + pop).height();
popover.className = 'fixed z-50'; $('#' + pop).attr("style", `position: fixed; top: ${clientY - (h / 2.0)}px; left: ${clientX + 10}px; margin: 0px; max-width: 1000px; display: block;`)
popover.style.cssText = ` setTimeout(function () {
background: #ffffff; var close = setInterval(function () {
border: 1px solid #d1d5db; if (!$(".popover:hover").length // mouse outside popover
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); && !$(e).is(':hover')) { // mouse outside element
max-width: 320px; $(e).popover('hide');
padding: 0.75rem; clearInterval(close);
border-radius: 0.5rem; }
opacity: 0; }, 100);
visibility: hidden; }, popushowAtLeast);
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;
} }
#custom-popover a { }, popupWaitBeforeShow);
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.each(function (d) { objects.attr("id", "pop")
const element = this; .attr("data-container", "body")
.attr("data-toggle", "popover")
.attr("data-placement", "right")
.attr("title", title);
element.addEventListener('mouseenter', () => { objects.each(function (d, i) {
if (popoverTimeout) clearTimeout(popoverTimeout); options = {trigger: "manual", html: true, animation: false};
this_ = this;
popoverTimeout = setTimeout(() => { var p = $(this).popover(options).on("mouseenter", function () {
if (element.matches(':hover')) { pop_show_e(this);
const content = contentFunction(d);
showPopover(element, title, content);
}
}, popupWaitBeforeShow);
}); });
p.on("show.bs.popover", function (e) {
element.addEventListener('mouseleave', () => { // this is to dynamically ajdust the content and bounds of the popup
if (popoverTimeout) { p.attr('data-content', contentFunction(d));
clearTimeout(popoverTimeout); p.data("bs.popover").setContent();
popoverTimeout = null; p.data("bs.popover").tip().css({"max-width": "1000px"});
}
// Delay hide to allow moving to popover
setTimeout(() => {
const popover = getPopover();
if (!popover.matches(':hover') && !element.matches(':hover')) {
hidePopover();
}
}, 100);
}); });
}); });
} }
@ -372,7 +255,7 @@ function draw(pathway, elem) {
} }
} }
popupContent += "<img src='" + n.image + "'><br>" popupContent += "<img src='" + n.image + "' width='" + 20 * nodeRadius + "'><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) {
@ -382,7 +265,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 btn-sm mt-2" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>'; popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
} }
return popupContent; return popupContent;
@ -402,7 +285,7 @@ function draw(pathway, elem) {
popupContent += adcontent; popupContent += adcontent;
} }
popupContent += "<img src='" + e.image + "'><br>" popupContent += "<img src='" + e.image + "' width='" + 20 * nodeRadius + "'><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>';
} }
@ -425,23 +308,6 @@ 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()
@ -450,12 +316,7 @@ function draw(pathway, elem) {
zoomable.attr("transform", event.transform); zoomable.attr("transform", event.transform);
}); });
// Apply zoom to the SVG element - this enables wheel zoom d3.select("svg").call(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'];

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_compound_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Compound</a>
onclick="document.getElementById('new_compound_modal').showModal(); return false;" </li>
> {% endif %}
<span class="glyphicon glyphicon-plus"></span> New Compound</a
>
</li>
{% endif %}

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_compound_structure_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Compound Structure</a>
onclick="document.getElementById('new_compound_structure_modal').showModal(); return false;" </li>
> {% endif %}
<span class="glyphicon glyphicon-plus"></span> New Compound Structure</a
>
</li>
{% endif %}

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_edge_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Edge</a>
onclick="document.getElementById('new_edge_modal').showModal(); return false;" </li>
> {% endif %}
<span class="glyphicon glyphicon-plus"></span> New Edge</a
>
</li>
{% endif %}

View File

@ -1,8 +1,4 @@
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_group_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Group</a>
onclick="document.getElementById('new_group_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Group</a
>
</li> </li>

View File

@ -1,10 +1,6 @@
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %} {% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_model_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Model</a>
onclick="document.getElementById('new_model_modal').showModal(); return false;" </li>
>
<span class="glyphicon glyphicon-plus"></span> New Model</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_node_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Node</a>
onclick="document.getElementById('new_node_modal').showModal(); return false;" </li>
> {% endif %}
<span class="glyphicon glyphicon-plus"></span> New Node</a
>
</li>
{% endif %}

View File

@ -1,25 +1,12 @@
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_package_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Package</a>
onclick="document.getElementById('new_package_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-plus"></span> New Package</a
>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#import_package_modal">
role="button" <span class="glyphicon glyphicon-import"></span> Import Package from JSON</a>
onclick="document.getElementById('import_package_modal').showModal(); return false;"
>
<span class="glyphicon glyphicon-import"></span> Import Package from JSON</a
>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#import_legacy_package_modal">
role="button" <span class="glyphicon glyphicon-import"></span> Import Package from legacy JSON</a>
onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;" </li>
>
<span class="glyphicon glyphicon-import"></span> Import Package from legacy
JSON</a
>
</li>

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_reaction_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Reaction</a>
onclick="document.getElementById('new_reaction_modal').showModal(); return false;" </li>
> {% endif %}
<span class="glyphicon glyphicon-plus"></span> New Reaction</a
>
</li>
{% endif %}

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_rule_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Rule</a>
onclick="document.getElementById('new_rule_modal').showModal(); return false;" </li>
>
<span class="glyphicon glyphicon-plus"></span> New Rule</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_scenario_modal">
role="button" <span class="glyphicon glyphicon-plus"></span> New Scenario</a>
onclick="document.getElementById('new_scenario_modal').showModal(); return false;" </li>
> {% endif %}
<span class="glyphicon glyphicon-plus"></span> New Scenario</a
>
</li>
{% endif %}

View File

@ -1,10 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_setting_modal">
role="button" <span class="glyphicon glyphicon-plus"></span>New Setting</a>
onclick="document.getElementById('new_setting_modal').showModal(); return false;" </li>
> {% endif %}
<span class="glyphicon glyphicon-plus"></span>New Setting</a
>
</li>
{% endif %}

View File

@ -1,60 +1,32 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_compound_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Edit Compound</a>
onclick="document.getElementById('edit_compound_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Compound</a <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
> <i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#add_structure_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> Add Structure</a>
onclick="document.getElementById('set_aliases_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
> <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
onclick="document.getElementById('add_structure_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-plus"></i> Add Structure</a
>
</li>
<li>
<a
role="button"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
>
</li>
<li>
<a
role="button"
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
>
</li>
{% endif %} {% endif %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
role="button" <i class="glyphicon glyphicon-duplicate"></i> Copy</a>
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
>
</li>
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
class="button" <i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
onclick="document.getElementById('generic_delete_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,42 +1,22 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_compound_structure_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
onclick="document.getElementById('edit_compound_structure_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
> <i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
onclick="document.getElementById('set_aliases_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a <a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
> <i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
</li> </li>
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
role="button" <i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
onclick="document.getElementById('set_scenario_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
>
</li>
<li>
<a
role="button"
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,26 +1,14 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
onclick="document.getElementById('set_aliases_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
> <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li> </li>
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
role="button" <i class="glyphicon glyphicon-trash"></i> Delete Edge</a>
onclick="document.getElementById('set_scenario_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,18 +1,10 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
role="button" <i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a>
onclick="document.getElementById('edit_group_member_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a <a role="button" data-toggle="modal" data-target="#generic_delete_modal">
> <i class="glyphicon glyphicon-trash"></i> Delete Group</a>
</li> </li>
<li> {% endif %}
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Group</a
>
</li>
{% endif %}

View File

@ -1,34 +1,18 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_model_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Edit Model</a>
onclick="document.getElementById('edit_model_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Model</a <a role="button" data-toggle="modal" data-target="#evaluate_model_modal">
> <i class="glyphicon glyphicon-ok"></i> Evaluate Model</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#retrain_model_modal">
role="button" <i class="glyphicon glyphicon-repeat"></i> Retrain Model</a>
onclick="document.getElementById('evaluate_model_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-ok"></i> Evaluate Model</a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
> <i class="glyphicon glyphicon-trash"></i> Delete Model</a>
</li> </li>
<li> {% endif %}
<a
role="button"
onclick="document.getElementById('retrain_model_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-repeat"></i> Retrain Model</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Model</a
>
</li>
{% endif %}

View File

@ -1,34 +1,18 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_node_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Edit Node</a>
onclick="document.getElementById('edit_node_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Node</a <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
> <i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
onclick="document.getElementById('set_aliases_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
> <i class="glyphicon glyphicon-trash"></i> Delete Node</a>
</li> </li>
<li>
<a
role="button"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Node</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,50 +1,26 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_package_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Edit Package</a>
onclick="document.getElementById('edit_package_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Package</a <a role="button" data-toggle="modal" data-target="#edit_package_permissions_modal">
> <i class="glyphicon glyphicon-user"></i> Edit Permissions</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#publish_package_modal">
role="button" <i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a>
onclick="document.getElementById('edit_package_permissions_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a <a role="button" data-toggle="modal" data-target="#export_package_modal">
> <i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#set_license_modal">
role="button" <i class="glyphicon glyphicon-duplicate"></i> License</a>
onclick="document.getElementById('publish_package_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
> <i class="glyphicon glyphicon-trash"></i> Delete Package</a>
</li> </li>
<li> {% endif %}
<a
role="button"
onclick="document.getElementById('export_package_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-bullhorn"></i> Export Package as JSON</a
>
</li>
<li>
<a
role="button"
onclick="document.getElementById('set_license_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-duplicate"></i> License</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Package</a
>
</li>
{% endif %}

View File

@ -1,104 +1,59 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#add_pathway_node_modal">
class="button" <i class="glyphicon glyphicon-plus"></i> Add Compound</a>
onclick="document.getElementById('add_pathway_node_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Add Compound</a <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">
> <i class="glyphicon glyphicon-plus"></i> Add Reaction</a>
</li> </li>
<li> <li role="separator" class="divider"></li>
<a
class="button"
onclick="document.getElementById('add_pathway_edge_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Add Reaction</a
>
</li>
<li role="separator" class="divider"></li>
{% endif %} {% endif %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
role="button" <i class="glyphicon glyphicon-duplicate"></i> Copy</a>
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-duplicate"></i> Copy</a <a class="button" data-toggle="modal" data-target="#download_pathway_csv_modal">
> <i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a>
</li> </li>
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#download_pathway_image_modal">
class="button" <i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a>
onclick="document.getElementById('download_pathway_csv_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as CSV</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('download_pathway_image_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a
>
</li>
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#identify_missing_rules_modal">
class="button" <i class="glyphicon glyphicon-question-sign"></i> Identify Missing Rules</a>
onclick="document.getElementById('identify_missing_rules_modal').showModal(); return false;" </li>
> <li role="separator" class="divider"></li>
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing <li>
Rules</a <a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
> <i class="glyphicon glyphicon-edit"></i> Edit Pathway</a>
</li> </li>
<li role="separator" class="divider"></li> <li>
<li> <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<a <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
class="button" </li>
onclick="document.getElementById('edit_pathway_modal').showModal(); return false;" <li>
> <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Pathway</a <i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
> </li>
</li> {# <li>#}
<li> {# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
<a {# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
role="button" {# </li>#}
onclick="document.getElementById('set_scenario_modal').showModal(); return false;" <li role="separator" class="divider"></li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a <a class="button" data-toggle="modal" data-target="#delete_pathway_node_modal">
> <i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
</li> </li>
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#delete_pathway_edge_modal">
role="button" <i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
onclick="document.getElementById('set_aliases_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
> <i class="glyphicon glyphicon-trash"></i> Delete Pathway</a>
</li> </li>
<li role="separator" class="divider"></li>
<li>
<a
class="button"
onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,52 +1,28 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_reaction_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
onclick="document.getElementById('edit_reaction_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
> <i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
onclick="document.getElementById('set_aliases_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a <a role="button" data-toggle="modal" data-target="#generic_set_external_reference_modal">
> <i class="glyphicon glyphicon-plus"></i> Set External Reference</a>
</li> </li>
<li>
<a
role="button"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
>
</li>
<li>
<a
role="button"
onclick="document.getElementById('generic_set_external_reference_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set External Reference</a
>
</li>
{% endif %} {% endif %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
role="button" <i class="glyphicon glyphicon-duplicate"></i> Copy</a>
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
>
</li>
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
class="button" <i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
onclick="document.getElementById('generic_delete_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,44 +1,24 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_rule_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
onclick="document.getElementById('edit_rule_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a <a role="button" data-toggle="modal" data-target="#set_aliases_modal">
> <i class="glyphicon glyphicon-plus"></i> Set Aliases</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
onclick="document.getElementById('set_aliases_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
>
</li>
<li>
<a
role="button"
onclick="document.getElementById('set_scenario_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a
>
</li>
{% endif %} {% endif %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#generic_copy_object_modal">
role="button" <i class="glyphicon glyphicon-duplicate"></i> Copy</a>
onclick="document.getElementById('generic_copy_object_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-duplicate"></i> Copy</a
>
</li>
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
class="button" <i class="glyphicon glyphicon-trash"></i> Delete Rule</a>
onclick="document.getElementById('generic_delete_modal').showModal(); return false;" </li>
>
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a
>
</li>
{% endif %} {% endif %}

View File

@ -1,26 +1,14 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#add_additional_information_modal">
class="button" <i class="glyphicon glyphicon-trash"></i> Add Additional Information</a>
onclick="document.getElementById('add_additional_information_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-trash"></i> Add Additional Information</a <a class="button" data-toggle="modal" data-target="#update_scenario_additional_information_modal">
> <i class="glyphicon glyphicon-trash"></i> Set Additional Information</a>
</li> </li>
<li> <li>
<a <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
class="button" <i class="glyphicon glyphicon-trash"></i> Delete Scenario</a>
onclick="document.getElementById('update_scenario_additional_information_modal').showModal(); return false;" </li>
> {% endif %}
<i class="glyphicon glyphicon-trash"></i> Set Additional Information</a
>
</li>
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Scenario</a
>
</li>
{% endif %}

View File

@ -1,38 +1,22 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#edit_user_modal">
role="button" <i class="glyphicon glyphicon-edit"></i> Update</a>
onclick="document.getElementById('edit_user_modal').showModal(); return false;" </li>
> <li>
<i class="glyphicon glyphicon-edit"></i> Update</a <a role="button" data-toggle="modal" data-target="#edit_password_modal">
> <i class="glyphicon glyphicon-lock"></i> Update Password</a>
</li> </li>
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#new_prediction_setting_modal">
role="button" <i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a>
onclick="document.getElementById('edit_password_modal').showModal(); return false;" </li>
> {# <li>#}
<i class="glyphicon glyphicon-lock"></i> Update Password</a {# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
> {# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
</li> {# </li>#}
<li> <li>
<a <a role="button" data-toggle="modal" data-target="#generic_delete_modal">
role="button" <i class="glyphicon glyphicon-trash"></i> Delete Account</a>
onclick="document.getElementById('new_prediction_setting_modal').showModal(); return false;" </li>
> {% endif %}
<i class="glyphicon glyphicon-plus"></i> New Prediction Setting</a
>
</li>
{# <li>#}
{# <a role="button" data-toggle="modal" data-target="#manage_api_token_modal">#}
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
{# </li>#}
<li>
<a
class="button"
onclick="document.getElementById('generic_delete_modal').showModal(); return false;"
>
<i class="glyphicon glyphicon-trash"></i> Delete Account</a
>
</li>
{% endif %}

View File

@ -1,17 +1,12 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div id="searchContent">
<div id=searchContent>
<form id="admin-form" action="{{ SERVER_BASE }}/admin" method="post"> <form id="admin-form" action="{{ SERVER_BASE }}/admin" method="post">
<div class="form-group"> <div class="form-group">
<label for="textarea">Query</label> <label for="textarea">Query</label>
<textarea <textarea id="textarea" class="form-control" rows="10" placeholder="Paste query here" required>
id="textarea"
class="form-control"
rows="10"
placeholder="Paste query here"
required
>
PREFIX pps: <http://localhost:8080/vocabulary#> PREFIX pps: <http://localhost:8080/vocabulary#>
SELECT ?name (count(?objId) as ?xcnt) SELECT ?name (count(?objId) as ?xcnt)
WHERE { WHERE {
@ -20,29 +15,32 @@ WHERE {
?packageId pps:reviewStatus 'reviewed' . ?packageId pps:reviewStatus 'reviewed' .
?packageId pps:pathway ?objId . ?packageId pps:pathway ?objId .
} GROUP BY ?name } GROUP BY ?name
</textarea </textarea>
> </div>
</div> <button id="submit" type="button" class="btn btn-primary">Submit</button>
<button id="submit" type="button" class="btn btn-primary">Submit</button>
</form> </form>
<p></p> <p></p>
</div> </div>
<div id="results"></div> <div id="results">
<div id="loading"></div> </div>
<script> <div id="loading"></div>
$(function () { </div>
$("#submit").on("click", function () { <script>
$(function() {
$('#submit').on('click', function() {
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}"); makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
data = { data = {
query: $("#textarea").val(), "query": $("#textarea").val()
}; }
$.post("{{ SERVER_BASE }}/expire", data, function (result) { $.post("{{ SERVER_BASE }}/expire", data, function(result) {
$("#loading").empty(); $("#loading").empty();
queryResultToTable("results", result); queryResultToTable("results", result);
}); })
});
}); });
</script> })
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,88 +1,70 @@
{% extends "framework_modern.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% load envipytags %}
{% block content %} {% block content %}
<div class="space-y-2 p-4">
<!-- Header Section -->
<div class="card bg-base-100">
<div class="card-body">
<h2 class="card-title text-2xl">User Prediction Jobs</h2>
<p class="mt-2">Job Logs Desc</p>
</div>
</div>
<!-- Jobs --> <div class="panel-group" id="reviewListAccordion">
<div class="collapse-arrow bg-base-200 collapse"> <div class="panel panel-default">
<input type="checkbox" checked /> <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
<div class="collapse-title text-xl font-medium">Recent Jobs</div> Jobs
<div class="collapse-content" id="job-content"> </div>
<div class="overflow-x-auto"> <div class="panel-body">
<table class="table-zebra table"> <p>
<thead> Job Logs Desc
<tr> </p>
<th>ID</th>
<th>Name</th>
<th>Status</th>
<th>Queued</th>
<th>Done</th>
<th>Result</th>
</tr>
</thead>
<tbody>
{% for job in jobs %}
<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.job_name }}</td>
<td>{{ job.status }}</td>
<td>{{ job.created }}</td>
<td>{{ job.done_at }}</td>
{% if job.task_result and job.task_result|is_url == True %}
<td>
<a href="{{ job.task_result }}" class="link link-primary"
>Result</a
>
</td>
{% elif job.task_result %}
<td>{{ job.task_result|slice:"40" }}...</td>
{% else %}
<td>Empty</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% if objects %} </div>
<!-- Unreviewable objects such as User / Group / Setting -->
<div class="card bg-base-100 shadow-xl"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<div class="card-body"> <h4 class="panel-title">
<ul class="menu bg-base-200 rounded-box"> <a id="job-accordion-link" data-toggle="collapse" data-parent="#job-accordion" href="#jobs">
{% for obj in objects %} Jobs
{% if object_type == 'user' %} </a>
<li> </h4>
<a href="{{ obj.url }}" class="hover:bg-base-300" </div>
>{{ obj.username }}</a <div id="jobs"
> class="panel-collapse collapse in">
</li> <div class="panel-body list-group-item" id="job-content">
{% else %} <table class="table table-bordered table-hover">
<li> <tr style="background-color: rgba(0, 0, 0, 0.08);">
<a href="{{ obj.url }}" class="hover:bg-base-300" <th scope="col">ID</th>
>{{ obj.name }}</a <th scope="col">Name</th>
> <th scope="col">Status</th>
</li> <th scope="col">Queued</th>
{% endif %} <th scope="col">Done</th>
{% endfor %} <th scope="col">Result</th>
</ul> </tr>
<tbody>
{% for job in jobs %}
<tr>
<td>{{ job.task_id }}</td>
<td>{{ job.job_name }}</td>
<td>{{ job.status }}</td>
<td>{{ job.created }}</td>
<td>{{ job.done_at }}</td>
{% if job.task_result and job.task_result|is_url == True %}
<td><a href="{{ job.task_result }}">Result</a></td>
{% elif job.task_result %}
<td>{{ job.task_result|slice:"40" }}...</td>
{% else %}
<td>Empty</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Unreviewable objects such as User / Group / Setting -->
<ul class='list-group'>
{% for obj in objects %}
{% if object_type == 'user' %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
{% else %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
{% endif %}
{% endfor %}
</ul>
</div> </div>
</div> </div>
{% endif %}
</div>
{% endblock content %} {% endblock content %}

View File

@ -1,533 +1,319 @@
{% extends "framework_modern.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
{# Serialize objects data for Alpine pagination #} {% if object_type != 'package' %}
{# prettier-ignore-start #} <div>
{# FIXME: This is a hack to get the objects data into the JavaScript code. #} <div id="load-all-error" style="display: none;">
<script> <div class="alert alert-danger" role="alert">
window.reviewedObjects = [ <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
{% for obj in reviewed_objects %} <span class="sr-only">Error:</span>
{ "name": "{{ obj.name|escapejs }}", "url": "{{ obj.url }}" }{% if not forloop.last %},{% endif %} Getting objects failed!
{% endfor %}
];
window.unreviewedObjects = [
{% for obj in unreviewed_objects %}
{ "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
type="text"
id="object-search"
class="input input-bordered hidden w-full max-w-xs"
placeholder="Search by name"
/>
</div>
{% endif %}
{% block action_modals %}
{% if object_type == 'package' %}
{% include "modals/collections/new_package_modal.html" %}
{% include "modals/collections/import_package_modal.html" %}
{% include "modals/collections/import_legacy_package_modal.html" %}
{% elif object_type == 'compound' %}
{% include "modals/collections/new_compound_modal.html" %}
{% elif object_type == 'rule' %}
{% include "modals/collections/new_rule_modal.html" %}
{% elif object_type == 'reaction' %}
{% include "modals/collections/new_reaction_modal.html" %}
{% elif object_type == 'pathway' %}
{# {% include "modals/collections/new_pathway_modal.html" %} #}
{% elif object_type == 'node' %}
{% include "modals/collections/new_node_modal.html" %}
{% elif object_type == 'edge' %}
{% include "modals/collections/new_edge_modal.html" %}
{% elif object_type == 'scenario' %}
{% include "modals/collections/new_scenario_modal.html" %}
{% elif object_type == 'model' %}
{% include "modals/collections/new_model_modal.html" %}
{% elif object_type == 'setting' %}
{#{% include "modals/collections/new_setting_modal.html" %}#}
{% elif object_type == 'user' %}
<div></div>
{% elif object_type == 'group' %}
{% include "modals/collections/new_group_modal.html" %}
{% endif %}
{% endblock action_modals %}
<div class="px-8 py-4">
<!-- Header Section -->
<div class="card bg-base-100">
<div class="card-body px-0 py-4">
<div class="flex items-center justify-between">
<h2 class="card-title text-2xl">
{% if object_type == 'package' %}
Packages
{% elif object_type == 'compound' %}
Compounds
{% elif object_type == 'structure' %}
Compound structures
{% elif object_type == 'rule' %}
Rules
{% elif object_type == 'reaction' %}
Reactions
{% elif object_type == 'pathway' %}
Pathways
{% elif object_type == 'node' %}
Nodes
{% elif object_type == 'edge' %}
Edges
{% elif object_type == 'scenario' %}
Scenarios
{% elif object_type == 'model' %}
Model
{% elif object_type == 'setting' %}
Settings
{% elif object_type == 'user' %}
Users
{% elif object_type == 'group' %}
Groups
{% endif %}
</h2>
<div id="actionsButton" class="dropdown dropdown-end hidden">
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
<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-wrench"
>
<path
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"
/>
</svg>
Actions
</div>
<ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
>
{% block actions %}
{% if object_type == 'package' %}
{% include "actions/collections/package.html" %}
{% elif object_type == 'compound' %}
{% include "actions/collections/compound.html" %}
{% elif object_type == 'structure' %}
{% include "actions/collections/compound_structure.html" %}
{% elif object_type == 'rule' %}
{% include "actions/collections/rule.html" %}
{% elif object_type == 'reaction' %}
{% include "actions/collections/reaction.html" %}
{% elif object_type == 'setting' %}
{% include "actions/collections/setting.html" %}
{% elif object_type == 'scenario' %}
{% include "actions/collections/scenario.html" %}
{% elif object_type == 'model' %}
{% include "actions/collections/model.html" %}
{% elif object_type == 'pathway' %}
{% include "actions/collections/pathway.html" %}
{% 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 class="mt-2">
<!-- Set Text above links -->
{% 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"
class="link link-primary"
>Learn more &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 &gt;&gt;</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 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>
<!-- Lists Container - Full Width with Reviewed on Right -->
<div class="w-full">
{% if reviewed_objects %}
{% if reviewed_objects|length > 0 %}
<!-- Reviewed -->
<div
class="collapse-arrow bg-base-200 collapse order-2 w-full"
x-data="paginatedList(window.reviewedObjects || [], { isReviewed: true, instanceId: 'reviewed' })"
>
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">
Reviewed
<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">
<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"
>
<span class="text-base-content/70 text-sm">
Showing <span x-text="showingStart"></span>-<span
x-text="showingEnd"
></span>
of <span x-text="totalItems"></span>
</span>
<div class="join">
<button
class="join-item btn btn-sm"
:disabled="currentPage === 1"
@click="prevPage()"
>
«
</button>
<template x-for="item in pageNumbers" :key="item.key">
<button
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()"
>
»
</button>
</div> </div>
</div>
</div> </div>
</div>
{% endif %}
{% endif %}
{% if unreviewed_objects %} <input type="text" id="object-search" class="form-control" placeholder="Search by name"
<!-- Unreviewed --> style="display: none;">
<div <p></p>
class="collapse-arrow bg-base-200 collapse order-1 w-full"
x-data="paginatedList(window.unreviewedObjects || [], { isReviewed: false, instanceId: 'unreviewed' })"
>
<input
type="checkbox"
{% if reviewed_objects|length == 0 or object_type == 'package' %}checked{% endif %}
/>
<div class="collapse-title text-xl font-medium">
Unreviewed
<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"
>
<span class="text-base-content/70 text-sm">
Showing <span x-text="showingStart"></span>-<span
x-text="showingEnd"
></span>
of <span x-text="totalItems"></span>
</span>
<div class="join">
<button
class="join-item btn btn-sm"
:disabled="currentPage === 1"
@click="prevPage()"
>
«
</button>
<template x-for="item in pageNumbers" :key="item.key">
<button
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()"
>
»
</button>
</div>
</div>
</div>
</div> </div>
{% endif %} {% endif %}
{% block action_modals %}
{% if object_type == 'package' %}
{% include "modals/collections/new_package_modal.html" %}
{% include "modals/collections/import_package_modal.html" %}
{% include "modals/collections/import_legacy_package_modal.html" %}
{% elif object_type == 'compound' %}
{% include "modals/collections/new_compound_modal.html" %}
{% elif object_type == 'rule' %}
{% include "modals/collections/new_rule_modal.html" %}
{% elif object_type == 'reaction' %}
{% include "modals/collections/new_reaction_modal.html" %}
{% elif object_type == 'pathway' %}
{# {% include "modals/collections/new_pathway_modal.html" %} #}
{% elif object_type == 'node' %}
{% include "modals/collections/new_node_modal.html" %}
{% elif object_type == 'edge' %}
{% include "modals/collections/new_edge_modal.html" %}
{% elif object_type == 'scenario' %}
{% include "modals/collections/new_scenario_modal.html" %}
{% elif object_type == 'model' %}
{% include "modals/collections/new_model_modal.html" %}
{% elif object_type == 'setting' %}
{#{% include "modals/collections/new_setting_modal.html" %}#}
{% elif object_type == 'user' %}
<div></div>
{% elif object_type == 'group' %}
{% include "modals/collections/new_group_modal.html" %}
{% endif %}
{% endblock action_modals %}
<div class="panel-group" id="reviewListAccordion">
<div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{% if object_type == 'package' %}
Packages
{% elif object_type == 'compound' %}
Compounds
{% elif object_type == 'structure' %}
Compound structures
{% elif object_type == 'rule' %}
Rules
{% elif object_type == 'reaction' %}
Reactions
{% elif object_type == 'pathway' %}
Pathways
{% elif object_type == 'node' %}
Nodes
{% elif object_type == 'edge' %}
Edges
{% elif object_type == 'scenario' %}
Scenarios
{% elif object_type == 'model' %}
Model
{% elif object_type == 'setting' %}
Settings
{% elif object_type == 'user' %}
Users
{% elif object_type == 'group' %}
Groups
{% endif %}
<div id="actionsButton"
style="float: right;font-weight: normal;font-size: medium;position: relative; top: 50%; transform: translateY(-50%);z-index:100;display: none;"
class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false"><span
class="glyphicon glyphicon-wrench"></span> Actions <span class="caret"></span><span
style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu">
{% block actions %}
{% if object_type == 'package' %}
{% include "actions/collections/package.html" %}
{% elif object_type == 'compound' %}
{% include "actions/collections/compound.html" %}
{% elif object_type == 'structure' %}
{% include "actions/collections/compound_structure.html" %}
{% elif object_type == 'rule' %}
{% include "actions/collections/rule.html" %}
{% elif object_type == 'reaction' %}
{% include "actions/collections/reaction.html" %}
{% elif object_type == 'setting' %}
{% include "actions/collections/setting.html" %}
{% elif object_type == 'scenario' %}
{% include "actions/collections/scenario.html" %}
{% elif object_type == 'model' %}
{% include "actions/collections/model.html" %}
{% elif object_type == 'pathway' %}
{% include "actions/collections/pathway.html" %}
{% 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 class="panel-body">
<!-- Set Text above links -->
{% 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 &gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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
&gt;&gt;</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>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>
{% if reviewed_objects %}
{% if reviewed_objects|length > 0 %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="ReviewedLink" data-toggle="collapse" data-parent="#reviewListAccordion"
href="#Reviewed">Reviewed</a>
</h4>
</div>
<div id="Reviewed" class="panel-collapse collapse in">
<div class="panel-body list-group-item" id="ReviewedContent">
{% if object_type == 'package' %}
{% for obj in reviewed_objects %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}
<span class="glyphicon glyphicon-star" aria-hidden="true"
style="float:right" data-toggle="tooltip"
data-placement="top" title="" data-original-title="Reviewed">
</span>
</a>
{% endfor %}
{% else %}
{% for obj in reviewed_objects|slice:":50" %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}{# <i>({{ obj.package.name }})</i> #}
<span class="glyphicon glyphicon-star" aria-hidden="true"
style="float:right" data-toggle="tooltip"
data-placement="top" title="" data-original-title="Reviewed">
</span>
</a>
{% endfor %}
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
{% if unreviewed_objects %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"><h4
class="panel-title"><a id="UnreviewedLink" data-toggle="collapse" data-parent="#unReviewListAccordion"
href="#Unreviewed">Unreviewed</a></h4></div>
<div id="Unreviewed" class="panel-collapse collapse {% if reviewed_objects|length == 0 or object_type == 'package' %}in{% endif %}">
<div class="panel-body list-group-item" id="UnreviewedContent">
{% if object_type == 'package' %}
{% for obj in unreviewed_objects %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}</a>
{% endfor %}
{% else %}
{% for obj in unreviewed_objects|slice:":50" %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name|safe }}</a>
{% endfor %}
{% endif %}
</div>
</div>
{% 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>
<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 {
width: 100%;
height: auto;
}
</style>
<div id="load-all-loading" class="spinner-widget" style="display: none">
<img id="loading-gif" src="{% static '/images/wait.gif' %}" alt="Loading...">
</div> </div>
{% if objects %} </div>
<!-- Unreviewable objects such as User / Group / Setting --> <script>
<div class="card bg-base-100"> $(function () {
<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>
<script> $('#object-search').show();
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");
}
// Show search input and connect to Alpine pagination {% if object_type != 'package' and object_type != 'user' and object_type != 'group' %}
const objectSearch = document.getElementById("object-search"); {% if reviewed_objects|length > 50 or unreviewed_objects|length > 50 %}
if (objectSearch) { $('#load-all-loading').show()
objectSearch.classList.remove("hidden");
objectSearch.addEventListener("input", function () { setTimeout(function () {
const query = this.value; $('#load-all-error').hide();
// Dispatch search to all paginatedList components
document $.getJSON('?all=true', function (resp) {
.querySelectorAll('[x-data*="paginatedList"]') $('#ReviewedContent').empty();
.forEach((el) => { $('#UnreviewedContent').empty();
if (el._x_dataStack && el._x_dataStack[0]) {
el._x_dataStack[0].search(query); for (o in resp.objects) {
} 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);
});
}); });
});
}
// 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>
});
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,75 +1,55 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% block content %} {% block content %}
<div>
<form action="" method="post">
{% csrf_token %}
<input
type="text"
class="form-control"
id="smiles"
name="smiles"
placeholder="SMILES"
value="{{ smiles }}"
/>
<input
type="text"
class="form-control"
id="smiles"
name="smirks"
placeholder="SMIRKS"
value="{{ smirks }}"
/>
<button type="submit" class="btn btn-primary">Test</button>
</form>
</div>
{% if result %}
{{ smiles }}
<p></p>
<img
width="400"
src="{% url 'depict' %}?smiles={{ smiles|urlencode }}"
/><br />
<p></p>
{% if rule %}
{{ smirks }}
<p></p>
{{ rule.reactants_smarts }}
<p></p>
{{ rule.products_smarts }}
<p></p>
<div>{{ rule.as_svg|safe }}</div>
{% endif %}
<h2>Diff</h2>
{% if diff %}
{% for d in diff %}
{{ d }}
{% endfor %}
{% else %}
{{ "No diff" }}
{% endif %}
<div> <div>
<div class="col-md-6"> <form action="" method="post">
<h2>Ambit</h2> {% csrf_token %}
{% for p in ambit_res %} <input type="text" class="form-control" id="smiles" name="smiles" placeholder="SMILES"
{{ p }}<br /> value="{{ smiles }}"/>
<img <input type="text" class="form-control" id="smiles" name="smirks" placeholder="SMIRKS"
width="400" value="{{ smirks }}"/>
src="{% url 'depict' %}?smiles={{ p|urlencode }}" <button type="submit" class="btn btn-primary">Test</button>
/><br /> </form>
{% endfor %}
</div>
<div class="col-md-6">
<h2>RDKit</h2>
{% for p in rdkit_res %}
{{ p }}<br />
<img
width="400"
src="{% url 'depict' %}?smiles={{ p|urlencode }}"
/><br />
{% endfor %}
</div>
</div> </div>
{% endif %} {% if result %}
{{ smiles }}<p></p>
<img width='400' src='{% url 'depict' %}?smiles={{ smiles|urlencode }}'><br>
<p></p>
{% if rule %}
{{ smirks }}
<p></p>
{{ rule.reactants_smarts }}
<p></p>
{{ rule.products_smarts }}
<p></p>
<div>
{{ rule.as_svg|safe }}
</div>
{% endif %}
<h2>Diff</h2>
{% if diff %}
{% for d in diff %}
{{ d }}
{% endfor %}
{% else %}
{{ "No diff" }}
{% endif %}
<div>
<div class="col-md-6">
<h2>Ambit</h2>
{% for p in ambit_res %}
{{ p }}<br>
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
{% endfor %}
</div>
<div class="col-md-6">
<h2>RDKit</h2>
{% for p in rdkit_res %}
{{ p }}<br>
<img width='400' src='{% url 'depict' %}?smiles={{ p|urlencode }}'><br>
{% endfor %}
</div>
</div>
{% endif %}
{% endblock content %} {% endblock content %}

View File

@ -1,77 +1,15 @@
{% extends "framework_modern.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<div class="w-full max-w-2xl">
<div class="alert alert-error mb-6 shadow-lg">
<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="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="alert alert-error" role="alert">
<div class="card-body"> <h4 class="alert-heading">Bad Request!</h4>
<h2 class="card-title mb-4 text-2xl">What happened?</h2> <p>Lorem</p>
<p class="text-base-content/70 mb-4"> <hr>
The server couldn't process your request because it contains invalid <p class="mb-0">
data or parameters. You can find out more about permissions in our <a target="_blank"
</p> href="https://wiki.envipath.org/index.php/packages"
<div class="card-actions mt-6 justify-end"> role="button">Wiki &gt;&gt;</a></p>
<a href="/" class="btn btn-primary"> </div>
<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>
{% endblock content %} {% endblock content %}

View File

@ -1,80 +1,15 @@
{% extends "framework_modern.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<div class="w-full max-w-2xl">
<div class="alert alert-warning mb-6 shadow-lg">
<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="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="alert alert-error" role="alert">
<div class="card-body"> <h4 class="alert-heading">Access Denied!</h4>
<h2 class="card-title mb-4 text-2xl">Permission Required</h2> <p>Access to X denied. </p>
<p class="text-base-content/70 mb-4"> <hr>
You need the appropriate permissions to access this content. If you <p class="mb-0">
believe this is an error, please contact the package owner or You can find out more about permissions in our <a target="_blank"
administrator. href="https://wiki.envipath.org/index.php/packages"
</p> role="button">Wiki &gt;&gt;</a></p>
<div class="card-actions mt-6 justify-end"> </div>
<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>
{% endblock content %} {% endblock content %}

View File

@ -1,77 +1,15 @@
{% extends "framework_modern.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<div class="w-full max-w-2xl">
<div class="alert alert-info mb-6 shadow-lg">
<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="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="alert alert-error" role="alert">
<div class="card-body"> <h4 class="alert-heading">Not Found!</h4>
<h2 class="card-title mb-4 text-2xl">404 Error</h2> <p>Does not exist</p>
<p class="text-base-content/70 mb-4"> <hr>
The page or resource you requested could not be found. It may have <p class="mb-0">
been moved, deleted, or the URL might be incorrect. You can find out more about permissions in our <a target="_blank"
</p> href="https://wiki.envipath.org/index.php/packages"
<div class="card-actions mt-6 justify-end"> role="button">Wiki &gt;&gt;</a></p>
<a href="/" class="btn btn-primary"> </div>
<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>
{% endblock content %} {% endblock content %}

View File

@ -1,76 +1,13 @@
{% extends "framework_modern.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<div class="w-full max-w-2xl">
<div class="alert alert-error mb-6 shadow-lg">
<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="alert alert-danger" role="alert">
<div class="card-body"> <h4 class="alert-heading">{{ error_message }}</h4>
<h2 class="card-title mb-4 text-2xl">Oops! Something went wrong</h2> <hr>
<p class="text-base-content/70 mb-4"> <p class="mb-0">
{{ error_description|default:"We encountered an unexpected error while processing your request. Our team has been notified and is working to resolve the issue." }} {{ error_detail }}
</p> </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 %}

View File

@ -1,81 +1,12 @@
{% extends "framework_modern.html" %} {% extends "framework.html" %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="flex min-h-[60vh] flex-col items-center justify-center p-8">
<div class="w-full max-w-2xl">
<div class="alert alert-warning mb-6 shadow-lg">
<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="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="alert alert-danger" role="alert">
<div class="card-body"> <h4 class="alert-heading">Your account has not been activated yet!</h4>
<h2 class="card-title mb-4 text-2xl">Account Activation Required</h2> <p>Your account has not been activated yet. If you have questions <a href="mailto:admin@envipath.org">contact
<p class="text-base-content/70 mb-4"> us.</a>
Your account has not been activated yet. An administrator needs to </p>
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 %}

View File

@ -124,7 +124,7 @@
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div <div
class="navbar-collapse collapse-framework navbar-collapse-framework collapse" class="collapse navbar-collapse collapse-framework navbar-collapse-framework"
id="navbarCollapse" id="navbarCollapse"
> >
<ul class="nav navbar-nav navbar-nav-framework"> <ul class="nav navbar-nav navbar-nav-framework">
@ -151,6 +151,11 @@
>Package</a >Package</a
> >
</li> </li>
<li>
<a href="{{ meta.server_url }}/search" id="searchLink"
>Search</a
>
</li>
<li> <li>
<a href="{{ meta.server_url }}/model" id="modelLink" <a href="{{ meta.server_url }}/model" id="modelLink"
>Modelling</a >Modelling</a
@ -228,7 +233,15 @@
>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>
@ -325,7 +338,7 @@
> >
</div> </div>
</div> </div>
<div id="license" class="panel-collapse in collapse"> <div id="license" class="panel-collapse collapse in">
<div class="panel-body list-group-item"> <div class="panel-body list-group-item">
<a target="_blank" href="{{ meta.current_package.license.link }}"> <a target="_blank" href="{{ meta.current_package.license.link }}">
<img src="{{ meta.current_package.license.image_link }}" /> <img src="{{ meta.current_package.license.image_link }}" />
@ -400,5 +413,10 @@
} }
}); });
</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>

View File

@ -21,14 +21,8 @@
type="text/css" type="text/css"
/> />
{# Alpine.js - For reactive components #} {# jQuery - Keep for compatibility with existing JS #}
<script <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></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
@ -41,10 +35,21 @@
<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 -->
@ -75,7 +80,7 @@
{% block main_content %} {% block main_content %}
{# Breadcrumbs - outside main content, optional #} {# Breadcrumbs - outside main content, optional #}
{% if breadcrumbs %} {% if breadcrumbs %}
<div id="bread" class="mx-auto max-w-7xl px-4 py-4"> <div id="bread" class="max-w-7xl mx-auto px-4 py-4">
<div class="breadcrumbs text-sm"> <div class="breadcrumbs text-sm">
<ul> <ul>
{% for elem in breadcrumbs %} {% for elem in breadcrumbs %}
@ -108,7 +113,7 @@
{# License - inside paper if present #} {# License - inside paper if present #}
{% if meta.url_contains_package and meta.current_package.license %} {% if meta.url_contains_package and meta.current_package.license %}
<div class="collapse-arrow bg-base-200 collapse p-8"> <div class="collapse collapse-arrow bg-base-200 m-8">
<input type="checkbox" checked /> <input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">License</div> <div class="collapse-title text-xl font-medium">License</div>
<div class="collapse-content"> <div class="collapse-content">
@ -132,7 +137,7 @@
{# Floating Help Tab #} {# Floating Help Tab #}
{% if not public_mode %} {% if not public_mode %}
<div class="fixed top-1/2 right-0 z-50 -translate-y-1/2"> <div class="fixed right-0 top-1/2 -translate-y-1/2 z-50">
<a <a
href="https://community.envipath.org/" href="https://community.envipath.org/"
target="_blank" target="_blank"
@ -166,11 +171,10 @@
{% endblock %} {% endblock %}
<script> <script>
document.addEventListener("DOMContentLoaded", function () { $(function () {
// Show actions button if there are actions defined // Hide actionsbutton if there's no action defined
const actionsButtonUl = document.querySelector("#actionsButton ul"); if ($("#actionsButton ul").children().length > 0) {
if (actionsButtonUl && actionsButtonUl.children.length > 0) { $("#actionsButton").show();
document.getElementById("actionsButton").style.display = "";
} }
}); });

View File

@ -1,267 +1,140 @@
{% load static %} {% load static %}
{# Modern DaisyUI Navbar with Mobile Drawer Menu #} {# Modern DaisyUI Navbar #}
<div class="drawer drawer-mobile"> <div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg">
<input id="drawer-toggle" type="checkbox" class="drawer-toggle" /> <div class="navbar-start">
<div class="drawer-content flex flex-col"> <a href="{{ meta.server_url }}" class="btn btn-ghost text-xl normal-case">
{# Navbar #} <svg class="fill-base-content h-8" viewBox="0 0 104 26" role="img">
<div class="navbar x-50 bg-neutral-50 text-neutral-950 shadow-lg"> <use href="{% static "/images/logo-name.svg" %}#ep-logo-name" />
<div class="navbar-start"> </svg>
{# Hamburger menu button - visible on mobile, hidden on desktop #} </a>
{% if not public_mode %} </div>
<label
for="drawer-toggle" {% if not public_mode %}
class="btn btn-square btn-ghost drawer-button lg:hidden" <div class="navbar-center hidden lg:flex">
> <a
<svg href="{{ meta.server_url }}/predict"
xmlns="http://www.w3.org/2000/svg" role="button"
fill="none" class="btn btn-ghost"
viewBox="0 0 24 24" id="predictLink"
class="inline-block h-5 w-5 stroke-current" >Predict</a
> >
<path <!-- <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> -->
stroke-linecap="round" <!--<li><a href="{{ meta.server_url }}/browse" id="browseLink">Browse</a></li>-->
stroke-linejoin="round" <div class="dropdown dropdown-center">
stroke-width="2" <div tabindex="0" role="button" class="btn btn-ghost">Browse</div>
d="M4 6h16M4 12h16M4 18h16" <ul
></path> tabindex="-1"
</svg> class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"
</label>
{% endif %}
<a
href="{{ meta.server_url }}"
class="btn btn-ghost text-xl normal-case"
> >
<svg class="fill-base-content h-8" viewBox="0 0 104 26" role="img"> <li>
<use href="{% static "/images/logo-name.svg" %}#ep-logo-name" /> <a href="{{ meta.server_url }}/Package" id="packageLink">Package</a>
</svg> </li>
</a> <li>
</div> <a href="{{ meta.server_url }}/pathway" id="pathwayLink">Pathway</a>
</li>
{% if not public_mode %} <li><a href="{{ meta.server_url }}/rule" id="ruleLink">Rule</a></li>
{# Desktop menu - hidden on mobile, visible on desktop #} <li>
<div class="navbar-center hidden lg:flex"> <a href="{{ meta.server_url }}/compound" id="compoundLink"
<a >Compound</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>
<a href="{{ meta.server_url }}/package" id="packageLink" <li>
>Package</a <a href="{{ meta.server_url }}/reaction" id="reactionLink"
> >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"
> >
<svg </li>
xmlns="http://www.w3.org/2000/svg" <li>
width="16" <a href="{{ meta.server_url }}/model" id="relative-reasoningLink"
height="16" >Model</a
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"
> >
<svg </li>
xmlns="http://www.w3.org/2000/svg" <li>
width="24" <a href="{{ meta.server_url }}/scenario" id="scenarioLink"
height="24" >Scenario</a
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>
<a href="{{ meta.user.url }}" id="accountbutton">Settings</a> </ul>
</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>
</div> {% endif %}
{# Mobile drawer menu - slides in from the left #}
<div class="drawer-side"> <div class="navbar-end">
<label for="drawer-toggle" class="drawer-overlay"></label> {% if not public_mode %}
<ul class="menu min-h-full w-80 bg-base-200 p-4 text-base-content"> <a id="search-trigger" role="button" class="cursor-pointer">
{# Drawer header with close button #} <div
<li class="mb-4"> class="badge badge-dash bg-base-200 text-base-content/50 m-1 flex items-center space-x-1 p-2"
<div class="flex items-center justify-between"> >
<span class="font-bold text-lg">Menu</span> <svg
<label xmlns="http://www.w3.org/2000/svg"
for="drawer-toggle" width="16"
class="btn btn-sm btn-circle btn-ghost" height="16"
aria-label="Close menu" 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"
> >
<svg <path d="m21 21-4.34-4.34" />
xmlns="http://www.w3.org/2000/svg" <circle cx="11" cy="11" r="8" />
class="h-6 w-6" </svg>
fill="none" <span id="search-shortcut">⌘K</span>
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>
</li> </a>
{% if not public_mode %} {% endif %}
{# Predict link #} {% if meta.user.username == 'anonymous' or public_mode %}
<li> <a href="{% url 'login' %}" id="loginButton" class="p-2">Login</a>
<a {% else %}
href="{{ meta.server_url }}/predict" <div class="dropdown dropdown-end">
class="text-lg" <div
id="predictLinkMobile" tabindex="0"
>Predict</a role="button"
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"
> >
</li> <circle cx="12" cy="12" r="10" />
{# Browse menu with submenu #} <circle cx="12" cy="10" r="3" />
<li> <path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662" />
<details> </svg>
<summary class="text-lg">Browse</summary> </div>
<ul> <ul
<li> tabindex="-1"
<a href="{{ meta.server_url }}/package" id="packageLinkMobile" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-sm"
>Package</a >
> <li><a href="{{ meta.user.url }}" id="accountbutton">Settings</a></li>
</li> <li>
<li> <form
<a href="{{ meta.server_url }}/pathway" id="pathwayLinkMobile" id="logoutForm"
>Pathway</a action="{% url 'logout' %}"
> method="post"
</li> style="display: none;"
<li> >
<a href="{{ meta.server_url }}/rule" id="ruleLinkMobile" {% csrf_token %}
>Rule</a <input type="hidden" name="logout" value="true" />
> </form>
</li> <a
<li> href="#"
<a href="{{ meta.server_url }}/compound" id="compoundLinkMobile" id="logoutButton"
>Compound</a onclick="event.preventDefault(); document.getElementById('logoutForm').submit();"
> >Logout</a
</li> >
<li> </li>
<a href="{{ meta.server_url }}/reaction" id="reactionLinkMobile" </ul>
>Reaction</a </div>
> {% 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>

View File

@ -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 -translate-x-8"> <div class="absolute bottom-40 left-1/8 z-10 -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,68 +20,16 @@
<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 mx-auto -mt-32 mb-10 w-full flex-col lg:flex-row-reverse" class="relative z-20 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" x-model="drawMode" /> <input type="checkbox" />
<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"
@ -134,24 +82,16 @@
<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"
x-show="!drawMode" class="scale-100 transform opacity-100 transition-all duration-300 ease-in-out"
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
@ -159,7 +99,6 @@
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>
@ -168,35 +107,26 @@
<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 <a class="absolute top-0 left-[calc(100%-5.4rem)]" href="#"
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"
x-show="drawMode" class="hidden w-full scale-95 transform opacity-0 transition-all duration-300 ease-in-out"
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"
@ -326,31 +256,6 @@
<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
@ -373,13 +278,16 @@
const date = new Date(topic.created_at).toLocaleDateString(); const date = new Date(topic.created_at).toLocaleDateString();
return ` return `
<div class="card bg-white shadow-sm hover:shadow-lg transition-shadow duration-300 h-52 w-75 flex-shrink-0"> <div class="card bg-white shadow-xs hover:shadow-lg transition-shadow duration-300 h-64 w-75 flex-shrink-0">
<div class="card-body flex flex-col h-full justify-between"> <div class="card-body flex flex-col h-full">
<h3 class="card-title leading-tight font-normal tracking-tight mb-2 line-clamp-5 overflow-hidden"> <h3 class="card-title leading-tight font-normal tracking-tight h-12 mb-2 line-clamp-2 text-ellipsis wrap-break-word overflow-hidden">
<a href="${topic.url}" target="_blank" class="hover:text-primary"> <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">
@ -405,20 +313,141 @@
// Make render function globally available // Make render function globally available
window.renderDiscourseTopics = renderDiscourseTopics; window.renderDiscourseTopics = renderDiscourseTopics;
// Ketcher iframe load handler - set up change event to sync SMILES // Toggle functionality with smooth animations
document.addEventListener("DOMContentLoaded", function () { function toggleInputMode() {
const indexKetcher = document.getElementById("index-ketcher"); const toggle = $('input[type="checkbox"]');
indexKetcher.addEventListener("load", function () { const textContainer = $("#text-input-container");
const ketcherContainer = $("#ketcher-container");
const formCard = $(".card");
const fieldset = $(".fieldset");
if (toggle.is(":checked")) {
// Draw mode - show Ketcher, hide text input
textContainer.addClass("opacity-0 transform scale-95");
textContainer.removeClass("opacity-100 transform scale-100");
// Adjust fieldset padding for Ketcher mode - reduce padding and make more compact
fieldset.removeClass("p-8");
fieldset.addClass("p-4");
// Wait for fade out to complete, then hide and show new content
setTimeout(() => {
textContainer.addClass("hidden");
ketcherContainer.removeClass("hidden opacity-0 transform scale-95");
ketcherContainer.addClass("opacity-100 transform scale-100");
// Force re-evaluation of iframe size
const iframe = document.getElementById("index-ketcher");
if (iframe) {
iframe.style.height = "400px";
}
}, 300);
} else {
// 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 %}

View File

@ -1,71 +1,44 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% block content %} {% block content %}
<div class="panel-group" id="migration-detail"> <div class="panel-group" id="migration-detail">
<div class="panel panel-default"> <div class="panel panel-default">
<div <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
class="panel-heading" Migration Status BT Rules
id="headingPanel" </div>
style="font-size:2rem;height: 46px" <div class="panel-body">
> <p>Rules with Error: {{ error }}/{{ total }} </p>
Migration Status BT Rules <p>Rules without Error: {{ success }}/{{ total }}</p>
</div> </div>
<div class="panel-body">
<p>Rules with Error: {{ error }}/{{ total }}</p>
<p>Rules without Error: {{ success }}/{{ total }}</p>
</div>
{% for obj in results %} {% for obj in results %}
<div <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
class="panel panel-default panel-heading list-group-item" {% if obj.status %}
style="background-color:silver" <span class="glyphicon glyphicon-ok" aria-hidden="true"
> style="float:right" data-toggle="tooltip"
{% if obj.status %} data-placement="top" title="" data-original-title="Reviewed">
<span
class="glyphicon glyphicon-ok"
aria-hidden="true"
style="float:right"
data-toggle="tooltip"
data-placement="top"
title=""
data-original-title="Reviewed"
>
</span> </span>
{% else %} {% else %}
<span <span class="glyphicon glyphicon-remove" aria-hidden="true"
class="glyphicon glyphicon-remove" style="float:right" data-toggle="tooltip"
aria-hidden="true" data-placement="top" title="" data-original-title="Reviewed">
style="float:right"
data-toggle="tooltip"
data-placement="top"
title=""
data-original-title="Reviewed"
>
</span> </span>
{% endif %} {% endif %}
<h4 class="panel-title"> <h4 class="panel-title">
<a <a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
id="{{ obj.id }}-link" href="#{{ obj.id }}">{{ obj.name|safe }}</a>
data-toggle="collapse" </h4>
data-parent="#migration-detail"
href="#{{ obj.id }}"
>{{ obj.name|safe }}</a
>
</h4>
</div> </div>
<div <div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
id="{{ obj.id }}" <div class="panel-body list-group-item">
class="panel-collapse {% if not obj.status %}in{% endif %} collapse" <a class="list-group-item" href="{{ obj.detail_url }}">{{ obj.name|safe }} Migration Detail Page</a>
> </div>
<div class="panel-body list-group-item">
<a class="list-group-item" href="{{ obj.detail_url }}"
>{{ obj.name|safe }} Migration Detail Page</a
>
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<script></script> <script>
</script>
{% endblock content %} {% endblock content %}

View File

@ -1,77 +1,45 @@
{% extends "framework.html" %} {% extends "framework.html" %}
{% block content %} {% block content %}
<div class="panel-group" id="migration-detail"> <div class="panel-group" id="migration-detail">
<div class="panel panel-default"> <div class="panel panel-default">
<div <div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
class="panel-heading" Migration Status for {{ bt_rule_name }}
id="headingPanel" </div>
style="font-size:2rem;height: 46px" <div class="panel-body">
> <p>A package contains pathways, rules, etc. and can reflect specific experimental
Migration Status for {{ bt_rule_name }} conditions. <a target="_blank" href="https://wiki.envipath.org/index.php/packages" role="button">Learn
</div> more &gt;&gt;</a></p>
<div class="panel-body"> </div>
<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 &gt;&gt;</a
>
</p>
</div>
{% for obj in results %} {% for obj in results %}
<div <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
class="panel panel-default panel-heading list-group-item" {% if obj.status %}
style="background-color:silver" <span class="glyphicon glyphicon-ok" aria-hidden="true"
> style="float:right" data-toggle="tooltip"
{% if obj.status %} data-placement="top" title="" data-original-title="Reviewed">
<span </span>
class="glyphicon glyphicon-ok" {% else %}
aria-hidden="true" <span class="glyphicon glyphicon-remove" aria-hidden="true"
style="float:right" style="float:right" data-toggle="tooltip"
data-toggle="tooltip" data-placement="top" title="" data-original-title="Reviewed">
data-placement="top" </span>
title="" {% endif %}
data-original-title="Reviewed" <h4 class="panel-title">
> <a id="{{ obj.id }}-link" data-toggle="collapse" data-parent="#migration-detail"
</span> href="#{{ obj.id }}">{{ obj.name|safe }}</a>
{% else %} </h4>
<span </div>
class="glyphicon glyphicon-remove" <div id="{{ obj.id }}" class="panel-collapse collapse {% if not obj.status %}in{% endif %}">
aria-hidden="true" <div class="panel-body list-group-item">
style="float:right" <pre>{{ obj.detail }}</pre>
data-toggle="tooltip" </div>
data-placement="top" </div>
title="" {% endfor %}
data-original-title="Reviewed"
>
</span>
{% endif %}
<h4 class="panel-title">
<a
id="{{ obj.id }}-link"
data-toggle="collapse"
data-parent="#migration-detail"
href="#{{ obj.id }}"
>{{ obj.name|safe }}</a
>
</h4>
</div> </div>
<div
id="{{ obj.id }}"
class="panel-collapse {% if not obj.status %}in{% endif %} collapse"
>
<div class="panel-body list-group-item">
<pre>{{ obj.detail }}</pre>
</div>
</div>
{% endfor %}
</div> </div>
</div>
<script></script> <script>
</script>
{% endblock content %} {% endblock content %}

View File

@ -0,0 +1,31 @@
<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>

View File

@ -1,85 +1,42 @@
<dialog <div class="modal fade" tabindex="-1" id="import_legacy_package_modal" role="dialog"
id="import_legacy_package_modal" aria-labelledby="import_legacy_package_modal" aria-hidden="true">
class="modal" <div class="modal-dialog">
x-data="modalForm()" <div class="modal-content">
@close="reset()" <div class="modal-header">
> <button type="button" class="close" data-dismiss="modal">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> <span class="sr-only">Close</span>
<h3 class="text-lg font-bold">Import Package from Legacy System</h3> </button>
<h4 class="modal-title">Import Package from legacy System</h4>
<!-- Close button (X) --> </div>
<form method="dialog"> <div class="modal-body">
<button <p>Create a Package based on the JSON Export of the legacy system.</p>
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" <form id="import-legacy-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post"
:disabled="isSubmitting" enctype="multipart/form-data">
> {% csrf_token %}
<p>
</button> <label class="btn btn-primary" for="legacyJsonFile">
</form> <input id="legacyJsonFile" name="file" type="file" style="display:none;"
onchange="$('#upload-legacy-file-info').html(this.files[0].name)">
<!-- Body --> Choose JSON File
<div class="py-4"> </label>
<p class="mb-4"> <span class="label label-info" id="upload-legacy-file-info"></span>
Create a Package based on the JSON Export of the legacy system. <input type="hidden" value="import-legacy-package-json" name="hidden" readonly="">
</p> </p>
<form </form>
id="import-legacy-package-modal-form" </div>
accept-charset="UTF-8" <div class="modal-footer">
method="post" <a id="import-legacy-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a>
enctype="multipart/form-data" <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
> </div>
{% csrf_token %}
<div class="form-control">
<label class="label">
<span class="label-text">Legacy JSON File</span>
</label>
<input
type="file"
id="legacyJsonFile"
name="file"
class="file-input file-input-bordered w-full"
accept=".json"
required
/>
</div> </div>
<input
type="hidden"
value="import-legacy-package-json"
name="hidden"
readonly
/>
</form>
</div> </div>
</div>
<!-- Footer --> <script>
<div class="modal-action"> $(function () {
<button $('#import-legacy-package-modal-form-submit').on('click', function (e) {
type="button" e.preventDefault();
class="btn" $('#import-legacy-package-modal-form').submit();
onclick="this.closest('dialog').close()" });
:disabled="isSubmitting" });
> </script>
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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,83 +1,42 @@
<dialog <div class="modal fade" tabindex="-1" id="import_package_modal" role="dialog"
id="import_package_modal" aria-labelledby="import_package_modal" aria-hidden="true">
class="modal" <div class="modal-dialog">
x-data="modalForm()" <div class="modal-content">
@close="reset()" <div class="modal-header">
> <button type="button" class="close" data-dismiss="modal">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> <span class="sr-only">Close</span>
<h3 class="text-lg font-bold">Import Package</h3> </button>
<h4 class="modal-title">Import Package</h4>
<!-- Close button (X) --> </div>
<form method="dialog"> <div class="modal-body">
<button <p>Create a Package based on a JSON Export.</p>
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" <form id="import-package-modal-form" accept-charset="UTF-8" data-remote="true" method="post"
:disabled="isSubmitting" enctype="multipart/form-data">
> {% csrf_token %}
<p>
</button> <label class="btn btn-primary" for="jsonFile">
</form> <input id="jsonFile" name="file" type="file" style="display:none;"
onchange="$('#upload-file-info').html(this.files[0].name)">
<!-- Body --> Choose JSON File
<div class="py-4"> </label>
<p class="mb-4">Create a Package based on a JSON Export.</p> <span class="label label-info" id="upload-file-info"></span>
<form <input type="hidden" value="import-package-json" name="hidden" readonly="">
id="import-package-modal-form" </p>
accept-charset="UTF-8" </form>
method="post" </div>
enctype="multipart/form-data" <div class="modal-footer">
> <a id="import-package-modal-form-submit" class="btn btn-primary" href="#">Submit</a>
{% csrf_token %} <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<div class="form-control"> </div>
<label class="label">
<span class="label-text">JSON File</span>
</label>
<input
type="file"
id="jsonFile"
name="file"
class="file-input file-input-bordered w-full"
accept=".json"
required
/>
</div> </div>
<input
type="hidden"
value="import-package-json"
name="hidden"
readonly
/>
</form>
</div> </div>
</div>
<!-- Footer --> <script>
<div class="modal-action"> $(function () {
<button $('#import-package-modal-form-submit').on('click', function (e) {
type="button" e.preventDefault();
class="btn" $('#import-package-modal-form').submit();
onclick="this.closest('dialog').close()" });
:disabled="isSubmitting" });
> </script>
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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,137 +1,78 @@
{% load static %} {% load static %}
<div class="modal fade bs-modal-lg" id="new_compound_modal" tabindex="-1" aria-labelledby="new_compound_modal" aria-modal="true"
<dialog role="dialog">
id="new_compound_modal" <div class="modal-dialog modal-lg">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <button type="button" class="close" data-dismiss="modal" aria-label="Close">
> <span aria-hidden="true">×</span>
<div class="modal-box max-w-3xl"> </button>
<!-- Header --> <h4 class="modal-title">Create a new Compound</h4>
<h3 class="text-lg font-bold">Create a new Compound</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <form id="new_compound_modal_form" accept-charset="UTF-8" action="{% url 'package compound list' meta.current_package.uuid %}" data-remote="true" method="post">
<form method="dialog"> {% csrf_token %}
<button <label for="compound-name">Name</label>
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" <input id="compound-name" class="form-control" name="compound-name" placeholder="Name"/>
:disabled="isSubmitting" <label for="compound-description">Description</label>
> <input id="compound-description" class="form-control" name="compound-description" placeholder="Description"/>
<label for="compound-smiles">SMILES</label>
</button> <input type="text" class="form-control" name="compound-smiles" placeholder="SMILES" id="compound-smiles">
</form> <p></p>
<div>
<!-- Body --> <iframe id="new_compound_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
<div class="py-4"> height="510"></iframe>
<form </div>
id="new-compound-modal-form" <p></p>
accept-charset="UTF-8" </form>
action="{% url 'package compound list' meta.current_package.uuid %}" </div>
method="post" <div class="modal-footer">
> <button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
{% csrf_token %} </button>
<button type="button" class="btn btn-primary" id="new_compound_modal_form_submit">Submit</button>
<div class="form-control mb-3"> </div>
<label class="label" for="compound-name">
<span class="label-text">Name</span>
</label>
<input
id="compound-name"
class="input input-bordered w-full"
name="compound-name"
placeholder="Name"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="compound-description">
<span class="label-text">Description</span>
</label>
<input
id="compound-description"
class="input input-bordered w-full"
name="compound-description"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="compound-smiles">
<span class="label-text">SMILES</span>
</label>
<input
type="text"
class="input input-bordered w-full"
name="compound-smiles"
placeholder="SMILES"
id="compound-smiles"
/>
</div>
<div class="mb-3">
<iframe
id="new_compound_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div>
</form>
</div> </div>
<!-- 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('new-compound-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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>
<script> <script>
document
.getElementById("new_compound_ketcher") function newCompoundModalketcherToNewCompoundModalTextInput() {
.addEventListener("load", function () { $('#compound-smiles').val(this.ketcher.getSmiles());
const iframe = this; }
const checkKetcherReady = () => {
const win = iframe.contentWindow; $(function() {
if (win.ketcher && "editor" in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({ $('#new_compound_ketcher').on('load', function() {
once: false, const checkKetcherReady = () => {
priority: 0, win = this.contentWindow
f: function () { if (win.ketcher && 'editor' in win.ketcher) {
document.getElementById("compound-smiles").value = win.ketcher.editor.event.change.handlers.push({
this.ketcher.getSmiles(); once: false,
}, priority: 0,
ketcher: win.ketcher, f: newCompoundModalketcherToNewCompoundModalTextInput,
}); ketcher: win.ketcher
} else { });
setTimeout(checkKetcherReady, 100); } else {
} 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>

View File

@ -1,96 +1,45 @@
{% load static %} <div class="modal fade" tabindex="-1" id="new_group_modal" role="dialog" aria-labelledby="new_group_modal"
aria-hidden="true">
<dialog <div class="modal-dialog">
id="new_group_modal" <div class="modal-content">
class="modal" <div class="modal-header">
x-data="modalForm()" <button type="button" class="close" data-dismiss="modal">
@close="reset()" <span aria-hidden="true">&times;</span>
> <span class="sr-only">Close</span>
<div class="modal-box"> </button>
<!-- Header --> <h4 class="modal-title">New Group</h4>
<h3 class="font-bold text-lg">New Group</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Create new Group. You can assign users to the group once
<form method="dialog"> it is created. Description can be changed after creation.</p>
<button <form id="new_group_modal_form" accept-charset="UTF-8" action="{{ SERVER_BASE }}/group"
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" data-remote="true"
:disabled="isSubmitting" method="post">
> {% csrf_token %}
<p>
</button> <label for="name">Name</label>
</form> <input id="name" type="text" name="group-name" class="form-control" placeholder="Name"/>
</p>
<!-- Body --> <p>
<div class="py-4"> <label for="description">Description</label>
<p class="mb-4"> <input id="description" type="text" class="form-control" placeholder="Description..."
Create new Group. You can assign users to the group once it is created. name="group-description"/>
Description can be changed after creation. </p>
</p> </form>
</div>
<form <div class="modal-footer">
id="new-group-modal-form" <a id="new_group_modal_form_submit" class="btn btn-primary" href="#">Submit</a>
accept-charset="UTF-8" <button type="button" class="btn btn-default" data-dismiss="modal">
action="{{ SERVER_BASE }}/group" Cancel
method="post" </button>
> </div>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="group-name">
<span class="label-text">Name</span>
</label>
<input
id="group-name"
class="input input-bordered w-full"
name="group-name"
placeholder="Name"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="group-description">
<span class="label-text">Description</span>
</label>
<input
id="group-description"
type="text"
class="input input-bordered w-full"
placeholder="Description..."
name="group-description"
/>
</div>
</form>
</div> </div>
</div>
<!-- Footer --> <script>
<div class="modal-action"> $(function() {
<button $('#new_group_modal_form_submit').on('click', function() {
type="button" $('#new_group_modal_form').submit();
class="btn" });
onclick="this.closest('dialog').close()" });
:disabled="isSubmitting" </script>
>
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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,337 +1,188 @@
<dialog
id="new_model_modal"
class="modal"
x-data="{
isSubmitting: false,
modelType: '',
buildAppDomain: false,
reset() { <div class="modal fade" tabindex="-1" id="new_model_modal" role="dialog" aria-labelledby="new_model_modal"
this.isSubmitting = false; aria-hidden="true">
this.modelType = ''; <div class="modal-dialog modal-lg">
this.buildAppDomain = false; <div class="modal-content">
}, <div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
get showMlrr() { <span aria-hidden="true">&times;</span>
return this.modelType === 'mlrr'; <span class="sr-only">Close</span>
}, </button>
<h4 class="modal-title">New Model</h4>
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-box max-w-3xl">
<!-- Header -->
<h3 class="text-lg font-bold">New Model</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="new_model_form"
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
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
available. For additional information have a look at our
<a
target="_blank"
href="https://wiki.envipath.org/index.php/relative-reasoning"
class="link"
>wiki &gt;&gt;</a
>
</span>
</div>
<!-- Name -->
<div class="form-control mb-3">
<label class="label" for="model-name">
<span class="label-text">Name</span>
</label>
<input
id="model-name"
name="model-name"
class="input input-bordered w-full"
placeholder="Name"
required
/>
</div>
<!-- Description -->
<div class="form-control mb-3">
<label class="label" for="model-description">
<span class="label-text">Description</span>
</label>
<input
id="model-description"
name="model-description"
class="input input-bordered w-full"
placeholder="Description"
/>
</div>
<!-- Model Type -->
<div class="form-control mb-3">
<label class="label" for="model-type">
<span class="label-text">Model Type</span>
</label>
<select
id="model-type"
name="model-type"
class="select select-bordered w-full"
x-model="modelType"
required
>
<option value="" disabled selected>Select Model Type</option>
{% for k, v in model_types.items %}
<option value="{{ v }}">{{ k }}</option>
{% endfor %}
</select>
</div>
<!-- Rule Packages (MLRR, RBRR) -->
<div class="form-control mb-3" x-show="showMlrr || showRbrr" x-cloak>
<label class="label" for="model-rule-packages">
<span class="label-text">Rule Packages</span>
</label>
<select
id="model-rule-packages"
name="model-rule-packages"
class="select select-bordered w-full h-32"
multiple
>
<optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
<optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %}
{% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
</select>
<label class="label">
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
</label>
</div>
<!-- Data Packages (MLRR, RBRR, Enviformer) -->
<div
class="form-control mb-3"
x-show="showMlrr || showRbrr || showEnviformer"
x-cloak
>
<label class="label" for="model-data-packages">
<span class="label-text">Data Packages</span>
</label>
<select
id="model-data-packages"
name="model-data-packages"
class="select select-bordered w-full h-32"
multiple
>
<optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
<optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %}
{% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
</select>
<label class="label">
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
</label>
</div>
<!-- Fingerprinter (MLRR) -->
<div class="form-control mb-3" x-show="showMlrr" x-cloak>
<label class="label" for="model-fingerprinter">
<span class="label-text">Fingerprinter</span>
</label>
<select
id="model-fingerprinter"
name="model-fingerprinter"
class="select select-bordered w-full h-32"
multiple
>
<option value="MACCS" selected>MACCS Fingerprinter</option>
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
<optgroup label="Additional Fingerprinter / Descriptor">
{% for k, v in additional_descriptors.items %}
<option value="{{ v }}">{{ k }}</option>
{% endfor %}
</optgroup>
{% endif %}
</select>
<label class="label">
<span class="label-text-alt">Hold Ctrl/Cmd to select multiple</span>
</label>
</div>
<!-- Threshold (MLRR, Enviformer) -->
<div
class="form-control mb-3"
x-show="showMlrr || showEnviformer"
x-cloak
>
<label class="label" for="model-threshold">
<span class="label-text">Threshold</span>
</label>
<input
type="number"
min="0"
max="1"
step="0.05"
value="0.5"
id="model-threshold"
name="model-threshold"
class="input input-bordered w-full"
/>
</div>
<!-- Applicability Domain (MLRR) -->
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
<div x-show="showMlrr" x-cloak>
<div class="form-control mb-3">
<label class="label cursor-pointer justify-start gap-3">
<input
type="checkbox"
id="build-app-domain"
name="build-app-domain"
class="checkbox"
x-model="buildAppDomain"
/>
<span class="label-text"
>Also build an Applicability Domain?</span
>
</label>
</div> </div>
<div class="modal-body">
<form id="new_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model"
data-remote="true" method="post">
{% csrf_token %}
<div class="jumbotron">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 want the object to be based on. There are multiple types of models available.
For additional information have a look at our
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki
&gt;&gt;</a>
</div>
<!-- Name -->
<label for="model-name">Name</label>
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
<div x-show="buildAppDomain" x-cloak class="ml-4 space-y-3"> <!-- Description -->
<div class="form-control"> <label for="model-description">Description</label>
<label class="label" for="num-neighbors"> <input id="model-description" name="model-description" class="form-control"
<span class="label-text">Number of Neighbors</span> placeholder="Description"/>
</label>
<input
id="num-neighbors"
name="num-neighbors"
type="number"
class="input input-bordered w-full"
value="5"
step="1"
min="0"
max="10"
/>
</div>
<div class="form-control"> <!-- Model Type -->
<label class="label" for="local-compatibility-threshold"> <label for="model-type">Model Type</label>
<span class="label-text">Local Compatibility Threshold</span> <select id="model-type" name="model-type" class="form-control" data-width='100%'>
</label> <option disabled selected>Select Model Type</option>
<input {% for k, v in model_types.items %}
id="local-compatibility-threshold" <option value="{{ v }}">{{ k }}</option>
name="local-compatibility-threshold" {% endfor %}
type="number" </select>
class="input input-bordered w-full"
value="0.5"
step="0.01"
min="0"
max="1"
/>
</div>
<div class="form-control"> <!-- Rule Packages -->
<label class="label" for="reliability-threshold"> <div id="rule-packages" class="ep-model-param mlrr rbrr">
<span class="label-text">Reliability Threshold</span> <label for="model-rule-packages">Rule Packages</label>
</label> <select id="model-rule-packages" name="model-rule-packages" data-actions-box='true'
<input class="form-control" multiple data-width='100%'>
id="reliability-threshold" <option disabled>Reviewed Packages</option>
name="reliability-threshold" {% for obj in meta.readable_packages %}
type="number" {% if obj.reviewed %}
class="input input-bordered w-full" <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
value="0.5" {% endif %}
step="0.01" {% endfor %}
min="0"
max="1" <option disabled>Unreviewed Packages</option>
/> {% for obj in meta.readable_packages %}
</div> {% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<!-- Data Packages -->
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
<label for="model-data-packages">Data Packages</label>
<select id="model-data-packages" name="model-data-packages" data-actions-box='true'
class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
<option disabled>Unreviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<!-- Fingerprinter -->
<div id="fingerprinter" class="ep-model-param mlrr">
<label for="model-fingerprinter">Fingerprinter</label>
<select id="model-fingerprinter" name="model-fingerprinter" data-actions-box='true'
class="form-control" multiple data-width='100%'>
<option value="MACCS" selected>MACCS Fingerprinter</option>
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
<option disabled selected>Select Additional Fingerprinter / Descriptor</option>
{% for k, v in additional_descriptors.items %}
<option value="{{ v }}">{{ k }}</option>
{% endfor %}
{% endif %}
</select>
</div>
<!-- Threshold -->
<div id="threshold" class="ep-model-param mlrr enviformer">
<label for="model-threshold">Threshold</label>
<input type="number" min="0" max="1" step="0.05" value="0.5" id="model-threshold"
name="model-threshold" class="form-control">
</div>
<div id="appdomain" class="ep-model-param mlrr">
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
<!-- Build AD? -->
<div class="checkbox">
<label>
<input type="checkbox" id="build-app-domain" name="build-app-domain">Also build an
Applicability Domain?
</label>
</div>
<div id="ad-params" style="display:none">
<!-- Num Neighbors -->
<label for="num-neighbors">Number of Neighbors</label>
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control"
value="5"
step="1" min="0" max="10">
<!-- Local Compatibility -->
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
<input id="local-compatibility-threshold" name="local-compatibility-threshold"
type="number"
class="form-control" 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" step="0.01" min="0" max="1">
</div>
{% endif %}
</div>
</form>
</div> </div>
</div> <div class="modal-footer">
{% endif %} <a id="new_model_modal_form_submit" class="btn btn-primary" href="#">Submit</a>
</form> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div> </div>
</div>
<!-- Footer --> <script>
<div class="modal-action"> $(function () {
<button // Built in Model Types
type="button" var nativeModelTypes = [
class="btn" "mlrr",
onclick="this.closest('dialog').close()" "rbrr",
:disabled="isSubmitting" "enviformer",
> ]
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>
<!-- Backdrop --> // Initially hide all "specific" forms
<form method="dialog" class="modal-backdrop"> $(".ep-model-param").each(function () {
<button :disabled="isSubmitting">close</button> $(this).hide();
</form> });
</dialog>
$('#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>

View File

@ -1,93 +1,62 @@
{% load static %} <div 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-content">
<div class="modal-box"> <div class="modal-header">
<!-- Header --> <button type="button"
<h3 class="font-bold text-lg">New Package</h3> class="close"
data-dismiss="modal">
<!-- Close button (X) --> <span aria-hidden="true">&times;</span>
<form method="dialog"> <span class="sr-only">Close</span>
<button </button>
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <h4 class="modal-title">New Package</h4>
:disabled="isSubmitting" </div>
> <div class="modal-body">
<p>Create new package. Description can be changed later.</p>
</button> <form 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 %}
<p>
<form <label for="name">Name</label>
id="new-package-modal-form" <input id="name" class="form-control"
accept-charset="UTF-8" name="package-name"
action="" placeholder="Name"/>
method="post" </p>
> <p>
{% csrf_token %} <label for="description">Description</label>
<input id="description"
<div class="form-control mb-3"> type="text"
<label class="label" for="package-name"> rows="3"
<span class="label-text">Name</span> class="form-control"
</label> placeholder="Description..."
<input name="package-description"/>
id="package-name" </p>
class="input input-bordered w-full" </form>
name="package-name" </div>
placeholder="Name" <div class="modal-footer">
required <a id="new_package_modal_form_submit"
/> class="btn btn-primary"
href="#">Submit</a>
<button type="button"
class="btn btn-default"
data-dismiss="modal">Cancel
</button>
</div>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="package-description">
<span class="label-text">Description</span>
</label>
<input
id="package-description"
type="text"
class="input input-bordered w-full"
placeholder="Description..."
name="package-description"
/>
</div>
</form>
</div> </div>
</div>
<!-- Footer --> <script>
<div class="modal-action"> $(function() {
<button $('#new_package_modal_form_submit').on('click', function (e) {
type="button" e.preventDefault();
class="btn" $('#new_package_modal_form').submit();
onclick="this.closest('dialog').close()" });
:disabled="isSubmitting" });
> </script>
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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -0,0 +1,302 @@
{% 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">&times;</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>

View File

@ -1,260 +1,109 @@
{% load static %} {% load static %}
<dialog <div id="new_prediction_setting_modal" class="modal" tabindex="-1">
id="new_prediction_setting_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="{ <div class="modal-header">
isSubmitting: false, <h5 class="modal-title">Create a Prediction Setting</h5>
tpMethod: '', <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</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 %}
reset() { <label for="prediction-setting-name">Name</label>
this.isSubmitting = false; <input id="prediction-setting-name" name="prediction-setting-name" class="form-control" placeholder="Name"/>
this.tpMethod = ''; <label for="prediction-setting-description">Description</label>
}, <input id="prediction-setting-description" name="prediction-setting-description" class="form-control"
placeholder="Description"/>
async submit() { <label for="prediction-setting-max-nodes">Max #Nodes</label>
const form = document.getElementById('new-prediction-setting-modal-form'); <input id="prediction-setting-max-nodes" type="number" class="form-control" name="prediction-setting-max-nodes" value="30" min="1" max="50" step="1">
if (!form.checkValidity()) { <label for="prediction-setting-max-depth">Max Depth</label>
form.reportValidity(); <input id="prediction-setting-max-depth" type="number" class="form-control" name="prediction-setting-max-depth" value="5" min="1" max="8" step="1">
return;
}
this.isSubmitting = true; <label for="tp-generation-method">TP Generation Method</label>
const formData = new FormData(form); <select id="tp-generation-method" name="tp-generation-method" class="form-control" data-width='100%'>
<option disabled selected>Select how TPs are generated</option>
<option value="rule-based-prediction-setting">Rule Based</option>
<option value="model-based-prediction-setting">Model Based</option>
</select>
<div id="rule-based-prediction-setting-specific-form">
<!-- Rule Packages -->
<label>Rule Packages</label><br>
<select id="rule-based-prediction-setting-packages" name="rule-based-prediction-setting-packages"
data-actions-box='true' class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
try { <option disabled>Unreviewed Packages</option>
const response = await fetch('/setting', { {% for obj in meta.readable_packages %}
method: 'POST', {% if not obj.reviewed %}
body: new URLSearchParams(formData) <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div id="model-based-prediction-setting-specific-form">
<label>Select Model</label><br>
<select id="model-based-prediction-setting-model" name="model-based-prediction-setting-model" class="form-control" data-width='100%'>
<option disabled selected>Select the model</option>
{% for m in models %}
<option value="{{ m.url }}">{{ m.name|safe }}</option>
{% endfor %}
</select>
<label for="model-based-prediction-setting-threshold">Threshold</label>
<input id="model-based-prediction-setting-threshold" name="model-based-prediction-setting-threshold" class="form-control" placeholder="0.25" type="number"/>
</div>
<input class="form-check-input" type="checkbox" value="on" id="prediction-setting-new-default" name="prediction-setting-new-default">
<label class="form-check-label" for="prediction-setting-new-default">Set this setting as new default</label>
</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="new-prediction-setting-modal-submit">Create</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
// Initially hide all "specific" forms
$("div[id$='-specific-form']").each( function() {
$(this).hide();
}); });
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) --> $("#rule-based-prediction-setting-packages").selectpicker();
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body --> // On change hide all and show only selected
<div class="py-4"> $("#tp-generation-method").change(function() {
<p class="mb-4"> $("div[id$='-specific-form']").each( function() {
To create a Prediction Setting fill the form below and click "Create" $(this).hide();
</p> });
<form val = $('option:selected', this).val();
id="new-prediction-setting-modal-form" $("#" + val + "-specific-form").show();
accept-charset="UTF-8" });
action=""
method="post"
>
{% csrf_token %}
<div class="form-control mb-3"> $('#new-prediction-setting-modal-submit').click(function(e){
<label class="label" for="prediction-setting-name"> e.preventDefault();
<span class="label-text">Name</span> // $('#new-prediction-setting-modal-form').submit();
</label>
<input
id="prediction-setting-name"
name="prediction-setting-name"
class="input input-bordered w-full"
placeholder="Name"
required
/>
</div>
<div class="form-control mb-3"> const formData = $('#new-prediction-setting-modal-form').serialize();
<label class="label" for="prediction-setting-description"> $.post('/setting', formData, function(response) {
<span class="label-text">Description</span> location.reload();
</label> });
<input });
id="prediction-setting-description" })
name="prediction-setting-description" </script>
class="input input-bordered w-full"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="prediction-setting-max-nodes">
<span class="label-text">Max #Nodes</span>
</label>
<input
id="prediction-setting-max-nodes"
type="number"
class="input input-bordered w-full"
name="prediction-setting-max-nodes"
value="30"
min="1"
max="50"
step="1"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="prediction-setting-max-depth">
<span class="label-text">Max Depth</span>
</label>
<input
id="prediction-setting-max-depth"
type="number"
class="input input-bordered w-full"
name="prediction-setting-max-depth"
value="5"
min="1"
max="8"
step="1"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="tp-generation-method">
<span class="label-text">TP Generation Method</span>
</label>
<select
id="tp-generation-method"
name="tp-generation-method"
class="select select-bordered w-full"
x-model="tpMethod"
required
>
<option value="" disabled selected>
Select how TPs are generated
</option>
<option value="rule-based-prediction-setting">Rule Based</option>
<option value="model-based-prediction-setting">Model Based</option>
</select>
</div>
<!-- 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
id="rule-based-prediction-setting-packages"
name="rule-based-prediction-setting-packages"
class="select select-bordered w-full h-32"
multiple
>
<optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
<optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %}
{% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
</select>
<label class="label">
<span class="label-text-alt"
>Hold Ctrl/Cmd to select multiple</span
>
</label>
</div>
</div>
<!-- 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
id="model-based-prediction-setting-model"
name="model-based-prediction-setting-model"
class="select select-bordered w-full"
>
<option value="" disabled selected>Select the model</option>
{% for m in models %}
<option value="{{ m.url }}">{{ m.name|safe }}</option>
{% endfor %}
</select>
</div>
<div class="form-control mb-3">
<label class="label" for="model-based-prediction-setting-threshold">
<span class="label-text">Threshold</span>
</label>
<input
id="model-based-prediction-setting-threshold"
name="model-based-prediction-setting-threshold"
class="input input-bordered w-full"
placeholder="0.25"
type="number"
min="0"
max="1"
step="0.05"
/>
</div>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input
type="checkbox"
class="checkbox"
value="on"
id="prediction-setting-new-default"
name="prediction-setting-new-default"
/>
<span class="label-text">Set this setting as new default</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="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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,105 +1,50 @@
{% load static %} {% load static %}
<div class="modal fade bs-modal-lg" id="new_reaction_modal" tabindex="-1" aria-labelledby="new_reaction_modal" aria-modal="true"
<dialog role="dialog">
id="new_reaction_modal" <div class="modal-dialog modal-lg">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <button type="button" class="close" data-dismiss="modal" aria-label="Close">
> <span aria-hidden="true">×</span>
<div class="modal-box max-w-3xl"> </button>
<!-- Header --> <h4 class="modal-title">Create a new Reaction</h4>
<h3 class="font-bold text-lg">Create a new Reaction</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <form id="new_reaction_modal_form" accept-charset="UTF-8" action="{% url 'package reaction list' meta.current_package.uuid %}" data-remote="true" method="post">
<form method="dialog"> {% csrf_token %}
<button <label for="reaction-name">Name</label>
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <input id="reaction-name" class="form-control" name="reaction-name" placeholder="Name"/>
:disabled="isSubmitting" <label for="reaction-description">Description</label>
> <input id="reaction-description" class="form-control" name="reaction-description" placeholder="Description"/>
<p></p>
</button> <div>
</form> <iframe id="new_reaction_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
height="510"></iframe>
<!-- Body --> </div>
<div class="py-4"> <input type="hidden" name="reaction-smirks" id="reaction-smirks">
<form <p></p>
id="new-reaction-modal-form" </form>
accept-charset="UTF-8" </div>
action="{% url 'package reaction list' meta.current_package.uuid %}" <div class="modal-footer">
method="post" <button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
> </button>
{% csrf_token %} <button type="button" class="btn btn-primary" id="new_reaction_modal_form_submit">Submit</button>
</div>
<div class="form-control mb-3">
<label class="label" for="reaction-name">
<span class="label-text">Name</span>
</label>
<input
id="reaction-name"
class="input input-bordered w-full"
name="reaction-name"
placeholder="Name"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="reaction-description">
<span class="label-text">Description</span>
</label>
<input
id="reaction-description"
class="input input-bordered w-full"
name="reaction-description"
placeholder="Description"
/>
</div>
<div class="mb-3">
<iframe
id="new_reaction_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div>
<input type="hidden" name="reaction-smirks" id="reaction-smirks" />
</form>
</div> </div>
<!-- Footer --> </div>
<div class="modal-action"> <script>
<button $(function() {
type="button" $('#new_reaction_modal_form_submit').on('click', function(e) {
class="btn" e.preventDefault();
onclick="this.closest('dialog').close()" $(this).prop("disabled",true);
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@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>
<!-- Backdrop --> k = getKetcher('new_reaction_ketcher');
<form method="dialog" class="modal-backdrop"> $('#reaction-smirks').val(k.getSmiles());
<button :disabled="isSubmitting">close</button>
</form> // submit form
</dialog> $('#new_reaction_modal_form').submit();
});
});
</script>

View File

@ -1,140 +1,72 @@
{% load static %} {% load static %}
<div class="modal fade bs-modal-lg" id="new_rule_modal" tabindex="-1" aria-labelledby="new_rule_modal" aria-modal="true"
<dialog role="dialog">
id="new_rule_modal" <div class="modal-dialog modal-lg">
class="modal" <div class="modal-content">
x-data="{ <div class="modal-header">
...modalForm(), <button type="button" class="close" data-dismiss="modal" aria-label="Close">
smirksVizHtml: '', <span aria-hidden="true">×</span>
updateSmirksViz() { </button>
const smirks = document.getElementById('rule-smirks').value; <h4 class="modal-title">Create a new Rule</h4>
if (!smirks) { </div>
this.smirksVizHtml = ''; <div class="modal-body">
return; <form id="new_rule_modal_form" accept-charset="UTF-8" action="{% url 'package rule list' meta.current_package.uuid %}" data-remote="true" method="post">
} {% csrf_token %}
<label for="rule-name">Name</label>
const img = new Image(); <input id="rule-name" class="form-control" name="rule-name" placeholder="Name"/>
img.src = '{% url 'depict' %}?is_query_smirks=true&smirks=' + encodeURIComponent(smirks); <label for="rule-description">Description</label>
img.style.width = '100%'; <input id="rule-description" class="form-control" name="rule-description" placeholder="Description"/>
img.style.height = '100%'; <label for="rule-smirks">SMIRKS</label>
img.style.objectFit = 'cover'; <input id="rule-smirks" class="form-control" name="rule-smirks" placeholder="SMIRKS"/>
<p></p>
img.onload = () => { <div id="rule-smirks-viz"></div>
this.smirksVizHtml = img.outerHTML; <input type="hidden" name="rule-type" id="rule-type" value="SimpleAmbitRule">
}; <p></p>
</form>
img.onerror = () => { </div>
this.smirksVizHtml = ` <div class="modal-footer">
<div class='alert alert-error' role='alert'> <button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
<h4 class='alert-heading'>Could not render SMIRKS!</h4> </button>
<p>Could not render SMIRKS - Have you entered a valid SMIRKS?</p> <button type="button" class="btn btn-primary" id="new_rule_modal_form_submit">Submit</button>
</div>`; </div>
};
}
}"
@close="reset(); smirksVizHtml = ''"
>
<div class="modal-box max-w-3xl">
<!-- Header -->
<h3 class="font-bold text-lg">Create a new Rule</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">
<form
id="new-rule-modal-form"
accept-charset="UTF-8"
action="{% url 'package rule list' meta.current_package.uuid %}"
method="post"
>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="rule-name">
<span class="label-text">Name</span>
</label>
<input
id="rule-name"
class="input input-bordered w-full"
name="rule-name"
placeholder="Name"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="rule-description">
<span class="label-text">Description</span>
</label>
<input
id="rule-description"
class="input input-bordered w-full"
name="rule-description"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="rule-smirks">
<span class="label-text">SMIRKS</span>
</label>
<input
id="rule-smirks"
class="input input-bordered w-full"
name="rule-smirks"
placeholder="SMIRKS"
@input="updateSmirksViz()"
/>
</div>
<div id="rule-smirks-viz" class="mb-3" x-html="smirksVizHtml"></div>
<input
type="hidden"
name="rule-type"
id="rule-type"
value="SimpleAmbitRule"
/>
</form>
</div> </div>
<!-- Footer --> </div>
<div class="modal-action"> <script>
<button $(function() {
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@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>
<!-- Backdrop --> $('#rule-smirks').on('input', function(e) {
<form method="dialog" class="modal-backdrop"> $('#rule-smirks-viz').empty()
<button :disabled="isSubmitting">close</button>
</form> smirks = $('#rule-smirks').val()
</dialog>
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 = 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>

View File

@ -1,177 +1,102 @@
{% load static %} <div class="modal fade" tabindex="-1" id="new_scenario_modal" role="dialog" aria-labelledby="new_scenario_modal"
aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title">New Scenario</h4>
</div>
<div class="modal-body">
<form id="new_scenario_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/scenario"
data-remote="true" method="post">
{% csrf_token %}
<div class="jumbotron">Please enter name, description, and date of scenario. Date should be
associated to the data, not the current date. For example, this could reflect the publishing
date of a study. You can leave all fields but the name empty and fill them in later.
<a target="_blank" href="https://wiki.envipath.org/index.php/scenario" role="button">wiki
&gt;&gt;</a>
</div>
<label for="scenario-name">Name</label>
<input id="scenario-name" name="scenario-name" class="form-control" placeholder="Name"/>
<label for="scenario-description">Description</label>
<input id="scenario-description" name="scenario-description" class="form-control"
placeholder="Description"/>
<label id="dateField" for="dateYear">Date</label>
<table>
<tr>
<th>
<input type="number" id="dateYear" name="scenario-date-year" class="form-control"
placeholder="YYYY" max="{% now "Y" %}">
</th>
<th>
<input type="number" id="dateMonth" name="scenario-date-month" min="1" max="12"
class="form-control" placeholder="MM" >
</th>
<th>
<input type="number" id="dateDay" name="scenario-date-day" min="1" max="31" class="form-control"
placeholder="DD">
</th>
</tr>
</table>
<label for="scenario-type">Scenario Type</label>
<select id="scenario-type" name="scenario-type" class="form-control" data-width='100%'>
<option value="empty" selected>Empty Scenario</option>
{% for k, v in scenario_types.items %}
<option value="{{ v.name }}">{{ k }}</option>
{% endfor %}
</select>
<dialog {% for type in scenario_types.values %}
id="new_scenario_modal" <div id="{{ type.name }}-specific-inputs">
class="modal" {% for widget in type.widgets %}
x-data="{ {{ widget|safe }}
...modalForm(), {% endfor %}
scenarioType: 'empty', </div>
validateYear(el) { {% endfor %}
if (el.value && el.value.length < 4) {
el.value = new Date().getFullYear();
}
}
}"
@close="reset()"
>
<div class="modal-box max-w-3xl">
<!-- Header -->
<h3 class="font-bold text-lg">New Scenario</h3>
<!-- Close button (X) --> </form>
<form method="dialog"> </div>
<button <div class="modal-footer">
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <a id="new_scenario_modal_form_submit" class="btn btn-primary" href="#">Submit</a>
:disabled="isSubmitting" <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
> </div>
</button>
</form>
<!-- Body -->
<div class="py-4">
<form
id="new-scenario-modal-form"
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
associated to the data, not the current date. For example, this
could reflect the publishing date of a study. You can leave all
fields but the name empty and fill them in later.
<a
target="_blank"
href="https://wiki.envipath.org/index.php/scenario"
class="link"
>wiki &gt;&gt;</a
>
</span>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="scenario-name">
<span class="label-text">Name</span>
</label>
<input
id="scenario-name"
name="scenario-name"
class="input input-bordered w-full"
placeholder="Name"
required
/>
</div>
<div class="form-control mb-3">
<label class="label" for="scenario-description">
<span class="label-text">Description</span>
</label>
<input
id="scenario-description"
name="scenario-description"
class="input input-bordered w-full"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label">
<span class="label-text">Date</span>
</label>
<div class="flex gap-2">
<input
type="number"
id="dateYear"
name="scenario-date-year"
class="input input-bordered w-24"
placeholder="YYYY"
max="{% now 'Y' %}"
@blur="validateYear($el)"
/>
<input
type="number"
id="dateMonth"
name="scenario-date-month"
min="1"
max="12"
class="input input-bordered w-20"
placeholder="MM"
/>
<input
type="number"
id="dateDay"
name="scenario-date-day"
min="1"
max="31"
class="input input-bordered w-20"
placeholder="DD"
/>
</div>
</div>
<div class="form-control mb-3">
<label class="label" for="scenario-type">
<span class="label-text">Scenario Type</span>
</label>
<select
id="scenario-type"
name="scenario-type"
class="select select-bordered w-full"
x-model="scenarioType"
>
<option value="empty" selected>Empty Scenario</option>
{% for k, v in scenario_types.items %}
<option value="{{ v.name }}">{{ k }}</option>
{% endfor %}
</select>
</div>
{% for type in scenario_types.values %}
<div
id="{{ type.name }}-specific-inputs"
x-show="scenarioType === '{{ type.name }}'"
x-cloak
>
{% for widget in type.widgets %}
{{ widget|safe }}
{% endfor %}
</div>
{% endfor %}
</form>
</div> </div>
</div>
<!-- Footer --> <script>
<div class="modal-action"> $(function () {
<button // Initially hide all "specific" forms
type="button" $("div[id$='-specific-inputs']").each(function () {
class="btn" $(this).hide();
onclick="this.closest('dialog').close()" });
:disabled="isSubmitting"
> // On change hide all and show only selected
Cancel $("#scenario-type").change(function () {
</button> $("div[id$='-specific-inputs']").each(function () {
<button $(this).hide();
type="button" });
class="btn btn-primary" val = $('option:selected', this).val();
@click="submit('new-scenario-modal-form')" $("#" + val + "-specific-inputs").show();
:disabled="isSubmitting" });
>
<span x-show="!isSubmitting">Submit</span> $('#new_scenario_modal_form_submit').on('click', function (e) {
<span e.preventDefault();
x-show="isSubmitting" $('#new_scenario_form').submit();
class="loading loading-spinner loading-sm" });
></span>
<span x-show="isSubmitting">Creating...</span> var dateYear = document.getElementById("dateYear");
</button> dateYear.addEventListener("change", () => {
</div> console.log("Final value after editing:", dateYear.value);
</div> if (dateYear.value.length < 4) {
dateYear.value = {% now "Y" %};
}
});
});
</script>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,117 +1,61 @@
{% load static %} {% load static %}
<!-- Add Additional Information --> <!-- Add Additional Information-->
<dialog <div id="add_additional_information_modal" class="modal" tabindex="-1">
id="add_additional_information_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="{ <div class="modal-header">
isSubmitting: false, <button type="button" class="close" data-dismiss="modal" aria-label="Close">
selectedType: '', <span aria-hidden="true">&times;</span>
</button>
reset() { <h3 class="modal-title">Add Additional Information</h3>
this.isSubmitting = false; </div>
this.selectedType = ''; <div class="modal-body">
}, <select id="select-additional-information-type" data-actions-box='true' class="form-control" data-width='100%'>
<option selected disabled>Select the type to add</option>
submit() { {% for add_inf in available_additional_information %}
if (!this.selectedType) return; <option value="{{ add_inf.name }}">{{ add_inf.display_name }}</option>
{% endfor %}
const form = document.getElementById('add_' + this.selectedType + '_add-additional-information-modal-form'); </select>
if (form && form.checkValidity()) { {% for add_inf in available_additional_information %}
this.isSubmitting = true; <div class="aiform {{ add_inf.name }}" style="display: none;">
form.submit(); <form id="add_{{ add_inf.name }}_add-additional-information-modal-form" accept-charset="UTF-8"
} else if (form) { action="" data-remote="true" method="post">
form.reportValidity(); {% csrf_token %}
} {{ add_inf.widget|safe }}
} <input type="hidden" name="hidden" value="add-additional-information">
}" </form>
@close="reset()" </div>
> {% endfor %}
<div class="modal-box"> </div>
<!-- Header --> <div class="modal-footer">
<h3 class="text-lg font-bold">Add Additional Information</h3> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="add-additional-information-modal-submit">Add
<!-- Close button (X) --> </button>
<form method="dialog"> </div>
<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
id="select-additional-information-type"
class="select select-bordered w-full"
x-model="selectedType"
>
<option value="" selected disabled>Select the type to add</option>
{% for add_inf in available_additional_information %}
<option value="{{ add_inf.name }}">
{{ add_inf.display_name }}
</option>
{% endfor %}
</select>
</div>
{% for add_inf in available_additional_information %}
<div
class="mt-4"
x-show="selectedType === '{{ add_inf.name }}'"
x-cloak
>
<form
id="add_{{ add_inf.name }}_add-additional-information-modal-form"
accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
{{ add_inf.widget|safe }}
<input
type="hidden"
name="hidden"
value="add-additional-information"
/>
</form>
</div> </div>
{% endfor %}
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#select-additional-information-type').change(function(e){
<div class="modal-action"> var selectedType = $("#select-additional-information-type :selected").val();
<button $('.aiform').hide();
type="button" $('.' + selectedType).show();
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>
<!-- Backdrop --> $('#add-additional-information-modal-submit').click(function(e){
<form method="dialog" class="modal-backdrop"> e.preventDefault();
<button :disabled="isSubmitting">close</button>
</form> var selectedType = $("#select-additional-information-type :selected").val();
</dialog> console.log(selectedType);
if (selectedType !== null && selectedType !== undefined && selectedType !== '') {
$('.' + selectedType + ' >form').submit();
}
});
});
</script>

View File

@ -1,181 +1,127 @@
{% load static %} {% load static %}
<dialog <div class="modal fade bs-modal-lg" id="add_pathway_edge_modal" tabindex="-1" aria-labelledby="add_pathway_edge_modal"
id="add_pathway_edge_modal" aria-modal="true"
class="modal" role="dialog">
x-data="{ <div class="modal-dialog modal-lg">
isSubmitting: false, <div class="modal-content">
reactionImageUrl: '', <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Add a Reaction</h4>
</div>
<div class="modal-body">
<form id="add_pathway_edge_modal_form" accept-charset="UTF-8"
action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}"
data-remote="true" method="post">
{% csrf_token %}
<label for="edge-name">Name</label>
<input id="edge-name" class="form-control" name="edge-name" placeholder="Name"/>
<label for="edge-description">Description</label>
<input id="edge-description" class="form-control" name="edge-description" placeholder="Description"/>
<p></p>
<div class="row">
<div class="col-xs-5">
<legend>Substrate(s)</legend>
</div>
<div class="col-xs-2">
reset() { </div>
this.isSubmitting = false; <div class="col-xs-5">
this.reactionImageUrl = ''; <legend>Product(s)</legend>
}, </div>
</div>
<div class="row">
<div class="col-xs-5">
<select id="add_pathway_edge_substrates" name="edge-substrates"
data-actions-box='true' class="form-control" multiple data-width='100%'>
{% for n in pathway.nodes %}
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name|safe }}</option>
{% endfor %}
</select>
</div>
<div class="col-xs-2" style="display: flex; justify-content: center; align-items: center;">
<i class="glyphicon glyphicon-arrow-right"></i>
</div>
<div class="col-xs-5">
<select id="add_pathway_edge_products" name="edge-products"
data-actions-box='true' class="form-control" multiple data-width='100%'>
{% for n in pathway.nodes %}
<option data-smiles="{{ n.default_node_label.smiles }}" value="{{ n.url }}">{{ n.default_node_label.name|safe }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<p></p>
<div class="col-xs-12" id="reaction_image">
updateReactionImage() { </div>
const substratesSelect = document.getElementById('add_pathway_edge_substrates'); </div>
const productsSelect = document.getElementById('add_pathway_edge_products'); </form>
</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="add_pathway_edge_modal_form_submit">Submit</button>
</div>
</div>
</div>
const substrates = []; </div>
for (const option of substratesSelect.selectedOptions) { <script>
substrates.push(option.dataset.smiles);
}
const products = []; function reactionImage() {
for (const option of productsSelect.selectedOptions) { var substrates = [];
products.push(option.dataset.smiles); $('#add_pathway_edge_substrates option:selected').each(function () {
} var smiles = $(this).data('smiles'); // read data-smiles attribute
substrates.push(smiles);
});
if (substrates.length > 0 && products.length > 0) { var products = []
const reaction = substrates.join('.') + '>>' + products.join('.'); $('#add_pathway_edge_products option:selected').each(function () {
this.reactionImageUrl = '{% url "depict" %}?smirks=' + encodeURIComponent(reaction); var smiles = $(this).data('smiles'); // read data-smiles attribute
} else { products.push(smiles);
this.reactionImageUrl = ''; });
}
},
submit() { if (substrates.length > 0 && products.length > 0) {
this.isSubmitting = true; reaction = substrates.join('.') + ">>" + products.join('.');
document.getElementById('add_pathway_edge_modal_form').submit(); $('#reaction_image').empty();
$('#reaction_image').append(
"<img width='100%' src='{% url 'depict' %}?smirks=" + encodeURIComponent(reaction) +"'>"
);
}
} }
}"
@close="reset()"
>
<div class="modal-box max-w-4xl">
<!-- Header -->
<h3 class="text-lg font-bold">Add a Reaction</h3>
<!-- Close button (X) --> $(function () {
<form method="dialog"> $("#add_pathway_edge_substrates").selectpicker();
<button $("#add_pathway_edge_products").selectpicker();
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body --> $("#add_pathway_edge_substrates").on('change', function (e) {
<div class="py-4"> reactionImage();
<form })
id="add_pathway_edge_modal_form"
accept-charset="UTF-8" $("#add_pathway_edge_products").on('change', function (e) {
action="{% url 'package pathway edge list' meta.current_package.uuid pathway.uuid %}" reactionImage();
data-remote="true" })
method="post"
> $(function () {
{% csrf_token %} $('#add_pathway_edge_modal_form_submit').on('click', function (e) {
<div class="form-control mb-3"> e.preventDefault();
<label class="label" for="edge-name"> $(this).prop("disabled", true);
<span class="label-text">Name</span>
</label>
<input // submit form
id="edge-name" $('#add_pathway_edge_modal_form').submit();
type="text" });
class="input input-bordered w-full" });
name="edge-name"
placeholder="Name" });
/>
</div> </script>
<div class="form-control mb-3">
<label class="label" for="edge-description">
<span class="label-text">Description</span>
</label>
<input
id="edge-description"
type="text"
class="input input-bordered w-full"
name="edge-description"
placeholder="Description"
/>
</div>
<div class="mb-3 grid grid-cols-11 gap-2">
<div class="col-span-5">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Substrate(s)</span>
</label>
<select
id="add_pathway_edge_substrates"
name="edge-substrates"
class="select select-bordered h-32 w-full"
multiple
@change="updateReactionImage()"
>
{% for n in pathway.nodes %}
<option
data-smiles="{{ n.default_node_label.smiles }}"
value="{{ n.url }}"
>
{{ n.default_node_label.name|safe }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-span-1 flex items-center justify-center">
<span class="text-2xl"></span>
</div>
<div class="col-span-5">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Product(s)</span>
</label>
<select
id="add_pathway_edge_products"
name="edge-products"
class="select select-bordered h-32 w-full"
multiple
@change="updateReactionImage()"
>
{% for n in pathway.nodes %}
<option
data-smiles="{{ n.default_node_label.smiles }}"
value="{{ n.url }}"
>
{{ n.default_node_label.name|safe }}
</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="mb-3" x-show="reactionImageUrl" x-cloak>
<img :src="reactionImageUrl" class="w-full" alt="Reaction preview" />
</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()"
: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>

View File

@ -1,137 +1,78 @@
{% load static %} {% load static %}
<dialog <div class="modal fade bs-modal-lg" id="add_pathway_node_modal" tabindex="-1" aria-labelledby="add_pathway_node_modal" aria-modal="true"
id="add_pathway_node_modal" role="dialog">
class="modal" <div class="modal-dialog modal-lg">
x-data="modalForm()" <div class="modal-content">
@close="reset()" <div class="modal-header">
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box max-w-4xl"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="text-lg font-bold">Add a Node</h3> <h4 class="modal-title">Add a Node</h4>
</div>
<!-- Close button (X) --> <div class="modal-body">
<form method="dialog"> <form id="add_pathway_node_modal_form" accept-charset="UTF-8" action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" <label for="node-name">Name</label>
:disabled="isSubmitting" <input id="node-name" class="form-control" name="node-name" placeholder="Name"/>
> <label for="node-description">Description</label>
<input id="node-description" class="form-control" name="node-description" placeholder="Description"/>
</button> <label for="node-smiles">SMILES</label>
</form> <input type="text" class="form-control" name="node-smiles" placeholder="SMILES" id="node-smiles">
<p></p>
<!-- Body --> <div>
<div class="py-4"> <iframe id="add_node_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
<form height="510"></iframe>
id="add_pathway_node_modal_form" </div>
accept-charset="UTF-8" <p></p>
action="{% url 'package pathway node list' meta.current_package.uuid pathway.uuid %}" </form>
data-remote="true" </div>
method="post" <div class="modal-footer">
> <button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
{% csrf_token %} </button>
<div class="form-control mb-3"> <button type="button" class="btn btn-primary" id="add_pathway_node_modal_form_submit">Submit</button>
<label class="label" for="node-name"> </div>
<span class="label-text">Name</span>
</label>
<input
id="node-name"
type="text"
class="input input-bordered w-full"
name="node-name"
placeholder="Name"
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="node-description">
<span class="label-text">Description</span>
</label>
<input
id="node-description"
type="text"
class="input input-bordered w-full"
name="node-description"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="node-smiles">
<span class="label-text">SMILES</span>
</label>
<input
type="text"
class="input input-bordered w-full"
name="node-smiles"
placeholder="SMILES"
id="node-smiles"
/>
</div>
<div class="mb-3">
<iframe
id="add_node_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div>
</form>
</div> </div>
<!-- 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('add_pathway_node_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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>
<script> <script>
document
.getElementById("add_node_ketcher") function newStructureModalketcherToNewStructureModalTextInput() {
.addEventListener("load", function () { $('#node-smiles').val(this.ketcher.getSmiles());
const iframe = this; }
const checkKetcherReady = () => {
const win = iframe.contentWindow; $(function() {
if (win.ketcher && "editor" in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({ $('#add_node_ketcher').on('load', function() {
once: false, const checkKetcherReady = () => {
priority: 0, win = this.contentWindow
f: function () { if (win.ketcher && 'editor' in win.ketcher) {
document.getElementById("node-smiles").value = win.ketcher.editor.event.change.handlers.push({
this.ketcher.getSmiles(); once: false,
}, priority: 0,
ketcher: win.ketcher, f: newStructureModalketcherToNewStructureModalTextInput,
}); ketcher: win.ketcher
} else { });
setTimeout(checkKetcherReady, 100); } else {
} 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>

View File

@ -1,137 +1,78 @@
{% load static %} {% load static %}
<dialog <div class="modal fade bs-modal-lg" id="add_structure_modal" tabindex="-1" aria-labelledby="add_structure_modal" aria-modal="true"
id="add_structure_modal" role="dialog">
class="modal" <div class="modal-dialog modal-lg">
x-data="modalForm()" <div class="modal-content">
@close="reset()" <div class="modal-header">
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box max-w-4xl"> <span aria-hidden="true">×</span>
<!-- Header --> </button>
<h3 class="text-lg font-bold">Create a new Structure</h3> <h4 class="modal-title">Create a new Structure</h4>
</div>
<!-- Close button (X) --> <div class="modal-body">
<form method="dialog"> <form id="add_structure_modal_form" accept-charset="UTF-8" action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" <label for="structure-name">Name</label>
:disabled="isSubmitting" <input id="structure-name" class="form-control" name="structure-name" placeholder="Name"/>
> <label for="structure-description">Description</label>
<input id="structure-description" class="form-control" name="structure-description" placeholder="Description"/>
</button> <label for="structure-smiles">SMILES</label>
</form> <input type="text" class="form-control" name="structure-smiles" placeholder="SMILES" id="structure-smiles">
<p></p>
<!-- Body --> <div>
<div class="py-4"> <iframe id="add_structure_ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
<form height="510"></iframe>
id="add_structure_modal_form" </div>
accept-charset="UTF-8" <p></p>
action="{% url 'package compound structure list' meta.current_package.uuid compound.uuid %}" </form>
data-remote="true" </div>
method="post" <div class="modal-footer">
> <button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
{% csrf_token %} </button>
<div class="form-control mb-3"> <button type="button" class="btn btn-primary" id="add_structure_modal_form_submit">Submit</button>
<label class="label" for="structure-name"> </div>
<span class="label-text">Name</span>
</label>
<input
id="structure-name"
type="text"
class="input input-bordered w-full"
name="structure-name"
placeholder="Name"
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="structure-description">
<span class="label-text">Description</span>
</label>
<input
id="structure-description"
type="text"
class="input input-bordered w-full"
name="structure-description"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="structure-smiles">
<span class="label-text">SMILES</span>
</label>
<input
type="text"
class="input input-bordered w-full"
name="structure-smiles"
placeholder="SMILES"
id="structure-smiles"
/>
</div>
<div class="mb-3">
<iframe
id="add_structure_ketcher"
src="{% static '/js/ketcher2/ketcher.html' %}"
width="100%"
height="510"
></iframe>
</div>
</form>
</div> </div>
<!-- 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('add_structure_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>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>
<script> <script>
document
.getElementById("add_structure_ketcher") function newStructureModalketcherToNewStructureModalTextInput() {
.addEventListener("load", function () { $('#structure-smiles').val(this.ketcher.getSmiles());
const iframe = this; }
const checkKetcherReady = () => {
const win = iframe.contentWindow; $(function() {
if (win.ketcher && "editor" in win.ketcher) {
win.ketcher.editor.event.change.handlers.push({ $('#add_structure_ketcher').on('load', function() {
once: false, const checkKetcherReady = () => {
priority: 0, win = this.contentWindow
f: function () { if (win.ketcher && 'editor' in win.ketcher) {
document.getElementById("structure-smiles").value = win.ketcher.editor.event.change.handlers.push({
this.ketcher.getSmiles(); once: false,
}, priority: 0,
ketcher: win.ketcher, f: newStructureModalketcherToNewStructureModalTextInput,
}); ketcher: win.ketcher
} else { });
setTimeout(checkKetcherReady, 100); } else {
} 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>

View File

@ -1,94 +1,66 @@
{% load static %} {% load static %}
<!-- Delete Edge --> <!-- Delete Edge -->
<dialog <div id="delete_pathway_edge_modal" class="modal" tabindex="-1">
id="delete_pathway_edge_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })" <div class="modal-header">
@close="reset()" <h3 class="modal-title">Delete Edge</h3>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="text-lg font-bold">Delete Edge</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> Deletes the Edge. Nodes referenced by this edge will remain.
<form method="dialog"> <p></p>
<button <form id="delete-pathway-edge-modal-form" accept-charset="UTF-8" action="" data-remote="true"
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" method="post">
:disabled="isSubmitting" {% csrf_token %}
> <select id="delete_pathway_edge_edges" name="edge-url"
data-actions-box='true' class="form-control" data-width='100%'>
</button> <option value="" disabled selected>Select Reaction to delete</option>
</form> {% for e in pathway.edges %}
<option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option>
<!-- Body --> {% endfor %}
<div class="py-4"> </select>
<p class="mb-4"> <input type="hidden" id="hidden" name="hidden" value="delete"/>
Deletes the Edge. Nodes referenced by this edge will remain. </form>
</p> <p></p>
<form <div id="delete_pathway_edge_image"></div>
id="delete-pathway-edge-modal-form" </div>
accept-charset="UTF-8" <div class="modal-footer">
action="" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
method="post" <button type="button" class="btn btn-primary" id="delete-pathway-edge-modal-submit">Delete</button>
> </div>
{% 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
id="delete_pathway_edge_edges"
name="edge-url"
class="select select-bordered w-full"
x-model="selectedEdge"
@change="imageUrl = selectedEdge ? selectedEdge + '?image=svg' : ''"
required
>
<option value="" disabled selected>
Select Reaction to delete
</option>
{% for e in pathway.edges %}
<option value="{{ e.url }}">{{ e.edge_label.name|safe }}</option>
{% endfor %}
</select>
</div> </div>
<input type="hidden" id="hidden" name="hidden" value="delete" />
</form>
<!-- Image Preview -->
<div class="mt-4" x-show="imageUrl" x-cloak>
<img :src="imageUrl" class="w-full" alt="Edge preview" />
</div>
</div> </div>
</div>
<script>
$(function () {
$("#delete_pathway_edge_edges").selectpicker();
<!-- Footer --> $("#delete_pathway_edge_edges").on('change', function (e) {
<div class="modal-action"> edge_url = $('#delete_pathway_edge_edges option:selected').val()
<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>
<!-- Backdrop --> if (edge_url !== "") {
<form method="dialog" class="modal-backdrop"> $('#delete_pathway_edge_image').empty();
<button :disabled="isSubmitting">close</button> $('#delete_pathway_edge_image').append(
</form> "<img width='100%' src='" + edge_url + "?image=svg'>"
</dialog> );
}
})
$('#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>

View File

@ -1,97 +1,66 @@
{% load static %} {% load static %}
<!-- Delete Node --> <!-- Delete Node -->
<dialog <div id="delete_pathway_node_modal" class="modal" tabindex="-1">
id="delete_pathway_node_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })" <div class="modal-header">
@close="reset()" <h3 class="modal-title">Delete Node</h3>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="text-lg font-bold">Delete Node</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> Deletes the Node. Edges having this Node as Substrate or Product will be removed as well.
<form method="dialog"> <p></p>
<button <form id="delete-pathway-node-modal-form" accept-charset="UTF-8" action="" data-remote="true"
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" method="post">
:disabled="isSubmitting" {% csrf_token %}
> <select id="delete_pathway_node_nodes" name="node-url"
data-actions-box='true' class="form-control" data-width='100%'>
</button> <option value="" disabled selected>Select Compound to delete</option>
</form> {% for n in pathway.nodes %}
<option value="{{ n.url }}">{{ n.default_node_label.name|safe }}</option>
<!-- Body --> {% endfor %}
<div class="py-4"> </select>
<p class="mb-4"> <input type="hidden" id="hidden" name="hidden" value="delete"/>
Deletes the Node. Edges having this Node as Substrate or Product will be </form>
removed as well. <p></p>
</p> <div id="delete_pathway_node_image"></div>
<form </div>
id="delete-pathway-node-modal-form" <div class="modal-footer">
accept-charset="UTF-8" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
action="" <button type="button" class="btn btn-primary" id="delete-pathway-node-modal-submit">Delete</button>
method="post" </div>
>
{% 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
id="delete_pathway_node_nodes"
name="node-url"
class="select select-bordered w-full"
x-model="selectedNode"
@change="imageUrl = selectedNode ? selectedNode + '?image=svg' : ''"
required
>
<option value="" disabled selected>
Select Compound to delete
</option>
{% for n in pathway.nodes %}
<option value="{{ n.url }}">
{{ n.default_node_label.name|safe }}
</option>
{% endfor %}
</select>
</div> </div>
<input type="hidden" id="hidden" name="hidden" value="delete" />
</form>
<!-- Image Preview -->
<div class="mt-4" x-show="imageUrl" x-cloak>
<img :src="imageUrl" class="w-full" alt="Node preview" />
</div>
</div> </div>
</div>
<script>
$(function () {
$("#delete_pathway_node_nodes").selectpicker();
<!-- Footer --> $("#delete_pathway_node_nodes").on('change', function (e) {
<div class="modal-action"> node_url = $('#delete_pathway_node_nodes option:selected').val()
<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>
<!-- Backdrop --> if (node_url !== "") {
<form method="dialog" class="modal-backdrop"> $('#delete_pathway_node_image').empty();
<button :disabled="isSubmitting">close</button> $('#delete_pathway_node_image').append(
</form> "<img width='100%' src='" + node_url + "?image=svg'>"
</dialog> );
}
})
$('#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>

View File

@ -1,69 +1,36 @@
{% load static %} {% load static %}
<!-- Download Pathway -->
<dialog <div id="download_pathway_csv_modal" class="modal" tabindex="-1">
id="download_pathway_csv_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h3 class="modal-title">Download Pathway as CSV</h3>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Download Pathway as CSV</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> By clicking on Download the Pathway will be converted into a CSV and directly downloaded.
<form method="dialog"> <form id="download-pathway-csv-modal-form" accept-charset="UTF-8" action="{{ pathway.url }}"
<button data-remote="true" method="GET">
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <input type="hidden" name="download" value="true"/>
:disabled="isSubmitting" </form>
> </div>
<div class="modal-footer">
</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</form> <button type="button" class="btn btn-primary" id="download-pathway-csv-modal-submit">Download</button>
</div>
<!-- Body --> </div>
<div class="py-4">
<p>
By clicking on Download the Pathway will be converted into a CSV and
directly downloaded.
</p>
<form
id="download-pathway-csv-modal-form"
accept-charset="UTF-8"
action="{{ pathway.url }}"
method="GET"
>
<input type="hidden" name="download" value="true" />
</form>
</div> </div>
</div>
<script>
$(function () {
<!-- Footer --> $('#download-pathway-csv-modal-submit').click(function (e) {
<div class="modal-action"> e.preventDefault();
<button $('#download-pathway-csv-modal-form').submit();
type="button" $('#download_pathway_csv_modal').modal('hide');
class="btn" });
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,57 +1,32 @@
{% load static %} {% load static %}
<!-- Download Pathway -->
<dialog <div id="download_pathway_image_modal" class="modal" tabindex="-1">
id="download_pathway_image_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h3 class="modal-title">Download Pathway as Image</h3>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Download Pathway as Image</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> By clicking on Download the Pathway will be saved as SVG.
<form method="dialog"> </div>
<button <div class="modal-footer">
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
:disabled="isSubmitting" <button type="button" class="btn btn-primary" id="download-pathway-image-modal-submit">Download</button>
> </div>
</div>
</button>
</form>
<!-- Body -->
<div class="py-4">
<p>By clicking on Download the Pathway will be saved as SVG.</p>
</div> </div>
</div>
<script>
$(function () {
<!-- Footer --> $('#download-pathway-image-modal-submit').click(function (e) {
<div class="modal-action"> e.preventDefault();
<button downloadSVG($('#pwsvg')[0], '{{ pathway.name.split|join:"_" }}.svg')
type="button" $('#download_pathway_image_modal').modal('hide');
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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,91 +1,47 @@
{% load static %} {% load static %}
<!-- Edit Compound -->
<dialog <div id="edit_compound_modal" class="modal" tabindex="-1">
id="edit_compound_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Edit Compound</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Edit Compound</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Edit Compound.</p>
<form method="dialog"> <form id="edit-compound-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="compound-name">Name</label>
> <input id="compound-name" class="form-control" name="compound-name" value="{{ compound.name|safe}}">
</p>
</button> <p>
</form> <label for="compound-description">Description</label>
<input id="compound-description" type="text" class="form-control"
<!-- Body --> value="{{ compound.description|safe }}"
<div class="py-4"> name="compound-description">
<form </p>
id="edit-compound-modal-form" </form>
accept-charset="UTF-8" </div>
action="" <div class="modal-footer">
method="post" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
> <button type="button" class="btn btn-primary" id="edit-compound-modal-submit">Update</button>
{% csrf_token %} </div>
<div class="form-control mb-3">
<label class="label" for="compound-name">
<span class="label-text">Name</span>
</label>
<input
id="compound-name"
class="input input-bordered w-full"
name="compound-name"
value="{{ compound.name|safe }}"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="compound-description">
<span class="label-text">Description</span>
</label>
<input
id="compound-description"
type="text"
class="input input-bordered w-full"
value="{{ compound.description|safe }}"
name="compound-description"
/>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-compound-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-compound-modal-form').submit();
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>
<!-- Backdrop --> });
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form> </script>
</dialog>

View File

@ -1,91 +1,46 @@
{% load static %} {% load static %}
<!-- Edit Compound -->
<dialog <div id="edit_compound_structure_modal" class="modal" tabindex="-1">
id="edit_compound_structure_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Create a Compound</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Edit Compound Structure</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Edit a Compound Structure.</p>
<form method="dialog"> <form id="edit-compound-structure-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="compound-structure-name">Name</label>
> <input id="compound-structure-name" class="form-control" name="compound-structure-name" value="{{ compound_structure.name|safe }}">
</p>
</button> <p>
</form> <label for="compound-structure-description">Description</label>
<input id="compound-structure-description" type="text" class="form-control"
<!-- Body --> value="{{ compound_structure.description|safe }}" name="compound-structure-description">
<div class="py-4"> </p>
<form </form>
id="edit-compound-structure-modal-form" </div>
accept-charset="UTF-8" <div class="modal-footer">
action="" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
method="post" <button type="button" class="btn btn-primary" id="edit-compound-structure-modal-submit">Create</button>
> </div>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="compound-structure-name">
<span class="label-text">Name</span>
</label>
<input
id="compound-structure-name"
class="input input-bordered w-full"
name="compound-structure-name"
value="{{ compound_structure.name|safe }}"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="compound-structure-description">
<span class="label-text">Description</span>
</label>
<input
id="compound-structure-description"
type="text"
class="input input-bordered w-full"
value="{{ compound_structure.description|safe }}"
name="compound-structure-description"
/>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-compound-structure-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-compound-structure-modal-form').submit();
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>
<!-- Backdrop --> });
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form> </script>
</dialog>

View File

@ -1,150 +1,121 @@
{% load static %} {% load static %}
<!-- Edit Group Member --> <!-- Edit Package Permission -->
<dialog <div id="edit_group_member_modal" class="modal" tabindex="-1">
id="edit_group_member_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="{ <div class="modal-header">
isSubmitting: false, <h5 class="modal-title">Add or Remove Group Member</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
reset() { <span aria-hidden="true">&times;</span>
this.isSubmitting = false;
},
submitForm(form) {
if (form && form.checkValidity()) {
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
>
<div class="modal-box">
<!-- Header -->
<h3 class="text-lg font-bold">Add or Remove Group Member</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 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 %}
<option value="{{ u.url }}">{{ u.username }}</option>
{% endfor %}
</optgroup>
<optgroup label="Groups">
{% for g in groups %}
<option value="{{ g.url }}">{{ g.name|safe }}</option>
{% endfor %}
</optgroup>
</select>
<input type="hidden" name="action" value="add" />
</div>
<button type="submit" class="btn btn-primary">Add</button>
</div>
</form>
<!-- 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
id="modal-form-group-member_{{ u.uuid }}"
accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
<div class="flex items-center gap-2">
<span class="flex-1">{{ u.username }}</span>
<input type="hidden" name="member" value="{{ u.url }}" />
<input type="hidden" name="action" value="remove" />
<button type="submit" class="btn btn-error btn-sm">
Remove
</button> </button>
</div> </div>
</form> <div class="modal-body">
{% endfor %} <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">
<div class="col-xs-8">
<legend>User or Group</legend>
</div>
<div class="col-xs-4">
<legend>Add/Remove</legend>
</div>
</div>
<div class="row">
<form id="modal-form-group-member" class="form-inline" role="form" accept-charset="UTF-8" action=""
data-remote="true" method="post">
{% csrf_token %}
<div class="col-xs-8">
<select id="select_member" name="member" data-actions-box='true'
class="selPackages" data-width='100%'>
<option disabled selected>User</option>
{% for u in users %}
<option value="{{ u.url }}">{{ u.username }}</option>
{% endfor %}
<option disabled>Groups</option>
{% for g in groups %}
<option value="{{ g.url }}">{{ g.name|safe }}</option>
{% endfor %}
</select>
<input type="hidden" name="action" value="add">
</div>
<div class="col-xs-2">
</div>
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-ok"></span>
</button>
</div>
</form>
</div>
<p></p>
{% for u in group.user_member.all %}
<div class="row">
<form id="modal-form-group-member_{{ u.uuid }}" class="form-inline" role="form"
accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<div class="col-xs-8">
{{ u.username }}
<input type="hidden" name="member" value="{{ u.url }}"/>
<input type="hidden" name="action" value="remove">
</div>
<div class="col-xs-2">
</div>
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-trash"></span>
</button>
</div>
</form>
</div>
{% endfor %}
<p></p>
{% for g in group.group_member.all %}
<div class="row">
<form id="modal-form-group-member_{{ g.uuid }}" class="form-inline" role="form"
accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<div class="col-xs-8">
{{ g.name|safe }}
<input type="hidden" name="member" value="{{ g.url }}"/>
<input type="hidden" name="action" value="remove">
</div>
<div class="col-xs-2">
</div>
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2">
<span class="glyphicon glyphicon-trash"></span>
</button>
</div>
</form>
</div>
{% endfor %}
</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-package-modal-submit">Update</button>
</div>
</div> </div>
{% endif %}
<!-- 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
id="modal-form-group-member_{{ g.uuid }}"
accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
<div class="flex items-center gap-2">
<span class="flex-1">{{ g.name|safe }}</span>
<input type="hidden" name="member" value="{{ g.url }}" />
<input type="hidden" name="action" value="remove" />
<button type="submit" class="btn btn-error btn-sm">
Remove
</button>
</div>
</form>
{% endfor %}
</div>
{% endif %}
</div> </div>
</div>
<script>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
>
Close
</button>
</div>
</div>
<!-- Backdrop --> $(function() {
<form method="dialog" class="modal-backdrop">
<button>close</button> $('#edit-package-modal-submit').click(function(e){
</form> e.preventDefault();
</dialog> $('#edit-package-modal-form').submit();
});
$("#select_member").selectpicker();
})
</script>

View File

@ -1,94 +1,45 @@
{% load static %} {% load static %}
<!-- Edit Model -->
<dialog <div id="edit_model_modal" class="modal" tabindex="-1">
id="edit_model_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <button type="button" class="close" data-dismiss="modal" aria-label="Close">
> <span aria-hidden="true">&times;</span>
<div class="modal-box"> </button>
<!-- Header --> <h3 class="modal-title">Update Model</h3>
<h3 class="font-bold text-lg">Update Model</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Alter Name and Description of the Model.</p>
<form method="dialog"> <form id="edit-model-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="model-name">Name</label>
> <input id="model-name" type="text" class="form-control" name="model-name"
value="{{ model.name|safe }}">
</button> </p>
</form> <p>
<label for="model-description">Description</label>
<!-- Body --> <input id="model-description" type="text" class="form-control" name="model-description"
<div class="py-4"> value="{{ model.description|safe }}">
<p class="mb-4">Alter Name and Description of the Model.</p> </p>
</form>
<form </div>
id="edit-model-modal-form" <div class="modal-footer">
accept-charset="UTF-8" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
action="" <button type="button" class="btn btn-primary" id="edit-model-modal-submit">Update</button>
method="post" </div>
>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="model-name">
<span class="label-text">Name</span>
</label>
<input
id="model-name"
type="text"
class="input input-bordered w-full"
name="model-name"
value="{{ model.name|safe }}"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="model-description">
<span class="label-text">Description</span>
</label>
<input
id="model-description"
type="text"
class="input input-bordered w-full"
name="model-description"
value="{{ model.description|safe }}"
/>
</div>
</form>
</div> </div>
</div>
<script>
$(function () {
<!-- Footer --> $('#edit-model-modal-submit').click(function (e) {
<div class="modal-action"> e.preventDefault();
<button $('#edit-model-modal-form').submit();
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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,91 +1,47 @@
{% load static %} {% load static %}
<!-- Edit Node -->
<dialog <div id="edit_node_modal" class="modal" tabindex="-1">
id="edit_node_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Edit Node</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Edit Node</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Edit Node.</p>
<form method="dialog"> <form id="edit-node-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="node-name">Name</label>
> <input id="node-name" class="form-control" name="node-name" value="{{ node.name|safe}}">
</p>
</button> <p>
</form> <label for="node-description">Description</label>
<input id="node-description" type="text" class="form-control"
<!-- Body --> value="{{ node.description|safe }}"
<div class="py-4"> name="node-description">
<form </p>
id="edit-node-modal-form" </form>
accept-charset="UTF-8" </div>
action="" <div class="modal-footer">
method="post" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
> <button type="button" class="btn btn-primary" id="edit-node-modal-submit">Create</button>
{% csrf_token %} </div>
<div class="form-control mb-3">
<label class="label" for="node-name">
<span class="label-text">Name</span>
</label>
<input
id="node-name"
class="input input-bordered w-full"
name="node-name"
value="{{ node.name|safe }}"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="node-description">
<span class="label-text">Description</span>
</label>
<input
id="node-description"
type="text"
class="input input-bordered w-full"
value="{{ node.description|safe }}"
name="node-description"
/>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-node-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-node-modal-form').submit();
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>
<!-- Backdrop --> });
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form> </script>
</dialog>

View File

@ -1,91 +1,45 @@
{% load static %} {% load static %}
<!-- Edit Package -->
<dialog <div id="edit_package_modal" class="modal" tabindex="-1">
id="edit_package_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Update Package</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Update Package</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Edit a Package.</p>
<form method="dialog"> <form id="edit-package-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="package-name">Name</label>
> <input id="package-name" class="form-control" name="package-name" value="{{ package.name|safe}}">
</p>
</button> <p>
</form> <label for="package-description">Description</label>
<input id="package-description" type="text" class="form-control"
<!-- Body --> value="{{ package.description|safe }}"
<div class="py-4"> name="package-description">
<form </p>
id="edit-package-modal-form" </form>
accept-charset="UTF-8" </div>
action="" <div class="modal-footer">
method="post" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
> <button type="button" class="btn btn-primary" id="edit-package-modal-submit">Update</button>
{% csrf_token %} </div>
<div class="form-control mb-3">
<label class="label" for="package-name">
<span class="label-text">Name</span>
</label>
<input
id="package-name"
class="input input-bordered w-full"
name="package-name"
value="{{ package.name|safe }}"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="package-description">
<span class="label-text">Description</span>
</label>
<input
id="package-description"
type="text"
class="input input-bordered w-full"
value="{{ package.description|safe }}"
name="package-description"
/>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-package-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-package-modal-form').submit();
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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,271 +1,183 @@
{% load static %} {% load static %}
<!-- Edit Package Permissions --> <!-- Edit Package Permission -->
<dialog <div id="edit_package_permissions_modal" class="modal" tabindex="-1">
id="edit_package_permissions_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="{ <div class="modal-header">
updatePermissions(checkbox) { <h5 class="modal-title">Grant or Revoke Permissions</h5>
const parts = checkbox.id.split('_'); <button type="button" class="close" data-dismiss="modal" aria-label="Close">
const perm = parts[0]; <span aria-hidden="true">&times;</span>
const id = parts[1]; </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>
const readBox = document.getElementById('read_' + id); <div class="row">
const writeBox = document.getElementById('write_' + id); <div class="col-xs-4">
const ownerBox = document.getElementById('owner_' + id); <legend>User or Group</legend>
</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>
if (perm === 'read' && !readBox.checked) { <div class="row">
writeBox.checked = false; <form id="modal-form-permissions" class="form-inline" role="form" accept-charset="UTF-8" action=""
ownerBox.checked = false; data-remote="true" method="post">
} {% csrf_token %}
<div class="col-xs-4">
<select id="select_grantee" name="grantee" data-actions-box='true'
class="selPackages" data-width='100%'>
<option disabled selected>User</option>
{% for u in users %}
<option value="{{ u.url }}">{{ u.username }}</option>
{% endfor %}
<option disabled>Groups</option>
{% for g in groups %}
<option value="{{ g.url }}">{{ g.name|safe }}</option>
{% endfor %}
</select>
</div>
<div class="col-xs-2">
<input type="checkbox" name="read" id="read_new"/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="write" id="write_new"/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="owner" id="owner_new"/>
</div>
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2 modify-perm-button">
<span class="glyphicon glyphicon-plus"></span>
</button>
</div>
</form>
</div>
<p></p>
{% for up in user_permissions %}
<div class="row">
<form id="modal-form-permissions_{{ up.user.uuid }}" class="form-inline" role="form"
accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<div class="col-xs-4">
{{ up.user.username }}
<input type="hidden" name="grantee" value="{{ up.user.url }}"/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="read" id="read_{{ up.user.uuid }}" {% if up.has_read %} checked {% endif %}/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="write" id="write_{{ up.user.uuid }}" {% if up.has_write %} checked {% endif %}/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="owner" id="owner_{{ up.user.uuid }}" {% if up.has_all %} checked {% endif %}/>
</div>
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2 modify-perm-button">
<span class="glyphicon glyphicon-ok"></span>
</button>
</div>
</form>
</div>
{% endfor %}
<p></p>
{% for gp in group_permissions %}
<div class="row">
<form id="modal-form-permissions_{{ gp.user.uuid }}" class="form-inline" role="form"
accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<div class="col-xs-4">
{{ gp.group.name|safe }}
<input type="hidden" name="grantee" value="{{ gp.group.url }}"/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="read" id="read_{{ gp.group.uuid }}" {% if gp.has_read %} checked {% endif %}/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="write" id="write_{{ gp.group.uuid }}" {% if gp.has_write %} checked {% endif %}/>
</div>
<div class="col-xs-2">
<input type="checkbox" name="owner" id="owner_{{ gp.group.uuid }}" {% if gp.has_all %} checked {% endif %}/>
</div>
<div class="col-xs-2">
<button type="submit" style="width:60%;" class="btn col-xs-2 modify-perm-button">
<span class="glyphicon glyphicon-ok"></span>
</button>
</div>
</form>
</div>
{% endfor %}
if (perm === 'write') { </div>
if (writeBox.checked) { <div class="modal-footer">
readBox.checked = true; <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
} else { <button type="button" class="btn btn-primary" id="edit-package-modal-submit">Update</button>
ownerBox.checked = false; </div>
} </div>
} </div>
</div>
<script>
if (perm === 'owner' && ownerBox.checked) { function checkboxClick() {
readBox.checked = true; // id looks like read_3cadef24-220e-4587-9fa5-0e9a17aca2da
writeBox.checked = true; parts = this.id.split('_');
} perm = parts[0];
id = parts[1];
readbox = '#read_' + id;
writebox = '#write_' + id;
ownerbox = '#owner_' + id;
if (perm == 'read' && !$(readbox).prop("checked")) {
$(writebox).prop("checked", false);
$(ownerbox).prop("checked", false);
} }
}"
>
<div class="modal-box max-w-2xl">
<!-- Header -->
<h3 class="text-lg font-bold">Grant or Revoke Permissions</h3>
<!-- Close button (X) --> if (perm == 'write') {
<form method="dialog"> if ($(writebox).prop("checked")) {
<button class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"> $(readbox).prop("checked", true);
}
</button> if (!$(writebox).prop("checked")) {
</form> $(ownerbox).prop("checked", false);
}
}
<!-- Body --> if (perm == 'owner') {
<div class="py-4"> if ($(ownerbox).prop("checked")) {
<p class="mb-4"> $(readbox).prop("checked", true);
Modify permissions for this package. Note that if you give $(writebox).prop("checked", true);
<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 --> $(function() {
<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 %}
<option value="{{ u.url }}">{{ u.username }}</option>
{% endfor %}
</optgroup>
<optgroup label="Groups">
{% for g in groups %}
<option value="{{ g.url }}">{{ g.name|safe }}</option>
{% endfor %}
</optgroup>
</select>
</div>
<div class="col-span-2 text-center">
<label class="label justify-center">
<span class="label-text">Read</span>
</label>
<input
type="checkbox"
name="read"
id="read_new"
class="checkbox"
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-2 text-center">
<label class="label justify-center">
<span class="label-text">Write</span>
</label>
<input
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>
</form>
<!-- User Permissions --> $('#edit-package-modal-submit').click(function(e){
{% if user_permissions %} e.preventDefault();
<div class="divider">User Permissions</div> $('#edit-package-modal-form').submit();
<div class="space-y-2"> });
{% for up in user_permissions %}
<form
id="modal-form-permissions_{{ up.user.uuid }}"
accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
<div class="grid grid-cols-12 gap-2 items-center">
<div class="col-span-5 truncate">
{{ up.user.username }}
<input
type="hidden"
name="grantee"
value="{{ up.user.url }}"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="read"
id="read_{{ up.user.uuid }}"
class="checkbox"
{% if up.has_read %}checked{% endif %}
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="write"
id="write_{{ up.user.uuid }}"
class="checkbox"
{% if up.has_write %}checked{% endif %}
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="owner"
id="owner_{{ up.user.uuid }}"
class="checkbox"
{% 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>
</form>
{% endfor %}
</div>
{% endif %}
<!-- Group Permissions --> $("#select_grantee").selectpicker();
{% if group_permissions %}
<div class="divider">Group Permissions</div>
<div class="space-y-2">
{% for gp in group_permissions %}
<form
id="modal-form-permissions_{{ gp.group.uuid }}"
accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
<div class="grid grid-cols-12 gap-2 items-center">
<div class="col-span-5 truncate">
{{ gp.group.name|safe }}
<input
type="hidden"
name="grantee"
value="{{ gp.group.url }}"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="read"
id="read_{{ gp.group.uuid }}"
class="checkbox"
{% if gp.has_read %}checked{% endif %}
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="write"
id="write_{{ gp.group.uuid }}"
class="checkbox"
{% if gp.has_write %}checked{% endif %}
@click="updatePermissions($el)"
/>
</div>
<div class="col-span-2 text-center">
<input
type="checkbox"
name="owner"
id="owner_{{ gp.group.uuid }}"
class="checkbox"
{% if gp.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>
</form>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Footer --> // Add click functions to permission checkboxes
<div class="modal-action"> $('[id^="read_"]').on('click', checkboxClick);
<button $('[id^="write_"]').on('click', checkboxClick);
type="button" $('[id^="owner_"]').on('click', checkboxClick);
class="btn"
onclick="this.closest('dialog').close()"
>
Close
</button>
</div>
</div>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button>close</button>
</form>
</dialog>

View File

@ -1,119 +1,46 @@
{% load static %} {% load static %}
<!-- Edit Package -->
<dialog <div id="edit_password_modal" class="modal" tabindex="-1">
id="edit_password_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Update your Password</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Update your Password</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>To change your password please fill out the following inputs</p>
<form method="dialog"> <form id="edit-password-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="old-password">Old Password</label>
> <input id="old-password" class="form-control" name="old-password" type="password" autocomplete="current-password">
</p>
</button> <p>
</form> <label for="new-password">New Password</label>
<input id="new-password" class="form-control" name="new-password" type="password", autocomplete="new-password">
<!-- Body --> </p>
<div class="py-4"> <p>
<p class="mb-4"> <label for="new-password-repeat">Repeat New Password</label>
To change your password please fill out the following inputs <input id="new-password-repeat" class="form-control" name="new-password-repeat" type="password" autocomplete="new-password">
</p> </p>
</form>
<form </div>
id="edit-password-modal-form" <div class="modal-footer">
accept-charset="UTF-8" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
action="" <button type="button" class="btn btn-primary" id="edit-password-modal-submit">Update</button>
method="post" </div>
>
{% csrf_token %}
<input type="hidden" name="hidden" value="update-password" />
<div class="form-control mb-3">
<label class="label" for="old-password">
<span class="label-text">Old Password</span>
</label>
<input
id="old-password"
class="input input-bordered w-full"
name="old-password"
type="password"
autocomplete="current-password"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="new-password">
<span class="label-text">New Password</span>
</label>
<input
id="new-password"
class="input input-bordered w-full"
name="new-password"
type="password"
autocomplete="new-password"
required
/>
</div>
<div class="form-control mb-3">
<label class="label" for="new-password-repeat">
<span class="label-text">Repeat New Password</span>
</label>
<input
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> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-password-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-password-modal-form').submit();
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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,92 +1,44 @@
{% load static %} {% load static %}
<!-- Edit Pathway -->
<dialog <div id="edit_pathway_modal" class="modal" tabindex="-1">
id="edit_pathway_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Edit Pathway</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Edit Pathway</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Edit Pathway.</p>
<form method="dialog"> <form id="edit-pathway-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="pathway-name">Name</label>
> <input id="pathway-name" class="form-control" name="pathway-name" value="{{ pathway.name|safe }}">
</p>
</button> <p>
</form> <label for="pathway-description">Description</label>
<textarea id="pathway-description" type="text" class="form-control" name="pathway-description"
<!-- Body --> rows="10">{{ pathway.description|safe }}</textarea>
<div class="py-4"> </p>
<form </form>
id="edit-pathway-modal-form" </div>
accept-charset="UTF-8" <div class="modal-footer">
action="" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
method="post" <button type="button" class="btn btn-primary" id="edit-pathway-modal-submit">Update</button>
> </div>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="pathway-name">
<span class="label-text">Name</span>
</label>
<input
id="pathway-name"
class="input input-bordered w-full"
name="pathway-name"
value="{{ pathway.name|safe }}"
required
/>
</div> </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
>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-pathway-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-pathway-modal-form').submit();
type="button" });
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,156 +1,106 @@
{% load static %} {% load static %}
<!-- Edit Prediction Setting --> <!-- Edit Package -->
<dialog <div id="update_prediction_settings_modal" class="modal" tabindex="-1">
id="update_prediction_settings_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Update Prediction Setting</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box max-w-3xl"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="text-lg font-bold">Update Prediction Setting</h3> </div>
<div class="modal-body">
<p>To update your prediction setting modify parameters in the form below und click "Update"</p>
<form id="edit-prediction-setting-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<div id="prediction-setting" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<!-- Close button (X) --> <table class="table table-bordered table-hover">
<form method="dialog"> <tr style="background-color: rgba(0, 0, 0, 0.08);">
<button <th scope="col" width="20%">Parameter</th>
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" <th scope="col" width="80%">Value</th>
:disabled="isSubmitting" </tr>
> <tbody>
{% if 'model' in user.prediction_settings %}
</button> <tr>
</form> <td width="20%">Model</td>
<td width="80%">
<!-- Body --> <table width="100%" class="table table-bordered table-hover">
<div class="py-4"> <tbody>
<p class="mb-4"> <tr>
To update your prediction setting modify parameters in the form below <td colspan="2">
and click "Update" <select id="model" name="model" class="form-control" data-width='100%'>
</p> {% for m in models %}
<form <option value="{{ m.id }}" {% if user.prediction_settings.model.url == m.url %}selected{% endif %}>{{ m.name|safe }}</option>
id="edit-prediction-setting-modal-form" {% endfor %}
accept-charset="UTF-8" </select>
action="" </td>
method="post" </tr>
> {% for k, v in user.prediction_settings.model_parameters.items %}
{% csrf_token %} <tr>
<div class="overflow-x-auto"> <th width="20%">Model Parameter</th>
<table class="table table-zebra w-full"> <th width="80%">Parameter Value</th>
<thead> </tr>
<tr> <tr>
<th class="w-1/5">Parameter</th> <td width="20%">
<th class="w-4/5">Value</th> {% if k == 'threshold' %}
</tr> Threshold
</thead> {% endif %}
<tbody> </td>
{% if 'model' in user.prediction_settings %} <td width="80%">
<tr> {% if k == 'threshold' %}
<td>Model</td> <input type="number" class="form-control" name="{{k}}" value="{{v}}"
<td> min="0" max="1" step="0.05">
<div class="form-control"> {% endif %}
<select </td>
id="model" </tr>
name="model" {% endfor %}
class="select select-bordered w-full" </tbody>
> </table>
{% for m in models %} </td>
<option </tr>
value="{{ m.id }}" {% endif %}
{% if user.prediction_settings.model.url == m.url %}selected{% endif %} {% for k, v in user.prediction_settings.truncator.items %}
> <tr>
{{ m.name|safe }} <td><p>
</option> {% if k == 'max_nodes' %}
{% endfor %} Max Nodes
</select> {% elif k == 'max_depth' %}
</div> Max Depth
{% for k, v in user.prediction_settings.model_parameters.items %} {% endif %}
{% if k == 'threshold' %} </p></td>
<div class="form-control mt-2"> <td><p>
<label class="label"> {% if k == 'max_nodes' %}
<span class="label-text">Threshold</span> <input type="number" class="form-control" name="{{k}}" value="{{v}}" min="1"
</label> max="50" step="1">
<input {% elif k == 'max_depth' %}
type="number" <input type="number" class="form-control" name="{{k}}" value="{{v}}" min="1"
class="input input-bordered w-full" max="8" step="1">
name="{{ k }}" {% endif %}
value="{{ v }}" </p></td>
min="0" </tr>
max="1" {% endfor %}
step="0.05" </tbody>
/> </table>
</div> </div>
{% endif %} </div>
{% endfor %} </form>
</td> </div>
</tr> <div class="modal-footer">
{% endif %} <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
{% for k, v in user.prediction_settings.truncator.items %} <button type="button" class="btn btn-primary" id="edit-prediction-setting-modal-submit">Update</button>
<tr> </div>
<td>
{% if k == 'max_nodes' %}
Max Nodes
{% elif k == 'max_depth' %}
Max Depth
{% endif %}
</td>
<td>
{% if k == 'max_nodes' %}
<input
type="number"
class="input input-bordered w-full"
name="{{ k }}"
value="{{ v }}"
min="1"
max="50"
step="1"
/>
{% elif k == 'max_depth' %}
<input
type="number"
class="input input-bordered w-full"
name="{{ k }}"
value="{{ v }}"
min="1"
max="8"
step="1"
/>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-prediction-setting-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-prediction-setting-modal-form').submit();
type="button" });
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('edit-prediction-setting-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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,91 +1,45 @@
{% load static %} {% load static %}
<!-- Edit Reaction -->
<dialog <div id="edit_reaction_modal" class="modal" tabindex="-1">
id="edit_reaction_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <button type="button" class="close" data-dismiss="modal" aria-label="Close">
> <span aria-hidden="true">&times;</span>
<div class="modal-box"> </button>
<!-- Header --> <h3 class="modal-title">Update Reaction</h3>
<h3 class="font-bold text-lg">Update Reaction</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <form id="edit-reaction-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<form method="dialog"> {% csrf_token %}
<button <p>
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <label for="reaction-name">Name</label>
:disabled="isSubmitting" <input id="reaction-name" class="form-control" name="reaction-name" value="{{ reaction.name|safe }}">
> </p>
<p>
</button> <label for="reaction-description">Description</label>
</form> <input id="reaction-description" type="text" class="form-control"
value="{{ reaction.description|safe }}" name="reaction-description">
<!-- Body --> </p>
<div class="py-4"> </form>
<form </div>
id="edit-reaction-modal-form" <div class="modal-footer">
accept-charset="UTF-8" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
action="" <button type="button" class="btn btn-primary" id="edit-reaction-modal-submit">Update</button>
method="post" </div>
>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="reaction-name">
<span class="label-text">Name</span>
</label>
<input
id="reaction-name"
class="input input-bordered w-full"
name="reaction-name"
value="{{ reaction.name|safe }}"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="reaction-description">
<span class="label-text">Description</span>
</label>
<input
id="reaction-description"
type="text"
class="input input-bordered w-full"
value="{{ reaction.description|safe }}"
name="reaction-description"
/>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-reaction-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-reaction-modal-form').submit();
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>
<!-- Backdrop --> });
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form> </script>
</dialog>

View File

@ -1,91 +1,45 @@
{% load static %} {% load static %}
<!-- Edit Rule -->
<dialog <div id="edit_rule_modal" class="modal" tabindex="-1">
id="edit_rule_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <button type="button" class="close" data-dismiss="modal" aria-label="Close">
> <span aria-hidden="true">&times;</span>
<div class="modal-box"> </button>
<!-- Header --> <h3 class="modal-title">Update Rule</h3>
<h3 class="font-bold text-lg">Update Rule</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <form id="edit-rule-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<form method="dialog"> {% csrf_token %}
<button <p>
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <label for="rule-name">Name</label>
:disabled="isSubmitting" <input id="rule-name" class="form-control" name="rule-name" value="{{ rule.name|safe }}">
> </p>
<p>
</button> <label for="rule-description">Description</label>
</form> <input id="rule-description" type="text" class="form-control"
value="{{ rule.description|safe }}" name="rule-description">
<!-- Body --> </p>
<div class="py-4"> </form>
<form </div>
id="edit-rule-modal-form" <div class="modal-footer">
accept-charset="UTF-8" <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
action="" <button type="button" class="btn btn-primary" id="edit-rule-modal-submit">Update</button>
method="post" </div>
>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="rule-name">
<span class="label-text">Name</span>
</label>
<input
id="rule-name"
class="input input-bordered w-full"
name="rule-name"
value="{{ rule.name|safe }}"
required
/>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="rule-description">
<span class="label-text">Description</span>
</label>
<input
id="rule-description"
type="text"
class="input input-bordered w-full"
value="{{ rule.description|safe }}"
name="rule-description"
/>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-rule-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-rule-modal-form').submit();
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>
<!-- Backdrop --> });
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form> </script>
</dialog>

View File

@ -1,128 +1,62 @@
{% load static %} {% load static %}
<!-- Edit User -->
<dialog <div id="edit_user_modal" class="modal" tabindex="-1">
id="edit_user_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h5 class="modal-title">Update User Defaults</h5>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Update User Defaults</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> <p>Edit User Defaults.</p>
<form method="dialog"> <form id="edit-user-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
<button {% csrf_token %}
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <p>
:disabled="isSubmitting" <label for="default-package">Default Package</label>
> <select id="default-package" name="default-package" class="form-control" data-width='100%'>
<option disabled>Select a Package</option>
</button> {% for p in meta.writeable_packages %}
</form> <option value="{{ p.url }}" {% if p.id == meta.user.default_package.id %}selected{% endif %}>{{ p.name|safe }}</option>
{% endfor %}
<!-- Body --> </select>
<div class="py-4"> </p>
<form <p>
id="edit-user-modal-form" <label for="default-group">Default Group</label>
accept-charset="UTF-8" <select id="default-group" name="default-group" class="form-control" data-width='100%'>
action="" <option disabled>Select a Group</option>
method="post" {% for g in meta.available_groups %}
> <option value="{{ g.url }}" {% if g.id == meta.user.default_group.id %}selected{% endif %}>{{ g.name|safe }}</option>
{% csrf_token %} {% endfor %}
</select>
<div class="form-control mb-3"> </p>
<label class="label" for="default-package"> <p>
<span class="label-text">Default Package</span> <label for="default-prediction-setting">Default Prediction Setting</label>
</label> <select id="default-prediction-setting" name="default-prediction-setting" class="form-control" data-width='100%'>
<select <option disabled>Select a Setting</option>
id="default-package" {% for s in meta.available_settings %}
name="default-package" <option value="{{ s.url }}" {% if s.id == meta.user.default_setting.id %}selected{% endif %}>{{ s.name|safe }}</option>
class="select select-bordered w-full" {% endfor %}
> </select>
<option disabled>Select a Package</option> </p>
{% for p in meta.writeable_packages %} </form>
<option </div>
value="{{ p.url }}" <div class="modal-footer">
{% if p.id == meta.user.default_package.id %}selected{% endif %} <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
> <button type="button" class="btn btn-primary" id="edit-user-modal-submit">Update</button>
{{ p.name|safe }} </div>
</option>
{% endfor %}
</select>
</div> </div>
<div class="form-control mb-3">
<label class="label" for="default-group">
<span class="label-text">Default Group</span>
</label>
<select
id="default-group"
name="default-group"
class="select select-bordered w-full"
>
<option disabled>Select a Group</option>
{% for g in meta.available_groups %}
<option
value="{{ g.url }}"
{% if g.id == meta.user.default_group.id %}selected{% endif %}
>
{{ g.name|safe }}
</option>
{% endfor %}
</select>
</div>
<div class="form-control mb-3">
<label class="label" for="default-prediction-setting">
<span class="label-text">Default Prediction Setting</span>
</label>
<select
id="default-prediction-setting"
name="default-prediction-setting"
class="select select-bordered w-full"
>
<option disabled>Select a Setting</option>
{% for s in meta.available_settings %}
<option
value="{{ s.url }}"
{% if s.id == meta.user.default_setting.id %}selected{% endif %}
>
{{ s.name|safe }}
</option>
{% endfor %}
</select>
</div>
</form>
</div> </div>
</div>
<script>
$(function() {
<!-- Footer --> $('#edit-user-modal-submit').click(function(e){
<div class="modal-action"> e.preventDefault();
<button $('#edit-user-modal-form').submit();
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>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -1,123 +1,73 @@
<dialog
id="evaluate_model_modal"
class="modal"
x-data="modalForm()"
@close="reset()"
>
<div class="modal-box max-w-3xl">
<!-- Header -->
<h3 class="text-lg font-bold">Evaluate Model</h3>
<!-- Close button (X) --> <div class="modal fade" tabindex="-1" id="evaluate_model_modal" role="dialog" aria-labelledby="evaluate_model_modal"
<form method="dialog"> aria-hidden="true">
<button <div class="modal-dialog modal-lg">
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2" <div class="modal-content">
:disabled="isSubmitting" <div class="modal-header">
> <button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button> <span class="sr-only">Close</span>
</form> </button>
<h4 class="modal-title">Evaluate Model</h4>
</div>
<div class="modal-body">
<form id="evaluate_model_form" accept-charset="UTF-8" action="{{ current_object.url }}"
data-remote="true" method="post">
{% csrf_token %}
<div class="jumbotron">
For evaluation, you need to select the packages you want to use.
While the model is evaluating, you can use the model for predictions.
</div>
<!-- Evaluation Packages -->
<label for="model-evaluation-packages">Evaluation Packages</label>
<select id="model-evaluation-packages" name="model-evaluation-packages" data-actions-box='true'
class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
<!-- Body --> <option disabled>Unreviewed Packages</option>
<div class="py-4"> {% for obj in meta.readable_packages %}
<form {% if not obj.reviewed %}
id="evaluate_model_form" <option value="{{ obj.url }}">{{ obj.name|safe }}</option>
accept-charset="UTF-8" {% endif %}
action="{{ current_object.url }}" {% endfor %}
method="post" </select>
>
{% csrf_token %} <!-- Eval Type -->
<div class="alert alert-info mb-4"> <label for="model-evaluation-type">Evaluation Type</label>
<span> <select id="model-evaluation-type" name="model-evaluation-type" class="form-control">
For evaluation, you need to select the packages you want to use. <option disabled selected>Select evaluation type</option>
While the model is evaluating, you can use the model for <option value="sg">Single Generation</option>
predictions. <option value="mg">Multiple Generations</option>
</span> </select>
<input type="hidden" name="hidden" value="evaluate">
</form>
</div>
<div class="modal-footer">
<a id="evaluate_model_form_submit" class="btn btn-primary" href="#">Evaluate</a>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div> </div>
<!-- Evaluation Packages -->
<div class="form-control">
<label class="label" for="model-evaluation-packages">
<span class="label-text">Evaluation Packages</span>
</label>
<select
id="model-evaluation-packages"
name="model-evaluation-packages"
class="select select-bordered w-full h-48"
multiple
required
>
<optgroup label="Reviewed Packages">
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
<optgroup label="Unreviewed Packages">
{% for obj in meta.readable_packages %}
{% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endif %}
{% endfor %}
</optgroup>
</select>
<label class="label">
<span class="label-text-alt"
>Hold Ctrl/Cmd to select multiple packages</span
>
</label>
</div>
<!-- Eval Type -->
<div class="form-control mt-4">
<label class="label" for="model-evaluation-type">
<span class="label-text">Evaluation Type</span>
</label>
<select
id="model-evaluation-type"
name="model-evaluation-type"
class="select select-bordered w-full"
required
>
<option value="" disabled selected>Select evaluation type</option>
<option value="sg">Single Generation</option>
<option value="mg">Multiple Generations</option>
</select>
</div>
<input type="hidden" name="hidden" value="evaluate" />
</form>
</div> </div>
</div>
<!-- Footer --> <script>
<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('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>
<!-- Backdrop --> $(function () {
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button> $("#model-evaluation-packages").selectpicker();
</form>
</dialog> $('#evaluate_model_form_submit').on('click', function (e) {
e.preventDefault();
$('#evaluate_model_form').submit();
});
});
</script>

View File

@ -1,56 +1,36 @@
{% load static %} {% load static %}
<!-- Export Package -->
<dialog <div id="export_package_modal" class="modal" tabindex="-1">
id="export_package_modal" <div class="modal-dialog">
class="modal" <div class="modal-content">
x-data="modalForm()" <div class="modal-header">
@close="reset()" <h3 class="modal-title">Export Package as JSON</h3>
> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<div class="modal-box"> <span aria-hidden="true">&times;</span>
<!-- Header --> </button>
<h3 class="font-bold text-lg">Export Package as JSON</h3> </div>
<div class="modal-body">
<!-- Close button (X) --> By clicking on Export the Package will be serialized into a JSON and directly downloaded.
<form method="dialog"> <form id="export-package-modal-form" accept-charset="UTF-8" action="{{ package.url }}"
<button data-remote="true" method="GET">
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <input type="hidden" name="export" value="true"/>
:disabled="isSubmitting" </form>
> </div>
<div class="modal-footer">
</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</form> <button type="button" class="btn btn-primary" id="export-package-modal-form-submit">Export</button>
</div>
<!-- Body --> </div>
<div class="py-4">
<p>
By clicking on Export the Package will be serialized into a JSON and
opened in a new tab.
</p>
</div> </div>
</div>
<script>
$(function () {
<!-- Footer --> $('#export-package-modal-form-submit').click(function (e) {
<div class="modal-action"> e.preventDefault();
<button $('#export-package-modal-form').submit();
type="button" $('#export_package_modal').modal('hide');
class="btn" });
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="window.open('{{ package.url }}?export=true', '_blank'); $el.closest('dialog').close();"
:disabled="isSubmitting"
>
Export
</button>
</div>
</div>
<!-- Backdrop --> })
<form method="dialog" class="modal-backdrop"> </script>
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

Some files were not shown because too many files have changed in this diff Show More