2 Commits

Author SHA1 Message Date
a092d4a558 [Chore] Updated Dockerfile (#418)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#418
2026-06-17 08:33:02 +12:00
2502c020f7 [Fix] Collection of minor Bugfixes (#417)
User feedback from on-prem installation

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#417
2026-06-17 07:56:32 +12:00
21 changed files with 157 additions and 88 deletions

View File

@ -6,18 +6,23 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
curl \
openssh-client \
git \
nodejs \
npm \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Install pnpm
RUN npm install -g pnpm
# Install Node 22 + pnpm
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& corepack enable \
&& corepack prepare pnpm@latest --activate \
&& rm -rf /var/lib/apt/lists/*
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:${PATH}"
@ -32,7 +37,7 @@ RUN mkdir -p -m 0700 /root/.ssh \
# We'll need access to private repos, let docker make use of host ssh agent and use it like:
# docker build --ssh default -t envipath/envipy:1.0 .
RUN --mount=type=ssh \
uv sync --locked --extra ms-login --extra pepper-plugin
uv sync --locked --extra pepper-plugin
# Now copy source and do a final sync to install the project itself
# Ensure .dockerignore is reasonable

View File

@ -254,7 +254,14 @@ class Classifier(Plugin):
def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
if cls.Config is None:
return None
return cls.Config(**(data or {}))
# remove empty strings a.k.a unset params to not overwrite defaults
cpy = {}
if data is not None:
for k, v in data.items():
if v != "":
cpy[k] = v
return cls.Config(**cpy)
@classmethod
def create(cls, data: dict | None = None):

View File

@ -2335,8 +2335,8 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
def get_timeseries_data(self):
for ai in self.additional_information.all():
if ai.__class__.__name__ == "OECD301FTimeSeries":
return ai.model_dump(mode="json")
if ai.type == "OECD301FTimeSeries":
return ai.get().model_dump(mode="json")
return None

File diff suppressed because one or more lines are too long

View File

@ -65,6 +65,24 @@ def run_both_engines(SMILES, SMIRKS):
def migration(request):
accepted_diffs = [
"bt0055-3469.1",
"bt0056-2685",
"bt0193-4263",
"bt0231-1871.1",
"bt0242-3803",
"bt0254-4224.1",
"bt0254-4224.2",
"bt0291-1129",
"bt0337-3543",
"bt0391-4285",
"bt0402-3576",
"bt0404-3928",
"bt0416-4269",
"bt0432-4254",
"bt0337-4117",
]
if request.method == "GET":
context = get_base_context(request)
@ -109,11 +127,11 @@ def migration(request):
),
"id": str(r.uuid),
"url": r.url,
"status": res,
"status": res or r.name in accepted_diffs,
}
)
if res:
if res or r.name in accepted_diffs:
success += 1
else:
error += 1
@ -135,7 +153,16 @@ def migration(request):
for r in migration_status["results"]:
r["detail_url"] = r["detail_url"].replace("http://localhost:8000", s.SERVER_URL)
if r["name"] in accepted_diffs:
r["status"] = True
migration_status["results"] = sorted(
migration_status["results"], key=lambda x: (x["status"], x["name"])
)
num_success = sum([int(x["status"]) for x in migration_status["results"]])
migration_status["success"] = num_success
migration_status["error"] = migration_status["total"] - num_success
context.update(**migration_status)
return render(request, "migration.html", context)

View File

@ -21,5 +21,11 @@
"django",
"tailwindcss",
"daisyui"
]
],
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"@tailwindcss/oxide"
]
}
}

View File

@ -1,3 +1,5 @@
allowBuilds:
'@parcel/watcher': true
onlyBuiltDependencies:
- '@parcel/watcher'
- '@tailwindcss/oxide'

View File

@ -59,6 +59,9 @@ document.addEventListener("alpine:init", () => {
get isEditMode() {
return this.mode === "edit";
},
get isRequired() {
return (this.schema.required || []).indexOf(this.fieldName) > -1
}
});
// Text widget

View File

@ -23,6 +23,7 @@ function predictFromNode(url) {
// elem = 'vizdiv'
function draw(pathway, elem) {
const initialzoom = 2.5
const nodeRadius = 20;
const linkDistance = 100;
const chargeStrength = -200;
@ -63,7 +64,7 @@ function draw(pathway, elem) {
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
}
node.fy = node.depth * levelSpacing + 50;
node.fy = (node.depth + initialzoom + 0.5) * levelSpacing + 50;
depthMap.set(node.depth, depthMap.get(node.depth) + 1);
});
}
@ -577,6 +578,8 @@ function draw(pathway, elem) {
// Apply zoom to the SVG element - this enables wheel zoom
svg.call(zoom);
svg.call(zoom.scaleBy, initialzoom);
// 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);

View File

@ -5,7 +5,7 @@
{% block action_button %}
<div class="flex items-center gap-2">
{% if meta.can_edit %}
{% if meta.can_edit or not meta.url_contains_package %}
<button
type="button"
class="btn btn-primary btn-sm"

View File

@ -3,14 +3,16 @@
{% block page_title %}Models{% endblock %}
{% block action_button %}
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
<button
type="button"
class="btn btn-primary btn-sm"
onclick="document.getElementById('new_model_modal').showModal(); return false;"
>
New Model
</button>
{% if meta.can_edit or not meta.url_contains_package %}
{% if meta.enabled_features.MODEL_BUILDING %}
<button
type="button"
class="btn btn-primary btn-sm"
onclick="document.getElementById('new_model_modal').showModal(); return false;"
>
New Model
</button>
{% endif %}
{% endif %}
{% endblock action_button %}

View File

@ -3,75 +3,73 @@
{% block page_title %}Packages{% endblock %}
{% block action_button %}
{% if meta.can_edit %}
<div class="flex items-center gap-2">
<button
type="button"
class="btn btn-primary btn-sm"
id="new-package-button"
onclick="document.getElementById('new_package_modal').showModal(); return false;"
<div class="flex items-center gap-2">
<button
type="button"
class="btn btn-primary btn-sm"
id="new-package-button"
onclick="document.getElementById('new_package_modal').showModal(); return false;"
>
<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-folder-plus-icon lucide-folder-plus"
>
<path d="M12 10v6" />
<path d="M9 13h6" />
<path
d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
/>
</svg>
</button>
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-sm">
Import
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
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-folder-plus-icon lucide-folder-plus"
class="lucide lucide-chevron-down ml-1"
>
<path d="M12 10v6" />
<path d="M9 13h6" />
<path
d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
/>
<path d="m6 9 6 6 6-6" />
</svg>
</button>
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-sm">
Import
<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-chevron-down ml-1"
>
<path d="m6 9 6 6 6-6" />
</svg>
</div>
<ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-56 p-2"
>
<li>
<a
role="button"
onclick="document.getElementById('import_package_modal').showModal(); return false;"
>
Import Package from JSON
</a>
</li>
<li>
<a
role="button"
onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;"
>
Import Package from legacy JSON
</a>
</li>
</ul>
</div>
<ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-56 p-2"
>
<li>
<a
role="button"
onclick="document.getElementById('import_package_modal').showModal(); return false;"
>
Import Package from JSON
</a>
</li>
<li>
<a
role="button"
onclick="document.getElementById('import_legacy_package_modal').showModal(); return false;"
>
Import Package from legacy JSON
</a>
</li>
</ul>
</div>
{% endif %}
</div>
{% endblock action_button %}
{% block action_modals %}

View File

@ -3,7 +3,7 @@
{% block page_title %}Pathways{% endblock %}
{% block action_button %}
{% if meta.can_edit %}
{% if meta.can_edit or not meta.url_contains_package %}
<div class="flex items-center gap-2">
<a
class="btn btn-primary btn-sm"

View File

@ -3,7 +3,7 @@
{% block page_title %}Reactions{% endblock %}
{% block action_button %}
{% if meta.can_edit %}
{% if meta.can_edit or not meta.url_contains_package %}
<button
type="button"
class="btn btn-primary btn-sm"

View File

@ -3,7 +3,7 @@
{% block page_title %}Rules{% endblock %}
{% block action_button %}
{% if meta.can_edit %}
{% if meta.can_edit or not meta.url_contains_package %}
<button
type="button"
class="btn btn-primary btn-sm"

View File

@ -11,7 +11,7 @@
{% endblock action_modals %}
{% block action_button %}
{% if meta.can_edit %}
{% if meta.can_edit or not meta.url_contains_package %}
<button
type="button"
class="btn btn-primary btn-sm"

View File

@ -3,7 +3,7 @@
{% block page_title %}{{ page_title|default:"Structures" }}{% endblock %}
{% block action_button %}
{% if meta.can_edit %}
{% if meta.can_edit or not meta.url_contains_package %}
<button
type="button"
class="btn btn-primary btn-sm"

View File

@ -44,6 +44,7 @@
:class="{ 'select-error': $store.validationErrors.hasError(fieldName, context) }"
x-model="value"
:multiple="multiple"
:required="isRequired"
>
<option value="" :selected="!value">Select...</option>

View File

@ -65,11 +65,11 @@
},
get showMlrr() {
return this.selectedType === 'mlrr';
return this.selectedType === 'ml-relative-reasoning';
},
get showRbrr() {
return this.selectedType === 'rbrr';
return this.selectedType === 'rule-based-relative-reasoning';
},
get showEnviformer() {

View File

@ -203,11 +203,11 @@
id="model-based-prediction-setting-threshold"
name="model-based-prediction-setting-threshold"
class="input input-bordered w-full"
placeholder="0.25"
value="0.25"
type="number"
min="0"
max="1"
step="0.05"
step="any"
/>
</div>

View File

@ -105,7 +105,7 @@
></iframe>
</div>
<label class="select mb-8 w-full">
<label class="select mb-8 w-full" id="prediction-setting-label">
<span class="label">Predictor</span>
<select id="prediction-setting" name="prediction-setting">
<option disabled>Select a Setting</option>
@ -148,6 +148,21 @@
</div>
{# prettier-ignore-start #}
<script>
// Hide predictor selection and update button text if mode is "build"
function radioChange(event) {
if (event.target.value === "build") {
document.getElementById("prediction-setting-label").hidden = true;
document.getElementById("predict-submit-button").innerText = "Build";
} else {
document.getElementById("prediction-setting-label").hidden = false;
document.getElementById("predict-submit-button").innerText = "Predict";
}
}
const radioButtons = document.querySelectorAll('input[name="predict"]');
radioButtons.forEach(radio => {
radio.addEventListener('change', radioChange);
});
// Helper function to safely get Ketcher instance from iframe
function getKetcherInstance(iframeId) {
const ketcherFrame = document.getElementById(iframeId);