feat: Implement admin-only specialties management interface
Playwright Tests / test (push) Canceled after 0s
Playwright Tests / test (push) Canceled after 0s
- Added MailerAttachmentPolicy for validating email attachments against server rules. - Introduced fis.specialties.js for managing hidden maintenance actions via an admin interface. - Created fis.specialties.scss for styling the specialties management UI. - Added German localization for specialties management in fis.specialties_txt_de.js. - Developed CSS for specialties management in fis.specialties.css. - Implemented server-side stored procedure fds__admin_backupAndClearInvoiceFile for backing up and clearing invoice files. - Created fds__invoice_file_backups table to store backup records of invoice files.
This commit is contained in:
@@ -75,6 +75,8 @@ let $adt = {
|
||||
, tm_to: 'Empfänger'
|
||||
, tm_subject: 'Betreff'
|
||||
, tm_body: 'Nachricht'
|
||||
, tm_attachpdf: 'PDF-Anhang mitsenden'
|
||||
, tm_attachpdf_note: 'Hängt ein aus Betreff und Nachricht erzeugtes Test-PDF (Briefbogen-Layout) an und prüft so auch den Anhang-Pfad des Mailers.'
|
||||
, tm_hint: 'Der Versand nutzt die normale E-Mail-Pipeline; bei gesetztem OverrideRecipient wird die Nachricht dorthin umgeleitet.'
|
||||
, tm_sent: 'Test-E-Mail übergeben.'
|
||||
, tm_failed: 'Test-E-Mail konnte nicht gesendet werden.'
|
||||
@@ -272,7 +274,7 @@ let $adm = {
|
||||
];
|
||||
if (e.overrideActive === true) { rows.push([$adt.override, e.overrideRecipient]); }
|
||||
// The pill only turns green once the credentials are verified against the Mailer API
|
||||
// (probe 'email', GET /mailer/fc?fn=verify) — mailerEnabled alone is not proof they work.
|
||||
// (probe 'email', GET /api/mailer/verify) — mailerEnabled alone is not proof they work.
|
||||
let card = $adm.probeCard(grid, $adt.mail, 'email', rows, probe);
|
||||
let ft = $$.dc('adm_card_ft', card);
|
||||
$$.bbtn($adt.testmail_btn, 'adm_btn adm_btn_primary').appendTo(ft).click($adm.testMailDlg);
|
||||
@@ -306,11 +308,12 @@ let $adm = {
|
||||
$ocms.dlgform([
|
||||
{ name: 'to', label: $adt.tm_to, type: 'email', required: true, value: ($ocms.auth.email || '') },
|
||||
{ name: 'subject', label: $adt.tm_subject, type: 'string', required: true, value: $adt.tm_default_subject },
|
||||
{ name: 'body', label: $adt.tm_body, type: 'text', required: true, value: $adt.tm_default_body }
|
||||
{ name: 'body', label: $adt.tm_body, type: 'text', required: true, value: $adt.tm_default_body },
|
||||
{ name: 'attachpdf', label: $adt.tm_attachpdf, type: 'bool', required: true, value: true, note: $adt.tm_attachpdf_note }
|
||||
], {
|
||||
title: $adt.testmail,
|
||||
button: $adt.testmail_btn,
|
||||
size: [420, 560],
|
||||
size: [420, 640],
|
||||
addcontent: $$.dc('adm_note').text($adt.tm_hint),
|
||||
url: $ocms.url('admin/testmail'),
|
||||
typedvalues: true,
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -2846,6 +2846,7 @@ $.extend($t, {
|
||||
, m_admin_status: 'Systemüberwachung'
|
||||
, m_usr: 'Benutzerverwaltung'
|
||||
, m_usrgrp: 'Gruppenverwaltung'
|
||||
, m_specialties: 'Spezialfunktionen'
|
||||
, rsp: 'Passwort ändern'
|
||||
, pnm: 'Die Passwörter stimmen nicht überein'
|
||||
, cps: 'Das neue Passwort wurde gespeichert.'
|
||||
@@ -3242,21 +3243,23 @@ $fis.draft = {
|
||||
})();
|
||||
|
||||
/* "Administration" is a single sidemenu dropdown (ocmsmenu supports nested items via `itm`)
|
||||
containing three buttons — Benutzerverwaltung (fn=usr), Gruppenverwaltung (fn=usrgrp) and
|
||||
Systemüberwachung (fn=admin) — all restricted to users whose fds_sys authorization exceeds 4.
|
||||
One auth check gates the whole dropdown; it (and therefore all module scripts) is only added
|
||||
after that check resolves, so lower-privileged users never see it or fetch any module's code.
|
||||
Called from fis_main_go.js once the DOM (and the initial main menu) is ready. */
|
||||
containing four buttons — Benutzerverwaltung (fn=usr), Gruppenverwaltung (fn=usrgrp),
|
||||
Systemüberwachung (fn=admin) and Spezialfunktionen (fn=specialties) — all restricted to users
|
||||
whose fds_sys authorization exceeds 4. One auth check gates the whole dropdown; it (and
|
||||
therefore all module scripts) is only added after that check resolves, so lower-privileged
|
||||
users never see it or fetch any module's code. Called from fis_main_go.js once the DOM (and
|
||||
the initial main menu) is ready. Returns a promise that settles once the menu is final. */
|
||||
$fis.addAdminMenuButtonsIfAuthorized = function () {
|
||||
if ($('#m_admin_grp').length > 0) { return; } // idempotent
|
||||
$fis.getAuth('fds_sys').then(function (auth) {
|
||||
if ($('#m_admin_grp').length > 0) { return Promise.resolve(); } // idempotent
|
||||
return $fis.getAuth('fds_sys').then(function (auth) {
|
||||
if ((auth || -1) > 4 && $('#m_admin_grp').length === 0) {
|
||||
$ocms.ocmsmenu.push({
|
||||
lbl: $t.m_admin, id: 'm_admin_grp', ico: 'glyphicon glyphicon-cog',
|
||||
itm: [
|
||||
{ lbl: $t.m_usr, id: 'm_usr', fnc: 'init:usr', ico: 'glyphicon glyphicon-user' },
|
||||
{ lbl: $t.m_usrgrp, id: 'm_usrgrp', fnc: 'init:usrgrp', ico: 'glyphicon glyphicon-list' },
|
||||
{ lbl: $t.m_admin_status, id: 'm_admin', fnc: 'init:admin', ico: 'glyphicon glyphicon-dashboard' }
|
||||
{ lbl: $t.m_admin_status, id: 'm_admin', fnc: 'init:admin', ico: 'glyphicon glyphicon-dashboard' },
|
||||
{ lbl: $t.m_specialties, id: 'm_specialties', fnc: 'init:specialties', ico: 'glyphicon glyphicon-wrench' }
|
||||
]
|
||||
});
|
||||
/* Re-render the main menu cleanly: drop the previously generated menu list
|
||||
@@ -3266,9 +3269,28 @@ $fis.addAdminMenuButtonsIfAuthorized = function () {
|
||||
}
|
||||
}).catch(function () { /* auth lookup failed → leave the menu unchanged */ });
|
||||
};
|
||||
|
||||
/* Deep link: the server renders the same SPA shell for every /{fn} URL (IntranetController.Index
|
||||
ignores fn), so opening e.g. /specialties directly would otherwise just show the dashboard.
|
||||
Opens the module named by the first path segment — but only one the user's menu actually
|
||||
offers (`init:<fn>` item, incl. the admin dropdown), so arbitrary paths, /do/… links and
|
||||
pre-auth flows never trigger a module load. Must run after the admin menu settled. */
|
||||
$fis.openModuleFromPath = function () {
|
||||
let fn = (window.location.pathname.split('/')[1] || '').toLowerCase();
|
||||
if (!/^[a-z]+$/.test(fn) || fn === 'home') { return; }
|
||||
let offered = false;
|
||||
let scan = function (items) {
|
||||
$.each(items || [], function (i, it) {
|
||||
if (it.fnc === 'init:' + fn) { offered = true; }
|
||||
if (it.itm) { scan(it.itm); }
|
||||
});
|
||||
};
|
||||
scan($ocms.ocmsmenu);
|
||||
if (offered) { $ocms.init(fn); }
|
||||
};
|
||||
$(document).ready(function () {
|
||||
$fis.notifications.init();
|
||||
$fis.draft.init();
|
||||
$fis.ov();
|
||||
$fis.addAdminMenuButtonsIfAuthorized();
|
||||
$fis.addAdminMenuButtonsIfAuthorized().then($fis.openModuleFromPath);
|
||||
});
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,120 @@
|
||||
/* basics */
|
||||
/* Color SCHEME */
|
||||
/* #8396bd */
|
||||
/* #e3e6e6 */
|
||||
/* #ffffff */
|
||||
/* #262626 */
|
||||
/* #262626 */
|
||||
/* #e3e6e6 */
|
||||
/* #FFC801 */
|
||||
/* #ffff4a */
|
||||
/* media breaks */
|
||||
/* other */
|
||||
/* mixin */
|
||||
/* basics */
|
||||
/* Color SCHEME */
|
||||
/* #1b4379 #0033b3 */
|
||||
/*neu: #56a532 ; alt: rgb(32,144,119);*/
|
||||
/* 10% sw */
|
||||
.spcfrm {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.spcfrm .spc_intro {
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem 0.6rem;
|
||||
background: #f3f6fb;
|
||||
border-left: 3px solid rgb(27, 67, 121);
|
||||
font-size: 0.9rem;
|
||||
color: #444;
|
||||
}
|
||||
.spcfrm .spc_grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(24rem, 1fr));
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
}
|
||||
.spcfrm .spc_card {
|
||||
border: 1px solid #d5d5d5;
|
||||
border-radius: 0.4rem;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(50, 50, 50, 0.12);
|
||||
overflow: hidden;
|
||||
}
|
||||
.spcfrm .spc_card_hd {
|
||||
padding: 0.6rem 0.85rem;
|
||||
background: rgb(27, 67, 121);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.spcfrm .spc_card_bd {
|
||||
padding: 0.75rem 0.85rem;
|
||||
}
|
||||
.spcfrm .spc_card_desc {
|
||||
font-size: 0.88rem;
|
||||
color: #555;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.spcfrm .spc_form .spc_field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
.spcfrm .spc_form .spc_field label {
|
||||
font-size: 0.85rem;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
.spcfrm .spc_form .spc_field input {
|
||||
padding: 0.3rem 0.45rem;
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.spcfrm .spc_form .spc_field input:invalid {
|
||||
border-color: #c0341d;
|
||||
}
|
||||
.spcfrm .spc_form .spc_field .spc_field_note {
|
||||
font-size: 0.78rem;
|
||||
color: #777;
|
||||
}
|
||||
.spcfrm .spc_form .spc_actions {
|
||||
text-align: right;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.spcfrm .spc_btn {
|
||||
cursor: pointer;
|
||||
border-radius: 0.28rem;
|
||||
padding: 0.3rem 0.8rem;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid #ababab;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
}
|
||||
.spcfrm .spc_btn.spc_btn_primary {
|
||||
background-color: rgb(86, 165, 50);
|
||||
border-color: rgb(69.68, 133.688372093, 40.511627907);
|
||||
color: #fff;
|
||||
}
|
||||
.spcfrm .spc_btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.spcfrm .spc_result {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.5rem 0.6rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.88rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
.spcfrm .spc_result .spc_result_line + .spc_result_line {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.spcfrm .spc_result.spc_result_ok {
|
||||
background: #e2f3da;
|
||||
color: #2f6d18;
|
||||
}
|
||||
.spcfrm .spc_result.spc_result_err {
|
||||
background: #f3ded9;
|
||||
color: #c0341d;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
let $spt = {
|
||||
mdl: 'Spezialfunktionen',
|
||||
intro: 'Versteckte Wartungsfunktionen für Administratoren. Jede Aktion wird protokolliert (fds__admin_activity / Anwendungslog).',
|
||||
run: 'Ausführen',
|
||||
working: 'Wird ausgeführt…',
|
||||
failed: 'Die Aktion ist fehlgeschlagen.',
|
||||
|
||||
/* Card: invoice file backup + clear */
|
||||
ifc_title: 'Rechnungsdatei zurücksetzen (Soft-Delete)',
|
||||
ifc_desc: 'Sichert die gespeicherte PDF-Datei einer finalisierten Rechnung (DB-Tabelle fds__invoice_file_backups + Kopie im Blob-Archiv) und entfernt sie aus der Rechnung. Beim nächsten Aufruf des Dokuments wird die PDF neu generiert (ADR 0025/0027).',
|
||||
ifc_id: 'Interne Rechnungs-ID',
|
||||
ifc_id_ph: 'z.B. W7VAL',
|
||||
ifc_id_note: 'Die interne ID (Spalte Id), nicht die Rechnungsnummer R2026-….',
|
||||
ifc_btn: 'Datei sichern und entfernen',
|
||||
ifc_confirm: 'Die Rechnungsdatei von "{0}" wird gesichert und aus der Rechnung entfernt. Fortfahren?',
|
||||
ifc_ok: 'Erledigt: Die Datei wurde gesichert und entfernt. Das Dokument wird beim nächsten Aufruf neu generiert.',
|
||||
ifc_ok_blob: 'Blob-Backup: {0}',
|
||||
ifc_ok_noblob: 'Kein Blob-Backup angelegt (Blob-Archiv deaktiviert, kein Blob vorhanden oder Kopie fehlgeschlagen — siehe Log).',
|
||||
ifc_reason: {
|
||||
invoice_not_found: 'Rechnung nicht gefunden.',
|
||||
not_final: 'Die Rechnung ist nicht finalisiert — nur finalisierte Rechnungen können zurückgesetzt werden (Entwürfe würden sonst dauerhaft als Entwurf gespeichert).',
|
||||
external: 'Externe (MFR-)Dokumente können nicht neu generiert werden.',
|
||||
no_file: 'Es ist keine Datei gespeichert — nichts zu tun.',
|
||||
db_error: 'Datenbankfehler — Details im Anwendungslog (CodeReference AdminBackupClearInvoiceFile).'
|
||||
}
|
||||
};
|
||||
|
||||
/* "Specialties" — admin-only page (URL /specialties, menu Administration → Spezialfunktionen)
|
||||
that exposes the otherwise hidden maintenance actions as cards with small forms. Each card
|
||||
calls its real action endpoint (which enforces the fds_sys > 4 gate itself); this module only
|
||||
renders and reports. Add a card per action in `cards`. */
|
||||
let $spc = {
|
||||
init2: function () {
|
||||
$ocms.getScript([], function () { $spc.init3(); });
|
||||
},
|
||||
init3: function () {
|
||||
$fis.cf(true);
|
||||
$fis.lf(true);
|
||||
$('#activemodule').text($spt.mdl);
|
||||
$('#topbar').ocmsmenu([]);
|
||||
let frm = $$.dc('spcfrm', $('#contentframe'));
|
||||
$$.dc('spc_intro', frm).text($spt.intro);
|
||||
let grid = $$.dc('spc_grid', frm);
|
||||
$.each($spc.cards, function (i, build) { build(grid); });
|
||||
},
|
||||
|
||||
/* ── building blocks ─────────────────────────────────────────────────── */
|
||||
card: function (grid, title, desc) {
|
||||
let card = $$.dc('spc_card', grid);
|
||||
let hd = $$.dc('spc_card_hd', card);
|
||||
$$.s(title).appendTo(hd);
|
||||
let bd = $$.dc('spc_card_bd', card);
|
||||
if (desc) { $$.dc('spc_card_desc', bd).text(desc); }
|
||||
let form = $('<form/>', { class: 'spc_form', novalidate: 'novalidate' }).appendTo(bd);
|
||||
let result = $$.dc('spc_result', bd).hide();
|
||||
return { card: card, body: bd, form: form, result: result };
|
||||
},
|
||||
field: function (form, name, label, options) {
|
||||
options = options || {};
|
||||
let fg = $$.dc('spc_field', form);
|
||||
let id = 'spc_' + name;
|
||||
$$.lbl(label, { for: id }).appendTo(fg);
|
||||
let inp = $$.i({ id: id, name: name, type: options.type || 'text', placeholder: options.placeholder || '' }).appendTo(fg);
|
||||
if (options.required) { inp.attr('required', 'required'); }
|
||||
if (options.pattern) { inp.attr('pattern', options.pattern); }
|
||||
if (options.note) { $$.dc('spc_field_note', fg).text(options.note); }
|
||||
return inp;
|
||||
},
|
||||
showResult: function (result, ok, lines) {
|
||||
result.empty().attr('class', 'spc_result ' + (ok ? 'spc_result_ok' : 'spc_result_err'));
|
||||
$.each([].concat(lines || []), function (i, l) { if (l) { $$.dc('spc_result_line', result).text(l); } });
|
||||
result.show();
|
||||
},
|
||||
/* Wires a card form: validates, confirms, POSTs, reports. `run(values)` must return a
|
||||
postXT options object without success/error/complete; `report(response)` returns
|
||||
{ ok, lines }. Callbacks run inside jQuery's dispatch and must never throw. */
|
||||
submit: function (c, options) {
|
||||
let btn = $$.bbtn(options.button, 'spc_btn spc_btn_primary').attr('type', 'submit').appendTo($$.dc('spc_actions', c.form));
|
||||
c.form.on('submit', function (ev) {
|
||||
ev.preventDefault();
|
||||
let f = c.form[0];
|
||||
if (typeof f.reportValidity === 'function' && !f.reportValidity()) { return; }
|
||||
let values = c.form.serializeObject(true, { typedvalues: false });
|
||||
if (typeof options.confirm === 'function' && !confirm(options.confirm(values))) { return; }
|
||||
c.result.hide();
|
||||
btn.prop('disabled', true).text($spt.working);
|
||||
$ocms.postXT($.extend(options.run(values), {
|
||||
success: function (r) {
|
||||
let rep = options.report(r || {});
|
||||
$spc.showResult(c.result, rep.ok, rep.lines);
|
||||
},
|
||||
error: function (jqXHR) {
|
||||
$spc.showResult(c.result, false, [$spt.failed, (jqXHR && jqXHR.internalText) || '']);
|
||||
},
|
||||
complete: function () { btn.prop('disabled', false).text(options.button); }
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
/* ── cards ───────────────────────────────────────────────────────────── */
|
||||
cards: [
|
||||
/* Invoice file backup + clear → POST /do/admin/backupclearinvoicefile/<id> (ADR 0027). */
|
||||
function (grid) {
|
||||
let c = $spc.card(grid, $spt.ifc_title, $spt.ifc_desc);
|
||||
$spc.field(c.form, 'id', $spt.ifc_id, { required: true, placeholder: $spt.ifc_id_ph, note: $spt.ifc_id_note, pattern: '[A-Za-z0-9]{1,10}' });
|
||||
$spc.submit(c, {
|
||||
button: $spt.ifc_btn,
|
||||
confirm: function (v) { return $spt.ifc_confirm.replace('{0}', v.id); },
|
||||
run: function (v) {
|
||||
return { url: $ocms.url('admin/backupclearinvoicefile/' + encodeURIComponent((v.id || '').trim())), method: 'post', data: {} };
|
||||
},
|
||||
report: function (r) {
|
||||
let b = r.backup || {};
|
||||
if (b.success === true) {
|
||||
return { ok: true, lines: [$spt.ifc_ok, b.blobBackupUri ? $spt.ifc_ok_blob.replace('{0}', b.blobBackupUri) : $spt.ifc_ok_noblob] };
|
||||
}
|
||||
return { ok: false, lines: [$spt.ifc_reason[b.reason] || ($spt.failed + ' (' + (b.reason || '?') + ')')] };
|
||||
}
|
||||
});
|
||||
}
|
||||
]
|
||||
};
|
||||
let $$spc = { init2: $spc.init2, auth: {} };
|
||||
export default $$spc;
|
||||
+1
@@ -0,0 +1 @@
|
||||
let $spt={mdl:"Spezialfunktionen",intro:"Versteckte Wartungsfunktionen für Administratoren. Jede Aktion wird protokolliert (fds__admin_activity / Anwendungslog).",run:"Ausführen",working:"Wird ausgeführt…",failed:"Die Aktion ist fehlgeschlagen.",ifc_title:"Rechnungsdatei zurücksetzen (Soft-Delete)",ifc_desc:"Sichert die gespeicherte PDF-Datei einer finalisierten Rechnung (DB-Tabelle fds__invoice_file_backups + Kopie im Blob-Archiv) und entfernt sie aus der Rechnung. Beim nächsten Aufruf des Dokuments wird die PDF neu generiert (ADR 0025/0027).",ifc_id:"Interne Rechnungs-ID",ifc_id_ph:"z.B. W7VAL",ifc_id_note:"Die interne ID (Spalte Id), nicht die Rechnungsnummer R2026-….",ifc_btn:"Datei sichern und entfernen",ifc_confirm:'Die Rechnungsdatei von "{0}" wird gesichert und aus der Rechnung entfernt. Fortfahren?',ifc_ok:"Erledigt: Die Datei wurde gesichert und entfernt. Das Dokument wird beim nächsten Aufruf neu generiert.",ifc_ok_blob:"Blob-Backup: {0}",ifc_ok_noblob:"Kein Blob-Backup angelegt (Blob-Archiv deaktiviert, kein Blob vorhanden oder Kopie fehlgeschlagen — siehe Log).",ifc_reason:{invoice_not_found:"Rechnung nicht gefunden.",not_final:"Die Rechnung ist nicht finalisiert — nur finalisierte Rechnungen können zurückgesetzt werden (Entwürfe würden sonst dauerhaft als Entwurf gespeichert).",external:"Externe (MFR-)Dokumente können nicht neu generiert werden.",no_file:"Es ist keine Datei gespeichert — nichts zu tun.",db_error:"Datenbankfehler — Details im Anwendungslog (CodeReference AdminBackupClearInvoiceFile)."}},$spc={init2:function(){$ocms.getScript([],(function(){$spc.init3()}))},init3:function(){$fis.cf(!0),$fis.lf(!0),$("#activemodule").text($spt.mdl),$("#topbar").ocmsmenu([]);let e=$$.dc("spcfrm",$("#contentframe"));$$.dc("spc_intro",e).text($spt.intro);let t=$$.dc("spc_grid",e);$.each($spc.cards,(function(e,n){n(t)}))},card:function(e,t,n){let i=$$.dc("spc_card",e),r=$$.dc("spc_card_hd",i);$$.s(t).appendTo(r);let c=$$.dc("spc_card_bd",i);return n&&$$.dc("spc_card_desc",c).text(n),{card:i,body:c,form:$("<form/>",{class:"spc_form",novalidate:"novalidate"}).appendTo(c),result:$$.dc("spc_result",c).hide()}},field:function(e,t,n,i){i=i||{};let r=$$.dc("spc_field",e),c="spc_"+t;$$.lbl(n,{for:c}).appendTo(r);let o=$$.i({id:c,name:t,type:i.type||"text",placeholder:i.placeholder||""}).appendTo(r);return i.required&&o.attr("required","required"),i.pattern&&o.attr("pattern",i.pattern),i.note&&$$.dc("spc_field_note",r).text(i.note),o},showResult:function(e,t,n){e.empty().attr("class","spc_result "+(t?"spc_result_ok":"spc_result_err")),$.each([].concat(n||[]),(function(t,n){n&&$$.dc("spc_result_line",e).text(n)})),e.show()},submit:function(e,t){let n=$$.bbtn(t.button,"spc_btn spc_btn_primary").attr("type","submit").appendTo($$.dc("spc_actions",e.form));e.form.on("submit",(function(i){i.preventDefault();let r=e.form[0];if("function"==typeof r.reportValidity&&!r.reportValidity())return;let c=e.form.serializeObject(!0,{typedvalues:!1});("function"!=typeof t.confirm||confirm(t.confirm(c)))&&(e.result.hide(),n.prop("disabled",!0).text($spt.working),$ocms.postXT($.extend(t.run(c),{success:function(n){let i=t.report(n||{});$spc.showResult(e.result,i.ok,i.lines)},error:function(t){$spc.showResult(e.result,!1,[$spt.failed,t&&t.internalText||""])},complete:function(){n.prop("disabled",!1).text(t.button)}})))}))},cards:[function(e){let t=$spc.card(e,$spt.ifc_title,$spt.ifc_desc);$spc.field(t.form,"id",$spt.ifc_id,{required:!0,placeholder:$spt.ifc_id_ph,note:$spt.ifc_id_note,pattern:"[A-Za-z0-9]{1,10}"}),$spc.submit(t,{button:$spt.ifc_btn,confirm:function(e){return $spt.ifc_confirm.replace("{0}",e.id)},run:function(e){return{url:$ocms.url("admin/backupclearinvoicefile/"+encodeURIComponent((e.id||"").trim())),method:"post",data:{}}},report:function(e){let t=e.backup||{};return!0===t.success?{ok:!0,lines:[$spt.ifc_ok,t.blobBackupUri?$spt.ifc_ok_blob.replace("{0}",t.blobBackupUri):$spt.ifc_ok_noblob]}:{ok:!1,lines:[$spt.ifc_reason[t.reason]||$spt.failed+" ("+(t.reason||"?")+")"]}}})}]},$$spc={init2:$spc.init2,auth:{}};export default $$spc;
|
||||
+1
@@ -0,0 +1 @@
|
||||
.spcfrm{padding:1.5rem}.spcfrm .spc_intro{margin-bottom:1rem;padding:.5rem .6rem;background:#f3f6fb;border-left:3px solid #1b4379;font-size:.9rem;color:#444}.spcfrm .spc_grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(24rem,1fr));gap:1rem;align-items:start}.spcfrm .spc_card{border:1px solid #d5d5d5;border-radius:.4rem;background:#fff;box-shadow:0 1px 3px rgba(50,50,50,.12);overflow:hidden}.spcfrm .spc_card_hd{padding:.6rem .85rem;background:#1b4379;color:#fff;font-weight:700}.spcfrm .spc_card_bd{padding:.75rem .85rem}.spcfrm .spc_card_desc{font-size:.88rem;color:#555;margin-bottom:.75rem}.spcfrm .spc_form .spc_field{display:flex;flex-direction:column;gap:.2rem;margin-bottom:.6rem}.spcfrm .spc_form .spc_field label{font-size:.85rem;color:#333;font-weight:600}.spcfrm .spc_form .spc_field input{padding:.3rem .45rem;border:1px solid #bbb;border-radius:.25rem;font-size:.95rem}.spcfrm .spc_form .spc_field input:invalid{border-color:#c0341d}.spcfrm .spc_form .spc_field .spc_field_note{font-size:.78rem;color:#777}.spcfrm .spc_form .spc_actions{text-align:right;margin-top:.25rem}.spcfrm .spc_btn{cursor:pointer;border-radius:.28rem;padding:.3rem .8rem;font-size:.9rem;border:1px solid #ababab;background-color:#fff;color:#333}.spcfrm .spc_btn.spc_btn_primary{background-color:#56a532;border-color:rgb(69.68,133.688372093,40.511627907);color:#fff}.spcfrm .spc_btn:disabled{opacity:.6;cursor:default}.spcfrm .spc_result{margin-top:.75rem;padding:.5rem .6rem;border-radius:.25rem;font-size:.88rem;word-break:break-word}.spcfrm .spc_result .spc_result_line+.spc_result_line{margin-top:.25rem}.spcfrm .spc_result.spc_result_ok{background:#e2f3da;color:#2f6d18}.spcfrm .spc_result.spc_result_err{background:#f3ded9;color:#c0341d}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user