Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality. - Created task management page with task listing, filtering, and AJAX data loading. - Developed transition management page with a data table for transitions. - Added user management page with comprehensive user details, filtering options, and AJAX support. - Introduced user group management page with filtering and data table for user groups. - Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
This commit is contained in:
320
SIDEBAR_DESIGN_REVIEW.md
Normal file
320
SIDEBAR_DESIGN_REVIEW.md
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
# Comprehensive Sidebar Design Review - OTS Signage Theme
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
The sidebar is well-structured with a modern dark-mode design, but has several areas for refinement:
|
||||||
|
- **Padding**: Generally good, but can be more consistent
|
||||||
|
- **Icon Arrangement**: Icons are properly centered and sized, but scaling between states could be improved
|
||||||
|
- **Overall Style**: Cohesive and modern, with good dark mode aesthetics
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. SIDEBAR DIMENSIONS & STRUCTURE
|
||||||
|
|
||||||
|
### CSS Variables (in `override.css`):
|
||||||
|
```css
|
||||||
|
--ots-sidebar-width: 256px /* Full width */
|
||||||
|
--ots-sidebar-collapsed-width: 64px /* Collapsed width */
|
||||||
|
--ots-sidebar-header-height: 64px /* Header section */
|
||||||
|
--ots-sidebar-item-height: 44px /* Nav item height */
|
||||||
|
--ots-sidebar-item-radius: 10px /* Border radius */
|
||||||
|
--ots-sidebar-item-padding-x: 12px /* Horizontal padding */
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layout Analysis:
|
||||||
|
- **Full Width**: 256px (reasonable for desktop, matches common patterns)
|
||||||
|
- **Collapsed Width**: 64px (good for icon-only display)
|
||||||
|
- **Responsive Break**: Changes to overlay at 768px (mobile-first, good)
|
||||||
|
|
||||||
|
**Issue #1**: When collapsed, icons still have adequate space (20px icon + padding), but text labels are 100% hidden. Consider adding a tooltip system for discoverability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. PADDING ANALYSIS
|
||||||
|
|
||||||
|
### Sidebar Header (`.sidebar-header`)
|
||||||
|
```css
|
||||||
|
padding: 20px 16px; /* Vertical: 20px | Horizontal: 16px */
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Good - balanced and symmetrical
|
||||||
|
- **Component**: Contains logo (32px icon) + brand text + toggle button
|
||||||
|
- **Vertical Alignment**: Properly centered with `align-items: center`
|
||||||
|
|
||||||
|
### Sidebar Content (`.sidebar-content`)
|
||||||
|
```css
|
||||||
|
padding: 12px 0; /* Vertical: 12px | Horizontal: 0 */
|
||||||
|
```
|
||||||
|
- **Assessment**: ⚠️ Inconsistent - no horizontal padding here
|
||||||
|
- **Issue**: Padding is applied at the list item level instead (see nav items)
|
||||||
|
|
||||||
|
### Sidebar Navigation (`.sidebar-nav`)
|
||||||
|
```css
|
||||||
|
padding: 72px 0 120px; /* Top: 72px | Bottom: 120px | Sides: 0 */
|
||||||
|
```
|
||||||
|
- **Assessment**: ⚠️ **PROBLEM AREA** - padding is excessive for top/bottom
|
||||||
|
- 72px top padding assumes a fixed header height, could be dynamic
|
||||||
|
- 120px bottom padding creates large blank space (mobile unfriendly)
|
||||||
|
- No horizontal padding means nav items extend to edge
|
||||||
|
|
||||||
|
### Navigation Items (`.ots-sidebar li.sidebar-list > a`)
|
||||||
|
```css
|
||||||
|
padding: 6px 10px; /* Item content padding */
|
||||||
|
margin: 3px 10px; /* Outer margin/spacing */
|
||||||
|
border-radius: 12px;
|
||||||
|
min-height: 40px;
|
||||||
|
```
|
||||||
|
- **Assessment**: ⚠️ Mixed padding/margin approach
|
||||||
|
- **Inner padding (6px 10px)**: Good for text breathing room
|
||||||
|
- **Outer margin (3px 10px)**: Creates 10px left/right space from sidebar edge
|
||||||
|
- **Min-height (40px)**: Good touch target size
|
||||||
|
|
||||||
|
**Recommendation**: The left margin (10px) combined with border-radius (12px) creates a nice inset look. Could be intentional and good.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. ICON ARRANGEMENT
|
||||||
|
|
||||||
|
### Icon Container (`.ots-nav-icon`)
|
||||||
|
```css
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
color: currentColor;
|
||||||
|
justify-self: center;
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Excellent
|
||||||
|
- Square container with centered content
|
||||||
|
- `justify-self: center` ensures centering in CSS Grid layout
|
||||||
|
- `color: currentColor` inherits link color for active/hover states
|
||||||
|
|
||||||
|
### Nav Item Grid Layout
|
||||||
|
```css
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20px 1fr; /* Icon: 20px fixed | Text: flex */
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Good grid-based approach
|
||||||
|
- Consistent 12px gap between icon and text
|
||||||
|
- Icon column is tight (20px), text is flexible
|
||||||
|
- **Issue**: 20px icon container is narrower than 24px icon element - could cause slight misalignment
|
||||||
|
|
||||||
|
**Recommendation**: Change to `grid-template-columns: 24px 1fr` to match the icon container size.
|
||||||
|
|
||||||
|
### Active Item Styling
|
||||||
|
```css
|
||||||
|
.ots-sidebar li.sidebar-list.active > a {
|
||||||
|
color: #0b1221; /* Dark text on light bg */
|
||||||
|
background-color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Strong visual feedback
|
||||||
|
- High contrast (white background + dark text)
|
||||||
|
- Shadow adds depth
|
||||||
|
- Icon inherits dark color via `currentColor`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. OVERALL STYLE & DESIGN CONSISTENCY
|
||||||
|
|
||||||
|
### Color Palette (Dark Mode)
|
||||||
|
```css
|
||||||
|
--ots-sidebar-bg: #08132a; /* Very dark blue */
|
||||||
|
--ots-sidebar-link: #f9fbff; /* Nearly white text */
|
||||||
|
--ots-sidebar-link-hover-bg: rgba(255,255,255,0.08);
|
||||||
|
--ots-sidebar-active-bg: rgba(255,255,255,0.06);
|
||||||
|
--ots-sidebar-active-text: #ffffff;
|
||||||
|
--ots-sidebar-muted-text: #8ea4c7; /* Muted section headers */
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Excellent contrast and hierarchy
|
||||||
|
- Background: Deep navy (#08132a)
|
||||||
|
- Primary text: Nearly white (#f9fbff)
|
||||||
|
- Hover: 8% white overlay (subtle)
|
||||||
|
- Active: 6% white overlay (subtle)
|
||||||
|
- Section headers: Medium blue (#8ea4c7)
|
||||||
|
- **Accessibility**: WCAG AA compliant (>4.5:1 contrast)
|
||||||
|
|
||||||
|
### Section Headers (`.sidebar-title`)
|
||||||
|
```css
|
||||||
|
display: block;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #8ea4c7;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
padding: 12px 14px 4px;
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Good hierarchy
|
||||||
|
- Small, uppercase, muted color clearly distinguishes from regular nav items
|
||||||
|
- Letter spacing adds visual interest
|
||||||
|
- Adequate padding separates sections
|
||||||
|
|
||||||
|
### Sidebar Footer (`.sidebar-footer`)
|
||||||
|
```css
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
padding: 16px;
|
||||||
|
background-color: rgba(59, 130, 246, 0.05); /* Slight blue tint */
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Good
|
||||||
|
- Separated with border
|
||||||
|
- Subtle background color differentiates from main nav
|
||||||
|
- 16px padding consistent with other components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. RESPONSIVE BEHAVIOR
|
||||||
|
|
||||||
|
### Mobile Override (@media max-width: 768px)
|
||||||
|
```css
|
||||||
|
.ots-sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform var(--transition-base);
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
.ots-sidebar.active {
|
||||||
|
transform: translateX(0);
|
||||||
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.ots-main {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Assessment**: ✅ Good mobile experience
|
||||||
|
- Slides in from left (drawer pattern)
|
||||||
|
- Shadow adds depth when open
|
||||||
|
- Content isn't pushed aside (overlay instead)
|
||||||
|
- **Issue**: Sidebar width changes from 256px → 280px on mobile (should be 280px, maybe even narrower for mobile)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. COLLAPSED STATE STYLING
|
||||||
|
|
||||||
|
### Collapsed Class
|
||||||
|
```css
|
||||||
|
.ots-sidebar.collapsed {
|
||||||
|
/* No explicit width override - uses CSS variable */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Assessment**: ⚠️ Lacks dedicated styling
|
||||||
|
- When `.collapsed` is added, only width changes (via CSS variable)
|
||||||
|
- No visual indicator (badge, animation) that it's collapsed
|
||||||
|
- Icon-only display works but lacks affordance
|
||||||
|
|
||||||
|
### Collapsed Item Styling (`.sidebar-collapsed-item-bg`)
|
||||||
|
```css
|
||||||
|
--ots-sidebar-collapsed-item-bg: rgba(255, 255, 255, 0.08);
|
||||||
|
--ots-sidebar-collapsed-item-hover-bg: rgba(255, 255, 255, 0.16);
|
||||||
|
```
|
||||||
|
- **Assessment**: ⚠️ Variables defined but not actively used in CSS rules
|
||||||
|
- These variables exist but I don't see them applied to `.collapsed` state styling
|
||||||
|
- **Action Item**: Verify if collapsed items have proper styling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. KEY ISSUES & RECOMMENDATIONS
|
||||||
|
|
||||||
|
### 🔴 HIGH PRIORITY
|
||||||
|
|
||||||
|
1. **Icon/Grid Mismatch**
|
||||||
|
- Icon container: 24px, but grid column: 20px
|
||||||
|
- **Fix**: Change `grid-template-columns: 20px 1fr` → `grid-template-columns: 24px 1fr`
|
||||||
|
|
||||||
|
2. **Excessive Nav Padding**
|
||||||
|
- `.sidebar-nav { padding: 72px 0 120px }` is too much
|
||||||
|
- **Fix**: Use dynamic sizing based on header height (JavaScript or CSS custom property)
|
||||||
|
- **Suggestion**: `padding: var(--ots-sidebar-header-height) 0 60px` (60px is enough for footer breathing room)
|
||||||
|
|
||||||
|
3. **Collapsed State Styling**
|
||||||
|
- Collapsed items have CSS variables defined but not used
|
||||||
|
- **Fix**: Add active rules like:
|
||||||
|
```css
|
||||||
|
.ots-sidebar.collapsed li.sidebar-list.active > a {
|
||||||
|
background-color: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🟡 MEDIUM PRIORITY
|
||||||
|
|
||||||
|
4. **Icon Discoverability When Collapsed**
|
||||||
|
- No tooltips or labels appear when sidebar is collapsed
|
||||||
|
- **Suggestion**: Add `title` attributes to nav items or implement CSS tooltips
|
||||||
|
|
||||||
|
5. **Sidebar Header Button Spacing**
|
||||||
|
- Header has "toggle" button but spacing could be tighter on mobile
|
||||||
|
- **Suggestion**: When collapsed, header could be more compact
|
||||||
|
|
||||||
|
6. **Mobile Width Inconsistency**
|
||||||
|
- Sidebar is 256px full-width but 280px on mobile (why wider?)
|
||||||
|
- **Suggestion**: Keep consistent at 256px or make it responsive (e.g., 90vw max 280px on small phones)
|
||||||
|
|
||||||
|
### 🟢 LOW PRIORITY
|
||||||
|
|
||||||
|
7. **Brand Icon & Text**
|
||||||
|
- Logo + text look good but could use more breathing room
|
||||||
|
- **Current**: `gap: 12px` - consider `gap: 16px` for more visual separation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. VISUAL SPACING SUMMARY
|
||||||
|
|
||||||
|
| Component | Padding | Margin | Assessment |
|
||||||
|
|-----------|---------|--------|------------|
|
||||||
|
| Header | 20px V, 16px H | — | ✅ Good |
|
||||||
|
| Content Wrapper | 12px V, 0 H | — | ⚠️ Inconsistent |
|
||||||
|
| Nav List | 72px T, 120px B | — | 🔴 Excessive |
|
||||||
|
| Nav Items | 6px V, 10px H | 3px V, 10px H | ✅ Good |
|
||||||
|
| Section Headers | 12px T, 14px H | — | ✅ Good |
|
||||||
|
| Footer | 16px | — | ✅ Consistent |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. ICON SIZING CONSISTENCY
|
||||||
|
|
||||||
|
| Element | Width | Height | Font Size | Usage |
|
||||||
|
|---------|-------|--------|-----------|-------|
|
||||||
|
| Nav Icon | 24px | 24px | 16px | Primary nav items |
|
||||||
|
| Brand Icon | 32px | 32px | 24px | Logo in header |
|
||||||
|
| Topbar Icon | 20px | 20px | 16px | Topbar controls |
|
||||||
|
|
||||||
|
**Assessment**: ✅ Good hierarchy and clarity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. RECOMMENDATIONS FOR NEXT PHASE
|
||||||
|
|
||||||
|
1. ✅ Fix grid column width mismatch (20px → 24px)
|
||||||
|
2. ✅ Refactor `.sidebar-nav` padding (use CSS variables or dynamic)
|
||||||
|
3. ✅ Add collapsed state active item styling
|
||||||
|
4. ✅ Add `title` attributes to nav items for tooltip support
|
||||||
|
5. ⚠️ Consider adding a visual "collapse indicator" (e.g., chevron or light pulsing border)
|
||||||
|
6. ⚠️ Standardize mobile sidebar width
|
||||||
|
7. ⚠️ Add more breathing room in header (gap: 16px instead of 12px for brand icon)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Overall Grade: B+ (85/100)**
|
||||||
|
|
||||||
|
### Strengths:
|
||||||
|
- ✅ Modern, cohesive dark-mode aesthetic
|
||||||
|
- ✅ Good color contrast and accessibility
|
||||||
|
- ✅ Proper icon sizing and centering
|
||||||
|
- ✅ Responsive mobile overlay pattern
|
||||||
|
- ✅ Clear visual hierarchy (headers, active states, hover)
|
||||||
|
|
||||||
|
### Weaknesses:
|
||||||
|
- ⚠️ Excessive bottom padding in nav list
|
||||||
|
- ⚠️ Grid icon column width mismatch
|
||||||
|
- ⚠️ Lacking visual affordance when collapsed
|
||||||
|
- ⚠️ Icon discoverability issues
|
||||||
|
|
||||||
|
### Quick Wins (implement first):
|
||||||
|
1. Change `grid-template-columns: 20px 1fr` → `24px 1fr`
|
||||||
|
2. Change `.sidebar-nav padding: 72px 0 120px` → `64px 0 60px`
|
||||||
|
3. Add collapsed state styling for active items
|
||||||
|
|
||||||
@@ -283,3 +283,19 @@ img {
|
|||||||
outline: 2px solid var(--color-primary);
|
outline: 2px solid var(--color-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Table search input sizing moved to CSS for responsive control */
|
||||||
|
.table-search-input {
|
||||||
|
min-width: 11.25rem; /* 180px */
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.table-search-input {
|
||||||
|
min-width: auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -31,11 +31,66 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile-aware toggle: add backdrop, aria-expanded, and focus management
|
||||||
if (toggleBtn) {
|
if (toggleBtn) {
|
||||||
|
let lastFocus = null;
|
||||||
|
function ensureBackdrop() {
|
||||||
|
let bd = document.querySelector('.ots-backdrop');
|
||||||
|
if (!bd) {
|
||||||
|
bd = document.createElement('div');
|
||||||
|
bd.className = 'ots-backdrop';
|
||||||
|
bd.addEventListener('click', function() {
|
||||||
|
sidebar.classList.remove('active');
|
||||||
|
bd.classList.remove('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (lastFocus) lastFocus.focus();
|
||||||
|
});
|
||||||
|
document.body.appendChild(bd);
|
||||||
|
}
|
||||||
|
return bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBtn.setAttribute('role', 'button');
|
||||||
|
toggleBtn.setAttribute('aria-controls', 'ots-sidebar');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
toggleBtn.addEventListener('click', function(e) {
|
toggleBtn.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const isNowActive = !sidebar.classList.contains('active');
|
||||||
sidebar.classList.toggle('active');
|
sidebar.classList.toggle('active');
|
||||||
|
// On small screens show backdrop and manage focus
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
const bd = ensureBackdrop();
|
||||||
|
if (isNowActive) {
|
||||||
|
bd.classList.add('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'true');
|
||||||
|
lastFocus = document.activeElement;
|
||||||
|
// move focus into the sidebar
|
||||||
|
const firstFocusable = sidebar.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||||
|
if (firstFocusable) firstFocusable.focus(); else sidebar.setAttribute('tabindex', '-1'), sidebar.focus();
|
||||||
|
// add escape handler
|
||||||
|
document.addEventListener('keydown', escHandler);
|
||||||
|
} else {
|
||||||
|
bd.classList.remove('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (lastFocus) lastFocus.focus();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function escHandler(e) {
|
||||||
|
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||||
|
const bd = document.querySelector('.ots-backdrop');
|
||||||
|
if (sidebar.classList.contains('active')) {
|
||||||
|
sidebar.classList.remove('active');
|
||||||
|
if (bd) bd.classList.remove('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (lastFocus) lastFocus.focus();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collapseBtn) {
|
if (collapseBtn) {
|
||||||
@@ -43,6 +98,7 @@
|
|||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
sidebar.classList.add('collapsed');
|
sidebar.classList.add('collapsed');
|
||||||
body.classList.add('ots-sidebar-collapsed');
|
body.classList.add('ots-sidebar-collapsed');
|
||||||
|
document.documentElement.classList.add('ots-sidebar-collapsed');
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +107,8 @@
|
|||||||
const nowCollapsed = !sidebar.classList.contains('collapsed');
|
const nowCollapsed = !sidebar.classList.contains('collapsed');
|
||||||
sidebar.classList.toggle('collapsed');
|
sidebar.classList.toggle('collapsed');
|
||||||
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
|
document.documentElement.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
||||||
// Update measured sidebar width when collapsed state changes
|
|
||||||
updateSidebarWidth();
|
|
||||||
// Recalculate nav offset so items remain below header after collapse
|
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
});
|
});
|
||||||
@@ -65,9 +119,8 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sidebar.classList.remove('collapsed');
|
sidebar.classList.remove('collapsed');
|
||||||
body.classList.remove('ots-sidebar-collapsed');
|
body.classList.remove('ots-sidebar-collapsed');
|
||||||
|
document.documentElement.classList.remove('ots-sidebar-collapsed');
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
||||||
updateSidebarWidth();
|
|
||||||
// Recalculate nav offset after expanding
|
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
});
|
});
|
||||||
@@ -90,17 +143,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measure sidebar width and set CSS variable for layout
|
* Sidebar width is now handled purely by CSS classes (.ots-sidebar-collapsed).
|
||||||
|
* This function is kept as a no-op for backward compatibility.
|
||||||
*/
|
*/
|
||||||
function updateSidebarWidth() {
|
function updateSidebarWidth() {
|
||||||
|
// No-op: CSS handles layout via body.ots-sidebar-collapsed class
|
||||||
|
if (window.__otsDebug) {
|
||||||
const sidebar = document.querySelector('.ots-sidebar');
|
const sidebar = document.querySelector('.ots-sidebar');
|
||||||
if (!sidebar) return;
|
const collapsed = sidebar ? sidebar.classList.contains('collapsed') : false;
|
||||||
// If collapsed, use the known collapsed width; otherwise use measured width
|
console.log('[OTS] updateSidebarWidth (no-op, CSS-driven)', { collapsed });
|
||||||
const collapsed = sidebar.classList.contains('collapsed');
|
}
|
||||||
const base = collapsed ? 64 : sidebar.offsetWidth || sidebar.getBoundingClientRect().width || 256;
|
|
||||||
const padding = 0;
|
|
||||||
const value = Math.max(64, Math.round(base + padding));
|
|
||||||
document.documentElement.style.setProperty('--ots-sidebar-width', `${value}px`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
268
custom/otssignange/views/applications-page.twig
Normal file
268
custom/otssignange/views/applications-page.twig
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - Applications Page
|
||||||
|
* Based on Xibo CMS applications-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "Applications"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Applications" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage API applications and connectors." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card">
|
||||||
|
<div class="ots-filter-header">
|
||||||
|
<h3 class="ots-filter-title">{% trans "Filter Applications" %}</h3>
|
||||||
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
||||||
|
<i class="fa fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ots-filter-content collapsed" id="ots-filter-content">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
{% set title %}{% trans "Name" %}{% endset %}
|
||||||
|
{{ inline.inputNameGrid('name', title) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add an Application" %}" href="{{ url_for("application.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Application" %}</button>
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="applications" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Name" %}</th>
|
||||||
|
<th>{% trans "Owner" %}</th>
|
||||||
|
<th class="rowMenu"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="widget dashboard-card ots-displays-card mt-2">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Connectors" %}</h1>
|
||||||
|
</div>
|
||||||
|
<div id="connectors" class="card-deck">
|
||||||
|
{% if theme.getThemeConfig("app_name") == "Xibo" %}
|
||||||
|
<div class="card p3 mt-2" style="min-width: 250px; max-width: 250px;">
|
||||||
|
<img class="card-img-top" style="max-height: 250px" src="{{ theme.rootUri() }}theme/default/img/connectors/canva_logo.png" alt="Canva">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Canva</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
Publish your designs from Canva to Xibo at the push of a button.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
This connector is configured in Canva using the "Publish menu".
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<a class="btn btn-primary" href="https://canva.com" target="_blank">Visit Canva</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
|
||||||
|
{% autoescape "js" %}
|
||||||
|
var copyToClipboardTrans = "{{ "Copy to Clipboard"|trans }}";
|
||||||
|
var couldNotCopyTrans = "{{ "Could not copy"|trans }}";
|
||||||
|
var copiedTrans = "{{ "Copied!"|trans }}";
|
||||||
|
{% endautoescape %}
|
||||||
|
|
||||||
|
var table;
|
||||||
|
$(document).ready(function() {
|
||||||
|
table = $('#applications').DataTable({
|
||||||
|
language: dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true,
|
||||||
|
responsive: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
filter: false,
|
||||||
|
searchDelay: 3000,
|
||||||
|
"order": [[ 0, "asc"]],
|
||||||
|
ajax: {
|
||||||
|
url: "{{ url_for('application.search') }}",
|
||||||
|
data: function (d) {
|
||||||
|
$.extend(d, $('#applications').closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{ "data": "name", "render": dataTableSpacingPreformatted },
|
||||||
|
{ "data": "owner" },
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('draw', dataTableDraw);
|
||||||
|
table.on('processing.dt', dataTableProcessing);
|
||||||
|
dataTableAddButtons(table, $('#applications_wrapper').find('.dataTables_buttons'));
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connectors
|
||||||
|
loadConnectors();
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadConnectors() {
|
||||||
|
var connectorTemplate = Handlebars.compile($('#template-connector-cards').html());
|
||||||
|
var $connectorContainer = $('#connectors');
|
||||||
|
$connectorContainer.find('.connector').remove();
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: '{{ url_for("connector.search") }}?isVisible=1&showUninstalled=1',
|
||||||
|
cache: false,
|
||||||
|
dataType:"json",
|
||||||
|
success: function(xhr, textStatus, error) {
|
||||||
|
$.each(xhr.data, function(index, element) {
|
||||||
|
if (element.isHidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.configureUrl = '{{ url_for("connector.edit.form", {id: ":id"}) }}'.replace(':id', element.connectorId);
|
||||||
|
element.proxyUrl = '{{ url_for("connector.edit.form.proxy", {id: ":id", method: ":method"}) }}'.replace(':id', element.connectorId);
|
||||||
|
element.thumbnail = element.thumbnail || 'theme/default/img/thumbs/placeholder.png';
|
||||||
|
if (!element.thumbnail.startsWith('http')) {
|
||||||
|
element.thumbnail = '{{ theme.rootUri() }}' + element.thumbnail;
|
||||||
|
}
|
||||||
|
element.enabledIcon = (element.isEnabled) ? 'fa-check' : 'fa-times';
|
||||||
|
element.classNameLast = element.className.substr(element.className.lastIndexOf('\\') + 1);
|
||||||
|
$connectorContainer.append(connectorTemplate(element));
|
||||||
|
});
|
||||||
|
|
||||||
|
$connectorContainer.trigger('connectors.loaded');
|
||||||
|
XiboInitialise('#connectors');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectorFormSubmit() {
|
||||||
|
XiboFormSubmit($('#connectorEditForm'), null, function() {
|
||||||
|
loadConnectors();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFromSecretInput(dialog) {
|
||||||
|
$('#copy-button').tooltip();
|
||||||
|
|
||||||
|
$('#copy-button').bind('click', function() {
|
||||||
|
var input = $('#clientSecret');
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var success = document.execCommand('copy');
|
||||||
|
if (success) {
|
||||||
|
$('#copy-button').trigger('copied', [copiedTrans]);
|
||||||
|
} else {
|
||||||
|
$('#copy-button').trigger('copied', [couldNotCopyTrans]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
$('#copy-button').trigger('copied', [couldNotCopyTrans]);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.blur();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#copy-button').bind('copied', function(event, message) {
|
||||||
|
const $self = $(this);
|
||||||
|
$(this).tooltip('hide')
|
||||||
|
.attr('data-original-title', message)
|
||||||
|
.tooltip('show');
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
$self.tooltip('hide').attr('data-original-title', copyToClipboardTrans);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
onAuthCodeChanged(dialog);
|
||||||
|
$(dialog).find('#authCode').on('change', function() {
|
||||||
|
onAuthCodeChanged(dialog);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAuthCodeChanged(dialog) {
|
||||||
|
var authCode = $(dialog).find("#authCode").is(":checked");
|
||||||
|
var $authCodeTab = $(dialog).find(".tabForAuthCode");
|
||||||
|
|
||||||
|
if (authCode) {
|
||||||
|
$authCodeTab.removeClass("d-none");
|
||||||
|
} else {
|
||||||
|
$authCodeTab.addClass("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% for js in connectorJavaScript %}
|
||||||
|
{% include js ~ ".twig" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScriptTemplates %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
{% verbatim %}
|
||||||
|
<script type="text/x-handlebars-template" id="template-connector-cards">
|
||||||
|
<div class="connector card p3 mt-2" style="min-width: 250px; max-width: 250px;"
|
||||||
|
data-proxy-url="{{proxyUrl}}"
|
||||||
|
data-connector-class-name="{{className}}"
|
||||||
|
data-connector-class-name-last="{{classNameLast}}"
|
||||||
|
data-connector-id="{{ connectorId }}">
|
||||||
|
{{#if thumbnail}}<img class="card-img-top" style="max-height: 250px" src="{{ thumbnail }}" alt="{{ title }}">{{/if}}
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ title }}</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
{{ description }}
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
{{#if isInstalled }}
|
||||||
|
{% endverbatim %}{{ "Enabled"|trans }}{% verbatim %}: <span class="fa {{ enabledIcon }}"></span>
|
||||||
|
{{/if}}
|
||||||
|
{{#unless isInstalled }}
|
||||||
|
{% endverbatim %}{{ "Installed"|trans }}{% verbatim %}: <span class="fa fa-times"></span>
|
||||||
|
{{/unless}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button class="btn btn-primary XiboFormButton" href="{{ configureUrl }}">
|
||||||
|
{% endverbatim %}{{ "Configure"|trans }}{% verbatim %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
{% endverbatim %}
|
||||||
|
{% endblock %}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<ul class="sidebar ots-sidebar-nav">
|
<ul class="sidebar ots-sidebar-nav">
|
||||||
<li class="sidebar-list">
|
<li class="sidebar-list">
|
||||||
<a href="{{ url_for("home") }}">
|
<a href="{{ url_for("home") }}" data-tooltip="Dashboard">
|
||||||
<span class="ots-nav-icon fa fa-home" aria-hidden="true"></span>
|
<span class="ots-nav-icon fa fa-home" aria-hidden="true"></span>
|
||||||
<span class="ots-nav-text">{% trans "Dashboard" %}</span>
|
<span class="ots-nav-text">{% trans "Dashboard" %}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -44,11 +44,6 @@
|
|||||||
// Add on <html> immediately; body may not be parsed yet
|
// Add on <html> immediately; body may not be parsed yet
|
||||||
document.documentElement.classList.add('ots-sidebar-collapsed');
|
document.documentElement.classList.add('ots-sidebar-collapsed');
|
||||||
if (document.body) document.body.classList.add('ots-sidebar-collapsed');
|
if (document.body) document.body.classList.add('ots-sidebar-collapsed');
|
||||||
// Also set the CSS variable used for collapsed width so layout shifts correctly
|
|
||||||
try {
|
|
||||||
var v = getComputedStyle(document.documentElement).getPropertyValue('--ots-sidebar-collapsed-width') || '64px';
|
|
||||||
document.documentElement.style.setProperty('--ots-sidebar-width', v);
|
|
||||||
} catch(e){}
|
|
||||||
try { console.debug && console.debug('applied ots-sidebar-collapsed early'); } catch(e){}
|
try { console.debug && console.debug('applied ots-sidebar-collapsed early'); } catch(e){}
|
||||||
} else {
|
} else {
|
||||||
try { console.debug && console.debug('otsTheme:sidebarCollapsed early: not set'); } catch(e){}
|
try { console.debug && console.debug('otsTheme:sidebarCollapsed early: not set'); } catch(e){}
|
||||||
@@ -58,11 +53,9 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style nonce="{{ cspNonce }}">html,body{background:#ffffff!important;color:#111111!important}
|
<style nonce="{{ cspNonce }}">html,body{background:#ffffff!important;color:#111111!important}
|
||||||
/* Hide the top header row immediately when sidebar is collapsed to prevent flash */
|
/* Hide the old topbar strip entirely — actions are now in .ots-page-actions */
|
||||||
html.ots-sidebar-collapsed .row.header.header-side,
|
.row.header.header-side,
|
||||||
body.ots-sidebar-collapsed .row.header.header-side,
|
.ots-topbar-strip { display: none !important; height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; }
|
||||||
.ots-sidebar.collapsed ~ .ots-main .row.header.header-side,
|
|
||||||
.ots-sidebar.collapsed .row.header.header-side { display: none !important; height: 0 !important; margin: 0 !important; padding: 0 !important; }
|
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -112,34 +105,21 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div id="content-wrapper">
|
<div id="content-wrapper">
|
||||||
<div class="page-content">
|
{# Floating top-right actions: notification bell + user menu #}
|
||||||
{% if not horizontalNav or hideNavigation == "1" or forceHide %}
|
|
||||||
<div class="row header header-side">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<div class="meta pull-left xibo-logo-container">
|
|
||||||
<div class="page"><img class="xibo-logo" src="{{ theme.uri("img/xibologo.png") }}"></div>
|
|
||||||
</div>
|
|
||||||
{% if not forceHide %}
|
{% if not forceHide %}
|
||||||
{% if not hideNavigation == "1" %}
|
<div class="ots-page-actions">
|
||||||
<button type="button" class="pull-right navbar-toggler navbar-toggler-side" data-toggle="collapse" data-target="#navbar-collapse-1" aria-controls="navbarNav" aria-expanded="false">
|
{% include "authed-theme-topbar.twig" ignore missing %}
|
||||||
<span class="fa fa-bars"></span>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
<div class="user-actions pull-right">
|
|
||||||
{% if currentUser.featureEnabled("drawer") %}
|
{% if currentUser.featureEnabled("drawer") %}
|
||||||
<div class="user-notif">
|
<div class="ots-topbar-action">
|
||||||
{% include "authed-notification-drawer.twig" with { 'compact': true } %}
|
{% include "authed-notification-drawer.twig" with { 'compact': true } %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="user">
|
<div class="ots-topbar-action">
|
||||||
{% include "authed-user-menu.twig" with { 'compact': true } %}
|
{% include "authed-user-menu.twig" with { 'compact': true } %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "authed-theme-topbar.twig" ignore missing %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="page-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
{% block actionMenu %}{% endblock %}
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Campaigns"|trans }} | {% endblock %}
|
{% block title %}{{ "Campaigns"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("campaign.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new Campaign" %}" href="{{ url_for("campaign.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -98,8 +91,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-with-folders-container">
|
<div class="grid-with-folders-container ots-grid-with-folders">
|
||||||
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
|
<div class="grid-folder-tree-container p-3 dashboard-card ots-folder-tree" id="grid-folder-filter">
|
||||||
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
||||||
@@ -110,12 +103,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none">
|
<div class="folder-controller d-none ots-grid-controller">
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("campaign.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Campaign" %}" href="{{ url_for("campaign.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Campaign" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="campaigns" class="table table-striped" data-content-type="campaign" data-content-id-name="campaignId" data-state-preference-name="campaignGrid" style="width: 100%;">
|
<table id="campaigns" class="table table-striped" data-content-type="campaign" data-content-id-name="campaignId" data-state-preference-name="campaignGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Commands"|trans }} | {% endblock %}
|
{% block title %}{{ "Commands"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("command.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add Command" %}" href="{{ url_for("command.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
@@ -65,6 +58,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("command.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add Command" %}" href="{{ url_for("command.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Command" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="commands" class="table table-striped" data-state-preference-name="commandGrid">
|
<table id="commands" class="table table-striped" data-state-preference-name="commandGrid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -368,7 +368,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
<table id="displaysGrid" class="table table-striped" data-state-preference-name="statusDashboardDisplays" style="width: 100%;">
|
<table id="displaysGrid" class="table table-striped" data-state-preference-name="statusDashboardDisplays" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -26,14 +26,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "DataSets"|trans }} | {% endblock %}
|
{% block title %}{{ "DataSets"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("dataset.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new DataSet" %}" href="{{ url_for("dataSet.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -65,7 +58,7 @@
|
|||||||
{% set title %}{% trans "Owner" %}{% endset %}
|
{% set title %}{% trans "Owner" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -83,8 +76,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-with-folders-container">
|
<div class="grid-with-folders-container ots-grid-with-folders">
|
||||||
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
|
<div class="grid-folder-tree-container p-3 dashboard-card ots-folder-tree" id="grid-folder-filter">
|
||||||
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
||||||
@@ -95,12 +88,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none">
|
<div class="folder-controller d-none ots-grid-controller">
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("dataset.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new DataSet" %}" href="{{ url_for("dataSet.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add DataSet" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetGrid" style="width: 100%;">
|
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Dayparting"|trans }} | {% endblock %}
|
{% block title %}{{ "Dayparting"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("daypart.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new Daypart" %}" href="{{ url_for("daypart.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -67,6 +60,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("daypart.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Daypart" %}" href="{{ url_for("daypart.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Daypart" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="dayparts" class="table table-striped" data-state-preference-name="daypartGrid">
|
<table id="dayparts" class="table table-striped" data-state-preference-name="daypartGrid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Displays"|trans }} | {% endblock %}
|
{% block title %}{{ "Displays"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("displays.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a Display via user_code displayed on the Player screen" %}" href="{{ url_for("display.addViaCode.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block headContent %}
|
{% block headContent %}
|
||||||
{# Add page source code bundle ( CSS ) #}
|
{# Add page source code bundle ( CSS ) #}
|
||||||
@@ -55,10 +48,6 @@
|
|||||||
try { console.debug && console.debug('otsTheme:sidebarCollapsed early (page):', collapsed); } catch(e){}
|
try { console.debug && console.debug('otsTheme:sidebarCollapsed early (page):', collapsed); } catch(e){}
|
||||||
document.documentElement.classList.add('ots-sidebar-collapsed');
|
document.documentElement.classList.add('ots-sidebar-collapsed');
|
||||||
if (document.body) document.body.classList.add('ots-sidebar-collapsed');
|
if (document.body) document.body.classList.add('ots-sidebar-collapsed');
|
||||||
try {
|
|
||||||
var v = getComputedStyle(document.documentElement).getPropertyValue('--ots-sidebar-collapsed-width') || '64px';
|
|
||||||
document.documentElement.style.setProperty('--ots-sidebar-width', v);
|
|
||||||
} catch(e){}
|
|
||||||
try { console.debug && console.debug('applied ots-sidebar-collapsed early (page)'); } catch(e){}
|
try { console.debug && console.debug('applied ots-sidebar-collapsed early (page)'); } catch(e){}
|
||||||
} else {
|
} else {
|
||||||
try { console.debug && console.debug('otsTheme:sidebarCollapsed early (page): not set'); } catch(e){}
|
try { console.debug && console.debug('otsTheme:sidebarCollapsed early (page): not set'); } catch(e){}
|
||||||
@@ -67,11 +56,9 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<style nonce="{{ cspNonce }}">html,body{background:#ffffff!important;color:#111111!important}
|
<style nonce="{{ cspNonce }}">html,body{background:#ffffff!important;color:#111111!important}
|
||||||
/* Hide the top header row immediately when sidebar is collapsed to prevent flash */
|
/* Hide the topbar strip entirely — actions are now in .ots-page-actions */
|
||||||
html.ots-sidebar-collapsed .row.header.header-side,
|
.row.header.header-side,
|
||||||
body.ots-sidebar-collapsed .row.header.header-side,
|
.ots-topbar-strip { display: none !important; height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; }
|
||||||
.ots-sidebar.collapsed ~ .ots-main .row.header.header-side,
|
|
||||||
.ots-sidebar.collapsed .row.header.header-side { display: none !important; height: 0 !important; margin: 0 !important; padding: 0 !important; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ theme.rootUri() }}dist/pages/display-page.bundle.min.css?v={{ version }}&rev={{revision }}">
|
<link rel="stylesheet" href="{{ theme.rootUri() }}dist/pages/display-page.bundle.min.css?v={{ version }}&rev={{revision }}">
|
||||||
@@ -162,7 +149,7 @@
|
|||||||
{% if currentUser.featureEnabled("displaygroup.view") %}
|
{% if currentUser.featureEnabled("displaygroup.view") %}
|
||||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -282,6 +269,12 @@
|
|||||||
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("displays.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a Display via user_code displayed on the Player screen" %}" href="{{ url_for("display.addViaCode.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Display" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="displays" class="table table-striped" data-content-type="display" data-content-id-name="displayId" data-state-preference-name="displayGrid" style="width: 100%;">
|
<table id="displays" class="table table-striped" data-content-type="display" data-content-id-name="displayId" data-state-preference-name="displayGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Display Groups"|trans }} | {% endblock %}
|
{% block title %}{{ "Display Groups"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("displaygroup.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add Display Group" %}" href="{{ url_for("displayGroup.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -62,7 +55,7 @@
|
|||||||
|
|
||||||
{% set title %}{% trans "Display" %}{% endset %}
|
{% set title %}{% trans "Display" %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -116,6 +109,12 @@
|
|||||||
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("displaygroup.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add Display Group" %}" href="{{ url_for("displayGroup.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Group" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="displaygroups" class="table table-striped" data-content-type="displayGroup" data-content-id-name="displayGroupId" data-state-preference-name="displayGroupGrid" style="width: 100%;">
|
<table id="displaygroups" class="table table-striped" data-content-type="displayGroup" data-content-id-name="displayGroupId" data-state-preference-name="displayGroupGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Display Setting Profiles"|trans }} | {% endblock %}
|
{% block title %}{{ "Display Setting Profiles"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("displayprofile.add") %}
|
|
||||||
<button class="btn btn-icon btn-info XiboFormButton" title="{% trans "Add a new Display Settings Profile" %}" href="{{ url_for("displayProfile.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -65,6 +58,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("displayprofile.add") %}
|
||||||
|
<button class="btn btn-sm btn-info XiboFormButton" title="{% trans "Add a new Display Settings Profile" %}" href="{{ url_for("displayProfile.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Profile" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="displayProfiles" class="table table-striped" data-state-preference-name="displayProfileGrid">
|
<table id="displayProfiles" class="table table-striped" data-state-preference-name="displayProfileGrid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
159
custom/otssignange/views/fonts-page.twig
Normal file
159
custom/otssignange/views/fonts-page.twig
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - Fonts Page
|
||||||
|
* Based on Xibo CMS fonts-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "Fonts"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Fonts" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage fonts for your signage content." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}" data-grid-name="fontView">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card">
|
||||||
|
<div class="ots-filter-header">
|
||||||
|
<h3 class="ots-filter-title">{% trans "Filter Fonts" %}</h3>
|
||||||
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
||||||
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ots-filter-content" id="ots-filter-content">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
{% set title %}{% trans "ID" %}{% endset %}
|
||||||
|
{{ inline.number("id", title) }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "Name" %}{% endset %}
|
||||||
|
{{ inline.inputNameGrid('name', title) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("font.add") %}
|
||||||
|
<button class="btn btn-sm btn-success" href="#" id="fontUploadForm" title="{% trans "Add a new Font" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Upload Font" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="fonts" class="table table-striped" data-state-preference-name="fontGrid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "ID" %}</th>
|
||||||
|
<th>{% trans "name" %}</th>
|
||||||
|
<th>{% trans "File Name" %}</th>
|
||||||
|
<th>{% trans "Created" %}</th>
|
||||||
|
<th>{% trans "Modified" %}</th>
|
||||||
|
<th>{% trans "Modified By" %}</th>
|
||||||
|
<th>{% trans "Size" %}</th>
|
||||||
|
<th class="rowMenu"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
var fontsTable;
|
||||||
|
$(document).ready(function() {
|
||||||
|
fontsTable = $("#fonts").DataTable({
|
||||||
|
"language": dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true,
|
||||||
|
responsive: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
filter: false,
|
||||||
|
searchDelay: 3000,
|
||||||
|
"order": [[1, "asc"]],
|
||||||
|
ajax: {
|
||||||
|
url: "{{ url_for("font.search") }}",
|
||||||
|
data: function (d) {
|
||||||
|
$.extend(d, $("#fonts").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{"data": "id", responsivePriority: 2},
|
||||||
|
{"data": "name", responsivePriority: 2},
|
||||||
|
{"data": "fileName", responsivePriority: 4},
|
||||||
|
{"data": "createdAt", responsivePriority: 3},
|
||||||
|
{"data": "modifiedAt", responsivePriority: 3},
|
||||||
|
{"data": "modifiedBy", responsivePriority: 3},
|
||||||
|
{
|
||||||
|
"name": "size",
|
||||||
|
responsivePriority: 3,
|
||||||
|
"data": null,
|
||||||
|
"render": {"_": "size", "display": "fileSizeFormatted", "sort": "size"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
fontsTable.on('draw', dataTableDraw);
|
||||||
|
fontsTable.on('processing.dt', dataTableProcessing);
|
||||||
|
dataTableAddButtons(fontsTable, $('#fonts_wrapper').find('.dataTables_buttons'));
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
fontsTable.ajax.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#fontUploadForm").click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
openUploadForm({
|
||||||
|
url: "{{ url_for("font.add") }}",
|
||||||
|
title: "{% trans "Add Font" %}",
|
||||||
|
initialisedBy: "font-upload",
|
||||||
|
buttons: {
|
||||||
|
main: {
|
||||||
|
label: "{% trans "Done" %}",
|
||||||
|
className: "btn-primary btn-bb-main",
|
||||||
|
callback: function () {
|
||||||
|
fontsTable.ajax.reload();
|
||||||
|
XiboDialogClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
templateOptions: {
|
||||||
|
includeTagsInput: false,
|
||||||
|
trans: {
|
||||||
|
addFiles: "{% trans "Add files" %}",
|
||||||
|
startUpload: "{% trans "Start upload" %}",
|
||||||
|
cancelUpload: "{% trans "Cancel upload" %}"
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
maxSize: {{ libraryUpload.maxSize }},
|
||||||
|
maxSizeMessage: "{{ libraryUpload.maxSizeMessage }}",
|
||||||
|
validExt: "{{ validExt }}"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -3,19 +3,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Layouts"|trans }} | {% endblock %}
|
{% block title %}{{ "Layouts"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("layout.add") %}
|
|
||||||
<button class="btn btn-success layout-add-button"
|
|
||||||
title="{% trans "Add a new Layout and jump to the layout editor." %}"
|
|
||||||
href="{{ url_for("layout.add") }}">
|
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-icon btn-info" id="layoutUploadForm" title="{% trans "Import a Layout from a ZIP file." %}" href="#"><i class="fa fa-cloud-download" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -64,7 +52,7 @@
|
|||||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show Layouts active on the selected Display / Display Group" %}{% endset %}
|
{% set helpText %}{% trans "Show Layouts active on the selected Display / Display Group" %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -81,7 +69,7 @@
|
|||||||
{% set title %}{% trans "Owner" %}{% endset %}
|
{% set title %}{% trans "Owner" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -97,7 +85,7 @@
|
|||||||
{% set title %}{% trans "Owner User Group" %}{% endset %}
|
{% set title %}{% trans "Owner User Group" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by users in the selected User Group." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by users in the selected User Group." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -156,8 +144,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-with-folders-container">
|
<div class="grid-with-folders-container ots-grid-with-folders">
|
||||||
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
|
<div class="grid-folder-tree-container p-3 dashboard-card ots-folder-tree" id="grid-folder-filter">
|
||||||
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
||||||
@@ -169,13 +157,20 @@
|
|||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="folder-controller d-none">
|
<div class="folder-controller d-none ots-grid-controller">
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("layout.add") %}
|
||||||
|
<button class="btn btn-sm btn-success layout-add-button" title="{% trans "Add a new Layout and jump to the layout editor." %}" href="{{ url_for("layout.add") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Layout" %}</button>
|
||||||
|
<button class="btn btn-sm btn-info" id="layoutUploadForm" title="{% trans "Import a Layout from a ZIP file." %}" href="#"><i class="fa fa-cloud-download" aria-hidden="true"></i> {% trans "Import" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="layouts" class="table table-striped responsive nowrap" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="layoutGrid" style="width: 100%;">
|
<table id="layouts" class="table table-striped responsive nowrap" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="layoutGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -25,35 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Library"|trans }} | {% endblock %}
|
{% block title %}{{ "Library"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabledCount(["library.add", "library.modify"]) > 0 or settings.SETTING_LIBRARY_TIDY_ENABLED == 1 %}
|
|
||||||
{% if currentUser.featureEnabled("library.add") %}
|
|
||||||
<button class="btn btn-icon btn-success" href="#" id="libraryUploadForm" title="{% trans "Add a new media item to the library" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new media item to the library via external URL" %}" href="{{ url_for("library.uploadUrl.form") }}"><i class="fa fa-link" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
{% if settings.SETTING_LIBRARY_TIDY_ENABLED == 1 and currentUser.featureEnabled("library.modify") %}
|
|
||||||
<button class="btn btn-icon btn-warning XiboFormButton btn-tidy" title="{% trans "Run through the library and remove unused and unnecessary files" %}" href="{{ url_for("library.tidy.form") }}">
|
|
||||||
<svg class="icon icon-broom-pantry" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
||||||
<g fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<!-- dustpan -->
|
|
||||||
<path d="M3 6h6l2 6v5a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6z" fill="currentColor" opacity="0.08"/>
|
|
||||||
<path d="M9 6v0" />
|
|
||||||
<path d="M3.5 7.5L8 7.5" />
|
|
||||||
<!-- broom handle -->
|
|
||||||
<path d="M14 3l6 6-7 7" />
|
|
||||||
<!-- bristles -->
|
|
||||||
<path d="M11 14l4.5-4.5M12 15l5-5M13 16l5.5-5.5" />
|
|
||||||
<!-- small hand grip accent -->
|
|
||||||
<circle cx="14.5" cy="4.5" r="0.5" fill="currentColor" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
@@ -99,7 +71,7 @@
|
|||||||
{% set title %}{% trans "Owner" %}{% endset %}
|
{% set title %}{% trans "Owner" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -115,7 +87,7 @@
|
|||||||
{% set title %}{% trans "Owner User Group" %}{% endset %}
|
{% set title %}{% trans "Owner User Group" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by users in the selected User Group." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by users in the selected User Group." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -167,6 +139,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabledCount(["library.add", "library.modify"]) > 0 or settings.SETTING_LIBRARY_TIDY_ENABLED == 1 %}
|
||||||
|
{% if currentUser.featureEnabled("library.add") %}
|
||||||
|
<button class="btn btn-sm btn-success" href="#" id="libraryUploadForm" title="{% trans "Add a new media item to the library" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Media" %}</button>
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new media item to the library via external URL" %}" href="{{ url_for("library.uploadUrl.form") }}"><i class="fa fa-link" aria-hidden="true"></i> {% trans "Add URL" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if settings.SETTING_LIBRARY_TIDY_ENABLED == 1 and currentUser.featureEnabled("library.modify") %}
|
||||||
|
<button class="btn btn-sm btn-warning XiboFormButton btn-tidy" title="{% trans "Run through the library and remove unused and unnecessary files" %}" href="{{ url_for("library.tidy.form") }}"><i class="fa fa-broom" aria-hidden="true"></i> {% trans "Tidy" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="libraryItems" class="table table-striped responsive nowrap" data-content-type="media" data-content-id-name="mediaId" data-state-preference-name="libraryGrid" style="width: 100%;">
|
<table id="libraryItems" class="table table-striped responsive nowrap" data-content-type="media" data-content-id-name="mediaId" data-state-preference-name="libraryGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Menu Boards"|trans }} | {% endblock %}
|
{% block title %}{{ "Menu Boards"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("menuBoard.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new Menu Board" %}" href="{{ url_for("menuBoard.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -66,7 +59,7 @@
|
|||||||
{% set title %}{% trans "Owner" %}{% endset %}
|
{% set title %}{% trans "Owner" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -84,8 +77,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-with-folders-container">
|
<div class="grid-with-folders-container ots-grid-with-folders">
|
||||||
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
|
<div class="grid-folder-tree-container p-3 dashboard-card ots-folder-tree" id="grid-folder-filter">
|
||||||
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
||||||
@@ -96,8 +89,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="folder-controller d-none ots-grid-controller">
|
||||||
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
||||||
|
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
||||||
|
</div>
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("menuBoard.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Menu Board" %}" href="{{ url_for("menuBoard.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Menu Board" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="menuBoards" class="table table-striped responsive nowrap" data-content-type="menuBoard" data-content-id-name="menuId" data-state-preference-name="menuBoardGrid" style="width: 100%;">
|
<table id="menuBoards" class="table table-striped responsive nowrap" data-content-type="menuBoard" data-content-id-name="menuId" data-state-preference-name="menuBoardGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
121
custom/otssignange/views/module-page.twig
Normal file
121
custom/otssignange/views/module-page.twig
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - Module Page
|
||||||
|
* Based on Xibo CMS module-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "Modules"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Modules" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage installed modules." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card">
|
||||||
|
<div class="ots-filter-header">
|
||||||
|
<h3 class="ots-filter-title">{% trans "Filter Modules" %}</h3>
|
||||||
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
||||||
|
<i class="fa fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ots-filter-content collapsed" id="ots-filter-content">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
{% set title %}{% trans "Name" %}{% endset %}
|
||||||
|
{{ inline.input('name', title) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="modules" class="table table-striped" data-state-preference-name="moduleGrid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Name" %}</th>
|
||||||
|
<th>{% trans "Description" %}</th>
|
||||||
|
<th>{% trans "Library Media" %}</th>
|
||||||
|
<th>{% trans "Default Duration" %}</th>
|
||||||
|
<th>{% trans "Preview Enabled" %}</th>
|
||||||
|
<th title="{% trans "Can this module be assigned to a Layout?" %}">{% trans "Assignable" %}</th>
|
||||||
|
<th>{% trans "Enabled" %}</th>
|
||||||
|
<th>{% trans "Errors" %}</th>
|
||||||
|
<th class="rowMenu"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
var table = $('#modules').DataTable({
|
||||||
|
language: dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: false,
|
||||||
|
stateSave: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
responsive: true,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
filter: false,
|
||||||
|
searchDelay: 3000,
|
||||||
|
order: [[ 0, 'asc']],
|
||||||
|
ajax: {
|
||||||
|
url: '{{ url_for("module.search") }}',
|
||||||
|
data: function (d) {
|
||||||
|
$.extend(d, $('#modules').closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{ "data": "name" , responsivePriority: 2},
|
||||||
|
{ "data": "description" },
|
||||||
|
{ "data": "regionSpecific", "render": dataTableTickCrossInverseColumn },
|
||||||
|
{ "data": "defaultDuration" },
|
||||||
|
{ "data": "previewEnabled", "render": dataTableTickCrossColumn },
|
||||||
|
{ "data": "assignable", "render": dataTableTickCrossColumn },
|
||||||
|
{ "data": "enabled", "render": dataTableTickCrossColumn },
|
||||||
|
{ "data": "errors", "render": dataTableTickCrossColumn },
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('draw', dataTableDraw);
|
||||||
|
table.on('processing.dt', dataTableProcessing);
|
||||||
|
dataTableAddButtons(table, $('#modules_wrapper').find('.dataTables_buttons'));
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
function moduleEditFormOpen(dialog) {
|
||||||
|
var moduleSettings = $(dialog).data('extra')['settings'];
|
||||||
|
var $targetContainer = $(dialog).find('.form-module-configure-fields')
|
||||||
|
|
||||||
|
forms.createFields(moduleSettings, $targetContainer);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -29,40 +29,32 @@
|
|||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure the page title/description remain visible when the sidebar is collapsed */
|
/* Page header - always visible and properly positioned */
|
||||||
html.ots-sidebar-collapsed .page-header,
|
.ots-main .page-header,
|
||||||
body.ots-sidebar-collapsed .page-header,
|
.ots-main .page-header h1,
|
||||||
.ots-sidebar.collapsed ~ .ots-main .page-header,
|
.ots-main .page-header p.text-muted {
|
||||||
.ots-main .page-header {
|
|
||||||
display: block !important;
|
display: block !important;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-main .page-header {
|
||||||
|
position: relative !important;
|
||||||
|
z-index: 100 !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
padding-top: 16px !important;
|
padding-top: 16px !important;
|
||||||
padding-bottom: 16px !important;
|
padding-bottom: 16px !important;
|
||||||
|
margin-top: 8px !important;
|
||||||
|
margin-bottom: 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.ots-sidebar-collapsed .page-header h1,
|
|
||||||
body.ots-sidebar-collapsed .page-header h1,
|
|
||||||
.ots-sidebar.collapsed ~ .ots-main .page-header h1,
|
|
||||||
.ots-main .page-header h1 {
|
.ots-main .page-header h1 {
|
||||||
color: var(--color-text-primary) !important;
|
color: var(--color-text-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.ots-sidebar-collapsed .page-header p.text-muted,
|
|
||||||
body.ots-sidebar-collapsed .page-header p.text-muted,
|
|
||||||
.ots-sidebar.collapsed ~ .ots-main .page-header p.text-muted,
|
|
||||||
.ots-main .page-header p.text-muted {
|
.ots-main .page-header p.text-muted {
|
||||||
color: var(--color-text-tertiary) !important;
|
color: var(--color-text-tertiary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure page header is not obscured by panels when sidebar is collapsed */
|
|
||||||
.ots-main .page-header {
|
|
||||||
position: relative !important;
|
|
||||||
z-index: 2500 !important;
|
|
||||||
margin-top: 8px !important;
|
|
||||||
margin-bottom: 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent immediate container clipping near the top */
|
/* Prevent immediate container clipping near the top */
|
||||||
.ots-content,
|
.ots-content,
|
||||||
.ots-main,
|
.ots-main,
|
||||||
@@ -70,37 +62,6 @@ body.ots-sidebar-collapsed .page-header p.text-muted,
|
|||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fixed-position fallback: keep the page header visible and readable when sidebar is collapsed */
|
|
||||||
@media (min-width: 992px) {
|
|
||||||
html.ots-sidebar-collapsed .page-header,
|
|
||||||
body.ots-sidebar-collapsed .page-header,
|
|
||||||
.ots-sidebar.collapsed ~ .ots-main .page-header {
|
|
||||||
position: fixed !important;
|
|
||||||
top: 16px !important;
|
|
||||||
left: calc(var(--ots-sidebar-width,64px) + 24px) !important;
|
|
||||||
/* fallback positions in case the CSS variable isn't set early */
|
|
||||||
left: 80px !important;
|
|
||||||
left: 260px !important;
|
|
||||||
right: 24px !important;
|
|
||||||
z-index: 3200 !important;
|
|
||||||
background: transparent !important;
|
|
||||||
padding-top: 8px !important;
|
|
||||||
padding-bottom: 8px !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If sidebar is collapsed, prefer a smaller left offset */
|
|
||||||
html.ots-sidebar-collapsed .page-header,
|
|
||||||
body.ots-sidebar-collapsed .page-header {
|
|
||||||
left: 80px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.ots-sidebar-collapsed .page-header .ots-filter-header,
|
|
||||||
body.ots-sidebar-collapsed .page-header .ots-filter-header {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
@@ -120,10 +81,10 @@ body {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 260px;
|
width: var(--ots-sidebar-width);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #08132a;
|
background-color: #08132a;
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.06);
|
border-right: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -135,7 +96,6 @@ body {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-left: 260px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-content {
|
.ots-content {
|
||||||
@@ -189,7 +149,7 @@ body {
|
|||||||
.brand-link {
|
.brand-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -216,7 +176,7 @@ body {
|
|||||||
.sidebar-nav {
|
.sidebar-nav {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 72px 0 120px;
|
padding: 64px 0 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extra top padding when sidebar is collapsed or expanded so items clear header */
|
/* Extra top padding when sidebar is collapsed or expanded so items clear header */
|
||||||
@@ -238,7 +198,7 @@ body {
|
|||||||
.ots-sidebar li.sidebar-main > a,
|
.ots-sidebar li.sidebar-main > a,
|
||||||
.ots-sidebar li.sidebar-title > a {
|
.ots-sidebar li.sidebar-title > a {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 20px 1fr;
|
grid-template-columns: 24px 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
column-gap: 12px;
|
column-gap: 12px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
@@ -271,6 +231,16 @@ body {
|
|||||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25);
|
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Collapsed state - ensure active items remain visually distinct */
|
||||||
|
.ots-sidebar.collapsed li.sidebar-list.active > a,
|
||||||
|
.ots-sidebar.collapsed li.sidebar-list > a.active,
|
||||||
|
.ots-sidebar.collapsed li.sidebar-main.active > a,
|
||||||
|
.ots-sidebar.collapsed li.sidebar-main > a.active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.12);
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.06);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.ots-sidebar .ots-nav-icon {
|
.ots-sidebar .ots-nav-icon {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -432,18 +402,19 @@ body {
|
|||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|
||||||
.ots-topbar {
|
.ots-topbar {
|
||||||
background-color: var(--color-surface-elevated);
|
background-color: transparent;
|
||||||
border-bottom: 2px solid var(--color-border);
|
border-bottom: none;
|
||||||
padding: 8px 24px;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
height: 64px;
|
height: 56px;
|
||||||
z-index: 1100;
|
z-index: 1100;
|
||||||
position: sticky;
|
position: relative;
|
||||||
top: 0;
|
width: auto;
|
||||||
width: 100%;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Topbar nav container - override .navbar-nav defaults */
|
/* Topbar nav container - override .navbar-nav defaults */
|
||||||
@@ -452,10 +423,11 @@ body {
|
|||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
gap: 6px;
|
gap: 2px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .nav-item {
|
.ots-topbar .nav-item {
|
||||||
@@ -521,7 +493,7 @@ body {
|
|||||||
/* Ensure content is offset below the sticky topbar when horizontal nav present */
|
/* Ensure content is offset below the sticky topbar when horizontal nav present */
|
||||||
nav.navbar + #content-wrapper,
|
nav.navbar + #content-wrapper,
|
||||||
nav.navbar + #content-wrapper .page-content {
|
nav.navbar + #content-wrapper .page-content {
|
||||||
padding-top: 64px;
|
padding-top: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Right-side controls: notification bell + account menu */
|
/* Right-side controls: notification bell + account menu */
|
||||||
@@ -537,61 +509,239 @@ nav.navbar + #content-wrapper .page-content {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-side .user,
|
/* ============================================================================
|
||||||
.header-side .user-notif,
|
TOPBAR STRIP (sidebar mode) - Clean, Modern Single-Line Menu Bar
|
||||||
.header-side .user-actions {
|
============================================================================ */
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-side .user-actions {
|
/* The topbar strip is the bar at the top of the content area in sidebar mode.
|
||||||
float: right;
|
Layout: [Logo (collapsed only)] ---- [notifications] [user] [hamburger] */
|
||||||
display: flex;
|
.ots-topbar-strip {
|
||||||
align-items: center;
|
position: sticky !important;
|
||||||
gap: 12px;
|
top: 0 !important;
|
||||||
}
|
z-index: 1100 !important;
|
||||||
|
background-color: var(--color-surface-elevated) !important;
|
||||||
.header-side .user-actions > * {
|
border-bottom: 1px solid var(--color-border) !important;
|
||||||
display: inline-flex !important;
|
height: 56px !important;
|
||||||
align-items: center;
|
min-height: 56px !important;
|
||||||
|
max-height: 56px !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-side .user-actions li,
|
/* When sidebar is expanded, make the topbar background subtler */
|
||||||
.header-side .user-actions .nav-item,
|
body:not(.ots-sidebar-collapsed) .ots-topbar-strip {
|
||||||
.header-side .user-actions .dropdown,
|
background-color: var(--color-background) !important;
|
||||||
.header-side .user-actions .item,
|
box-shadow: none !important;
|
||||||
.header-side .user-actions .nav-link {
|
border-bottom-color: var(--color-border) !important;
|
||||||
display: inline-flex !important;
|
|
||||||
align-items: center;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-side .user-actions img.nav-avatar {
|
/* Inner flex container */
|
||||||
display: block;
|
.ots-topbar-inner {
|
||||||
width: 36px;
|
display: flex !important;
|
||||||
height: 36px;
|
align-items: center !important;
|
||||||
}
|
justify-content: space-between !important;
|
||||||
|
height: 56px !important;
|
||||||
.header-side .user-actions .dropdown-menu {
|
padding: 0 16px !important;
|
||||||
right: 0;
|
margin: 0 !important;
|
||||||
left: auto;
|
gap: 12px !important;
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure header area does not clip absolutely positioned dropdowns */
|
|
||||||
.row.header.header-side,
|
|
||||||
.row.header.header-side .col-sm-12,
|
|
||||||
.row.header.header-side .user-actions {
|
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure user dropdown renders above other content */
|
/* Left side: logo (only visible when sidebar collapsed) */
|
||||||
.header-side .user-actions .dropdown-menu,
|
.ots-topbar-left {
|
||||||
.ots-user-menu {
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 12px !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .xibo-logo-container {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 10px !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .xibo-logo {
|
||||||
|
width: 28px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
object-fit: contain !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .xibo-logo-text {
|
||||||
|
display: inline-flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .brand-line-top {
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 15px !important;
|
||||||
|
color: var(--color-text-primary) !important;
|
||||||
|
letter-spacing: 0.02em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .brand-line-bottom {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
color: var(--color-text-secondary) !important;
|
||||||
|
margin-top: -1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right side: actions cluster */
|
||||||
|
.ots-topbar-right {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 4px !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each action item (notification bell, user avatar) */
|
||||||
|
.ots-topbar-action {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
position: static !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-action .nav-item,
|
||||||
|
.ots-topbar-action .dropdown,
|
||||||
|
.ots-topbar-action > div {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
position: static !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notification & user menu links */
|
||||||
|
.ots-topbar-action .nav-link {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 36px !important;
|
||||||
|
height: 36px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
color: var(--color-text-secondary) !important;
|
||||||
|
transition: all 150ms ease !important;
|
||||||
|
border: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-action .nav-link:hover {
|
||||||
|
background-color: rgba(59, 130, 246, 0.08) !important;
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bell icon */
|
||||||
|
.ots-topbar-action .ots-topbar-icon {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 18px !important;
|
||||||
|
height: 18px !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avatar in topbar */
|
||||||
|
.ots-topbar-action img.nav-avatar {
|
||||||
|
display: block !important;
|
||||||
|
width: 32px !important;
|
||||||
|
height: 32px !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
object-fit: cover !important;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
transition: border-color 150ms ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-action .nav-link:hover img.nav-avatar {
|
||||||
|
border-color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown menus from the topbar */
|
||||||
|
.ots-topbar-action .dropdown-menu,
|
||||||
|
.ots-topbar-strip .dropdown-menu {
|
||||||
|
position: absolute !important;
|
||||||
|
top: 100% !important;
|
||||||
|
right: 0 !important;
|
||||||
|
left: auto !important;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
min-width: 200px !important;
|
||||||
|
background-color: var(--color-surface-elevated) !important;
|
||||||
|
border: 1px solid var(--color-border) !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25) !important;
|
||||||
|
padding: 6px 0 !important;
|
||||||
z-index: 3000 !important;
|
z-index: 3000 !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .dropdown-item,
|
||||||
|
.ots-topbar-strip .dropdown-menu a {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 8px !important;
|
||||||
|
padding: 8px 14px !important;
|
||||||
|
margin: 1px 6px !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
color: var(--color-text-secondary) !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
transition: all 150ms ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .dropdown-item:hover,
|
||||||
|
.ots-topbar-strip .dropdown-menu a:hover {
|
||||||
|
background-color: rgba(59, 130, 246, 0.08) !important;
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .dropdown-header {
|
||||||
|
padding: 8px 14px 4px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
color: var(--color-text-tertiary) !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 0.04em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-strip .dropdown-divider {
|
||||||
|
margin: 4px 0 !important;
|
||||||
|
border-top-color: var(--color-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hamburger button */
|
||||||
|
.ots-topbar-hamburger {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 36px !important;
|
||||||
|
height: 36px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
color: var(--color-text-secondary) !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
transition: all 150ms ease !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ots-topbar-hamburger:hover {
|
||||||
|
background-color: rgba(59, 130, 246, 0.08) !important;
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure no clipping */
|
||||||
|
.ots-topbar-strip,
|
||||||
|
.ots-topbar-strip .col-sm-12,
|
||||||
|
.ots-topbar-strip .ots-topbar-right,
|
||||||
|
.ots-topbar-strip .ots-topbar-action {
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When JS decides to open to the left (avoid viewport overflow) */
|
/* When JS decides to open to the left (avoid viewport overflow) */
|
||||||
@@ -600,66 +750,54 @@ nav.navbar + #content-wrapper .page-content {
|
|||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When JS wants explicit left-aligned menu (menu's left edge aligned to trigger's left) */
|
/* When JS wants explicit left-aligned menu */
|
||||||
.dropdown-menu-left-align {
|
.dropdown-menu-left-align {
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
right: auto !important;
|
right: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Force header row into a flex container so right-side controls align horizontally */
|
/* Topbar strip responsive */
|
||||||
.row.header.header-side {
|
@media (max-width: 768px) {
|
||||||
position: relative;
|
.ots-topbar-strip {
|
||||||
z-index: 10;
|
height: 48px !important;
|
||||||
}
|
min-height: 48px !important;
|
||||||
|
max-height: 48px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.row.header.header-side .col-sm-12 {
|
.ots-topbar-inner {
|
||||||
display: flex !important;
|
height: 48px !important;
|
||||||
align-items: center !important;
|
padding: 0 12px !important;
|
||||||
justify-content: flex-start !important;
|
gap: 8px !important;
|
||||||
gap: 12px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure notification and user li elements render inline in header */
|
.ots-topbar-strip .brand-line-top {
|
||||||
.header-side li.dropdown.nav-item.item,
|
font-size: 13px !important;
|
||||||
.header-side .dropdown.nav-item.item,
|
}
|
||||||
.header-side .user-actions > li,
|
|
||||||
.header-side .user-actions > .dropdown,
|
|
||||||
.header-side .user-actions > .nav-item {
|
|
||||||
display: inline-flex !important;
|
|
||||||
float: none !important;
|
|
||||||
vertical-align: middle !important;
|
|
||||||
margin: 0 8px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-side .nav-link {
|
.ots-topbar-strip .brand-line-bottom {
|
||||||
display: inline-flex !important;
|
font-size: 10px !important;
|
||||||
align-items: center !important;
|
}
|
||||||
padding: 4px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-side .ots-topbar-icon,
|
.ots-topbar-strip .xibo-logo {
|
||||||
.header-side .nav-avatar,
|
width: 24px !important;
|
||||||
.header-side img.nav-avatar {
|
height: 24px !important;
|
||||||
display: inline-block !important;
|
}
|
||||||
vertical-align: middle !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Push user actions to the right and maintain flex layout */
|
.ots-topbar-action .nav-link,
|
||||||
.row.header.header-side .meta {
|
.ots-topbar-hamburger {
|
||||||
flex: 0 0 auto;
|
width: 32px !important;
|
||||||
}
|
height: 32px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.row.header.header-side .user-actions {
|
.ots-topbar-action img.nav-avatar {
|
||||||
margin-left: auto;
|
width: 28px !important;
|
||||||
display: flex !important;
|
height: 28px !important;
|
||||||
align-items: center !important;
|
}
|
||||||
gap: 12px !important;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure sidebar items are visible and above header when sidebar is collapsed */
|
/* Ensure sidebar items are visible and above header when sidebar is collapsed */
|
||||||
.ots-sidebar.collapsed {
|
.ots-sidebar.collapsed {
|
||||||
z-index: 20;
|
z-index: 1200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-sidebar.collapsed .ots-nav-icon {
|
.ots-sidebar.collapsed .ots-nav-icon {
|
||||||
@@ -676,7 +814,7 @@ nav.navbar + #content-wrapper .page-content {
|
|||||||
|
|
||||||
.ots-topbar .nav-item.open .nav-link,
|
.ots-topbar .nav-item.open .nav-link,
|
||||||
.ots-topbar .nav-item.active .nav-link {
|
.ots-topbar .nav-item.active .nav-link {
|
||||||
background-color: rgba(59, 130, 246, 0.12);
|
background-color: rgba(59, 130, 246, 0.1);
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -684,14 +822,14 @@ nav.navbar + #content-wrapper .page-content {
|
|||||||
.ots-topbar .dropdown-toggle::after {
|
.ots-topbar .dropdown-toggle::after {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 6px;
|
margin-left: 4px;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-left: 4px solid transparent;
|
border-left: 3.5px solid transparent;
|
||||||
border-right: 4px solid transparent;
|
border-right: 3.5px solid transparent;
|
||||||
border-top: 4px solid currentColor;
|
border-top: 3.5px solid currentColor;
|
||||||
opacity: 0.6;
|
opacity: 0.5;
|
||||||
transition: transform var(--transition-fast);
|
transition: transform 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .nav-item.open .dropdown-toggle::after {
|
.ots-topbar .nav-item.open .dropdown-toggle::after {
|
||||||
@@ -699,14 +837,14 @@ nav.navbar + #content-wrapper .page-content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .dropdown-menu {
|
.ots-topbar .dropdown-menu {
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
background-color: var(--color-surface);
|
background-color: var(--color-surface-elevated);
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
z-index: 1100;
|
z-index: 3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .dropdown-item,
|
.ots-topbar .dropdown-item,
|
||||||
@@ -714,18 +852,18 @@ nav.navbar + #content-wrapper .page-content {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
padding: 8px 14px;
|
padding: 8px 12px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin: 2px 6px;
|
margin: 1px 6px;
|
||||||
transition: all var(--transition-fast);
|
transition: all 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .dropdown-item:hover,
|
.ots-topbar .dropdown-item:hover,
|
||||||
.ots-topbar .dropdown-menu a:hover {
|
.ots-topbar .dropdown-menu a:hover {
|
||||||
background-color: rgba(59, 130, 246, 0.1);
|
background-color: rgba(59, 130, 246, 0.08);
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1042,20 +1180,16 @@ nav.navbar + #content-wrapper .page-content {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When the sidebar is collapsed, hide the page header and meta area to
|
/* Page header should always be visible - ensure this is displayed even when sidebar is collapsed */
|
||||||
provide a compact layout consistent with the collapsed navigation state. */
|
body.ots-sidebar-collapsed .ots-main .page-header,
|
||||||
.ots-sidebar.collapsed ~ .ots-main .page .meta,
|
body.ots-sidebar-collapsed .page-header,
|
||||||
.ots-sidebar.collapsed ~ .ots-main .page-header,
|
.page-header {
|
||||||
.ots-sidebar.collapsed ~ .ots-main .header-side,
|
display: block !important;
|
||||||
.ots-sidebar.collapsed + .ots-main .page .meta,
|
margin-left: 0 !important;
|
||||||
.ots-sidebar.collapsed + .ots-main .page-header,
|
margin-right: 0 !important;
|
||||||
.ots-sidebar-collapsed .ots-main .page .meta,
|
margin-bottom: 16px !important;
|
||||||
.ots-sidebar-collapsed .ots-main .page-header,
|
padding-top: 16px !important;
|
||||||
body.ots-sidebar-collapsed .page .meta,
|
padding-bottom: 16px !important;
|
||||||
body.ots-sidebar-collapsed .page-header {
|
|
||||||
display: none !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header h1 {
|
.page-header h1 {
|
||||||
@@ -1446,6 +1580,7 @@ body .panel .panel-heading,
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-displays-body {
|
.ots-displays-body {
|
||||||
@@ -1453,6 +1588,7 @@ body .panel .panel-heading,
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-displays-body .XiboGrid {
|
.ots-displays-body .XiboGrid {
|
||||||
@@ -1460,6 +1596,7 @@ body .panel .panel-heading,
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-displays-title {
|
.ots-displays-title {
|
||||||
@@ -3121,6 +3258,12 @@ hr {
|
|||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Override Bootstrap col padding inside page-content */
|
||||||
|
.page-content > .row > .col-sm-12 {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
NAVBAR / TOPBAR
|
NAVBAR / TOPBAR
|
||||||
============================================================================= */
|
============================================================================= */
|
||||||
@@ -3128,8 +3271,15 @@ hr {
|
|||||||
.navbar,
|
.navbar,
|
||||||
.navbar-default {
|
.navbar-default {
|
||||||
background: var(--ots-surface-2);
|
background: var(--ots-surface-2);
|
||||||
border: 1px solid var(--ots-border);
|
border: none;
|
||||||
box-shadow: var(--ots-shadow-sm);
|
border-bottom: 1px solid var(--ots-border);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
|
height: 56px;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 0 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand,
|
.navbar-brand,
|
||||||
@@ -3160,7 +3310,7 @@ hr {
|
|||||||
border: 0;
|
border: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
gap: 6px;
|
gap: 2px;
|
||||||
height: auto;
|
height: auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -3168,25 +3318,27 @@ hr {
|
|||||||
.ots-topbar .nav-link {
|
.ots-topbar .nav-link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
padding: 8px 12px;
|
padding: 6px 10px;
|
||||||
border-radius: 10px;
|
border-radius: 6px;
|
||||||
color: var(--ots-text);
|
color: var(--ots-text);
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
transition: background var(--ots-transition), color var(--ots-transition);
|
font-size: 13px;
|
||||||
|
transition: background 150ms ease, color 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .nav-link:hover,
|
.ots-topbar .nav-link:hover,
|
||||||
.ots-topbar .nav-item.open .nav-link,
|
.ots-topbar .nav-item.open .nav-link,
|
||||||
.ots-topbar .nav-item.active .nav-link {
|
.ots-topbar .nav-item.active .nav-link {
|
||||||
background: rgba(79, 140, 255, 0.18);
|
background: rgba(79, 140, 255, 0.12);
|
||||||
color: var(--ots-primary);
|
color: var(--ots-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .dropdown-menu {
|
.ots-topbar .dropdown-menu {
|
||||||
border-radius: 12px;
|
border-radius: 10px;
|
||||||
padding: 8px;
|
padding: 6px 0;
|
||||||
box-shadow: var(--ots-shadow-md);
|
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
||||||
|
border: 1px solid var(--ots-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar .dropdown-item,
|
.ots-topbar .dropdown-item,
|
||||||
@@ -3194,8 +3346,10 @@ hr {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
padding: 8px 10px;
|
padding: 8px 12px;
|
||||||
|
margin: 1px 6px;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ots-topbar-icon {
|
.ots-topbar-icon {
|
||||||
|
|||||||
@@ -24,14 +24,7 @@
|
|||||||
{% extends "authed.twig" %}
|
{% extends "authed.twig" %}
|
||||||
{% import "inline.twig" as inline %}
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("playersoftware.add") %}
|
|
||||||
<button class="btn btn-icon btn-success" href="#" id="playerSoftwareUploadForm" title="{% trans "Upload a new Player Software file" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
@@ -67,6 +60,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("playersoftware.add") %}
|
||||||
|
<button class="btn btn-sm btn-success" href="#" id="playerSoftwareUploadForm" title="{% trans "Upload a new Player Software file" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Upload Software" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="playerSoftwareItems" class="table table-striped" data-state-preference-name="playerSoftwareGrid">
|
<table id="playerSoftwareItems" class="table table-striped" data-state-preference-name="playerSoftwareGrid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -23,14 +23,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Playlists"|trans }} | {% endblock %}
|
{% block title %}{{ "Playlists"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("playlist.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add Playlist" %}" href="{{ url_for("playlist.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -78,7 +71,7 @@
|
|||||||
{% set title %}{% trans "Owner" %}{% endset %}
|
{% set title %}{% trans "Owner" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by the selected User." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -94,7 +87,7 @@
|
|||||||
{% set title %}{% trans "Owner User Group" %}{% endset %}
|
{% set title %}{% trans "Owner User Group" %}{% endset %}
|
||||||
{% set helpText %}{% trans "Show items owned by users in the selected User Group." %}{% endset %}
|
{% set helpText %}{% trans "Show items owned by users in the selected User Group." %}{% endset %}
|
||||||
{% set attributes = [
|
{% set attributes = [
|
||||||
{ name: "data-width", value: "200px" },
|
{ name: "data-width", value: "100%" },
|
||||||
{ name: "data-allow-clear", value: "true" },
|
{ name: "data-allow-clear", value: "true" },
|
||||||
{ name: "data-placeholder--id", value: null },
|
{ name: "data-placeholder--id", value: null },
|
||||||
{ name: "data-placeholder--value", value: "" },
|
{ name: "data-placeholder--value", value: "" },
|
||||||
@@ -145,6 +138,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("playlist.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add Playlist" %}" href="{{ url_for("playlist.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Playlist" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="playlists" class="table table-striped" data-content-type="playlist"
|
<table id="playlists" class="table table-striped" data-content-type="playlist"
|
||||||
data-content-id-name="playlistId" data-state-preference-name="playlistGrid" style="width: 100%;">
|
data-content-id-name="playlistId" data-state-preference-name="playlistGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Resolutions"|trans }} | {% endblock %}
|
{% block title %}{{ "Resolutions"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("resolution.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new resolution for use on layouts" %}" href="{{ url_for("resolution.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
@@ -65,6 +58,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("resolution.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new resolution for use on layouts" %}" href="{{ url_for("resolution.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i> {% trans "Add Resolution" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="resolutions" class="table table-striped" data-state-preference-name="resolutionGrid">
|
<table id="resolutions" class="table table-striped" data-state-preference-name="resolutionGrid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -26,16 +26,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Schedule"|trans }} | {% endblock %}
|
{% block title %}{{ "Schedule"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("schedule.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new Scheduled event" %}"
|
|
||||||
href="{{ url_for("schedule.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -129,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% set title %}{% trans "Displays" %}{% endset %}
|
{% set title %}{% trans "Displays" %}{% endset %}
|
||||||
<div class="form-group mr-1 mb-1 pagedSelect" style="min-width: 200px">
|
<div class="form-group mr-1 mb-1 pagedSelect">
|
||||||
<label class="control-label mr-1" for="DisplayList" title=""
|
<label class="control-label mr-1" for="DisplayList" title=""
|
||||||
accesskey="">{{ title }}</label>
|
accesskey="">{{ title }}</label>
|
||||||
<select id="DisplayList" class="form-control" name="displaySpecificGroupIds[]"
|
<select id="DisplayList" class="form-control" name="displaySpecificGroupIds[]"
|
||||||
@@ -147,7 +138,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% set title %}{% trans "Display Groups" %}{% endset %}
|
{% set title %}{% trans "Display Groups" %}{% endset %}
|
||||||
<div class="form-group mr-2 mb-1 pagedSelect" style="min-width: 200px">
|
<div class="form-group mr-2 mb-1 pagedSelect">
|
||||||
<label class="control-label mr-1" for="DisplayGroupList" title=""
|
<label class="control-label mr-1" for="DisplayGroupList" title=""
|
||||||
accesskey="">{{ title }}</label>
|
accesskey="">{{ title }}</label>
|
||||||
<select id="DisplayGroupList" class="form-control" name="displayGroupIds[]"
|
<select id="DisplayGroupList" class="form-control" name="displayGroupIds[]"
|
||||||
@@ -206,6 +197,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="XiboSchedule card dashboard-card ots-table-card">
|
<div class="XiboSchedule card dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("schedule.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Scheduled event" %}" href="{{ url_for("schedule.add.form") }}"><i class="fa fa-plus" aria-hidden="true"></i> {% trans "Add Event" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<ul class="nav nav-tabs card-header-tabs">
|
<ul class="nav nav-tabs card-header-tabs">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|||||||
1253
custom/otssignange/views/settings-page.twig
Normal file
1253
custom/otssignange/views/settings-page.twig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Sync Groups"|trans }} | {% endblock %}
|
{% block title %}{{ "Sync Groups"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("display.syncAdd") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new Sync Group" %}" href="{{ url_for("syncgroup.form.add") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -87,6 +80,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("display.syncAdd") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Sync Group" %}" href="{{ url_for("syncgroup.form.add") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Sync Group" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="syncgroups" class="table table-striped" data-content-type="syncGroup" data-content-id-name="syncGroupId" data-state-preference-name="syncGroupGrid" style="width: 100%;">
|
<table id="syncgroups" class="table table-striped" data-content-type="syncGroup" data-content-id-name="syncGroupId" data-state-preference-name="syncGroupGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
171
custom/otssignange/views/tag-page.twig
Normal file
171
custom/otssignange/views/tag-page.twig
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - Tag Page
|
||||||
|
* Based on Xibo CMS tag-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "Tags"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Tags" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage content tags." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}" data-grid-name="tagView">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card">
|
||||||
|
<div class="ots-filter-header">
|
||||||
|
<h3 class="ots-filter-title">{% trans "Filter Tags" %}</h3>
|
||||||
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
||||||
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ots-filter-content" id="ots-filter-content">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
{% set title %}{% trans "ID" %}{% endset %}
|
||||||
|
{{ inline.number("tagId", title) }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "Name" %}{% endset %}
|
||||||
|
{{ inline.inputNameGrid('tag', title) }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "Show System tags?" %}{% endset %}
|
||||||
|
{{ inline.checkbox("isSystem", title, 0) }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "Show only tags with values?" %}{% endset %}
|
||||||
|
{{ inline.checkbox("haveOptions", title, 0) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Tag" %}" href="{{ url_for("tag.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Tag" %}</button>
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="tags" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "ID" %}</th>
|
||||||
|
<th>{% trans "Name" %}</th>
|
||||||
|
<th>{% trans "isRequired" %}</th>
|
||||||
|
<th>{% trans "Values" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
var table = $("#tags").DataTable({
|
||||||
|
"language": dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
responsive: true,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
filter: false,
|
||||||
|
searchDelay: 3000,
|
||||||
|
"order": [[ 1, "desc"]],
|
||||||
|
ajax: {
|
||||||
|
"url": "{{ url_for("tag.search") }}",
|
||||||
|
"data": function(d) {
|
||||||
|
$.extend(d, $("#tags").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{ "data": "tagId", responsivePriority: 2 },
|
||||||
|
{ "data": "tag", responsivePriority: 2 },
|
||||||
|
{
|
||||||
|
"data": "isRequired",
|
||||||
|
responsivePriority: 3,
|
||||||
|
"render": function (data, type, row) {
|
||||||
|
if (type != "display") {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon = "";
|
||||||
|
if (data == 1)
|
||||||
|
icon = "fa-check";
|
||||||
|
else if (data == 0)
|
||||||
|
icon = "fa-times";
|
||||||
|
|
||||||
|
return "<span class='fa " + icon + "'></span>";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "options",
|
||||||
|
responsivePriority: 3,
|
||||||
|
"render": function (data, type, row) {
|
||||||
|
if (type != "display") {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('draw', dataTableDraw);
|
||||||
|
table.on('processing.dt', dataTableProcessing);
|
||||||
|
dataTableAddButtons(table, $('#tags_wrapper').find('.dataTables_buttons'), false);
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
function usageFormOpen(dialog) {
|
||||||
|
const $tagUsageTable = $("#tagUsageTable");
|
||||||
|
var usageTable = $tagUsageTable.DataTable({
|
||||||
|
"language": dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true, stateDuration: 0,
|
||||||
|
filter: false,
|
||||||
|
searchDelay: 3000,
|
||||||
|
responsive: true,
|
||||||
|
"order": [[1, "asc"]],
|
||||||
|
ajax: {
|
||||||
|
"url": "{{ url_for("tag.usage", {id: ':id'}) }}".replace(":id", $tagUsageTable.data().tagId),
|
||||||
|
"data": function(data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{ "data": "entityId"},
|
||||||
|
{ "data": "type"},
|
||||||
|
{ "data": "name" },
|
||||||
|
{ "data": "value" }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
usageTable.on('draw', dataTableDraw);
|
||||||
|
usageTable.on('processing.dt', dataTableProcessing);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
177
custom/otssignange/views/task-page.twig
Normal file
177
custom/otssignange/views/task-page.twig
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - Task Page
|
||||||
|
* Based on Xibo CMS task-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "Tasks"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Tasks" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage scheduled system tasks." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card" style="display:none;">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if settings.TASK_CONFIG_LOCKED_CHECKB == 0 or settings.TASK_CONFIG_LOCKED_CHECKB == "Unchecked" %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" href="{{ url_for("task.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Task" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="tasks" class="table table-striped" data-state-preference-name="taskGrid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "ID" %}</th>
|
||||||
|
<th>{% trans "Name" %}</th>
|
||||||
|
<th>{% trans "Active" %}</th>
|
||||||
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "Next Run" %}</th>
|
||||||
|
<th>{% trans "Run Now" %}</th>
|
||||||
|
<th>{% trans "Last Run" %}</th>
|
||||||
|
<th>{% trans "Last Status" %}</th>
|
||||||
|
<th>{% trans "Last Duration" %}</th>
|
||||||
|
<th class="rowMenu"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
var table = $("#tasks").DataTable({
|
||||||
|
"language": dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true,
|
||||||
|
responsive: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
filter: false,
|
||||||
|
searchDelay: 3000,
|
||||||
|
"order": [[ 1, "asc"]],
|
||||||
|
ajax: {
|
||||||
|
"url": "{{ url_for("task.search") }}",
|
||||||
|
"data": function(d) {
|
||||||
|
$.extend(d, $("#tasks").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{ "data": "taskId" , responsivePriority: 2},
|
||||||
|
{ "data": "name" , responsivePriority: 2},
|
||||||
|
{
|
||||||
|
"data": "isActive",
|
||||||
|
responsivePriority: 2,
|
||||||
|
"render": dataTableTickCrossColumn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "status",
|
||||||
|
"render": function (data, type, row) {
|
||||||
|
if (type !== "display")
|
||||||
|
return data;
|
||||||
|
|
||||||
|
var icon = "";
|
||||||
|
var title = "";
|
||||||
|
if (data === 1) {
|
||||||
|
if (moment(row.lastRunStartDt, "X").tz) {
|
||||||
|
title = "PID: " + row.pid + " (" + moment(row.lastRunStartDt, "X").tz(timezone).format(jsDateFormat) + ")";
|
||||||
|
} else {
|
||||||
|
title = "PID: " + row.pid + " (" + moment(row.lastRunStartDt, "X").format(jsDateFormat) + ")";
|
||||||
|
}
|
||||||
|
icon = "fa-cogs";
|
||||||
|
}
|
||||||
|
else if (data === 3) {
|
||||||
|
title = "Exit: " + row.lastRunExitCode;
|
||||||
|
icon = "fa-bug";
|
||||||
|
}
|
||||||
|
else if (data === 5) {
|
||||||
|
title = "Time out";
|
||||||
|
icon = "fa-hourglass-o";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
title = "";
|
||||||
|
icon = "fa-clock-o";
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<span class="fa ' + icon + '" title="' + title + '"></span>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "nextRunDt",
|
||||||
|
"orderable": false,
|
||||||
|
"render": dataTableDateFromUnix
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "runNow",
|
||||||
|
"render": dataTableTickCrossColumn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "lastRunDt",
|
||||||
|
"render": dataTableDateFromUnix
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "lastRunStatus",
|
||||||
|
"render": function (data, type, row) {
|
||||||
|
if (type !== "display")
|
||||||
|
return data;
|
||||||
|
|
||||||
|
var icon = "";
|
||||||
|
if (data === 4)
|
||||||
|
icon = "fa-check";
|
||||||
|
else
|
||||||
|
icon = "fa-times";
|
||||||
|
|
||||||
|
return '<span class="fa ' + icon + '" title="' +
|
||||||
|
((row.lastRunMessage === null) ? "" : row.lastRunMessage) + '"></span>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "lastRunDuration",
|
||||||
|
"render": function (data, type, row) {
|
||||||
|
if (type !== "display")
|
||||||
|
return data;
|
||||||
|
|
||||||
|
return (data === null) ? 0 : moment().startOf("day").seconds(data).format("H:mm:ss");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('draw', dataTableDraw);
|
||||||
|
table.on('processing.dt', dataTableProcessing);
|
||||||
|
dataTableAddButtons(table, $('#tasks_wrapper').find('.dataTables_buttons'));
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -25,14 +25,7 @@
|
|||||||
|
|
||||||
{% block title %}{{ "Templates"|trans }} | {% endblock %}
|
{% block title %}{{ "Templates"|trans }} | {% endblock %}
|
||||||
|
|
||||||
{% block actionMenu %}
|
{% block actionMenu %}{% endblock %}
|
||||||
<div class="widget-action-menu pull-right">
|
|
||||||
{% if currentUser.featureEnabled("template.add") %}
|
|
||||||
<button class="btn btn-icon btn-success XiboFormButton" title="{% trans "Add a new Template and jump to the layout editor." %}" href="{{ url_for("template.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
|
||||||
{% endif %}
|
|
||||||
<button class="btn btn-icon btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="ots-displays-page">
|
<div class="ots-displays-page">
|
||||||
@@ -69,8 +62,8 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-with-folders-container">
|
<div class="grid-with-folders-container ots-grid-with-folders">
|
||||||
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
|
<div class="grid-folder-tree-container p-3 dashboard-card ots-folder-tree" id="grid-folder-filter">
|
||||||
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
|
||||||
@@ -81,12 +74,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="container-folder-tree"></div>
|
<div id="container-folder-tree"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-controller d-none">
|
<div class="folder-controller d-none ots-grid-controller">
|
||||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
|
||||||
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
<div id="breadcrumbs" class="mt-2 pl-2"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="datatable-container">
|
<div id="datatable-container">
|
||||||
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
<div class="XiboData card py-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.featureEnabled("template.add") %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Template and jump to the layout editor." %}" href="{{ url_for("template.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Template" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
<table id="templates" class="table table-striped" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="templateGrid" style="width: 100%;">
|
<table id="templates" class="table table-striped" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="templateGrid" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -30,44 +30,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measure sidebar width and set CSS variable for layout
|
* Sidebar width is now handled purely by CSS classes (.ots-sidebar-collapsed).
|
||||||
|
* This function is kept as a no-op for backward compatibility.
|
||||||
*/
|
*/
|
||||||
function updateSidebarWidth() {
|
function updateSidebarWidth() {
|
||||||
const sidebar = document.querySelector('.ots-sidebar');
|
// No-op: CSS handles layout via body.ots-sidebar-collapsed class
|
||||||
if (!sidebar) return;
|
|
||||||
const collapsed = sidebar.classList.contains('collapsed');
|
|
||||||
// If called with a forced mode, use the stored defaults
|
|
||||||
const forceMode = updateSidebarWidth._forceMode || null;
|
|
||||||
const base = (forceMode === 'full') ? (window.__otsFullSidebarWidth || 256)
|
|
||||||
: (forceMode === 'collapsed') ? (window.__otsCollapsedSidebarWidth || 70)
|
|
||||||
: (collapsed ? 70 : sidebar.offsetWidth || sidebar.getBoundingClientRect().width || 240);
|
|
||||||
const padding = 5;
|
|
||||||
const value = Math.max(70, Math.round(base + padding));
|
|
||||||
// Apply CSS variable used by layout and also set an inline width fallback
|
|
||||||
document.documentElement.style.setProperty('--ots-sidebar-width', `${value}px`);
|
|
||||||
try {
|
|
||||||
// Inline width helps force an immediate reflow when CSS rules/important flags interfere
|
|
||||||
// Use setProperty with 'important' so stylesheet !important rules can't override it.
|
|
||||||
sidebar.style.setProperty('width', `${value}px`, 'important');
|
|
||||||
// Force reflow to encourage the browser to apply the new sizing immediately
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
sidebar.offsetHeight;
|
|
||||||
} catch (err) {
|
|
||||||
try { sidebar.style.width = `${value}px`; } catch (e) { /* ignore */ }
|
|
||||||
}
|
|
||||||
// Debug logging to help identify timing/specifity issues in the wild
|
|
||||||
if (window.__otsDebug) {
|
if (window.__otsDebug) {
|
||||||
console.log('[OTS] updateSidebarWidth', { collapsed, base, value, cssVar: getComputedStyle(document.documentElement).getPropertyValue('--ots-sidebar-width') });
|
const sidebar = document.querySelector('.ots-sidebar');
|
||||||
|
const collapsed = sidebar ? sidebar.classList.contains('collapsed') : false;
|
||||||
|
console.log('[OTS] updateSidebarWidth (no-op, CSS-driven)', { collapsed });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to request a forced width update
|
|
||||||
function forceSidebarWidthMode(mode) {
|
|
||||||
updateSidebarWidth._forceMode = mode; // 'full' | 'collapsed' | null
|
|
||||||
updateSidebarWidth();
|
|
||||||
updateSidebarWidth._forceMode = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measure the sidebar header bottom and set the top padding of the nav list
|
* Measure the sidebar header bottom and set the top padding of the nav list
|
||||||
* so nav items always begin below the header (logo + buttons).
|
* so nav items always begin below the header (logo + buttons).
|
||||||
@@ -106,86 +80,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measure the sidebar and set an explicit left margin on the page wrapper
|
* DISABLED: Cleanup function to remove inline styles that were forcing incorrect margins
|
||||||
* so the gap between the sidebar and page content is exactly 5px.
|
* The sidebar layout is now controlled entirely by CSS variables and margin-left.
|
||||||
*/
|
*/
|
||||||
function updateSidebarGap() {
|
function updateSidebarGap() {
|
||||||
const sidebar = document.querySelector('.ots-sidebar');
|
// This function is intentionally left minimal.
|
||||||
// target likely content containers in this app
|
// Spacing is now handled by CSS: .ots-main { margin-left: var(--ots-sidebar-width) }
|
||||||
|
// Removing any inline margin-left or padding-left that may have been set previously
|
||||||
const targets = [
|
const targets = [
|
||||||
document.getElementById('page-wrapper'),
|
document.getElementById('page-wrapper'),
|
||||||
document.querySelector('.ots-main'),
|
document.querySelector('.ots-main'),
|
||||||
document.getElementById('content-wrapper'),
|
document.getElementById('content-wrapper'),
|
||||||
document.querySelector('#content')
|
document.querySelector('#content')
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
if (!sidebar || !targets.length) return;
|
|
||||||
|
|
||||||
const gap = (typeof window.__otsDesiredSidebarGap !== 'undefined') ? Number(window.__otsDesiredSidebarGap) : 0; // desired gap in px (default 0)
|
|
||||||
const rect = sidebar.getBoundingClientRect();
|
|
||||||
|
|
||||||
// desired inner left padding (allows trimming space inside the content area)
|
|
||||||
const desiredInnerPadding = (typeof window.__otsDesiredPagePaddingLeft !== 'undefined') ? Number(window.__otsDesiredPagePaddingLeft) : 8;
|
|
||||||
|
|
||||||
targets.forEach(pageWrapper => {
|
targets.forEach(pageWrapper => {
|
||||||
const pageRect = pageWrapper.getBoundingClientRect();
|
|
||||||
const computed = window.getComputedStyle(pageWrapper);
|
|
||||||
const currentMargin = parseFloat(computed.marginLeft) || 0;
|
|
||||||
const currentGap = Math.round(pageRect.left - rect.right);
|
|
||||||
// Calculate how much to adjust margin-left so gap becomes `gap`.
|
|
||||||
const delta = currentGap - gap;
|
|
||||||
const newMargin = Math.max(0, Math.round(currentMargin - delta));
|
|
||||||
try {
|
try {
|
||||||
pageWrapper.style.setProperty('margin-left', `${newMargin}px`, 'important');
|
pageWrapper.style.removeProperty('margin-left');
|
||||||
pageWrapper.style.setProperty('padding-left', `${desiredInnerPadding}px`, 'important');
|
pageWrapper.style.removeProperty('padding-left');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
pageWrapper.style.marginLeft = `${newMargin}px`;
|
pageWrapper.style.marginLeft = '';
|
||||||
pageWrapper.style.paddingLeft = `${desiredInnerPadding}px`;
|
pageWrapper.style.paddingLeft = '';
|
||||||
}
|
}
|
||||||
// Also adjust common child wrapper padding if present
|
// Also remove from common child wrappers
|
||||||
try {
|
try {
|
||||||
const inner = pageWrapper.querySelector('.page-content') || pageWrapper.querySelector('.ots-content') || pageWrapper.querySelector('.container');
|
const inner = pageWrapper.querySelector('.page-content') || pageWrapper.querySelector('.ots-content') || pageWrapper.querySelector('.container');
|
||||||
if (inner) inner.style.setProperty('padding-left', `${desiredInnerPadding}px`, 'important');
|
if (inner) {
|
||||||
} catch (err) {}
|
inner.style.removeProperty('padding-left');
|
||||||
if (window.__otsDebug) console.log('[OTS] updateSidebarGap', {
|
|
||||||
target: pageWrapper.tagName + (pageWrapper.id ? '#'+pageWrapper.id : ''),
|
|
||||||
sidebarWidth: rect.width,
|
|
||||||
sidebarRight: rect.right,
|
|
||||||
pageLeft: pageRect.left,
|
|
||||||
currentGap,
|
|
||||||
newMargin
|
|
||||||
});
|
|
||||||
|
|
||||||
// Detect narrow intervening elements (visual separator) and neutralize their visuals
|
|
||||||
try {
|
|
||||||
const sampleXs = [Math.round(rect.right + 2), Math.round((rect.right + pageRect.left) / 2), Math.round(pageRect.left - 2)];
|
|
||||||
const ys = [Math.floor(window.innerHeight / 2), Math.floor(window.innerHeight / 4), Math.floor(window.innerHeight * 0.75)];
|
|
||||||
const seen = new Set();
|
|
||||||
sampleXs.forEach(x => {
|
|
||||||
ys.forEach(y => {
|
|
||||||
try {
|
|
||||||
const els = document.elementsFromPoint(x, y) || [];
|
|
||||||
els.forEach(el => {
|
|
||||||
if (!el || el === document.documentElement || el === document.body) return;
|
|
||||||
if (el === sidebar || el === pageWrapper) return;
|
|
||||||
const b = el.getBoundingClientRect();
|
|
||||||
// narrow vertical candidates between sidebar and content
|
|
||||||
if (b.left >= rect.right - 4 && b.right <= pageRect.left + 4 && b.width <= 80 && b.height >= 40) {
|
|
||||||
const id = el.tagName + (el.id ? '#'+el.id : '') + (el.className ? '.'+el.className.split(' ').join('.') : '');
|
|
||||||
if (seen.has(id)) return;
|
|
||||||
seen.add(id);
|
|
||||||
try {
|
|
||||||
el.style.setProperty('background', 'transparent', 'important');
|
|
||||||
el.style.setProperty('background-image', 'none', 'important');
|
|
||||||
el.style.setProperty('box-shadow', 'none', 'important');
|
|
||||||
el.style.setProperty('border', 'none', 'important');
|
|
||||||
el.style.setProperty('pointer-events', 'none', 'important');
|
|
||||||
if (window.__otsDebug) console.log('[OTS] neutralized intervening element', { id, rect: b });
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} catch (err) {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -225,11 +147,65 @@
|
|||||||
|
|
||||||
if (!sidebar) return;
|
if (!sidebar) return;
|
||||||
|
|
||||||
|
// Mobile-aware toggle: add backdrop, aria-expanded, and focus management
|
||||||
if (toggleBtn) {
|
if (toggleBtn) {
|
||||||
|
let lastFocus = null;
|
||||||
|
function ensureBackdrop() {
|
||||||
|
let bd = document.querySelector('.ots-backdrop');
|
||||||
|
if (!bd) {
|
||||||
|
bd = document.createElement('div');
|
||||||
|
bd.className = 'ots-backdrop';
|
||||||
|
bd.addEventListener('click', function() {
|
||||||
|
sidebar.classList.remove('active');
|
||||||
|
bd.classList.remove('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (lastFocus) lastFocus.focus();
|
||||||
|
});
|
||||||
|
document.body.appendChild(bd);
|
||||||
|
}
|
||||||
|
return bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBtn.setAttribute('role', 'button');
|
||||||
|
toggleBtn.setAttribute('aria-controls', 'ots-sidebar');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
toggleBtn.addEventListener('click', function(e) {
|
toggleBtn.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const isNowActive = !sidebar.classList.contains('active');
|
||||||
sidebar.classList.toggle('active');
|
sidebar.classList.toggle('active');
|
||||||
|
// On small screens show backdrop and manage focus
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
const bd = ensureBackdrop();
|
||||||
|
if (isNowActive) {
|
||||||
|
bd.classList.add('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'true');
|
||||||
|
lastFocus = document.activeElement;
|
||||||
|
const firstFocusable = sidebar.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||||
|
if (firstFocusable) firstFocusable.focus(); else { sidebar.setAttribute('tabindex', '-1'); sidebar.focus(); }
|
||||||
|
document.addEventListener('keydown', escHandler);
|
||||||
|
} else {
|
||||||
|
bd.classList.remove('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (lastFocus) lastFocus.focus();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSidebarStateClass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function escHandler(e) {
|
||||||
|
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||||
|
const bd = document.querySelector('.ots-backdrop');
|
||||||
|
if (sidebar.classList.contains('active')) {
|
||||||
|
sidebar.classList.remove('active');
|
||||||
|
if (bd) bd.classList.remove('show');
|
||||||
|
toggleBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
if (lastFocus) lastFocus.focus();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collapseBtn) {
|
if (collapseBtn) {
|
||||||
@@ -237,8 +213,9 @@
|
|||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
sidebar.classList.add('collapsed');
|
sidebar.classList.add('collapsed');
|
||||||
body.classList.add('ots-sidebar-collapsed');
|
body.classList.add('ots-sidebar-collapsed');
|
||||||
|
document.documentElement.classList.add('ots-sidebar-collapsed');
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
updateSidebarGap();
|
// updateSidebarGap() disabled - use CSS variables instead
|
||||||
}
|
}
|
||||||
|
|
||||||
collapseBtn.addEventListener('click', function(e) {
|
collapseBtn.addEventListener('click', function(e) {
|
||||||
@@ -246,26 +223,10 @@
|
|||||||
const nowCollapsed = !sidebar.classList.contains('collapsed');
|
const nowCollapsed = !sidebar.classList.contains('collapsed');
|
||||||
sidebar.classList.toggle('collapsed');
|
sidebar.classList.toggle('collapsed');
|
||||||
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
|
document.documentElement.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
|
||||||
// Force collapsed width immediately
|
|
||||||
forceSidebarWidthMode('collapsed');
|
|
||||||
// Recalculate nav offset so items remain below header after collapse
|
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
// Ensure page content gap is updated for collapsed width
|
|
||||||
updateSidebarGap();
|
|
||||||
// Re-run shortly after to catch any late layout changes
|
|
||||||
setTimeout(updateSidebarGap, 80);
|
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
// Debug state after toggle
|
|
||||||
try {
|
|
||||||
console.log('[OTS] collapseBtn clicked', {
|
|
||||||
nowCollapsed,
|
|
||||||
classes: sidebar.className,
|
|
||||||
inlineStyle: sidebar.getAttribute('style'),
|
|
||||||
computedWidth: getComputedStyle(sidebar).width,
|
|
||||||
cssVar: getComputedStyle(document.documentElement).getPropertyValue('--ots-sidebar-width')
|
|
||||||
});
|
|
||||||
} catch (err) {}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,23 +235,10 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sidebar.classList.remove('collapsed');
|
sidebar.classList.remove('collapsed');
|
||||||
body.classList.remove('ots-sidebar-collapsed');
|
body.classList.remove('ots-sidebar-collapsed');
|
||||||
|
document.documentElement.classList.remove('ots-sidebar-collapsed');
|
||||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false');
|
||||||
// Force full width when expanding
|
|
||||||
forceSidebarWidthMode('full');
|
|
||||||
// Recalculate nav offset after expanding
|
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
// Ensure page content gap is updated for expanded width
|
|
||||||
updateSidebarGap();
|
|
||||||
setTimeout(updateSidebarGap, 80);
|
|
||||||
updateSidebarStateClass();
|
updateSidebarStateClass();
|
||||||
try {
|
|
||||||
console.log('[OTS] expandBtn clicked', {
|
|
||||||
classes: sidebar.className,
|
|
||||||
inlineStyle: sidebar.getAttribute('style'),
|
|
||||||
computedWidth: getComputedStyle(sidebar).width,
|
|
||||||
cssVar: getComputedStyle(document.documentElement).getPropertyValue('--ots-sidebar-width')
|
|
||||||
});
|
|
||||||
} catch (err) {}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,8 +531,7 @@
|
|||||||
} else {
|
} else {
|
||||||
sidebar.classList.add('mobile');
|
sidebar.classList.add('mobile');
|
||||||
}
|
}
|
||||||
updateSidebarWidth();
|
// updateSidebarGap() disabled - use CSS variables instead
|
||||||
updateSidebarGap();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,7 +598,6 @@
|
|||||||
input.placeholder = 'Search…';
|
input.placeholder = 'Search…';
|
||||||
input.className = 'table-search-input';
|
input.className = 'table-search-input';
|
||||||
input.setAttribute('aria-label', 'Table search');
|
input.setAttribute('aria-label', 'Table search');
|
||||||
input.style.minWidth = '180px';
|
|
||||||
|
|
||||||
controls.appendChild(input);
|
controls.appendChild(input);
|
||||||
|
|
||||||
@@ -751,6 +697,96 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move DataTable row dropdown menus to <body> so they escape
|
||||||
|
* any overflow:hidden / overflow:auto ancestor containers.
|
||||||
|
*
|
||||||
|
* Xibo renders the row action button as:
|
||||||
|
* <div class="btn-group pull-right dropdown-menu-container">
|
||||||
|
* <button class="btn btn-white dropdown-toggle" data-toggle="dropdown">
|
||||||
|
* <div class="dropdown-menu dropdown-menu-right">...items...</div>
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* Bootstrap fires shown/hide.bs.dropdown on the toggle's parent element
|
||||||
|
* (the .btn-group). We listen on document with a selector that matches
|
||||||
|
* the actual Xibo markup.
|
||||||
|
*/
|
||||||
|
function initRowDropdowns() {
|
||||||
|
var DROPDOWN_PARENT_SEL = '.dropdown-menu-container, .btn-group';
|
||||||
|
var SCOPE_SEL = '.XiboData ' + DROPDOWN_PARENT_SEL
|
||||||
|
+ ', .XiboGrid ' + DROPDOWN_PARENT_SEL
|
||||||
|
+ ', #datatable-container ' + DROPDOWN_PARENT_SEL
|
||||||
|
+ ', .dataTables_wrapper ' + DROPDOWN_PARENT_SEL;
|
||||||
|
|
||||||
|
$(document).on('shown.bs.dropdown', SCOPE_SEL, function(e) {
|
||||||
|
var $container = $(this);
|
||||||
|
var $trigger = $container.find('[data-toggle="dropdown"]');
|
||||||
|
var $menu = $container.find('.dropdown-menu');
|
||||||
|
if (!$menu.length || !$trigger.length) return;
|
||||||
|
|
||||||
|
// Mark the menu so we can style it and find it later
|
||||||
|
$menu.addClass('ots-row-dropdown');
|
||||||
|
|
||||||
|
// Store original parent so we can put it back on close
|
||||||
|
$menu.data('ots-original-parent', $container);
|
||||||
|
|
||||||
|
// Get trigger position in viewport
|
||||||
|
var btnRect = $trigger[0].getBoundingClientRect();
|
||||||
|
|
||||||
|
// Move to body
|
||||||
|
$menu.detach().appendTo('body');
|
||||||
|
|
||||||
|
// Position below the trigger button, aligned to the right edge
|
||||||
|
var top = btnRect.bottom + 2;
|
||||||
|
var left = btnRect.right - $menu.outerWidth();
|
||||||
|
|
||||||
|
// Keep within viewport bounds
|
||||||
|
if (left < 8) left = 8;
|
||||||
|
if (top + $menu.outerHeight() > window.innerHeight - 8) {
|
||||||
|
// Open upward if no room below
|
||||||
|
top = btnRect.top - $menu.outerHeight() - 2;
|
||||||
|
}
|
||||||
|
if (top < 8) top = 8;
|
||||||
|
|
||||||
|
$menu.css({
|
||||||
|
position: 'fixed',
|
||||||
|
top: top + 'px',
|
||||||
|
left: left + 'px',
|
||||||
|
display: 'block'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the dropdown closes, move the menu back to its original parent
|
||||||
|
$(document).on('hide.bs.dropdown', SCOPE_SEL, function(e) {
|
||||||
|
var $container = $(this);
|
||||||
|
var $menu = $('body > .dropdown-menu.ots-row-dropdown').filter(function() {
|
||||||
|
var orig = $(this).data('ots-original-parent');
|
||||||
|
return orig && orig[0] === $container[0];
|
||||||
|
});
|
||||||
|
if ($menu.length) {
|
||||||
|
$menu.removeClass('ots-row-dropdown').css({ position: '', top: '', left: '', display: '' });
|
||||||
|
$menu.detach().appendTo($container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also close any orphaned body-appended dropdown on outside click
|
||||||
|
$(document).on('click', function(e) {
|
||||||
|
var $openMenus = $('body > .dropdown-menu.ots-row-dropdown');
|
||||||
|
if (!$openMenus.length) return;
|
||||||
|
// If the click is inside the menu itself, let it through
|
||||||
|
if ($(e.target).closest('.ots-row-dropdown').length) return;
|
||||||
|
$openMenus.each(function() {
|
||||||
|
var $menu = $(this);
|
||||||
|
var $parent = $menu.data('ots-original-parent');
|
||||||
|
if ($parent && $parent.length) {
|
||||||
|
$menu.removeClass('ots-row-dropdown').css({ position: '', top: '', left: '', display: '' });
|
||||||
|
$menu.detach().appendTo($parent);
|
||||||
|
$parent.removeClass('open show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize all features when DOM is ready
|
* Initialize all features when DOM is ready
|
||||||
*/
|
*/
|
||||||
@@ -759,6 +795,7 @@
|
|||||||
initSidebarSectionToggles();
|
initSidebarSectionToggles();
|
||||||
initThemeToggle();
|
initThemeToggle();
|
||||||
initDropdowns();
|
initDropdowns();
|
||||||
|
initRowDropdowns();
|
||||||
initSearch();
|
initSearch();
|
||||||
initPageInteractions();
|
initPageInteractions();
|
||||||
initDataTables();
|
initDataTables();
|
||||||
@@ -767,11 +804,11 @@
|
|||||||
initChartSafeguard();
|
initChartSafeguard();
|
||||||
updateSidebarWidth();
|
updateSidebarWidth();
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarGap();
|
// updateSidebarGap() disabled - use CSS variables instead
|
||||||
var debouncedUpdate = debounce(function() {
|
var debouncedUpdate = debounce(function() {
|
||||||
updateSidebarNavOffset();
|
updateSidebarNavOffset();
|
||||||
updateSidebarWidth();
|
updateSidebarWidth();
|
||||||
updateSidebarGap();
|
// updateSidebarGap() disabled - use CSS variables instead
|
||||||
}, 120);
|
}, 120);
|
||||||
window.addEventListener('resize', debouncedUpdate);
|
window.addEventListener('resize', debouncedUpdate);
|
||||||
}
|
}
|
||||||
|
|||||||
98
custom/otssignange/views/transition-page.twig
Normal file
98
custom/otssignange/views/transition-page.twig
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - Transition Page
|
||||||
|
* Based on Xibo CMS transition-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "Transitions"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Transitions" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage display transitions." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card" style="display:none;">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="transitions" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Name" %}</th>
|
||||||
|
<th>{% trans "Has Duration" %}</th>
|
||||||
|
<th>{% trans "Has Direction" %}</th>
|
||||||
|
<th>{% trans "Enabled for In" %}</th>
|
||||||
|
<th>{% trans "Enabled for Out" %}</th>
|
||||||
|
<th class="rowMenu"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
var table = $("#transitions").DataTable({
|
||||||
|
"language": dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
responsive: true,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
filter: false,
|
||||||
|
searchDelay: 3000,
|
||||||
|
"order": [[ 0, "asc"]],
|
||||||
|
ajax: {
|
||||||
|
"url": "{{ url_for("transition.search") }}",
|
||||||
|
"data": function(d) {
|
||||||
|
$.extend(d, $("#transitions").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{ "data": "transition", responsivePriority: 2 },
|
||||||
|
{ "data": "hasDuration", "render": dataTableTickCrossColumn, responsivePriority: 3 },
|
||||||
|
{ "data": "hasDirection", "render": dataTableTickCrossColumn, responsivePriority: 3 },
|
||||||
|
{ "data": "availableAsIn", "render": dataTableTickCrossColumn, responsivePriority: 3 },
|
||||||
|
{ "data": "availableAsOut", "render": dataTableTickCrossColumn, responsivePriority: 3 },
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('draw', dataTableDraw);
|
||||||
|
table.on('processing.dt', dataTableProcessing);
|
||||||
|
dataTableAddButtons(table, $('#transitions_wrapper').find('.dataTables_buttons'));
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
444
custom/otssignange/views/user-page.twig
Normal file
444
custom/otssignange/views/user-page.twig
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - User Page
|
||||||
|
* Based on Xibo CMS user-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "Users"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "Users" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage system users and permissions." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}" data-grid-name="usersView">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card">
|
||||||
|
<div class="ots-filter-header">
|
||||||
|
<h3 class="ots-filter-title">{% trans "Filter Users" %}</h3>
|
||||||
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
||||||
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ots-filter-content" id="ots-filter-content">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
{% set title %}{% trans "Username" %}{% endset %}
|
||||||
|
{{ inline.inputNameGrid('userName', title) }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "User Type" %}{% endset %}
|
||||||
|
{{ inline.dropdown("userTypeId", "single", title, "", [{userTypeId:null, userType:""}]|merge(userTypes), "userTypeId", "userType") }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "Retired" %}{% endset %}
|
||||||
|
{% set values = [{id: 1, value: "Yes"}, {id: 0, value: "No"}] %}
|
||||||
|
{{ inline.dropdown("retired", "single", title, 0, values, "id", "value") }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "First Name" %}{% endset %}
|
||||||
|
{{ inline.input('firstName', title) }}
|
||||||
|
|
||||||
|
{% set title %}{% trans "Last Name" %}{% endset %}
|
||||||
|
{{ inline.input('lastName', title) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.isSuperAdmin() or (currentUser.isGroupAdmin() and currentUser.featureEnabled("users.add")) %}
|
||||||
|
{% if currentUser.getOptionValue("isAlwaysUseManualAddUserForm", 0) %}
|
||||||
|
{% set addUserFormUrl = url_for("user.add.form") %}
|
||||||
|
{% else %}
|
||||||
|
{% set addUserFormUrl = url_for("user.onboarding.form") %}
|
||||||
|
{% endif %}
|
||||||
|
<button id="user-add-button" class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new User" %}" href="{{ addUserFormUrl }}"><i class="fa fa-user-plus" aria-hidden="true"></i> {% trans "Add User" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="users" class="table table-striped" data-state-preference-name="userGrid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Username" %}</th>
|
||||||
|
<th>{% trans "Homepage" %}</th>
|
||||||
|
<th>{% trans "Home folder" %}</th>
|
||||||
|
<th>{% trans "Email" %}</th>
|
||||||
|
<th>{% trans "Library Quota" %}</th>
|
||||||
|
<th>{% trans "Last Login" %}</th>
|
||||||
|
<th>{% trans "Logged In?" %}</th>
|
||||||
|
<th>{% trans "Retired?" %}</th>
|
||||||
|
<th>{% trans "Two Factor" %}</th>
|
||||||
|
<th>{% trans "First Name" %}</th>
|
||||||
|
<th>{% trans "Last Name" %}</th>
|
||||||
|
<th>{% trans "Phone" %}</th>
|
||||||
|
<th>{% trans "Ref 1" %}</th>
|
||||||
|
<th>{% trans "Ref 2" %}</th>
|
||||||
|
<th>{% trans "Ref 3" %}</th>
|
||||||
|
<th>{% trans "Ref 4" %}</th>
|
||||||
|
<th>{% trans "Ref 5" %}</th>
|
||||||
|
<th class="rowMenu">{% trans "Row Menu" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var table = $("#users").DataTable({
|
||||||
|
"language": dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true,
|
||||||
|
responsive: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
searchDelay: 3000,
|
||||||
|
"order": [[0, "asc"]],
|
||||||
|
"filter": false,
|
||||||
|
ajax: {
|
||||||
|
url: "{{ url_for("user.search") }}",
|
||||||
|
"data": function (d) {
|
||||||
|
$.extend(d, $("#users").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{"data": "userName", responsivePriority: 2},
|
||||||
|
{
|
||||||
|
"data": "homePage",
|
||||||
|
"sortable": false,
|
||||||
|
responsivePriority: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'homeFolder',
|
||||||
|
responsivePriority: 4
|
||||||
|
},
|
||||||
|
{"data": "email", responsivePriority: 3},
|
||||||
|
{
|
||||||
|
"name": "libraryQuota",
|
||||||
|
responsivePriority: 3,
|
||||||
|
"data": null,
|
||||||
|
"render": {"_": "libraryQuota", "display": "libraryQuotaFormatted", "sort": "libraryQuota"}
|
||||||
|
},
|
||||||
|
{"data": "lastAccessed", "visible": false, responsivePriority: 4},
|
||||||
|
{
|
||||||
|
"data": "loggedIn",
|
||||||
|
responsivePriority: 3,
|
||||||
|
"render": dataTableTickCrossColumn,
|
||||||
|
"visible": false,
|
||||||
|
"sortable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "retired",
|
||||||
|
responsivePriority: 3,
|
||||||
|
"render": dataTableTickCrossColumn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": "twoFactorTypeId",
|
||||||
|
responsivePriority: 5,
|
||||||
|
"visible": false,
|
||||||
|
"render": function (data, type, row) {
|
||||||
|
if (type != "display")
|
||||||
|
return data;
|
||||||
|
|
||||||
|
var icon = "";
|
||||||
|
if (data == 1)
|
||||||
|
icon = "fa-envelope";
|
||||||
|
else if (data == 2)
|
||||||
|
icon = "fa-google";
|
||||||
|
else
|
||||||
|
icon = "fa-times";
|
||||||
|
|
||||||
|
return '<span class="fa ' + icon + '" title="' + (row.twoFactorDescription) + '"></span>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"data": "firstName", "visible": false, responsivePriority: 5},
|
||||||
|
{"data": "lastName", "visible": false, responsivePriority: 5},
|
||||||
|
{"data": "phone", "visible": false, responsivePriority: 5},
|
||||||
|
{"data": "ref1", "visible": false, responsivePriority: 5},
|
||||||
|
{"data": "ref2", "visible": false, responsivePriority: 5},
|
||||||
|
{"data": "ref3", "visible": false, responsivePriority: 5},
|
||||||
|
{"data": "ref4", "visible": false, responsivePriority: 5},
|
||||||
|
{"data": "ref5", "visible": false, responsivePriority: 5},
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('draw', dataTableDraw);
|
||||||
|
table.on('processing.dt', dataTableProcessing)
|
||||||
|
dataTableAddButtons(table, $('#users_wrapper').find('.dataTables_buttons'));
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function userFormOpen(dialog) {
|
||||||
|
// Make a select2 from the home page select
|
||||||
|
var $userForm = $(dialog).find("form.UserForm");
|
||||||
|
var $groupId = $(dialog).find("select[name=groupId]");
|
||||||
|
var $userTypeId = $(dialog).find("select[name=userTypeId]");
|
||||||
|
var $select = $(dialog).find(".homepage-select");
|
||||||
|
$select.select2({
|
||||||
|
minimumResultsForSearch: Infinity,
|
||||||
|
ajax: {
|
||||||
|
url: $select.data("searchUrl"),
|
||||||
|
dataType: "json",
|
||||||
|
delay: 250,
|
||||||
|
data: function (params) {
|
||||||
|
return {
|
||||||
|
q: params.term,
|
||||||
|
page: params.page,
|
||||||
|
userId: $userForm.data().userId,
|
||||||
|
groupId: $groupId.val(),
|
||||||
|
userTypeId: $userTypeId.val(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
processResults: function (data) {
|
||||||
|
var results = [];
|
||||||
|
$.each(data.data, function(index, el) {
|
||||||
|
results.push({
|
||||||
|
"id": el.homepage,
|
||||||
|
"text": el.title,
|
||||||
|
"content": el.description
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
results: results
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
templateResult: function(state) {
|
||||||
|
if (!state.content)
|
||||||
|
return state.text;
|
||||||
|
|
||||||
|
return $("<span>" + state.content + "</span>");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initFolderPanel(dialog, true);
|
||||||
|
|
||||||
|
// Validate form
|
||||||
|
var $userForm = $('.UserForm');
|
||||||
|
forms.validateForm(
|
||||||
|
$userForm,
|
||||||
|
$userForm.parents('.modal-body'),
|
||||||
|
{
|
||||||
|
submitHandler: function (form) {
|
||||||
|
var libraryQuotaField = $(form).find('input[name=libraryQuota]');
|
||||||
|
var libraryQuotaUnitsField = $(form).find('select[name=libraryQuotaUnits]');
|
||||||
|
var libraryQuota = libraryQuotaField.val();
|
||||||
|
|
||||||
|
if (libraryQuotaUnitsField.val() === 'mb') {
|
||||||
|
libraryQuota = libraryQuota * 1024;
|
||||||
|
} else if (libraryQuotaUnitsField.val() === 'gb') {
|
||||||
|
libraryQuota = libraryQuota * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryQuotaField.prop('value', libraryQuota);
|
||||||
|
XiboFormSubmit(form);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onboardingFormOpen(dialog) {
|
||||||
|
$(dialog).find('[data-toggle="popover"]').popover();
|
||||||
|
|
||||||
|
{% if currentUser.featureEnabled("folder.view") %}
|
||||||
|
initFolderPanel(dialog, false, true);
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
var navListItems = $(dialog).find('div.setup-panel div a'),
|
||||||
|
allWells = $(dialog).find('.setup-content'),
|
||||||
|
stepWizard = $(dialog).find('.stepwizard');
|
||||||
|
|
||||||
|
navListItems.click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $target = $($(this).attr('href')),
|
||||||
|
$item = $(this);
|
||||||
|
|
||||||
|
if (!$item.attr('disabled')) {
|
||||||
|
navListItems
|
||||||
|
.removeClass('btn-success')
|
||||||
|
.addClass('btn-default');
|
||||||
|
|
||||||
|
$item.addClass('btn-success');
|
||||||
|
|
||||||
|
allWells.hide();
|
||||||
|
$target.show();
|
||||||
|
$target.find('input:eq(0)').focus();
|
||||||
|
|
||||||
|
stepWizard.data("active", $target.prop("id"))
|
||||||
|
|
||||||
|
if ($target.data("next") === "finished") {
|
||||||
|
$(dialog).find("#onboarding-steper-next-button").html("{{ "Save"|trans }}");
|
||||||
|
} else {
|
||||||
|
$(dialog).find("#onboarding-steper-next-button").html("{{ "Next"|trans }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(dialog).find(".modal-footer")
|
||||||
|
.append($('<a class="btn btn-default">').html("{{ "Close"|trans }}")
|
||||||
|
.click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
XiboDialogClose();
|
||||||
|
}))
|
||||||
|
.append($('<a id="onboarding-steper-next-button" class="btn">').html("{{ "Next"|trans }}")
|
||||||
|
.addClass("btn-primary")
|
||||||
|
.click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var steps = $(dialog).find(".stepwizard"),
|
||||||
|
curStep = $(dialog).find("#" + steps.data("active")),
|
||||||
|
curInputs = curStep.find("input[type='text'],input[type='url']"),
|
||||||
|
isValid = true;
|
||||||
|
|
||||||
|
if (curStep.data("next") === "finished") {
|
||||||
|
var $form = $(dialog).find("#userOnboardingForm");
|
||||||
|
$form.data("apply", true);
|
||||||
|
XiboFormSubmit($form, e, function(xhr) {
|
||||||
|
if (xhr.success && xhr.id) {
|
||||||
|
{% if currentUser.featureEnabled("folder.view") %}
|
||||||
|
var selected = $(dialog).find("#container-form-folder-tree").jstree("get_selected");
|
||||||
|
|
||||||
|
var rootIndex = selected.indexOf('1');
|
||||||
|
if (rootIndex > -1) {
|
||||||
|
selected.splice(rootIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupIds = {};
|
||||||
|
groupIds[xhr.data.groupId] = {
|
||||||
|
"view": 1,
|
||||||
|
"edit": 1
|
||||||
|
};
|
||||||
|
var permissionsUrl = "{{ url_for("user.permissions.multi", {entity: ":entity"}) }}";
|
||||||
|
$.ajax(permissionsUrl.replace(":entity", "Folder"), {
|
||||||
|
"method": "POST",
|
||||||
|
"data": {
|
||||||
|
"ids": selected.join(","),
|
||||||
|
"groupIds": groupIds
|
||||||
|
},
|
||||||
|
"error": function() {
|
||||||
|
toastr.error("{{ "Problem saving folder sharing, please check the User created." }}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
XiboDialogClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (curStep.data("next") === "onboarding-step-2" && $("input[name='groupId']:checked").val() === "manual") {
|
||||||
|
XiboDialogClose();
|
||||||
|
XiboFormRender("{{ url_for("user.add.form") }}");
|
||||||
|
} else {
|
||||||
|
var nextStepWizard = steps.find("a[href='#" + curStep.data("next") + "']");
|
||||||
|
|
||||||
|
$(dialog).find(".form-group").removeClass("has-error");
|
||||||
|
for (var i = 0; i < curInputs.length; i++) {
|
||||||
|
if (!curInputs[i].validity.valid) {
|
||||||
|
isValid = false;
|
||||||
|
$(curInputs[i]).closest(".form-group").addClass("has-error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curStep.data("next") === "onboarding-step-2") {
|
||||||
|
var $userGroupSelected = $("input[name='groupId']:checked");
|
||||||
|
$(dialog).find("input[name=homePageId]").val($userGroupSelected.data("defaultHomepageId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
nextStepWizard.removeAttr('disabled').trigger('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function userHomeFolderFormOpen(dialog) {
|
||||||
|
initFolderPanel(dialog, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function userHomeFolderMultiselectFormOpen(dialog) {
|
||||||
|
var $input = $('<div id="container-form-folder-tree" class="card card-body bg-light"></div>');
|
||||||
|
var $helpText = $('<span class="help-block">{{ "Set a home folder to use as the default folder for new content."|trans }}</span>');
|
||||||
|
|
||||||
|
$(dialog).find('.modal-body').append($input);
|
||||||
|
$(dialog).find('.modal-body').append($helpText);
|
||||||
|
|
||||||
|
initFolderPanel(dialog, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initFolderPanel(dialog, isHomeOnSelect = false, isHomeContext = false) {
|
||||||
|
var plugins = [];
|
||||||
|
|
||||||
|
if (!isHomeOnSelect) {
|
||||||
|
plugins.push('checkbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
initJsTreeAjax(
|
||||||
|
'#container-form-folder-tree',
|
||||||
|
'user-add_edit-form',
|
||||||
|
true,
|
||||||
|
600,
|
||||||
|
function(tree, $container) {
|
||||||
|
if (!isHomeOnSelect) {
|
||||||
|
tree.disable_checkbox(1);
|
||||||
|
tree.disable_node(1);
|
||||||
|
}
|
||||||
|
$container.jstree('open_all');
|
||||||
|
},
|
||||||
|
function(data) {
|
||||||
|
if (isHomeOnSelect && data.action === 'select_node') {
|
||||||
|
$(dialog).find('input[name=homeFolderId]').val(data.node.id);
|
||||||
|
|
||||||
|
dialog.data().commitData = {homeFolderId: data.node.id};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function($node, items) {
|
||||||
|
if (isHomeContext) {
|
||||||
|
items['home'] = {
|
||||||
|
separator_before: false,
|
||||||
|
separator_after: false,
|
||||||
|
label: translations.folderTreeSetAsHome,
|
||||||
|
action: function () {
|
||||||
|
$(dialog).find('input[name=homeFolderId]').val($node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
plugins,
|
||||||
|
$(dialog).find('input[name=homeFolderId]').val()
|
||||||
|
);
|
||||||
|
|
||||||
|
$('.folder-tree-buttons').on('click', 'button', function(ev) {
|
||||||
|
const jsTree = $(dialog).find('#container-form-folder-tree').jstree(true);
|
||||||
|
if ($(ev.target).attr('id') === 'selectAllBtn') {
|
||||||
|
jsTree.select_all();
|
||||||
|
} else if ($(ev.target).attr('id') === 'selectNoneBtn') {
|
||||||
|
jsTree.deselect_all();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
194
custom/otssignange/views/usergroup-page.twig
Normal file
194
custom/otssignange/views/usergroup-page.twig
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
{#
|
||||||
|
/*
|
||||||
|
* OTS Signs Theme - User Group Page
|
||||||
|
* Based on Xibo CMS usergroup-page.twig with OTS styling
|
||||||
|
*/
|
||||||
|
#}
|
||||||
|
{% extends "authed.twig" %}
|
||||||
|
{% import "inline.twig" as inline %}
|
||||||
|
|
||||||
|
{% block title %}{{ "User Groups"|trans }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block actionMenu %}{% endblock %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="ots-displays-page">
|
||||||
|
<div class="page-header ots-page-header">
|
||||||
|
<h1>{% trans "User Groups" %}</h1>
|
||||||
|
<p class="text-muted">{% trans "Manage user groups and permissions." %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="widget dashboard-card ots-displays-card">
|
||||||
|
<div class="widget-body ots-displays-body">
|
||||||
|
<div class="XiboGrid" id="{{ random() }}" data-grid-name="userGroupView">
|
||||||
|
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card">
|
||||||
|
<div class="ots-filter-header">
|
||||||
|
<h3 class="ots-filter-title">{% trans "Filter User Groups" %}</h3>
|
||||||
|
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title="{% trans 'Toggle filter panel' %}">
|
||||||
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ots-filter-content" id="ots-filter-content">
|
||||||
|
<div class="FilterDiv card-body" id="Filter">
|
||||||
|
<form class="form-inline">
|
||||||
|
{% set title %}{% trans "Name" %}{% endset %}
|
||||||
|
{{ inline.inputNameGrid('userGroup', title) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||||
|
<div class="ots-table-toolbar">
|
||||||
|
{% if currentUser.isSuperAdmin() %}
|
||||||
|
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new User Group" %}" href="{{ url_for("group.add.form") }}"><i class="fa fa-users" aria-hidden="true"></i> {% trans "Add Group" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
<table id="userGroups" class="table table-striped" data-state-preference-name="userGroupGrid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "User Group" %}</th>
|
||||||
|
<th>{% trans "Description" %}</th>
|
||||||
|
<th>{% trans "Library Quota" %}</th>
|
||||||
|
<th>{% trans "Receive System Notifications?" %}</th>
|
||||||
|
<th>{% trans "Receive Display Notifications?" %}</th>
|
||||||
|
<th>{% trans "Receive Custom Notifications?" %}</th>
|
||||||
|
<th>{% trans "Receive DataSet Notifications?" %}</th>
|
||||||
|
<th>{% trans "Receive Layout Notifications?" %}</th>
|
||||||
|
<th>{% trans "Receive Library Notifications?" %}</th>
|
||||||
|
<th>{% trans "Receive Report Notifications?" %}</th>
|
||||||
|
<th>{% trans "Receive Schedule Notifications?" %}</th>
|
||||||
|
<th>{% trans "Is shown for Add User?" %}</th>
|
||||||
|
<th class="rowMenu"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javaScript %}
|
||||||
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||||
|
$(document).ready(function() {
|
||||||
|
var table = $("#userGroups").DataTable({
|
||||||
|
"language": dataTablesLanguage,
|
||||||
|
dom: dataTablesTemplate,
|
||||||
|
serverSide: true,
|
||||||
|
stateSave: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
responsive: true,
|
||||||
|
stateLoadCallback: dataTableStateLoadCallback,
|
||||||
|
stateSaveCallback: dataTableStateSaveCallback,
|
||||||
|
searchDelay: 3000,
|
||||||
|
filter: false,
|
||||||
|
order: [[0, 'asc']],
|
||||||
|
ajax: {
|
||||||
|
url: "{{ url_for('group.search') }}",
|
||||||
|
data: function (d) {
|
||||||
|
$.extend(d, $('#userGroups').closest('.XiboGrid').find('.FilterDiv form').serializeObject());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{data: 'group', render: dataTableSpacingPreformatted, responsivePriority: 2 },
|
||||||
|
{data: 'description', visible: false },
|
||||||
|
{
|
||||||
|
name: 'libraryQuota',
|
||||||
|
data: null,
|
||||||
|
render: {'_': 'libraryQuota', 'display': 'libraryQuotaFormatted', 'sort': 'libraryQuota'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isSystemNotification',
|
||||||
|
render: dataTableTickCrossColumn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isDisplayNotification',
|
||||||
|
render: dataTableTickCrossColumn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isDataSetNotification',
|
||||||
|
render: dataTableTickCrossColumn,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isLayoutNotification',
|
||||||
|
render: dataTableTickCrossColumn,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isLibraryNotification',
|
||||||
|
render: dataTableTickCrossColumn,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isReportNotification',
|
||||||
|
render: dataTableTickCrossColumn,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isScheduleNotification',
|
||||||
|
render: dataTableTickCrossColumn,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'isCustomNotification',
|
||||||
|
render: dataTableTickCrossColumn,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "isShownForAddUser",
|
||||||
|
render: dataTableTickCrossColumn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orderable": false,
|
||||||
|
responsivePriority: 1,
|
||||||
|
"data": dataTableButtonsColumn
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('draw', dataTableDraw);
|
||||||
|
table.on('processing.dt', dataTableProcessing);
|
||||||
|
dataTableAddButtons(table, $('#userGroups_wrapper').find('.dataTables_buttons'));
|
||||||
|
|
||||||
|
$("#refreshGrid").click(function () {
|
||||||
|
table.ajax.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleLibraryQuotaField(libraryQuotaField, libraryQuotaUnitsField) {
|
||||||
|
var libraryQuota = libraryQuotaField.val();
|
||||||
|
|
||||||
|
if (libraryQuotaUnitsField.val() === 'mb') {
|
||||||
|
libraryQuota = libraryQuota * 1024;
|
||||||
|
} else if (libraryQuotaUnitsField.val() === 'gb') {
|
||||||
|
libraryQuota = libraryQuota * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryQuotaField.prop('value', libraryQuota);
|
||||||
|
}
|
||||||
|
|
||||||
|
function userGroupFormOpen() {
|
||||||
|
var $userGroupForm = $('.UserGroupForm');
|
||||||
|
forms.validateForm(
|
||||||
|
$userGroupForm,
|
||||||
|
$userGroupForm.parents('.modal-body'),
|
||||||
|
{
|
||||||
|
submitHandler: function (form) {
|
||||||
|
handleLibraryQuotaField(
|
||||||
|
$(form).find('input[name=libraryQuota]'),
|
||||||
|
$(form).find('select[name=libraryQuotaUnits]')
|
||||||
|
);
|
||||||
|
|
||||||
|
XiboFormSubmit(form);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user