-
Notifications
You must be signed in to change notification settings - Fork 1
SS-1176 Users are able to set a custom default start URL for their apps #249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
4994f10
27b763b
b5e8313
636ff9c
1425fdb
90f2b98
06b74f8
42c1e9f
b5cf2ed
758939e
60002fc
b3860ab
ad3feae
dc37d17
7b0ec61
8d560bd
9ce5d5b
3527fd0
6c4b717
cde08ad
83f9954
47476ce
a9b049b
bbcb968
a329f08
7d84b18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,13 @@ | ||||||||||
| from crispy_forms.bootstrap import Accordion, AccordionGroup, PrependedText | ||||||||||
| from crispy_forms.layout import HTML, Div, Field, Layout, MultiField | ||||||||||
| from django import forms | ||||||||||
| from django.core.exceptions import ValidationError | ||||||||||
| from django.utils.safestring import mark_safe | ||||||||||
|
|
||||||||||
| from apps.forms.base import AppBaseForm | ||||||||||
| from apps.forms.field.common import SRVCommonDivField | ||||||||||
| from apps.models import CustomAppInstance, VolumeInstance | ||||||||||
| from apps.types_.url_additional_path_validation import UrlAdditionalPathValidation | ||||||||||
| from projects.models import Flavor | ||||||||||
|
|
||||||||||
| __all__ = ["CustomAppForm"] | ||||||||||
|
|
@@ -15,11 +19,24 @@ class CustomAppForm(AppBaseForm): | |||||||||
| image = forms.CharField(max_length=255, required=True) | ||||||||||
| path = forms.CharField(max_length=255, required=False) | ||||||||||
|
|
||||||||||
| custom_default_url = forms.CharField(max_length=255, required=False, label="Custom default start URL") | ||||||||||
|
|
||||||||||
| def _setup_form_fields(self): | ||||||||||
| # Handle Volume field | ||||||||||
| super()._setup_form_fields() | ||||||||||
| self.fields["volume"].initial = None | ||||||||||
|
|
||||||||||
| self.fields["custom_default_url"].widget.attrs.update({"class": "textinput form-control"}) | ||||||||||
| self.fields["custom_default_url"].help_text = "Specify a non-default start URL if your app requires that." | ||||||||||
| self.fields["custom_default_url"].bottom_help_text = mark_safe( | ||||||||||
| ( | ||||||||||
| "We will display this URL for your app in our app catalogue." | ||||||||||
| " Keep in mind that when your app does not have anything on the root URL" | ||||||||||
| " (<span id='id_custom_default_url_form_help_text'></span>), if a user manually" | ||||||||||
| " navigates to the root URL, they will see an empty page there." | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| def _setup_form_helper(self): | ||||||||||
| super()._setup_form_helper() | ||||||||||
|
|
||||||||||
|
|
@@ -40,10 +57,32 @@ def _setup_form_helper(self): | |||||||||
| ), | ||||||||||
| SRVCommonDivField("port", placeholder="8000"), | ||||||||||
| SRVCommonDivField("image"), | ||||||||||
| Accordion( | ||||||||||
| AccordionGroup( | ||||||||||
| "Advanced settings", | ||||||||||
| PrependedText( | ||||||||||
| "custom_default_url", | ||||||||||
| "Subdomain/", | ||||||||||
| template="apps/partials/srv_prepend_input_group_custom_app.html", | ||||||||||
|
||||||||||
| "Subdomain/", | |
| template="apps/partials/srv_prepend_input_group_custom_app.html", | |
| mark_safe("<span id='id_custom_default_url_prepend'>Subdomain/</span>"), | |
| template="apps/partials/srv_prepend_append_input_group.html", |
I think one way to this this is to not use a separate template but use apps/partials/srv_prepend_input_group_custom_app.html, add an id to the Prepend input group element using span so that it is accessible through the javascript id match that you use. I have also tried to shorten the id, you can ofcourse choose another name.
Then I suggest moving parts of your javascript to the templates/apps/create_base.html file. The changes can be the following:
Load get_setting
{% load get_setting %}In the script section make the following changes
+ const subdomainInput = document.getElementById("id_subdomain");
+ const customDefaultUrlFormModifiedUrlText = document.getElementById("id_custom_default_url_prepend");
+ function updateUrlWithSubdomain() {
+ const subdomainValue = subdomainInput.value.trim();
+ const displayValue = subdomainValue || "subdomain_name";
+ customDefaultUrlFormModifiedUrlText.innerHTML = `${displayValue}.{% get_setting 'DOMAIN' %}/</b>`;
+ }
// User is "finished typing," do something
function doneTyping () {
let subdomain_input = $("#id_subdomain").val();
if (subdomain_input.length == 0) {
//console.log("Empty subdomain input");
clearSubdomainValidation();
// Empty text field. Enable the submit button and clear any validation styles.
$("#submit-id-submit").prop('disabled', false);
}
else {
//console.log(`Checking subdomain validity for: ${subdomain_input}`);
// Run check if there is text in the input field
// Convert the input subdomain to lowercase for the validation.
// OK because the input form and the server does the same.
checkSubdomainValidity(subdomain_input.toLowerCase());
}
+ updateUrlWithSubdomain();
}
........
........
........
htmx.onLoad(function(content) {
runScript();
+ updateUrlWithSubdomain();
})We already have an eventlistener listening to keystrokes on the subdomain input box. So we can use that one to update the URL for your input prepend text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for this suggestion. It helped me a lot to understand the existing scripts.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -221,12 +221,15 @@ def update_status_time(status_object, status_ts, event_msg=None): | |||||
| status_object.save(update_fields=["time", "info"]) | ||||||
|
|
||||||
|
|
||||||
| def get_URI(values): | ||||||
| def get_URI(instance): | ||||||
| values = instance.k8s_values | ||||||
| # Subdomain is empty if app is already deleted | ||||||
| subdomain = values["subdomain"] if "subdomain" in values else "" | ||||||
| URI = f"https://{subdomain}.{values['global']['domain']}" | ||||||
|
|
||||||
| URI = URI.strip("/") | ||||||
| if hasattr(instance, "custom_default_url") and instance.custom_default_url != "": | ||||||
| URI = URI + "/" + instance.custom_default_url | ||||||
| logger.info("Modified URI by adding custom default url for the custom app: " + URI) | ||||||
|
||||||
| logger.info("Modified URI by adding custom default url for the custom app: " + URI) | |
| logger.info("Modified URI by adding custom default url for the custom app: %s", URI) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Generated by Django 5.1.1 on 2024-10-29 14:36 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("apps", "0017_alter_streamlitinstance_port"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="customappinstance", | ||
| name="custom_default_url", | ||
| field=models.CharField(blank=True, default="", max_length=255), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ def setUp(self): | |
| "port": 8000, | ||
| "image": "ghcr.io/scilifelabdatacentre/image:tag", | ||
| "tags": ["tag1", "tag2", "tag3"], | ||
| "custom_default_url": "valid-custom_default_url/", | ||
|
||
| } | ||
|
|
||
| def test_form_valid_data(self): | ||
|
|
||
|
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||
| import regex as re | ||||||||||||||||
| from django.core.exceptions import ValidationError | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class UrlAdditionalPathValidation: | ||||||||||||||||
| """ | ||||||||||||||||
| Validating a custom url path addition to the existing URL. | ||||||||||||||||
| The validation limits to that additional part only. | ||||||||||||||||
| For example, if the original URL is: https://xyz.serve-dev.scilifelab.se, | ||||||||||||||||
| and we want to add a path so that it becomes https://xyz.serve-dev.scilifelab.se/new-path, | ||||||||||||||||
| then it will only validate 'new-path' part. | ||||||||||||||||
| """ | ||||||||||||||||
|
|
||||||||||||||||
| __name = None | ||||||||||||||||
|
|
||||||||||||||||
| def __init__(self, name: str): | ||||||||||||||||
| self.__name = name | ||||||||||||||||
|
||||||||||||||||
| __name = None | |
| def __init__(self, name: str): | |
| self.__name = name | |
| def __init__(self, name: str): | |
| self._name = name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed it.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we are allowing unicode characters in the subpath?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. We had a discussion and agreed on keeping unicode characters.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,7 +79,6 @@ def get_unique_apps(queryset, app_ids_to_exclude): | |
| app_orms = (app_model for app_model in APP_REGISTRY.iter_orm_models() if issubclass(app_model, SocialMixin)) | ||
|
|
||
| for app_orm in app_orms: | ||
| logger.info("Processing: %s", app_orm) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to remove this? |
||
| filters = ~Q(app_status__status="Deleted") & Q(access="public") | ||
| if collection: | ||
| filters &= Q(collections__slug=collection) | ||
|
|
||
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| {% include "apps/partials/div_label_with_help_toggle.html" with help_message=field.help_text %} | ||
| {% load get_setting %} | ||
| <div> | ||
| <div class="input-group"> | ||
| {% if crispy_prepended_text %} | ||
| <span class="input-group-text" id="id_original_url_custom_default_url_form"></span> | ||
| {% endif %} | ||
| {{ field }} | ||
| </div> | ||
| {% if field.field.bottom_help_text %} | ||
| <small class="form-text text-muted">{{ field.field.bottom_help_text }}</small> | ||
| {% endif %} | ||
|
|
||
| {% if field.errors %} | ||
| {% for error in field.errors %} | ||
| <div class="client-validation-feedback client-validation-invalid"> | ||
| {{ error }} | ||
| </div> | ||
| {% endfor %} | ||
| {% endif %} | ||
| </div> | ||
|
|
||
| <script> | ||
|
|
||
| const subdomainInput = document.getElementById("id_subdomain"); | ||
| const customDefaultUrlFormModifiedUrlText = document.getElementById("id_original_url_custom_default_url_form"); | ||
| const customDefaultUrlFormModifiedHelpTextUrl = document.getElementById("id_custom_default_url_form_help_text"); | ||
|
|
||
| function updateUrlWithSubdomain() { | ||
| const subdomainValue = subdomainInput.value.trim(); | ||
| const displayValue = subdomainValue || "subdomain_name"; | ||
|
|
||
| customDefaultUrlFormModifiedUrlText.innerHTML = `https://${displayValue}.{% get_setting 'DOMAIN' %}/`; | ||
| customDefaultUrlFormModifiedHelpTextUrl.innerHTML = `https://${displayValue}.{% get_setting 'DOMAIN' %}/</b>`; | ||
| } | ||
|
|
||
| updateUrlWithSubdomain(); | ||
|
|
||
| subdomainInput.addEventListener("blur", updateUrlWithSubdomain); | ||
|
|
||
| </script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. Addressed it.