use flask flash for notifications
This commit is contained in:
parent
6509c5a249
commit
a2d6d59ca4
@ -5,13 +5,14 @@ from flask import current_app as app
|
|||||||
from jinja2.exceptions import TemplateNotFound
|
from jinja2.exceptions import TemplateNotFound
|
||||||
import pendulum
|
import pendulum
|
||||||
import os
|
import os
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from atst.domain.requests import Requests
|
from atst.domain.requests import Requests
|
||||||
from atst.domain.users import Users
|
from atst.domain.users import Users
|
||||||
from atst.domain.authnid import AuthenticationContext
|
from atst.domain.authnid import AuthenticationContext
|
||||||
from atst.domain.audit_log import AuditLog
|
from atst.domain.audit_log import AuditLog
|
||||||
from atst.domain.auth import logout as _logout
|
from atst.domain.auth import logout as _logout
|
||||||
from werkzeug.exceptions import NotFound
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("atst", __name__)
|
bp = Blueprint("atst", __name__)
|
||||||
@ -28,10 +29,9 @@ def root():
|
|||||||
redirect_url,
|
redirect_url,
|
||||||
"?{}".format(url.urlencode({"next": request.args.get("next")})),
|
"?{}".format(url.urlencode({"next": request.args.get("next")})),
|
||||||
)
|
)
|
||||||
|
flash("login_next")
|
||||||
|
|
||||||
return render_template(
|
return render_template("login.html", redirect_url=redirect_url)
|
||||||
"login.html", redirect=bool(request.args.get("next")), redirect_url=redirect_url
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/help")
|
@bp.route("/help")
|
||||||
|
@ -9,6 +9,7 @@ from atst.domain.invitations import (
|
|||||||
WrongUserError as InvitationWrongUserError,
|
WrongUserError as InvitationWrongUserError,
|
||||||
)
|
)
|
||||||
from atst.domain.workspaces import WorkspaceError
|
from atst.domain.workspaces import WorkspaceError
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
def log_error(e):
|
def log_error(e):
|
||||||
@ -39,7 +40,8 @@ def make_error_pages(app):
|
|||||||
# pylint: disable=unused-variable
|
# pylint: disable=unused-variable
|
||||||
def session_expired(e):
|
def session_expired(e):
|
||||||
log_error(e)
|
log_error(e)
|
||||||
url_args = {"sessionExpired": True, "next": request.path}
|
url_args = {"next": request.path}
|
||||||
|
flash("session_expired")
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
url_args[app.form_cache.PARAM_NAME] = app.form_cache.write(request.form)
|
url_args[app.form_cache.PARAM_NAME] = app.form_cache.write(request.form)
|
||||||
return redirect(url_for("atst.root", **url_args))
|
return redirect(url_for("atst.root", **url_args))
|
||||||
|
@ -13,6 +13,7 @@ from atst.domain.requests import Requests
|
|||||||
from atst.domain.exceptions import NotFoundError
|
from atst.domain.exceptions import NotFoundError
|
||||||
from atst.forms.ccpo_review import CCPOReviewForm
|
from atst.forms.ccpo_review import CCPOReviewForm
|
||||||
from atst.forms.internal_comment import InternalCommentForm
|
from atst.forms.internal_comment import InternalCommentForm
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
def map_ccpo_authorizing(user):
|
def map_ccpo_authorizing(user):
|
||||||
@ -63,6 +64,7 @@ def submit_approval(request_id):
|
|||||||
|
|
||||||
return redirect(url_for("requests.requests_index"))
|
return redirect(url_for("requests.requests_index"))
|
||||||
else:
|
else:
|
||||||
|
flash("form_errors")
|
||||||
return render_approval(request, form)
|
return render_approval(request, form)
|
||||||
|
|
||||||
|
|
||||||
@ -94,4 +96,5 @@ def create_internal_comment(request_id):
|
|||||||
url_for("requests.approval", request_id=request_id, _anchor="ccpo-notes")
|
url_for("requests.approval", request_id=request_id, _anchor="ccpo-notes")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
flash("form_errors")
|
||||||
return render_approval(request, internal_comment_form=form)
|
return render_approval(request, internal_comment_form=form)
|
||||||
|
@ -13,6 +13,7 @@ from atst.domain.requests.financial_verification import (
|
|||||||
)
|
)
|
||||||
from atst.models.attachment import Attachment
|
from atst.models.attachment import Attachment
|
||||||
from atst.domain.task_orders import TaskOrders
|
from atst.domain.task_orders import TaskOrders
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
def fv_extended(_http_request):
|
def fv_extended(_http_request):
|
||||||
@ -201,11 +202,13 @@ def financial_verification(request_id):
|
|||||||
g.current_user, request, is_extended=is_extended
|
g.current_user, request, is_extended=is_extended
|
||||||
).execute()
|
).execute()
|
||||||
|
|
||||||
|
if request.review_comment:
|
||||||
|
flash("request_review_comment", {"comment": request.review_comment})
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"requests/financial_verification.html",
|
"requests/financial_verification.html",
|
||||||
f=form,
|
f=form,
|
||||||
jedi_request=request,
|
jedi_request=request,
|
||||||
review_comment=request.review_comment,
|
|
||||||
extended=is_extended,
|
extended=is_extended,
|
||||||
saved_draft=saved_draft,
|
saved_draft=saved_draft,
|
||||||
)
|
)
|
||||||
@ -236,11 +239,8 @@ def update_financial_verification(request_id):
|
|||||||
|
|
||||||
if updated_request.task_order.verified:
|
if updated_request.task_order.verified:
|
||||||
workspace = Requests.auto_approve_and_create_workspace(updated_request)
|
workspace = Requests.auto_approve_and_create_workspace(updated_request)
|
||||||
return redirect(
|
flash("new_workspace")
|
||||||
url_for(
|
return redirect(url_for("workspaces.new_project", workspace_id=workspace.id))
|
||||||
"workspaces.new_project", workspace_id=workspace.id, newWorkspace=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return redirect(url_for("requests.requests_index", modal="pendingCCPOApproval"))
|
return redirect(url_for("requests.requests_index", modal="pendingCCPOApproval"))
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from . import requests_bp
|
|||||||
from atst.domain.requests import Requests
|
from atst.domain.requests import Requests
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
from atst.forms.data import SERVICE_BRANCHES
|
from atst.forms.data import SERVICE_BRANCHES
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
class RequestsIndex(object):
|
class RequestsIndex(object):
|
||||||
@ -99,4 +100,8 @@ class RequestsIndex(object):
|
|||||||
@requests_bp.route("/requests", methods=["GET"])
|
@requests_bp.route("/requests", methods=["GET"])
|
||||||
def requests_index():
|
def requests_index():
|
||||||
context = RequestsIndex(g.current_user).execute()
|
context = RequestsIndex(g.current_user).execute()
|
||||||
|
|
||||||
|
if context.get("num_action_required"):
|
||||||
|
flash("requests_action_required", {"count": context.get("num_action_required")})
|
||||||
|
|
||||||
return render_template("requests/index.html", **context)
|
return render_template("requests/index.html", **context)
|
||||||
|
@ -124,6 +124,10 @@ class JEDIRequestFlow(object):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_review_screen(self):
|
||||||
|
return self.screens[-1] == self.current_screen
|
||||||
|
|
||||||
def create_or_update_request(self):
|
def create_or_update_request(self):
|
||||||
request_data = self.map_request_data(self.form_section, self.form.data)
|
request_data = self.map_request_data(self.form_section, self.form.data)
|
||||||
if self.request_id:
|
if self.request_id:
|
||||||
|
@ -13,6 +13,7 @@ from atst.forms.data import (
|
|||||||
FUNDING_TYPES,
|
FUNDING_TYPES,
|
||||||
TASK_ORDER_SOURCES,
|
TASK_ORDER_SOURCES,
|
||||||
)
|
)
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
@requests_bp.context_processor
|
@requests_bp.context_processor
|
||||||
@ -31,6 +32,9 @@ def option_data():
|
|||||||
def requests_form_new(screen):
|
def requests_form_new(screen):
|
||||||
jedi_flow = JEDIRequestFlow(screen, request=None, current_user=g.current_user)
|
jedi_flow = JEDIRequestFlow(screen, request=None, current_user=g.current_user)
|
||||||
|
|
||||||
|
if jedi_flow.is_review_screen and not jedi_flow.can_submit:
|
||||||
|
flash("request_incomplete")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"requests/screen-%d.html" % int(screen),
|
"requests/screen-%d.html" % int(screen),
|
||||||
f=jedi_flow.form,
|
f=jedi_flow.form,
|
||||||
@ -54,6 +58,12 @@ def requests_form_update(screen=1, request_id=None):
|
|||||||
screen, request=request, request_id=request_id, current_user=g.current_user
|
screen, request=request, request_id=request_id, current_user=g.current_user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if jedi_flow.is_review_screen and not jedi_flow.can_submit:
|
||||||
|
flash("request_incomplete")
|
||||||
|
|
||||||
|
if request.review_comment:
|
||||||
|
flash("request_review_comment", {"comment": request.review_comment})
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"requests/screen-%d.html" % int(screen),
|
"requests/screen-%d.html" % int(screen),
|
||||||
f=jedi_flow.form,
|
f=jedi_flow.form,
|
||||||
@ -63,7 +73,6 @@ def requests_form_update(screen=1, request_id=None):
|
|||||||
next_screen=screen + 1,
|
next_screen=screen + 1,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
jedi_request=jedi_flow.request,
|
jedi_request=jedi_flow.request,
|
||||||
review_comment=request.review_comment,
|
|
||||||
can_submit=jedi_flow.can_submit,
|
can_submit=jedi_flow.can_submit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -103,6 +112,7 @@ def requests_update(screen=1, request_id=None):
|
|||||||
where = "/requests"
|
where = "/requests"
|
||||||
return redirect(where)
|
return redirect(where)
|
||||||
else:
|
else:
|
||||||
|
flash("form_errors")
|
||||||
rerender_args = dict(
|
rerender_args = dict(
|
||||||
f=jedi_flow.form,
|
f=jedi_flow.form,
|
||||||
data=post_data,
|
data=post_data,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from flask import Blueprint, render_template, g, request as http_request, redirect
|
from flask import Blueprint, render_template, g, request as http_request, redirect
|
||||||
from atst.forms.edit_user import EditUserForm
|
from atst.forms.edit_user import EditUserForm
|
||||||
from atst.domain.users import Users
|
from atst.domain.users import Users
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("users", __name__)
|
bp = Blueprint("users", __name__)
|
||||||
@ -10,9 +11,12 @@ bp = Blueprint("users", __name__)
|
|||||||
def user():
|
def user():
|
||||||
user = g.current_user
|
user = g.current_user
|
||||||
form = EditUserForm(data=user.to_dictionary())
|
form = EditUserForm(data=user.to_dictionary())
|
||||||
return render_template(
|
next_ = http_request.args.get("next")
|
||||||
"user/edit.html", next=http_request.args.get("next"), form=form, user=user
|
|
||||||
)
|
if next_:
|
||||||
|
flash("user_must_complete_profile")
|
||||||
|
|
||||||
|
return render_template("user/edit.html", next=next_, form=form, user=user)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/user", methods=["POST"])
|
@bp.route("/user", methods=["POST"])
|
||||||
@ -20,11 +24,12 @@ def update_user():
|
|||||||
user = g.current_user
|
user = g.current_user
|
||||||
form = EditUserForm(http_request.form)
|
form = EditUserForm(http_request.form)
|
||||||
next_url = http_request.args.get("next")
|
next_url = http_request.args.get("next")
|
||||||
rerender_args = {"form": form, "user": user, "next": next_url}
|
|
||||||
if form.validate():
|
if form.validate():
|
||||||
Users.update(user, form.data)
|
Users.update(user, form.data)
|
||||||
rerender_args["updated"] = True
|
flash("user_updated")
|
||||||
if next_url:
|
if next_url:
|
||||||
return redirect(next_url)
|
return redirect(next_url)
|
||||||
|
else:
|
||||||
|
flash("form_errors")
|
||||||
|
|
||||||
return render_template("user/edit.html", **rerender_args)
|
return render_template("user/edit.html", form=form, user=user, next=next_url)
|
||||||
|
@ -8,6 +8,7 @@ from atst.domain.workspaces import Workspaces
|
|||||||
from atst.forms.workspace import WorkspaceForm
|
from atst.forms.workspace import WorkspaceForm
|
||||||
from atst.domain.authz import Authorization
|
from atst.domain.authz import Authorization
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
@workspaces_bp.route("/workspaces")
|
@workspaces_bp.route("/workspaces")
|
||||||
@ -33,6 +34,7 @@ def edit_workspace(workspace_id):
|
|||||||
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
|
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
flash("form_errors")
|
||||||
return render_template("workspaces/edit.html", form=form, workspace=workspace)
|
return render_template("workspaces/edit.html", form=form, workspace=workspace)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from . import workspaces_bp
|
|||||||
from atst.domain.workspaces import Workspaces
|
from atst.domain.workspaces import Workspaces
|
||||||
from atst.domain.invitations import Invitations
|
from atst.domain.invitations import Invitations
|
||||||
from atst.queue import queue
|
from atst.queue import queue
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
def send_invite_email(owner_name, token, new_member_email):
|
def send_invite_email(owner_name, token, new_member_email):
|
||||||
@ -40,10 +41,5 @@ def revoke_invitation(workspace_id, token):
|
|||||||
def resend_invitation(workspace_id, token):
|
def resend_invitation(workspace_id, token):
|
||||||
invite = Invitations.resend(g.current_user, workspace_id, token)
|
invite = Invitations.resend(g.current_user, workspace_id, token)
|
||||||
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
||||||
return redirect(
|
flash("resent_workspace_invitation", {"user_name": invite.user_name})
|
||||||
url_for(
|
return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id))
|
||||||
"workspaces.workspace_members",
|
|
||||||
workspace_id=workspace_id,
|
|
||||||
resentInvitationTo=invite.user_name,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
@ -21,12 +21,13 @@ from atst.domain.authz import Authorization
|
|||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
from atst.domain.invitations import Invitations
|
from atst.domain.invitations import Invitations
|
||||||
|
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
@workspaces_bp.route("/workspaces/<workspace_id>/members")
|
@workspaces_bp.route("/workspaces/<workspace_id>/members")
|
||||||
def workspace_members(workspace_id):
|
def workspace_members(workspace_id):
|
||||||
workspace = Workspaces.get_with_members(g.current_user, workspace_id)
|
workspace = Workspaces.get_with_members(g.current_user, workspace_id)
|
||||||
new_member_name = http_request.args.get("newMemberName")
|
new_member_name = http_request.args.get("newMemberName")
|
||||||
resent_invitation_to = http_request.args.get("resentInvitationTo")
|
|
||||||
new_member = next(
|
new_member = next(
|
||||||
filter(lambda m: m.user_name == new_member_name, workspace.members), None
|
filter(lambda m: m.user_name == new_member_name, workspace.members), None
|
||||||
)
|
)
|
||||||
@ -51,7 +52,6 @@ def workspace_members(workspace_id):
|
|||||||
status_choices=MEMBER_STATUS_CHOICES,
|
status_choices=MEMBER_STATUS_CHOICES,
|
||||||
members=members_list,
|
members=members_list,
|
||||||
new_member=new_member,
|
new_member=new_member,
|
||||||
resent_invitation_to=resent_invitation_to,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -76,12 +76,13 @@ def create_member(workspace_id):
|
|||||||
invite = Invitations.create(user, new_member, form.data["email"])
|
invite = Invitations.create(user, new_member, form.data["email"])
|
||||||
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
||||||
|
|
||||||
|
flash(
|
||||||
|
"new_workspace_member",
|
||||||
|
{"new_member": new_member, "workspace": workspace},
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for("workspaces.workspace_members", workspace_id=workspace.id)
|
||||||
"workspaces.workspace_members",
|
|
||||||
workspace_id=workspace.id,
|
|
||||||
newMemberName=new_member.user_name,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except AlreadyExistsError:
|
except AlreadyExistsError:
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -107,6 +108,10 @@ def view_member(workspace_id, member_id):
|
|||||||
form = EditMemberForm(workspace_role=member.role_name)
|
form = EditMemberForm(workspace_role=member.role_name)
|
||||||
editable = g.current_user == member.user
|
editable = g.current_user == member.user
|
||||||
can_revoke_access = Workspaces.can_revoke_access_for(workspace, member)
|
can_revoke_access = Workspaces.can_revoke_access_for(workspace, member)
|
||||||
|
|
||||||
|
if member.has_dod_id_error:
|
||||||
|
flash("workspace_member_dod_id_error")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"workspaces/members/edit.html",
|
"workspaces/members/edit.html",
|
||||||
workspace=workspace,
|
workspace=workspace,
|
||||||
@ -155,13 +160,13 @@ def update_member(workspace_id, member_id):
|
|||||||
g.current_user, workspace, member, ids_and_roles
|
g.current_user, workspace, member, ids_and_roles
|
||||||
)
|
)
|
||||||
|
|
||||||
|
flash(
|
||||||
|
"workspace_role_updated",
|
||||||
|
{"member_name": member.user_name, "updated_role": new_role_name},
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for("workspaces.workspace_members", workspace_id=workspace.id)
|
||||||
"workspaces.workspace_members",
|
|
||||||
workspace_id=workspace.id,
|
|
||||||
memberName=member.user_name,
|
|
||||||
updatedRole=new_role_name,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -177,10 +182,5 @@ def update_member(workspace_id, member_id):
|
|||||||
)
|
)
|
||||||
def revoke_access(workspace_id, member_id):
|
def revoke_access(workspace_id, member_id):
|
||||||
revoked_role = Workspaces.revoke_access(g.current_user, workspace_id, member_id)
|
revoked_role = Workspaces.revoke_access(g.current_user, workspace_id, member_id)
|
||||||
return redirect(
|
flash("revoked_workspace_access", {"member_name": revoked_role.user.full_name})
|
||||||
url_for(
|
return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id))
|
||||||
"workspaces.workspace_members",
|
|
||||||
workspace_id=workspace_id,
|
|
||||||
revokedMemberName=revoked_role.user_name,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
107
atst/utils/flash.py
Normal file
107
atst/utils/flash.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
from flask import flash, render_template_string
|
||||||
|
|
||||||
|
MESSAGES = {
|
||||||
|
"new_workspace_member": {
|
||||||
|
"title_template": "Member added successfully",
|
||||||
|
"message_template": """
|
||||||
|
<p>{{ new_member.user_name }} was successfully invited via email to this workspace. They do not yet have access to any environments.</p>
|
||||||
|
<p><a href="{{ url_for('workspaces.update_member', workspace_id=workspace.id, member_id=new_member.user_id) }}">Add environment access.</a></p>
|
||||||
|
""",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
|
"revoked_workspace_access": {
|
||||||
|
"title_template": "Removed workspace access",
|
||||||
|
"message_template": """
|
||||||
|
<p>Removed {{ member_name }} from this workspace.</p>
|
||||||
|
""",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
|
"resent_workspace_invitation": {
|
||||||
|
"title_template": "Invitation resent",
|
||||||
|
"message_template": """
|
||||||
|
<p>Successfully sent a new invitation to {{ user_name }}.</p>
|
||||||
|
""",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
|
"workspace_role_updated": {
|
||||||
|
"title_template": "Workspace role updated successfully",
|
||||||
|
"message_template": """
|
||||||
|
<p>{{ member_name }}'s role was successfully updated to {{ updated_role }}</p>
|
||||||
|
""",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
|
"session_expired": {
|
||||||
|
"title_template": "Session Expired",
|
||||||
|
"message_template": """
|
||||||
|
Your session expired due to inactivity. Please log in again to continue.
|
||||||
|
""",
|
||||||
|
"category": "error",
|
||||||
|
},
|
||||||
|
"login_next": {
|
||||||
|
"title_template": "Log in Required.",
|
||||||
|
"message_template": """
|
||||||
|
After you log in, you will be redirected to your destination page.
|
||||||
|
""",
|
||||||
|
"category": "warning",
|
||||||
|
},
|
||||||
|
"new_workspace": {
|
||||||
|
"title_template": "Workspace created!",
|
||||||
|
"message_template": """
|
||||||
|
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
|
||||||
|
""",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
|
"workspace_member_dod_id_error": {
|
||||||
|
"title_template": "CAC ID Error",
|
||||||
|
"message_template": """
|
||||||
|
The member attempted to accept this invite, but their CAC ID did not match the CAC ID you specified on the invite. Please confirm that the DOD ID is accurate.
|
||||||
|
""",
|
||||||
|
"category": "error",
|
||||||
|
},
|
||||||
|
"form_errors": {
|
||||||
|
"title_template": "There were some errors",
|
||||||
|
"message_template": "<p>Please see below.</p>",
|
||||||
|
"category": "error",
|
||||||
|
},
|
||||||
|
"user_must_complete_profile": {
|
||||||
|
"title_template": "You must complete your profile",
|
||||||
|
"message_template": "<p>Before continuing, you must complete your profile</p>",
|
||||||
|
"category": "info",
|
||||||
|
},
|
||||||
|
"user_updated": {
|
||||||
|
"title_template": "User information updated.",
|
||||||
|
"message_template": "",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
|
"request_incomplete": {
|
||||||
|
"title_template": "Please complete all sections",
|
||||||
|
"message_template": """
|
||||||
|
<p>In order to submit your JEDI Cloud request, you'll need to complete all required sections of this form without error. Missing or invalid fields are noted below.</p>
|
||||||
|
""",
|
||||||
|
"category": "error",
|
||||||
|
},
|
||||||
|
"requests_action_required": {
|
||||||
|
"title_template": "Action required on {{ count }} requests.",
|
||||||
|
"message_template": "",
|
||||||
|
"category": "info",
|
||||||
|
},
|
||||||
|
"request_review_comment": {
|
||||||
|
"title_template": "Changes Requested",
|
||||||
|
"message_template": """
|
||||||
|
<p>CCPO has requested changes to your submission with the following notes:
|
||||||
|
<br>
|
||||||
|
{{ comment }}
|
||||||
|
<br>
|
||||||
|
Please contact info@jedi.cloud or 123-123-4567 for further discussion.</p>
|
||||||
|
""",
|
||||||
|
"category": "warning",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def formatted_flash(message_name, message_args=None):
|
||||||
|
config = MESSAGES[message_name]
|
||||||
|
args = message_args or {}
|
||||||
|
title = render_template_string(config["title_template"], **args)
|
||||||
|
message = render_template_string(config["message_template"], **args)
|
||||||
|
flash({"title": title, "message": message}, config["category"])
|
@ -1,5 +1,4 @@
|
|||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
|
|
||||||
{% set title_text = 'Edit {} project'.format(project.name) if project else 'Add a new project' %}
|
{% set title_text = 'Edit {} project'.format(project.name) if project else 'Add a new project' %}
|
||||||
|
|
||||||
|
9
templates/fragments/flash.html
Normal file
9
templates/fragments/flash.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% from "components/alert.html" import Alert %}
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message_config in messages %}
|
||||||
|
{{ Alert(message_config["title"], message=message_config.get("message"), level=category) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
@ -1,6 +1,5 @@
|
|||||||
{% extends "base_public.html" %}
|
{% extends "base_public.html" %}
|
||||||
{% from "components/sidenav_item.html" import SidenavItem %}
|
{% from "components/sidenav_item.html" import SidenavItem %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
|
|
||||||
{% block title %}Help | JEDI Cloud{% endblock %}
|
{% block title %}Help | JEDI Cloud{% endblock %}
|
||||||
|
|
||||||
|
@ -11,29 +11,14 @@
|
|||||||
<div class='col'>
|
<div class='col'>
|
||||||
|
|
||||||
<div class='login-banner'>
|
<div class='login-banner'>
|
||||||
{% if request.args.get("sessionExpired") %}
|
|
||||||
{{ Alert('Session Expired',
|
|
||||||
message='Your session expired due to inactivity. Please log in again to continue.',
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
<h1 class="login-banner__heading">Access the JEDI Cloud</h1>
|
<h1 class="login-banner__heading">Access the JEDI Cloud</h1>
|
||||||
|
|
||||||
<img class="login-banner__logo" src="{{url_for('static', filename='img/ccpo-logo.svg')}}" alt="Cloud Computing Program Office Logo">
|
<img class="login-banner__logo" src="{{url_for('static', filename='img/ccpo-logo.svg')}}" alt="Cloud Computing Program Office Logo">
|
||||||
|
|
||||||
<a class="usa-button usa-button-big login-banner__button" href='{{ redirect_url }}'><span>Sign in with CAC</span></a>
|
<a class="usa-button usa-button-big login-banner__button" href='{{ redirect_url }}'><span>Sign in with CAC</span></a>
|
||||||
|
|
||||||
{% if False %}
|
|
||||||
<a class="usa-button usa-button-big usa-button-secondary" href='{{ url_for("dev.login_dev", **request.args) }}'><span>DEV Login</span></a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if redirect %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('Log in Required.',
|
|
||||||
message='After you log in, you will be redirected to your destination page.',
|
|
||||||
level='warning'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{ Alert('Certificate Selection',
|
{{ Alert('Certificate Selection',
|
||||||
message='When you are prompted to select a certificate, please select <strong>E-mail Certificate</strong> from the provided choices.',
|
message='When you are prompted to select a certificate, please select <strong>E-mail Certificate</strong> from the provided choices.',
|
||||||
|
@ -6,9 +6,7 @@
|
|||||||
|
|
||||||
{% include 'requests/menu.html' %}
|
{% include 'requests/menu.html' %}
|
||||||
|
|
||||||
{% if review_comment %}
|
{% include "fragments/flash.html" %}
|
||||||
{% include 'requests/comment.html' %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block form_action %}
|
{% block form_action %}
|
||||||
{% if request_id %}
|
{% if request_id %}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/phone_input.html" import PhoneInput %}
|
{% from "components/phone_input.html" import PhoneInput %}
|
||||||
|
|
||||||
@ -9,12 +8,7 @@
|
|||||||
|
|
||||||
<article class='col col--grow request-approval'>
|
<article class='col col--grow request-approval'>
|
||||||
|
|
||||||
{% if review_form.errors or internal_comment_form.errors %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('There were some errors',
|
|
||||||
message="<p>Please see below.</p>",
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<section class='panel'>
|
<section class='panel'>
|
||||||
<header class='panel__heading panel__heading--divider request-approval__heading'>
|
<header class='panel__heading panel__heading--divider request-approval__heading'>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
|
|
||||||
{% call Alert('Changes Requested', level='warning') %}
|
|
||||||
<p>CCPO has requested changes to your submission with the following notes:
|
|
||||||
<br>
|
|
||||||
{{ review_comment }}
|
|
||||||
<br>
|
|
||||||
Please contact info@jedi.cloud or 123-123-4567 for further discussion.</p>
|
|
||||||
{% endcall %}
|
|
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
{% include 'requests/review_menu.html' %}
|
{% include 'requests/review_menu.html' %}
|
||||||
|
|
||||||
|
{% include "fragments/flash.html" %}
|
||||||
|
|
||||||
{% if saved_draft %}
|
{% if saved_draft %}
|
||||||
{% call Alert('Draft saved', level='success') %}
|
{% call Alert('Draft saved', level='success') %}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
@ -19,10 +21,6 @@
|
|||||||
{{ Alert('Pending Financial Verification', fragment="fragments/pending_financial_verification.html") }}
|
{{ Alert('Pending Financial Verification', fragment="fragments/pending_financial_verification.html") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if review_comment %}
|
|
||||||
{% include 'requests/comment.html' %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<financial inline-template v-bind:initial-data='{{ f.data|mixedContentToJson }}'>
|
<financial inline-template v-bind:initial-data='{{ f.data|mixedContentToJson }}'>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% if extended %}
|
{% if extended %}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/modal.html" import Modal %}
|
{% from "components/modal.html" import Modal %}
|
||||||
{% from "components/empty_state.html" import EmptyState %}
|
{% from "components/empty_state.html" import EmptyState %}
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
@ -46,13 +45,7 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
{% if num_action_required %}
|
{% include "fragments/flash.html" %}
|
||||||
{% set title -%}
|
|
||||||
Action required on {{ num_action_required }} requests.
|
|
||||||
{%- endset %}
|
|
||||||
|
|
||||||
{{ Alert (title)}}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not requests %}
|
{% if not requests %}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% extends 'requests/_new.html' %}
|
{% extends 'requests/_new.html' %}
|
||||||
|
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/options_input.html" import OptionsInput %}
|
{% from "components/options_input.html" import OptionsInput %}
|
||||||
{% from "components/date_input.html" import DateInput %}
|
{% from "components/date_input.html" import DateInput %}
|
||||||
@ -11,12 +10,7 @@
|
|||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
|
|
||||||
{% if f.errors %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('There were some errors',
|
|
||||||
message="<p>Please see below.</p>",
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<details-of-use inline-template v-bind:initial-data='{{ f.data|tojson }}'>
|
<details-of-use inline-template v-bind:initial-data='{{ f.data|tojson }}'>
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% extends 'requests/_new.html' %}
|
{% extends 'requests/_new.html' %}
|
||||||
|
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/options_input.html" import OptionsInput %}
|
{% from "components/options_input.html" import OptionsInput %}
|
||||||
{% from "components/date_input.html" import DateInput %}
|
{% from "components/date_input.html" import DateInput %}
|
||||||
@ -12,12 +11,7 @@
|
|||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
|
|
||||||
{% if f.errors %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('There were some errors',
|
|
||||||
message="<p>Please see below.</p>",
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>Please tell us more about you.</p>
|
<p>Please tell us more about you.</p>
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% extends 'requests/_new.html' %}
|
{% extends 'requests/_new.html' %}
|
||||||
|
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/checkbox_input.html" import CheckboxInput %}
|
{% from "components/checkbox_input.html" import CheckboxInput %}
|
||||||
|
|
||||||
@ -10,12 +9,7 @@
|
|||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
|
|
||||||
{% if f.errors %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('There were some errors',
|
|
||||||
message="<p>Please see below.</p>",
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<poc inline-template v-bind:initial-data='{{ f.data|tojson }}'>
|
<poc inline-template v-bind:initial-data='{{ f.data|tojson }}'>
|
||||||
<div>
|
<div>
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
{% extends 'requests/_new.html' %}
|
{% extends 'requests/_new.html' %}
|
||||||
|
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
@ -21,12 +20,7 @@
|
|||||||
|
|
||||||
<p>Before you can submit your request, please take a moment to review the information entered in the form. You may make changes by clicking the edit link on each section. When all information looks right, go ahead and submit.</p>
|
<p>Before you can submit your request, please take a moment to review the information entered in the form. You may make changes by clicking the edit link on each section. When all information looks right, go ahead and submit.</p>
|
||||||
|
|
||||||
{% if f.errors or not can_submit %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('Please complete all sections',
|
|
||||||
message="<p>In order to submit your JEDI Cloud request, you'll need to complete all required sections of this form without error. Missing or invalid fields are noted below.</p>",
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% with editable=True %}
|
{% with editable=True %}
|
||||||
{% include "requests/_review.html" %}
|
{% include "requests/_review.html" %}
|
||||||
|
@ -1,26 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class='col'>
|
<div class='col'>
|
||||||
|
|
||||||
{% if next is not none %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('You must complete your profile',
|
|
||||||
message='<p>Before continuing, you must complete your profile</p>',
|
|
||||||
level='info'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.errors %}
|
|
||||||
{{ Alert('There were some errors',
|
|
||||||
message="<p>Please see below.</p>",
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if updated %}
|
|
||||||
{{ Alert('User information updated.', level='success') }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class='panel'>
|
<div class='panel'>
|
||||||
<div class='panel__heading'>
|
<div class='panel__heading'>
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
{% extends "workspaces/base.html" %}
|
{% extends "workspaces/base.html" %}
|
||||||
|
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
|
||||||
|
|
||||||
{% block workspace_content %}
|
{% block workspace_content %}
|
||||||
|
|
||||||
{% if form.errors %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('There were some errors',
|
|
||||||
message="<p>Please see below.</p>",
|
|
||||||
level='error'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form method="POST" action="{{ url_for('workspaces.edit_workspace', workspace_id=workspace.id) }}" autocomplete="false">
|
<form method="POST" action="{{ url_for('workspaces.edit_workspace', workspace_id=workspace.id) }}" autocomplete="false">
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
|
@ -4,14 +4,11 @@
|
|||||||
{% from "components/modal.html" import Modal %}
|
{% from "components/modal.html" import Modal %}
|
||||||
{% from "components/selector.html" import Selector %}
|
{% from "components/selector.html" import Selector %}
|
||||||
{% from "components/options_input.html" import OptionsInput %}
|
{% from "components/options_input.html" import OptionsInput %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/confirmation_button.html" import ConfirmationButton %}
|
{% from "components/confirmation_button.html" import ConfirmationButton %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if member.has_dod_id_error %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('CAC ID Error', message='The member attempted to accept this invite, but their CAC ID did not match the CAC ID you specified on the invite. Please confirm that the DOD ID is accurate.', level='error') }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form method="POST" action="{{ url_for('workspaces.update_member', workspace_id=workspace.id, member_id=member.user_id) }}" autocomplete="false">
|
<form method="POST" action="{{ url_for('workspaces.update_member', workspace_id=workspace.id, member_id=member.user_id) }}" autocomplete="false">
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{% extends "workspaces/base.html" %}
|
{% extends "workspaces/base.html" %}
|
||||||
|
|
||||||
{% from "components/empty_state.html" import EmptyState %}
|
{% from "components/empty_state.html" import EmptyState %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
{% block workspace_content %}
|
{% block workspace_content %}
|
||||||
@ -21,53 +20,7 @@
|
|||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% if new_member %}
|
{% include "fragments/flash.html" %}
|
||||||
{% set message -%}
|
|
||||||
<p>{{ new_member.user_name }} was successfully invited via email to this workspace. They do not yet have access to any environments.</p>
|
|
||||||
<p><a href="{{ url_for('workspaces.update_member', workspace_id=workspace.id, member_id=new_member.user_id) }}">Add environment access.</a></p>
|
|
||||||
{%- endset %}
|
|
||||||
|
|
||||||
{{ Alert('Member added successfully',
|
|
||||||
message=message,
|
|
||||||
level='success'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if resent_invitation_to %}
|
|
||||||
{% set message -%}
|
|
||||||
<p>Successfully sent a new invitation to {{ resent_invitation_to }}.</p>
|
|
||||||
{%- endset %}
|
|
||||||
|
|
||||||
{{ Alert('Invitation resent',
|
|
||||||
message=message,
|
|
||||||
level='success'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if revoked_member_name %}
|
|
||||||
{% set message -%}
|
|
||||||
<p>Removed {{ revoked_member_name }} from this workspace.</p>
|
|
||||||
{%- endset %}
|
|
||||||
|
|
||||||
{{ Alert('Removed workspace access',
|
|
||||||
message=message,
|
|
||||||
level='success'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set member_name = request.args.get("memberName") %}
|
|
||||||
{% set updated_role = request.args.get("updatedRole") %}
|
|
||||||
{% if updated_role %}
|
|
||||||
{% set message -%}
|
|
||||||
<p>{{ member_name }}'s role was successfully updated to {{ updated_role }}</p>
|
|
||||||
{%- endset %}
|
|
||||||
|
|
||||||
{{ Alert('Workspace role updated successfully',
|
|
||||||
message=message,
|
|
||||||
level='success'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
<members-list
|
<members-list
|
||||||
inline-template
|
inline-template
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/empty_state.html" import EmptyState %}
|
{% from "components/empty_state.html" import EmptyState %}
|
||||||
|
|
||||||
{% extends "workspaces/base.html" %}
|
{% extends "workspaces/base.html" %}
|
||||||
|
@ -8,14 +8,7 @@
|
|||||||
{% block workspace_content %}
|
{% block workspace_content %}
|
||||||
|
|
||||||
{% set modalName = "newProjectConfirmation" %}
|
{% set modalName = "newProjectConfirmation" %}
|
||||||
{% if request.args.get("newWorkspace") %}
|
{% include "fragments/flash.html" %}
|
||||||
{{ Alert('Workspace created!',
|
|
||||||
message="\
|
|
||||||
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
|
|
||||||
",
|
|
||||||
level='success'
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<new-project inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
|
<new-project inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
|
||||||
<form method="POST" action="{{ url_for('workspaces.create_project', workspace_id=workspace.id) }}" v-on:submit="handleSubmit">
|
<form method="POST" action="{{ url_for('workspaces.create_project', workspace_id=workspace.id) }}" v-on:submit="handleSubmit">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user