forked from enviPath/enviPy
[Feature] Leftovers after Release (#303)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#303
This commit is contained in:
23
epapi/v1/endpoints/settings.py
Normal file
23
epapi/v1/endpoints/settings.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from django.conf import settings as s
|
||||||
|
from ninja import Router
|
||||||
|
from ninja_extra.pagination import paginate
|
||||||
|
|
||||||
|
from epdb.logic import SettingManager
|
||||||
|
|
||||||
|
from ..pagination import EnhancedPageNumberPagination
|
||||||
|
from ..schemas import SettingOutSchema
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/settings/", response=EnhancedPageNumberPagination.Output[SettingOutSchema])
|
||||||
|
@paginate(
|
||||||
|
EnhancedPageNumberPagination,
|
||||||
|
page_size=s.API_PAGINATION_DEFAULT_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
def list_all_pathways(request):
|
||||||
|
"""
|
||||||
|
List all pathways from reviewed packages.
|
||||||
|
"""
|
||||||
|
user = request.user
|
||||||
|
return SettingManager.get_all_settings(user)
|
||||||
@ -1,7 +1,18 @@
|
|||||||
from ninja import Router
|
from ninja import Router
|
||||||
from ninja.security import SessionAuth
|
from ninja.security import SessionAuth
|
||||||
|
|
||||||
from .auth import BearerTokenAuth
|
from .auth import BearerTokenAuth
|
||||||
from .endpoints import packages, scenarios, compounds, rules, reactions, pathways, models, structure
|
from .endpoints import (
|
||||||
|
compounds,
|
||||||
|
models,
|
||||||
|
packages,
|
||||||
|
pathways,
|
||||||
|
reactions,
|
||||||
|
rules,
|
||||||
|
scenarios,
|
||||||
|
settings,
|
||||||
|
structure,
|
||||||
|
)
|
||||||
|
|
||||||
# Main router with authentication
|
# Main router with authentication
|
||||||
router = Router(
|
router = Router(
|
||||||
@ -20,3 +31,4 @@ router.add_router("", reactions.router)
|
|||||||
router.add_router("", pathways.router)
|
router.add_router("", pathways.router)
|
||||||
router.add_router("", models.router)
|
router.add_router("", models.router)
|
||||||
router.add_router("", structure.router)
|
router.add_router("", structure.router)
|
||||||
|
router.add_router("", settings.router)
|
||||||
|
|||||||
@ -102,3 +102,10 @@ class PackageOutSchema(Schema):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_review_status(obj):
|
def resolve_review_status(obj):
|
||||||
return "reviewed" if obj.reviewed else "unreviewed"
|
return "reviewed" if obj.reviewed else "unreviewed"
|
||||||
|
|
||||||
|
|
||||||
|
class SettingOutSchema(Schema):
|
||||||
|
uuid: UUID
|
||||||
|
url: str = ""
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|||||||
@ -28,7 +28,7 @@ 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", "is_staff", "is_superuser"]
|
||||||
|
|
||||||
|
|
||||||
class UserPackagePermissionAdmin(admin.ModelAdmin):
|
class UserPackagePermissionAdmin(admin.ModelAdmin):
|
||||||
@ -48,7 +48,7 @@ class JobLogAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class EPAdmin(admin.ModelAdmin):
|
class EPAdmin(admin.ModelAdmin):
|
||||||
search_fields = ["name", "description"]
|
search_fields = ["name", "description", "url", "uuid"]
|
||||||
list_display = ["name", "url", "created"]
|
list_display = ["name", "url", "created"]
|
||||||
ordering = ["-created"]
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,8 @@ from .models import (
|
|||||||
Edge,
|
Edge,
|
||||||
EnviFormer,
|
EnviFormer,
|
||||||
EPModel,
|
EPModel,
|
||||||
|
Group,
|
||||||
|
GroupPackagePermission,
|
||||||
MLRelativeReasoning,
|
MLRelativeReasoning,
|
||||||
Node,
|
Node,
|
||||||
PackageBasedModel,
|
PackageBasedModel,
|
||||||
@ -204,6 +206,82 @@ def get_user(request, user_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
########
|
||||||
|
# Group #
|
||||||
|
########
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMember(Schema):
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class GroupWrapper(Schema):
|
||||||
|
group: List[SimpleGroup]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSchema(Schema):
|
||||||
|
description: str
|
||||||
|
id: str = Field(None, alias="url")
|
||||||
|
identifier: str = "group"
|
||||||
|
members: List[GroupMember] = Field([], alias="members")
|
||||||
|
name: str = Field(None, alias="name")
|
||||||
|
ownerid: str = Field(None, alias="owner.url")
|
||||||
|
ownername: str = Field(None, alias="owner.name")
|
||||||
|
packages: List["SimplePackage"] = Field([], alias="packages")
|
||||||
|
readers: List[GroupMember] = Field([], alias="readers")
|
||||||
|
writers: List[GroupMember] = Field([], alias="writers")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_members(obj: Group):
|
||||||
|
res = []
|
||||||
|
for member in obj.user_member.all():
|
||||||
|
res.append(GroupMember(id=member.url, identifier="usermember", name=member.username))
|
||||||
|
|
||||||
|
for member in obj.group_member.all():
|
||||||
|
res.append(GroupMember(id=member.url, identifier="groupmember", name=member.name))
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_packages(obj: Group):
|
||||||
|
return Package.objects.filter(
|
||||||
|
id__in=[
|
||||||
|
GroupPackagePermission.objects.filter(group=obj).values_list(
|
||||||
|
"package_id", flat=True
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_readers(obj: Group):
|
||||||
|
return GroupSchema.resolve_members(obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_writers(obj: Group):
|
||||||
|
return [GroupMember(id=obj.owner.url, identifier="usermember", name=obj.owner.username)]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/group", response={200: GroupWrapper, 403: Error})
|
||||||
|
def get_groups(request):
|
||||||
|
return {"group": GroupManager.get_groups(request.user)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/group/{uuid:group_uuid}", response={200: GroupSchema, 403: Error})
|
||||||
|
def get_group(request, group_uuid):
|
||||||
|
try:
|
||||||
|
g = GroupManager.get_group_by_id(request.user, group_uuid)
|
||||||
|
return g
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Getting Group with id {group_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
# Search #
|
||||||
|
##########
|
||||||
class Search(Schema):
|
class Search(Schema):
|
||||||
packages: List[str] = Field(alias="packages[]")
|
packages: List[str] = Field(alias="packages[]")
|
||||||
search: str
|
search: str
|
||||||
|
|||||||
17
epdb/migrations/0015_user_is_reviewer.py
Normal file
17
epdb/migrations/0015_user_is_reviewer.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-19 19:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0014_rename_expansion_schema_setting_expansion_scheme"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="is_reviewer",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -72,6 +72,7 @@ class User(AbstractUser):
|
|||||||
null=True,
|
null=True,
|
||||||
blank=False,
|
blank=False,
|
||||||
)
|
)
|
||||||
|
is_reviewer = models.BooleanField(default=False)
|
||||||
|
|
||||||
USERNAME_FIELD = "email"
|
USERNAME_FIELD = "email"
|
||||||
REQUIRED_FIELDS = ["username"]
|
REQUIRED_FIELDS = ["username"]
|
||||||
@ -1828,8 +1829,8 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
queue.append(prod)
|
queue.append(prod)
|
||||||
|
|
||||||
# We shouldn't lose or make up nodes...
|
# We shouldn't lose or make up nodes...
|
||||||
assert len(nodes) == len(self.nodes)
|
if len(nodes) != len(self.nodes):
|
||||||
logger.debug(f"{self.name}: Num Nodes {len(nodes)} vs. DB Nodes {len(self.nodes)}")
|
logger.debug(f"{self.name}: Num Nodes {len(nodes)} vs. DB Nodes {len(self.nodes)}")
|
||||||
|
|
||||||
links = [e.d3_json() for e in self.edges]
|
links = [e.d3_json() for e in self.edges]
|
||||||
|
|
||||||
@ -1880,6 +1881,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
"source": pseudo_idx,
|
"source": pseudo_idx,
|
||||||
"target": node_url_to_idx[target],
|
"target": node_url_to_idx[target],
|
||||||
"app_domain": link.get("app_domain", None),
|
"app_domain": link.get("app_domain", None),
|
||||||
|
"multi_step": link["multi_step"],
|
||||||
}
|
}
|
||||||
adjusted_links.append(new_link)
|
adjusted_links.append(new_link)
|
||||||
|
|
||||||
@ -2146,6 +2148,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
pathway: "Pathway",
|
pathway: "Pathway",
|
||||||
smiles: str,
|
smiles: str,
|
||||||
@ -2185,8 +2188,11 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
if data:
|
if data:
|
||||||
rule_ids = defaultdict(list)
|
rule_ids = defaultdict(list)
|
||||||
for e in Edge.objects.filter(start_nodes__in=[self]):
|
for e in Edge.objects.filter(start_nodes__in=[self]):
|
||||||
for r in e.edge_label.rules.all():
|
# TODO While the Pathway is being predicted we sometimes
|
||||||
rule_ids[str(r.uuid)].append(e.simple_json())
|
# TODO receive 'NoneType' object has no attribute 'rules'
|
||||||
|
if e.edge_label:
|
||||||
|
for r in e.edge_label.rules.all():
|
||||||
|
rule_ids[str(r.uuid)].append(e.simple_json())
|
||||||
|
|
||||||
for t in data["assessment"]["transformations"]:
|
for t in data["assessment"]["transformations"]:
|
||||||
if t["rule"]["uuid"] in rule_ids:
|
if t["rule"]["uuid"] in rule_ids:
|
||||||
@ -2230,6 +2236,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
"reaction": {"name": self.edge_label.name, "url": self.edge_label.url}
|
"reaction": {"name": self.edge_label.name, "url": self.edge_label.url}
|
||||||
if self.edge_label
|
if self.edge_label
|
||||||
else None,
|
else None,
|
||||||
|
"multi_step": self.edge_label.multi_step if self.edge_label else False,
|
||||||
"reaction_probability": self.kv.get("probability"),
|
"reaction_probability": self.kv.get("probability"),
|
||||||
"start_node_urls": [x.url for x in self.start_nodes.all()],
|
"start_node_urls": [x.url for x in self.start_nodes.all()],
|
||||||
"end_node_urls": [x.url for x in self.end_nodes.all()],
|
"end_node_urls": [x.url for x in self.end_nodes.all()],
|
||||||
@ -2270,6 +2277,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
|
|||||||
return edge_json
|
return edge_json
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@transaction.atomic
|
||||||
def create(
|
def create(
|
||||||
pathway,
|
pathway,
|
||||||
start_nodes: List[Node],
|
start_nodes: List[Node],
|
||||||
@ -3819,6 +3827,11 @@ class Scenario(EnviPathModel):
|
|||||||
|
|
||||||
yield inst
|
yield inst
|
||||||
|
|
||||||
|
def related_pathways(self):
|
||||||
|
return Pathway.objects.filter(
|
||||||
|
scenarios__in=[self], package__reviewed=True, package=self.package
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
class UserSettingPermission(Permission):
|
class UserSettingPermission(Permission):
|
||||||
uuid = models.UUIDField(
|
uuid = models.UUIDField(
|
||||||
|
|||||||
@ -1352,6 +1352,14 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u
|
|||||||
)
|
)
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
|
is_image_request = request.GET.get("image")
|
||||||
|
|
||||||
|
if is_image_request:
|
||||||
|
if is_image_request == "svg":
|
||||||
|
return HttpResponse(current_structure.as_svg, content_type="image/svg+xml")
|
||||||
|
else:
|
||||||
|
return HttpResponseBadRequest("Currently only SVG as image formate supported!")
|
||||||
|
|
||||||
context = get_base_context(request)
|
context = get_base_context(request)
|
||||||
context["title"] = (
|
context["title"] = (
|
||||||
f"enviPath - {current_package.name} - {current_compound.name} - {current_structure.name}"
|
f"enviPath - {current_package.name} - {current_compound.name} - {current_structure.name}"
|
||||||
@ -2569,6 +2577,28 @@ def package_scenario(request, package_uuid, scenario_uuid):
|
|||||||
return redirect(current_scenario.url)
|
return redirect(current_scenario.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
new_scenario_name = request.POST.get("scenario-name")
|
||||||
|
|
||||||
|
if new_scenario_name is not None:
|
||||||
|
new_scenario_name = nh3.clean(new_scenario_name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
|
if new_scenario_name:
|
||||||
|
current_scenario.name = new_scenario_name
|
||||||
|
|
||||||
|
new_scenario_description = request.POST.get("scenario-description")
|
||||||
|
|
||||||
|
if new_scenario_description is not None:
|
||||||
|
new_scenario_description = nh3.clean(
|
||||||
|
new_scenario_description, tags=s.ALLOWED_HTML_TAGS
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
if new_scenario_description:
|
||||||
|
current_scenario.description = new_scenario_description
|
||||||
|
|
||||||
|
if any([new_scenario_name, new_scenario_description]):
|
||||||
|
current_scenario.save()
|
||||||
|
return redirect(current_scenario.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
else:
|
else:
|
||||||
@ -2784,10 +2814,17 @@ def settings(request):
|
|||||||
context["object_type"] = "setting"
|
context["object_type"] = "setting"
|
||||||
context["breadcrumbs"] = [
|
context["breadcrumbs"] = [
|
||||||
{"Home": s.SERVER_URL},
|
{"Home": s.SERVER_URL},
|
||||||
{"Group": s.SERVER_URL + "/setting"},
|
{"Setting": s.SERVER_URL + "/setting"},
|
||||||
]
|
]
|
||||||
|
|
||||||
context["objects"] = SettingManager.get_all_settings(current_user)
|
# Context for paginated template
|
||||||
|
context["entity_type"] = "setting"
|
||||||
|
context["api_endpoint"] = "/api/v1/settings/"
|
||||||
|
context["per_page"] = s.API_PAGINATION_DEFAULT_PAGE_SIZE
|
||||||
|
context["list_title"] = "settings"
|
||||||
|
context["list_mode"] = "combined"
|
||||||
|
|
||||||
|
return render(request, "collections/settings_paginated.html", context)
|
||||||
|
|
||||||
return render(request, "collections/objects_list.html", context)
|
return render(request, "collections/objects_list.html", context)
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
@ -2867,7 +2904,26 @@ def settings(request):
|
|||||||
|
|
||||||
|
|
||||||
def setting(request, setting_uuid):
|
def setting(request, setting_uuid):
|
||||||
pass
|
current_user = _anonymous_or_real(request)
|
||||||
|
current_setting = SettingManager.get_setting_by_id(current_user, setting_uuid)
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
context = get_base_context(request)
|
||||||
|
context["title"] = f"enviPath - {current_setting.name}"
|
||||||
|
|
||||||
|
context["object_type"] = "setting"
|
||||||
|
context["breadcrumbs"] = [
|
||||||
|
{"Home": s.SERVER_URL},
|
||||||
|
{"Setting": s.SERVER_URL + "/setting"},
|
||||||
|
{f"{current_setting.name}": current_setting.url},
|
||||||
|
]
|
||||||
|
|
||||||
|
context["setting"] = current_setting
|
||||||
|
context["current_object"] = current_setting
|
||||||
|
|
||||||
|
return render(request, "objects/setting.html", context)
|
||||||
|
else:
|
||||||
|
return HttpResponseNotAllowed(["GET"])
|
||||||
|
|
||||||
|
|
||||||
def jobs(request):
|
def jobs(request):
|
||||||
|
|||||||
@ -498,7 +498,8 @@ function draw(pathway, elem) {
|
|||||||
.enter().append("line")
|
.enter().append("line")
|
||||||
// Check if target is pseudo and draw marker only if not pseudo
|
// Check if target is pseudo and draw marker only if not pseudo
|
||||||
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
||||||
.attr("marker-end", d => d.target.pseudo ? '' : 'url(#arrow)')
|
.attr("marker-end", d => d.target.pseudo ? '' : d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)')
|
||||||
|
|
||||||
|
|
||||||
// add element to links array
|
// add element to links array
|
||||||
link.each(function (d) {
|
link.each(function (d) {
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
{% if meta.can_edit %}
|
{% if meta.can_edit %}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('edit_scenario_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> Edit Scenario</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
|
|||||||
20
templates/collections/settings_paginated.html
Normal file
20
templates/collections/settings_paginated.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{% extends "collections/paginated_base.html" %}
|
||||||
|
|
||||||
|
{% block page_title %}Settings{% endblock %}
|
||||||
|
|
||||||
|
{% block action_button %}
|
||||||
|
{% endblock action_button %}
|
||||||
|
|
||||||
|
{% block action_modals %}
|
||||||
|
{% endblock action_modals %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<p>A setting includes configuration parameters for pathway predictions.</p>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://wiki.envipath.org/index.php/Setting"
|
||||||
|
class="link link-primary"
|
||||||
|
>
|
||||||
|
Learn more >>
|
||||||
|
</a>
|
||||||
|
{% endblock description %}
|
||||||
91
templates/modals/objects/edit_scenario_modal.html
Normal file
91
templates/modals/objects/edit_scenario_modal.html
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<dialog
|
||||||
|
id="edit_scenario_modal"
|
||||||
|
class="modal"
|
||||||
|
x-data="modalForm()"
|
||||||
|
@close="reset()"
|
||||||
|
>
|
||||||
|
<div class="modal-box">
|
||||||
|
<!-- Header -->
|
||||||
|
<h3 class="font-bold text-lg">Edit Scenario</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="edit-scenario-modal-form"
|
||||||
|
accept-charset="UTF-8"
|
||||||
|
action=""
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label" for="scenario-name">
|
||||||
|
<span class="label-text">Name</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="scenario-name"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
name="scenario-name"
|
||||||
|
value="{{ scenario.name|safe }}"
|
||||||
|
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"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
value="{{ scenario.description|safe }}"
|
||||||
|
name="scenario-description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
onclick="this.closest('dialog').close()"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="submit('edit-scenario-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>
|
||||||
|
</dialog>
|
||||||
@ -306,6 +306,21 @@
|
|||||||
>
|
>
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999" />
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999" />
|
||||||
</marker>
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="doublearrow"
|
||||||
|
viewBox="0 0 20 30"
|
||||||
|
refX="53"
|
||||||
|
refY="5"
|
||||||
|
markerWidth="18"
|
||||||
|
markerHeight="18"
|
||||||
|
orient="auto-start-reverse"
|
||||||
|
markerUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<!-- first triangle -->
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999" />
|
||||||
|
<!-- second triangle -->
|
||||||
|
<path d="M 10 0 L 20 5 L 10 10 Z" fill="#999" />
|
||||||
|
</marker>
|
||||||
<marker
|
<marker
|
||||||
id="arrow_passes_app_domain"
|
id="arrow_passes_app_domain"
|
||||||
viewBox="0 0 10 10"
|
viewBox="0 0 10 10"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
|
{% include "modals/objects/edit_scenario_modal.html" %}
|
||||||
{% include "modals/objects/add_additional_information_modal.html" %}
|
{% include "modals/objects/add_additional_information_modal.html" %}
|
||||||
{% include "modals/objects/update_scenario_additional_information_modal.html" %}
|
{% include "modals/objects/update_scenario_additional_information_modal.html" %}
|
||||||
{% include "modals/objects/generic_delete_modal.html" %}
|
{% include "modals/objects/generic_delete_modal.html" %}
|
||||||
@ -160,6 +161,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pathways -->
|
||||||
|
{% if scenario.related_pathways %}
|
||||||
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-xl font-medium">Pathways</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<ul class="menu bg-base-100 rounded-box">
|
||||||
|
{% for p in scenario.related_pathways %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ p.url }}" class="hover:bg-base-200"
|
||||||
|
>{{ p.name }} <i>({{ p.package.name }})</i></a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
67
templates/objects/setting.html
Normal file
67
templates/objects/setting.html
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{% extends "framework_modern.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% block action_modals %}
|
||||||
|
{% endblock action_modals %}
|
||||||
|
|
||||||
|
<div class="space-y-2 p-4">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="card bg-base-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="card-title text-2xl">{{ setting.name }}</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 %}
|
||||||
|
{% endblock %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- The actual setting -->
|
||||||
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-content">
|
||||||
|
{% with setting_to_render=setting can_be_default=False %}
|
||||||
|
{% include "objects/setting_template.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Show actions button if there are actions
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const actionsButton = document.getElementById("actionsButton");
|
||||||
|
const actionsList = actionsButton?.querySelector("ul");
|
||||||
|
if (actionsList && actionsList.children.length > 0) {
|
||||||
|
actionsButton?.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
@ -31,6 +31,32 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="card bg-base-200 mb-6 ">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">Welcome to the new enviPath!</h3>
|
||||||
|
<p class="mb-4 text-sm">
|
||||||
|
Rebuilt from the ground up for faster predictions, greater stability,
|
||||||
|
and powerful scalability. Explore a decade of research on a modern,
|
||||||
|
reliable platform.<br />
|
||||||
|
The old system is still accessible but will be shut down at a later
|
||||||
|
date. If you want to back up your data, download it from the
|
||||||
|
<a
|
||||||
|
href="https://legacy.envipath.org"
|
||||||
|
class="link link-primary"
|
||||||
|
target="_blank"
|
||||||
|
>old system</a
|
||||||
|
>
|
||||||
|
or contact us via the
|
||||||
|
<a
|
||||||
|
href="https://community.envipath.org/"
|
||||||
|
class="link link-primary"
|
||||||
|
target="_blank"
|
||||||
|
>community forum</a
|
||||||
|
>
|
||||||
|
for assistance.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Tab Navigation -->
|
<!-- Tab Navigation -->
|
||||||
<div class="border-base-300 mb-6 border-b">
|
<div class="border-base-300 mb-6 border-b">
|
||||||
<div class="flex justify-start">
|
<div class="flex justify-start">
|
||||||
|
|||||||
Reference in New Issue
Block a user