Add custom error and not found pages, and implement SAML authentication configuration
- Created a new error page (error.twig) with a user-friendly design for displaying error messages. - Created a new not found page (not-found.twig) to handle 404 errors with appropriate messaging and actions. - Added a SAML authentication configuration file (settings-custom.php) to support group-based admin assignment and user provisioning.
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
{#
|
||||
/**
|
||||
* OTS Signs — Layout Designer Page (with embed mode support)
|
||||
* OTS Signs — Layout Designer Page
|
||||
*
|
||||
* Overrides the Xibo core layout-designer-page.twig to add an embeddable
|
||||
* layout editor mode for use inside an iframe in external applications.
|
||||
* Overrides the Xibo core layout-designer-page.twig to add two tailored
|
||||
* editor experiences alongside the standard full-page mode.
|
||||
*
|
||||
* Usage:
|
||||
* Normal: /layout/designer/{layoutId}
|
||||
* Embed: /layout/designer/{layoutId}?embed=1
|
||||
* Normal: /layout/designer/{layoutId}
|
||||
* Deep-link: /layout/designer/{layoutId}?deeplink=1[&returnUrl=/app/layouts/123]
|
||||
* Embed: /layout/designer/{layoutId}?embed=1
|
||||
*
|
||||
* In embed mode:
|
||||
* - All CMS navigation is hidden (sidebar, topbar, help pane)
|
||||
* - Back/Exit buttons in the editor toolbar are hidden
|
||||
* Deep-link mode (?deeplink=1):
|
||||
* - Designed for direct navigation from an external React app.
|
||||
* - All Xibo chrome is stripped (sidebar, topbar, help pane, back buttons).
|
||||
* - A minimal OTS branded back bar is rendered at the top of the viewport.
|
||||
* - Clicking "Back" navigates to ?returnUrl= (relative paths only) or
|
||||
* falls back to window.history.back() if the param is absent/invalid.
|
||||
* - The editor options dropdown (Publish, Checkout, Discard…) stays visible.
|
||||
*
|
||||
* Embed mode (?embed=1):
|
||||
* - All CMS navigation is hidden (sidebar, topbar, help pane).
|
||||
* - Back/Exit buttons in the editor toolbar are hidden.
|
||||
* - postMessage events are sent to the parent window:
|
||||
* xibo:editor:ready, xibo:editor:save, xibo:editor:publish, xibo:editor:exit
|
||||
* - Parent can send: xibo:editor:requestSave
|
||||
@@ -33,7 +42,7 @@
|
||||
{% block headContent %}
|
||||
{{ parent() }}
|
||||
|
||||
{# Embed mode: early body class to prevent FOUC #}
|
||||
{# Early mode detection — prevents FOUC for both embed and deep-link modes #}
|
||||
<script nonce="{{ cspNonce }}">
|
||||
(function(){
|
||||
try {
|
||||
@@ -42,6 +51,9 @@
|
||||
if (isEmbed) {
|
||||
document.documentElement.classList.add('ots-embed-mode');
|
||||
}
|
||||
if (params.get('deeplink') === '1') {
|
||||
document.documentElement.classList.add('ots-deeplink-mode');
|
||||
}
|
||||
} catch(e) {
|
||||
// Cross-origin iframe detection may throw — treat as embed
|
||||
document.documentElement.classList.add('ots-embed-mode');
|
||||
@@ -101,11 +113,130 @@
|
||||
.ots-embed-mode .editor-top-bar #layoutJumpListContainer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* ── Deep-link mode styles ──────────────────────────────── */
|
||||
|
||||
/* Hide Back/Exit button area */
|
||||
.ots-deeplink-mode .back-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide the help pane */
|
||||
.ots-deeplink-mode #help-pane {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide floating page actions (notification bell, user menu) */
|
||||
.ots-deeplink-mode .ots-page-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Remove content wrapper padding/margins for full-bleed editor */
|
||||
.ots-deeplink-mode #content-wrapper {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ots-deeplink-mode .page-content {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ots-deeplink-mode .page-content > .row {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ots-deeplink-mode .page-content > .row > .col-sm-12 {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Full viewport height for the editor */
|
||||
.ots-deeplink-mode body,
|
||||
.ots-deeplink-mode #layout-editor {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Push body content down to clear the fixed OTS back bar (44px) */
|
||||
.ots-deeplink-mode body {
|
||||
padding-top: 44px !important;
|
||||
}
|
||||
|
||||
/* ── OTS branded back bar ──────────────────────────────── */
|
||||
|
||||
/* Hidden by default; revealed only in deep-link mode */
|
||||
#ots-deeplink-backbar {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 44px;
|
||||
background: #0f172a;
|
||||
border-bottom: 2px solid #e87800;
|
||||
z-index: 1300;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ots-deeplink-mode #ots-deeplink-backbar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#ots-deeplink-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #e87800;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
line-height: 1;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
#ots-deeplink-back:hover {
|
||||
background: rgba(232, 120, 0, 0.12);
|
||||
}
|
||||
|
||||
#ots-deeplink-back:focus-visible {
|
||||
outline: 2px solid #e87800;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
#ots-deeplink-back svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ots-deeplink-wordmark {
|
||||
margin-left: auto;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
{# Deep-link mode: branded back bar (position:fixed — shown only when ots-deeplink-mode is active) #}
|
||||
<div id="ots-deeplink-backbar" role="navigation" aria-label="{{ "Back navigation"|trans }}">
|
||||
<button id="ots-deeplink-back" type="button">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M10 12L6 8l4-4" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ "Back"|trans }}
|
||||
</button>
|
||||
<span class="ots-deeplink-wordmark">OTS Signs</span>
|
||||
</div>
|
||||
|
||||
<!-- Editor structure -->
|
||||
<div id="layout-editor" data-published-layout-id="{{ publishedLayoutId }}" data-layout-id="{{ layout.layoutId }}" data-layout-help={{ help }}></div>
|
||||
|
||||
@@ -697,4 +828,45 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# ── Deep-link mode: returnUrl back navigation ────────────── #}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
if (params.get('deeplink') !== '1') return;
|
||||
|
||||
// Mirror the html-level class onto body (needed for body-targeted CSS rules).
|
||||
document.body.classList.add('ots-deeplink-mode');
|
||||
|
||||
/**
|
||||
* Allow only safe same-origin relative paths.
|
||||
* Accepts: /app/layouts, /layouts/123?foo=bar
|
||||
* Rejects: //evil.com, https://evil.com, javascript:void(0), empty string
|
||||
*/
|
||||
function validateReturnUrl(url) {
|
||||
if (!url || typeof url !== 'string') return false;
|
||||
// Must start with '/' but not '//' (protocol-relative URL)
|
||||
if (url.charAt(0) !== '/' || url.charAt(1) === '/') return false;
|
||||
// Reject anything containing a scheme (e.g. https://)
|
||||
if (url.indexOf('://') !== -1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var rawReturn = params.get('returnUrl');
|
||||
var safeReturn = validateReturnUrl(rawReturn) ? rawReturn : null;
|
||||
|
||||
var backBtn = document.getElementById('ots-deeplink-back');
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function() {
|
||||
if (safeReturn) {
|
||||
window.location.href = safeReturn;
|
||||
} else {
|
||||
window.history.back();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user