mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
5a51548ebe
- Updated billing contract structure to support multiple target types (organization, device group, folder, device). - Refactored API handlers to accommodate new billing contract model, including creation, deletion, and listing functionalities. - Enhanced database interactions for billing contracts, ensuring backward compatibility with legacy billing_org_contracts. - Added new billing statistics endpoint to provide insights on active sessions and expiring contracts. - Improved error handling and validation in billing package management.
291 lines
17 KiB
HTML
291 lines
17 KiB
HTML
<%- include('layouts/main', {
|
|
title: title,
|
|
pageStyles: pageStyles,
|
|
pageScripts: pageScripts,
|
|
currentPage: currentPage,
|
|
breadcrumb: breadcrumb,
|
|
body: `
|
|
|
|
<div class="commercialization-page" data-active-tab="${typeof activeTab !== 'undefined' ? activeTab : 'overview'}">
|
|
<div class="page-header">
|
|
<h1>${activeTab === 'overview' ? _('commercialization.title') : _('commercialization.tabs.' + activeTab)} <span class="beta-badge" title="${_('commercialization.alpha_tooltip')}">${_('commercialization.alpha_badge')}</span></h1>
|
|
<p class="page-subtitle">${activeTab === 'overview' ? _('commercialization.subtitle') : _('commercialization.title')}</p>
|
|
</div>
|
|
|
|
<div id="timesync-banner" class="timesync-banner hidden"></div>
|
|
|
|
<section id="tab-overview" class="tab-panel ${activeTab === 'overview' ? '' : 'hidden'}">
|
|
<div class="card-grid">
|
|
<div class="stat-card" id="stat-clock">
|
|
<h3>${_('commercialization.clock.title')}</h3>
|
|
<p class="stat-value" id="clock-status">—</p>
|
|
<p class="stat-meta" id="clock-offset"></p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>${_('commercialization.stats.active_sessions')}</h3>
|
|
<p class="stat-value" id="stat-active-sessions">0</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>${_('commercialization.stats.expiring_contracts')}</h3>
|
|
<p class="stat-value" id="stat-expiring-contracts">0</p>
|
|
<p class="stat-meta">${_('commercialization.stats.expiring_hint')}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tab-packages" class="tab-panel ${activeTab === 'packages' ? '' : 'hidden'}">
|
|
<div class="panel-toolbar">
|
|
<button type="button" class="btn btn-primary" id="btn-new-package">${_('commercialization.packages.new')}</button>
|
|
<button type="button" class="btn btn-secondary" id="btn-new-contract">${_('commercialization.contracts.new')}</button>
|
|
</div>
|
|
<h3 class="section-heading">${_('commercialization.packages.heading')}</h3>
|
|
<div class="table-wrap">
|
|
<table class="data-table" id="packages-table">
|
|
<thead>
|
|
<tr>
|
|
<th>${_('commercialization.packages.name')}</th>
|
|
<th>${_('commercialization.packages.description')}</th>
|
|
<th>${_('commercialization.packages.included_minutes')}</th>
|
|
<th>${_('commercialization.packages.overage_rate')}</th>
|
|
<th>${_('commercialization.packages.currency')}</th>
|
|
<th>${_('commercialization.contracts.actions')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<h3 class="section-heading">${_('commercialization.contracts.assignments_title')}</h3>
|
|
<div class="panel-toolbar filters-toolbar">
|
|
<label class="filter-label">${_('commercialization.filters.target_type')}
|
|
<select id="filter-contract-type" class="form-control">
|
|
<option value="">${_('commercialization.filters.all_types')}</option>
|
|
<option value="org">${_('commercialization.targets.org')}</option>
|
|
<option value="device_group">${_('commercialization.targets.device_group')}</option>
|
|
<option value="folder">${_('commercialization.targets.folder')}</option>
|
|
<option value="device">${_('commercialization.targets.device')}</option>
|
|
</select>
|
|
</label>
|
|
<label class="filter-label">${_('commercialization.contracts.status')}
|
|
<select id="filter-contract-status" class="form-control">
|
|
<option value="">${_('commercialization.filters.all_statuses')}</option>
|
|
<option value="active">${_('commercialization.status.active')}</option>
|
|
<option value="suspended">${_('commercialization.status.suspended')}</option>
|
|
</select>
|
|
</label>
|
|
<label class="filter-label">${_('commercialization.filters.search')}
|
|
<input type="search" id="filter-contract-search" class="form-control" placeholder="${_('commercialization.filters.search_placeholder')}">
|
|
</label>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table class="data-table" id="contracts-table">
|
|
<thead>
|
|
<tr>
|
|
<th>${_('commercialization.contracts.target')}</th>
|
|
<th>${_('commercialization.contracts.package')}</th>
|
|
<th>${_('commercialization.contracts.remaining')}</th>
|
|
<th>${_('commercialization.contracts.status')}</th>
|
|
<th>${_('commercialization.contracts.validity')}</th>
|
|
<th>${_('commercialization.contracts.actions')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tab-sessions" class="tab-panel ${activeTab === 'sessions' ? '' : 'hidden'}">
|
|
<div class="panel-toolbar filters-toolbar">
|
|
<label class="filter-label">${_('commercialization.filters.organization')}
|
|
<select id="filter-session-org" class="form-control"></select>
|
|
</label>
|
|
<label class="filter-label">${_('commercialization.contracts.status')}
|
|
<select id="filter-session-status" class="form-control">
|
|
<option value="">${_('commercialization.filters.all_statuses')}</option>
|
|
<option value="active">${_('commercialization.status.active')}</option>
|
|
<option value="pending_report">${_('commercialization.status.pending_report')}</option>
|
|
<option value="closed">${_('commercialization.status.closed')}</option>
|
|
</select>
|
|
</label>
|
|
<label class="filter-label">${_('commercialization.sessions.device')}
|
|
<input type="text" id="filter-session-device" class="form-control" placeholder="${_('commercialization.filters.device_id_placeholder')}">
|
|
</label>
|
|
<button type="button" class="btn btn-secondary" id="btn-refresh-sessions">${_('commercialization.actions.refresh')}</button>
|
|
<button type="button" class="btn btn-secondary" id="btn-export-sessions">${_('commercialization.export.sessions_csv')}</button>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table class="data-table" id="sessions-table">
|
|
<thead>
|
|
<tr>
|
|
<th>${_('commercialization.sessions.device')}</th>
|
|
<th>${_('commercialization.sessions.org')}</th>
|
|
<th>${_('commercialization.sessions.operator')}</th>
|
|
<th>${_('commercialization.sessions.duration')}</th>
|
|
<th>${_('commercialization.sessions.phase')}</th>
|
|
<th>${_('commercialization.sessions.amount')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tab-reports" class="tab-panel ${activeTab === 'reports' ? '' : 'hidden'}">
|
|
<div class="panel-toolbar filters-toolbar">
|
|
<label class="filter-label">${_('commercialization.filters.organization')}
|
|
<select id="filter-report-org" class="form-control"></select>
|
|
</label>
|
|
<button type="button" class="btn btn-secondary" id="btn-refresh-reports">${_('commercialization.actions.refresh')}</button>
|
|
<button type="button" class="btn btn-secondary" id="btn-export-reports-csv">${_('commercialization.export.csv')}</button>
|
|
<button type="button" class="btn btn-secondary" id="btn-export-reports-pdf">${_('commercialization.export.pdf')}</button>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table class="data-table" id="reports-table">
|
|
<thead>
|
|
<tr>
|
|
<th>${_('commercialization.reports.session')}</th>
|
|
<th>${_('commercialization.reports.summary')}</th>
|
|
<th>${_('commercialization.reports.date')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tab-settings" class="tab-panel ${activeTab === 'settings' ? '' : 'hidden'}">
|
|
<div class="card">
|
|
<h3>${_('commercialization.clock.title')}</h3>
|
|
<p id="settings-clock-detail" class="text-muted"></p>
|
|
<dl class="clock-status-grid">
|
|
<div><dt>${_('commercialization.clock.status_offset')}</dt><dd id="clock-offset">—</dd></div>
|
|
<div><dt>${_('commercialization.clock.status_server')}</dt><dd id="clock-ntp-server">—</dd></div>
|
|
<div><dt>${_('commercialization.clock.status_os')}</dt><dd id="clock-os-sync">—</dd></div>
|
|
<div><dt>${_('commercialization.clock.status_error')}</dt><dd id="clock-last-error">—</dd></div>
|
|
</dl>
|
|
<button type="button" class="btn btn-secondary" id="btn-timesync-check">${_('commercialization.clock.check_now')}</button>
|
|
<form id="clock-settings-form" class="settings-form" style="margin-top: 1rem;">
|
|
<label class="form-label">${_('commercialization.clock.ntp_servers')}
|
|
<textarea id="clock-ntp-servers" class="form-control" rows="2" placeholder="pool.ntp.org,time.google.com"></textarea>
|
|
</label>
|
|
<p class="text-muted form-hint">${_('commercialization.clock.ntp_servers_hint')}</p>
|
|
<label class="form-label">${_('commercialization.clock.max_skew_ms')}
|
|
<input type="number" id="clock-max-skew" class="form-control" min="100" max="600000" step="100" value="2000">
|
|
</label>
|
|
<div class="form-group">
|
|
<label><input type="checkbox" id="clock-require-sync"> ${_('commercialization.clock.require_sync')}</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label><input type="checkbox" id="clock-trust-os"> ${_('commercialization.clock.trust_os')}</label>
|
|
</div>
|
|
<p class="text-muted form-hint">${_('commercialization.clock.trust_os_hint')}</p>
|
|
<button type="button" class="btn btn-primary" id="btn-save-clock-settings">${_('commercialization.clock.save')}</button>
|
|
</form>
|
|
</div>
|
|
<div class="card" style="margin-top: 1rem;">
|
|
<h3>${_('commercialization.notifications.title')}</h3>
|
|
<p class="text-muted">${_('commercialization.notifications.desc')}</p>
|
|
<p id="email-smtp-status" class="smtp-status-line"></p>
|
|
<p><a href="/settings#email">${_('commercialization.notifications.smtp_link')}</a></p>
|
|
<form id="email-notifications-form" class="settings-form">
|
|
<div class="form-group">
|
|
<label><input type="checkbox" id="notif-help-requests-enabled"> ${_('commercialization.notifications.help_requests_enabled')}</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label><input type="checkbox" id="notif-assigned-operators"> ${_('commercialization.notifications.notify_assigned_operators')}</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label><input type="checkbox" id="notif-fallback-alert"> ${_('commercialization.notifications.fallback_alert_email')}</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label><input type="checkbox" id="notif-folder-subject"> ${_('commercialization.notifications.include_folder_in_subject')}</label>
|
|
</div>
|
|
<button type="button" class="btn btn-primary" id="btn-save-email-notifications">${_('commercialization.notifications.save')}</button>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="modal-overlay" id="package-modal">
|
|
<div class="modal" role="dialog" aria-modal="true">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="package-modal-title">${_('commercialization.packages.create_title')}</h2>
|
|
<button type="button" class="modal-close" data-close-modal="package-modal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<label class="form-label">${_('commercialization.packages.name')}
|
|
<input type="text" id="package-name" class="form-control" maxlength="128" required>
|
|
</label>
|
|
<label class="form-label">${_('commercialization.packages.description')}
|
|
<textarea id="package-description" class="form-control" rows="2" maxlength="512"></textarea>
|
|
</label>
|
|
<label class="form-label">${_('commercialization.packages.included_minutes')}
|
|
<input type="number" id="package-minutes" class="form-control" value="600" min="1" step="1" required>
|
|
</label>
|
|
<label class="form-label">${_('commercialization.packages.overage_rate')}
|
|
<input type="number" id="package-overage" class="form-control" value="100" min="0" step="0.01" required>
|
|
</label>
|
|
<label class="form-label">${_('commercialization.packages.currency')}
|
|
<input type="text" id="package-currency" class="form-control" value="PLN" maxlength="8" required>
|
|
</label>
|
|
<p id="package-modal-error" class="form-error hidden"></p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-close-modal="package-modal">${_('commercialization.form.cancel')}</button>
|
|
<button type="button" class="btn btn-primary" id="package-modal-submit">${_('commercialization.packages.create_submit')}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-overlay" id="assign-modal">
|
|
<div class="modal modal-wide" role="dialog" aria-modal="true">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title" id="assign-modal-title">${_('commercialization.assign.title')}</h2>
|
|
<button type="button" class="modal-close" data-close-modal="assign-modal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<label class="form-label">${_('commercialization.assign.target_type')}
|
|
<select id="assign-target-type" class="form-control">
|
|
<option value="org">${_('commercialization.targets.org')}</option>
|
|
<option value="device_group">${_('commercialization.targets.device_group')}</option>
|
|
<option value="folder">${_('commercialization.targets.folder')}</option>
|
|
<option value="device">${_('commercialization.targets.device')}</option>
|
|
</select>
|
|
</label>
|
|
<label class="form-label">${_('commercialization.assign.target')}
|
|
<select id="assign-target-select" class="form-control" required></select>
|
|
</label>
|
|
<label class="form-label">${_('commercialization.contracts.package')}
|
|
<select id="assign-package-select" class="form-control" required></select>
|
|
</label>
|
|
<div class="form-row">
|
|
<label class="form-label">${_('commercialization.contracts.remaining')}
|
|
<input type="number" id="assign-remaining" class="form-control" min="0" step="1" placeholder="${_('commercialization.assign.optional')}">
|
|
</label>
|
|
<label class="form-label">${_('commercialization.contracts.hourly_rate')}
|
|
<input type="number" id="assign-hourly-rate" class="form-control" min="0" step="0.01" placeholder="${_('commercialization.assign.optional')}">
|
|
</label>
|
|
</div>
|
|
<div class="form-row">
|
|
<label class="form-label">${_('commercialization.contracts.custom_overage')}
|
|
<input type="number" id="assign-overage-rate" class="form-control" min="0" step="0.01" placeholder="${_('commercialization.assign.optional')}">
|
|
</label>
|
|
<label class="form-label">${_('commercialization.contracts.valid_from')}
|
|
<input type="date" id="assign-valid-from" class="form-control">
|
|
</label>
|
|
<label class="form-label">${_('commercialization.contracts.valid_until')}
|
|
<input type="date" id="assign-valid-until" class="form-control">
|
|
</label>
|
|
</div>
|
|
<p id="assign-modal-error" class="form-error hidden"></p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-close-modal="assign-modal">${_('commercialization.form.cancel')}</button>
|
|
<button type="button" class="btn btn-primary" id="assign-modal-submit">${_('commercialization.assign.submit')}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
`
|
|
}) %>
|