commit
4daf1630f3
@ -0,0 +1,5 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
global.appRoot = require('path').resolve(__dirname); |
||||||
|
|
||||||
|
require('./server/server').init(); |
@ -0,0 +1,349 @@ |
|||||||
|
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ |
||||||
|
|
||||||
|
/* Document |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Correct the line height in all browsers. |
||||||
|
* 2. Prevent adjustments of font size after orientation changes in iOS. |
||||||
|
*/ |
||||||
|
|
||||||
|
html { |
||||||
|
line-height: 1.15; /* 1 */ |
||||||
|
-webkit-text-size-adjust: 100%; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/* Sections |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove the margin in all browsers. |
||||||
|
*/ |
||||||
|
|
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Render the `main` element consistently in IE. |
||||||
|
*/ |
||||||
|
|
||||||
|
main { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Correct the font size and margin on `h1` elements within `section` and |
||||||
|
* `article` contexts in Chrome, Firefox, and Safari. |
||||||
|
*/ |
||||||
|
|
||||||
|
h1 { |
||||||
|
font-size: 2em; |
||||||
|
margin: 0.67em 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* Grouping content |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Add the correct box sizing in Firefox. |
||||||
|
* 2. Show the overflow in Edge and IE. |
||||||
|
*/ |
||||||
|
|
||||||
|
hr { |
||||||
|
box-sizing: content-box; /* 1 */ |
||||||
|
height: 0; /* 1 */ |
||||||
|
overflow: visible; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers. |
||||||
|
* 2. Correct the odd `em` font sizing in all browsers. |
||||||
|
*/ |
||||||
|
|
||||||
|
pre { |
||||||
|
font-family: monospace, monospace; /* 1 */ |
||||||
|
font-size: 1em; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/* Text-level semantics |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove the gray background on active links in IE 10. |
||||||
|
*/ |
||||||
|
|
||||||
|
a { |
||||||
|
background-color: transparent; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Remove the bottom border in Chrome 57- |
||||||
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. |
||||||
|
*/ |
||||||
|
|
||||||
|
abbr[title] { |
||||||
|
border-bottom: none; /* 1 */ |
||||||
|
text-decoration: underline; /* 2 */ |
||||||
|
text-decoration: underline dotted; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the correct font weight in Chrome, Edge, and Safari. |
||||||
|
*/ |
||||||
|
|
||||||
|
b, |
||||||
|
strong { |
||||||
|
font-weight: bolder; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers. |
||||||
|
* 2. Correct the odd `em` font sizing in all browsers. |
||||||
|
*/ |
||||||
|
|
||||||
|
code, |
||||||
|
kbd, |
||||||
|
samp { |
||||||
|
font-family: monospace, monospace; /* 1 */ |
||||||
|
font-size: 1em; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the correct font size in all browsers. |
||||||
|
*/ |
||||||
|
|
||||||
|
small { |
||||||
|
font-size: 80%; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Prevent `sub` and `sup` elements from affecting the line height in |
||||||
|
* all browsers. |
||||||
|
*/ |
||||||
|
|
||||||
|
sub, |
||||||
|
sup { |
||||||
|
font-size: 75%; |
||||||
|
line-height: 0; |
||||||
|
position: relative; |
||||||
|
vertical-align: baseline; |
||||||
|
} |
||||||
|
|
||||||
|
sub { |
||||||
|
bottom: -0.25em; |
||||||
|
} |
||||||
|
|
||||||
|
sup { |
||||||
|
top: -0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
/* Embedded content |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove the border on images inside links in IE 10. |
||||||
|
*/ |
||||||
|
|
||||||
|
img { |
||||||
|
border-style: none; |
||||||
|
} |
||||||
|
|
||||||
|
/* Forms |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Change the font styles in all browsers. |
||||||
|
* 2. Remove the margin in Firefox and Safari. |
||||||
|
*/ |
||||||
|
|
||||||
|
button, |
||||||
|
input, |
||||||
|
optgroup, |
||||||
|
select, |
||||||
|
textarea { |
||||||
|
font-family: inherit; /* 1 */ |
||||||
|
font-size: 100%; /* 1 */ |
||||||
|
line-height: 1.15; /* 1 */ |
||||||
|
margin: 0; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Show the overflow in IE. |
||||||
|
* 1. Show the overflow in Edge. |
||||||
|
*/ |
||||||
|
|
||||||
|
button, |
||||||
|
input { /* 1 */ |
||||||
|
overflow: visible; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove the inheritance of text transform in Edge, Firefox, and IE. |
||||||
|
* 1. Remove the inheritance of text transform in Firefox. |
||||||
|
*/ |
||||||
|
|
||||||
|
button, |
||||||
|
select { /* 1 */ |
||||||
|
text-transform: none; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Correct the inability to style clickable types in iOS and Safari. |
||||||
|
*/ |
||||||
|
|
||||||
|
button, |
||||||
|
[type="button"], |
||||||
|
[type="reset"], |
||||||
|
[type="submit"] { |
||||||
|
-webkit-appearance: button; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove the inner border and padding in Firefox. |
||||||
|
*/ |
||||||
|
|
||||||
|
button::-moz-focus-inner, |
||||||
|
[type="button"]::-moz-focus-inner, |
||||||
|
[type="reset"]::-moz-focus-inner, |
||||||
|
[type="submit"]::-moz-focus-inner { |
||||||
|
border-style: none; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Restore the focus styles unset by the previous rule. |
||||||
|
*/ |
||||||
|
|
||||||
|
button:-moz-focusring, |
||||||
|
[type="button"]:-moz-focusring, |
||||||
|
[type="reset"]:-moz-focusring, |
||||||
|
[type="submit"]:-moz-focusring { |
||||||
|
outline: 1px dotted ButtonText; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Correct the padding in Firefox. |
||||||
|
*/ |
||||||
|
|
||||||
|
fieldset { |
||||||
|
padding: 0.35em 0.75em 0.625em; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Correct the text wrapping in Edge and IE. |
||||||
|
* 2. Correct the color inheritance from `fieldset` elements in IE. |
||||||
|
* 3. Remove the padding so developers are not caught out when they zero out |
||||||
|
* `fieldset` elements in all browsers. |
||||||
|
*/ |
||||||
|
|
||||||
|
legend { |
||||||
|
box-sizing: border-box; /* 1 */ |
||||||
|
color: inherit; /* 2 */ |
||||||
|
display: table; /* 1 */ |
||||||
|
max-width: 100%; /* 1 */ |
||||||
|
padding: 0; /* 3 */ |
||||||
|
white-space: normal; /* 1 */ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the correct vertical alignment in Chrome, Firefox, and Opera. |
||||||
|
*/ |
||||||
|
|
||||||
|
progress { |
||||||
|
vertical-align: baseline; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove the default vertical scrollbar in IE 10+. |
||||||
|
*/ |
||||||
|
|
||||||
|
textarea { |
||||||
|
overflow: auto; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Add the correct box sizing in IE 10. |
||||||
|
* 2. Remove the padding in IE 10. |
||||||
|
*/ |
||||||
|
|
||||||
|
[type="checkbox"], |
||||||
|
[type="radio"] { |
||||||
|
box-sizing: border-box; /* 1 */ |
||||||
|
padding: 0; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Correct the cursor style of increment and decrement buttons in Chrome. |
||||||
|
*/ |
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button, |
||||||
|
[type="number"]::-webkit-outer-spin-button { |
||||||
|
height: auto; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Correct the odd appearance in Chrome and Safari. |
||||||
|
* 2. Correct the outline style in Safari. |
||||||
|
*/ |
||||||
|
|
||||||
|
[type="search"] { |
||||||
|
-webkit-appearance: textfield; /* 1 */ |
||||||
|
outline-offset: -2px; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove the inner padding in Chrome and Safari on macOS. |
||||||
|
*/ |
||||||
|
|
||||||
|
[type="search"]::-webkit-search-decoration { |
||||||
|
-webkit-appearance: none; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 1. Correct the inability to style clickable types in iOS and Safari. |
||||||
|
* 2. Change font properties to `inherit` in Safari. |
||||||
|
*/ |
||||||
|
|
||||||
|
::-webkit-file-upload-button { |
||||||
|
-webkit-appearance: button; /* 1 */ |
||||||
|
font: inherit; /* 2 */ |
||||||
|
} |
||||||
|
|
||||||
|
/* Interactive |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/* |
||||||
|
* Add the correct display in Edge, IE 10+, and Firefox. |
||||||
|
*/ |
||||||
|
|
||||||
|
details { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Add the correct display in all browsers. |
||||||
|
*/ |
||||||
|
|
||||||
|
summary { |
||||||
|
display: list-item; |
||||||
|
} |
||||||
|
|
||||||
|
/* Misc |
||||||
|
========================================================================== */ |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the correct display in IE 10+. |
||||||
|
*/ |
||||||
|
|
||||||
|
template { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the correct display in IE 10. |
||||||
|
*/ |
||||||
|
|
||||||
|
[hidden] { |
||||||
|
display: none; |
||||||
|
} |
@ -0,0 +1,231 @@ |
|||||||
|
@charset "UTF-8"; |
||||||
|
|
||||||
|
/*! |
||||||
|
* Pikaday |
||||||
|
* Copyright © 2014 David Bushell | BSD & MIT license | https://dbushell.com/ |
||||||
|
*/ |
||||||
|
|
||||||
|
.pika-single { |
||||||
|
z-index: 9999; |
||||||
|
display: block; |
||||||
|
position: relative; |
||||||
|
color: #333; |
||||||
|
background: #fff; |
||||||
|
border: 1px solid #ccc; |
||||||
|
border-bottom-color: #bbb; |
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
clear child float (pika-lendar), using the famous micro clearfix hack |
||||||
|
http://nicolasgallagher.com/micro-clearfix-hack/ |
||||||
|
*/ |
||||||
|
.pika-single:before, |
||||||
|
.pika-single:after { |
||||||
|
content: " "; |
||||||
|
display: table; |
||||||
|
} |
||||||
|
.pika-single:after { clear: both } |
||||||
|
|
||||||
|
.pika-single.is-hidden { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-single.is-bound { |
||||||
|
position: absolute; |
||||||
|
box-shadow: 0 5px 15px -5px rgba(0,0,0,.5); |
||||||
|
} |
||||||
|
|
||||||
|
.pika-lendar { |
||||||
|
float: left; |
||||||
|
width: 240px; |
||||||
|
margin: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-title { |
||||||
|
position: relative; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-label { |
||||||
|
display: inline-block; |
||||||
|
position: relative; |
||||||
|
z-index: 9999; |
||||||
|
overflow: hidden; |
||||||
|
margin: 0; |
||||||
|
padding: 5px 3px; |
||||||
|
font-size: 14px; |
||||||
|
line-height: 20px; |
||||||
|
font-weight: bold; |
||||||
|
background-color: #fff; |
||||||
|
} |
||||||
|
.pika-title select { |
||||||
|
cursor: pointer; |
||||||
|
position: absolute; |
||||||
|
z-index: 9998; |
||||||
|
margin: 0; |
||||||
|
left: 0; |
||||||
|
top: 5px; |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-prev, |
||||||
|
.pika-next { |
||||||
|
display: block; |
||||||
|
cursor: pointer; |
||||||
|
position: relative; |
||||||
|
outline: none; |
||||||
|
border: 0; |
||||||
|
padding: 0; |
||||||
|
width: 20px; |
||||||
|
height: 30px; |
||||||
|
/* hide text using text-indent trick, using width value (it's enough) */ |
||||||
|
text-indent: 20px; |
||||||
|
white-space: nowrap; |
||||||
|
overflow: hidden; |
||||||
|
background-color: transparent; |
||||||
|
background-position: center center; |
||||||
|
background-repeat: no-repeat; |
||||||
|
background-size: 75% 75%; |
||||||
|
opacity: .5; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-prev:hover, |
||||||
|
.pika-next:hover { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-prev, |
||||||
|
.is-rtl .pika-next { |
||||||
|
float: left; |
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg=='); |
||||||
|
} |
||||||
|
|
||||||
|
.pika-next, |
||||||
|
.is-rtl .pika-prev { |
||||||
|
float: right; |
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII='); |
||||||
|
} |
||||||
|
|
||||||
|
.pika-prev.is-disabled, |
||||||
|
.pika-next.is-disabled { |
||||||
|
cursor: default; |
||||||
|
opacity: .2; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-select { |
||||||
|
display: inline-block; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-table { |
||||||
|
width: 100%; |
||||||
|
border-collapse: collapse; |
||||||
|
border-spacing: 0; |
||||||
|
border: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-table th, |
||||||
|
.pika-table td { |
||||||
|
width: 14.285714285714286%; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-table th { |
||||||
|
color: #999; |
||||||
|
font-size: 12px; |
||||||
|
line-height: 25px; |
||||||
|
font-weight: bold; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-button { |
||||||
|
cursor: pointer; |
||||||
|
display: block; |
||||||
|
box-sizing: border-box; |
||||||
|
-moz-box-sizing: border-box; |
||||||
|
outline: none; |
||||||
|
border: 0; |
||||||
|
margin: 0; |
||||||
|
width: 100%; |
||||||
|
padding: 5px; |
||||||
|
color: #666; |
||||||
|
font-size: 12px; |
||||||
|
line-height: 15px; |
||||||
|
text-align: right; |
||||||
|
background: #f5f5f5; |
||||||
|
height: initial; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-week { |
||||||
|
font-size: 11px; |
||||||
|
color: #999; |
||||||
|
} |
||||||
|
|
||||||
|
.is-today .pika-button { |
||||||
|
color: #33aaff; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.is-selected .pika-button, |
||||||
|
.has-event .pika-button { |
||||||
|
color: #fff; |
||||||
|
font-weight: bold; |
||||||
|
background: #33aaff; |
||||||
|
box-shadow: inset 0 1px 3px #178fe5; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
.has-event .pika-button { |
||||||
|
background: #005da9; |
||||||
|
box-shadow: inset 0 1px 3px #0076c9; |
||||||
|
} |
||||||
|
|
||||||
|
.is-disabled .pika-button, |
||||||
|
.is-inrange .pika-button { |
||||||
|
background: #D5E9F7; |
||||||
|
} |
||||||
|
|
||||||
|
.is-startrange .pika-button { |
||||||
|
color: #fff; |
||||||
|
background: #6CB31D; |
||||||
|
box-shadow: none; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
.is-endrange .pika-button { |
||||||
|
color: #fff; |
||||||
|
background: #33aaff; |
||||||
|
box-shadow: none; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
.is-disabled .pika-button { |
||||||
|
pointer-events: none; |
||||||
|
cursor: default; |
||||||
|
color: #999; |
||||||
|
opacity: .3; |
||||||
|
} |
||||||
|
|
||||||
|
.is-outside-current-month .pika-button { |
||||||
|
color: #999; |
||||||
|
opacity: .3; |
||||||
|
} |
||||||
|
|
||||||
|
.is-selection-disabled { |
||||||
|
pointer-events: none; |
||||||
|
cursor: default; |
||||||
|
} |
||||||
|
|
||||||
|
.pika-button:hover, |
||||||
|
.pika-row.pick-whole-week:hover .pika-button { |
||||||
|
color: #fff; |
||||||
|
background: #ff8000; |
||||||
|
box-shadow: none; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
/* styling for abbr */ |
||||||
|
.pika-table abbr { |
||||||
|
border-bottom: none; |
||||||
|
cursor: help; |
||||||
|
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@ |
|||||||
|
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):t.dayjs_plugin_customParseFormat=n()}(this,function(){"use strict";var t,n=/(\[[^[]*\])|([-:/.()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,e=/\d\d/,r=/\d\d?/,o=/\d*[^\s\d-:/.()]+/;var s=function(t){return function(n){this[t]=+n}},i=[/[+-]\d\d:?\d\d/,function(t){var n,e;(this.zone||(this.zone={})).offset=(n=t.match(/([+-]|\d\d)/g),0===(e=60*n[1]+ +n[2])?0:"+"===n[0]?-e:e)}],a={A:[/[AP]M/,function(t){this.afternoon="PM"===t}],a:[/[ap]m/,function(t){this.afternoon="pm"===t}],S:[/\d/,function(t){this.milliseconds=100*+t}],SS:[e,function(t){this.milliseconds=10*+t}],SSS:[/\d{3}/,function(t){this.milliseconds=+t}],s:[r,s("seconds")],ss:[r,s("seconds")],m:[r,s("minutes")],mm:[r,s("minutes")],H:[r,s("hours")],h:[r,s("hours")],HH:[r,s("hours")],hh:[r,s("hours")],D:[r,s("day")],DD:[e,s("day")],Do:[o,function(n){var e=t.ordinal,r=n.match(/\d+/);if(this.day=r[0],e)for(var o=1;o<=31;o+=1)e(o).replace(/\[|\]/g,"")===n&&(this.day=o)}],M:[r,s("month")],MM:[e,s("month")],MMM:[o,function(n){var e=t,r=e.months,o=e.monthsShort,s=o?o.findIndex(function(t){return t===n}):r.findIndex(function(t){return t.substr(0,3)===n});if(s<0)throw new Error;this.month=s+1}],MMMM:[o,function(n){var e=t.months.indexOf(n);if(e<0)throw new Error;this.month=e+1}],Y:[/[+-]?\d+/,s("year")],YY:[e,function(t){t=+t,this.year=t+(t>68?1900:2e3)}],YYYY:[/\d{4}/,s("year")],Z:i,ZZ:i};var u=function(t,e,r){try{var o=function(t){for(var e=t.match(n),r=e.length,o=0;o<r;o+=1){var s=e[o],i=a[s],u=i&&i[0],f=i&&i[1];e[o]=f?{regex:u,parser:f}:s.replace(/^\[|\]$/g,"")}return function(t){for(var n={},o=0,s=0;o<r;o+=1){var i=e[o];if("string"==typeof i)s+=i.length;else{var a=i.regex,u=i.parser,f=t.substr(s),h=a.exec(f)[0];u.call(n,h),t=t.replace(h,"")}}return function(t){var n=t.afternoon;if(void 0!==n){var e=t.hours;n?e<12&&(t.hours+=12):12===e&&(t.hours=0),delete t.afternoon}}(n),n}}(e)(t),s=o.year,i=o.month,u=o.day,f=o.hours,h=o.minutes,d=o.seconds,c=o.milliseconds,m=o.zone;if(m)return new Date(Date.UTC(s,i-1,u,f||0,h||0,d||0,c||0)+60*m.offset*1e3);var l=new Date,v=u||(s||i?1:l.getDate()),p=s||l.getFullYear(),M=i>0?i-1:l.getMonth(),y=f||0,D=h||0,Y=d||0,g=c||0;return r?new Date(Date.UTC(p,M,v,y,D,Y,g)):new Date(p,M,v,y,D,Y,g)}catch(t){return new Date("")}};return function(n,e,r){var o=e.prototype,s=o.parse;o.parse=function(n){var e=n.date,o=n.format,i=n.pl,a=n.utc;this.$u=a,o?(t=i?r.Ls[i]:this.$locale(),this.$d=u(e,o,a),this.init(n),i&&(this.$L=i)):s.call(this,n)}}}); |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -0,0 +1,440 @@ |
|||||||
|
:root { |
||||||
|
--cdr-border: #ccc; |
||||||
|
--cdr-header-border: #bbb; |
||||||
|
--cdr-header-color: #cfd7ff; |
||||||
|
|
||||||
|
|
||||||
|
--cdr-odd-row: #eaeaea; |
||||||
|
--cdr-even-row: #f3f3f3; |
||||||
|
--cdr-group-row: #fffadf; |
||||||
|
--cdr-active-row: #ffb0ee; |
||||||
|
|
||||||
|
|
||||||
|
--filter-wildcard-color: #444; |
||||||
|
--filter-wildcard-active-color: #ff18ed; |
||||||
|
|
||||||
|
--important-date-background: #ffa3da; |
||||||
|
--weekend-background: #c6dfff; |
||||||
|
|
||||||
|
--filter-notify-active-color: #ff4df1; |
||||||
|
--filter-notify-color: #bfb0be; |
||||||
|
|
||||||
|
--filter-calltype-color: #212121; |
||||||
|
--filter-calltype-active-color: #ff18ed; |
||||||
|
|
||||||
|
--filter-calltype-transition1-color: #555; |
||||||
|
--filter-calltype-transition2-color: #ff4df1; |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.left-panel .filters { |
||||||
|
position: relative; |
||||||
|
padding-left: .8rem; |
||||||
|
margin-bottom: .75rem; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .filters > * { |
||||||
|
margin-bottom: .5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .filters .filter .wrapper { |
||||||
|
display: flex; align-items: center; |
||||||
|
} |
||||||
|
.left-panel .filters .filter .value { |
||||||
|
flex: 1; |
||||||
|
min-width: 6rem; |
||||||
|
margin-right: .5rem; |
||||||
|
background-color: var(--textbox-background); |
||||||
|
} |
||||||
|
.left-panel .filters .filter .wildcard { |
||||||
|
width: 1.4rem; |
||||||
|
height: 1.4rem; |
||||||
|
margin-right: .5rem; |
||||||
|
padding: .1rem; |
||||||
|
cursor: pointer; |
||||||
|
fill: var(--filter-wildcard-color); |
||||||
|
transition: fill .15s; |
||||||
|
} |
||||||
|
.left-panel .filters .filter .wildcard.active { |
||||||
|
fill: var(--filter-wildcard-active-color); |
||||||
|
} |
||||||
|
.left-panel .filters .filter .status { |
||||||
|
width: .5rem; |
||||||
|
height: .5rem; |
||||||
|
background-color: var(--filter-notify-color); |
||||||
|
border-radius: .25rem; |
||||||
|
transition: background-color .2s ease-out; |
||||||
|
} |
||||||
|
.left-panel .filters .filter .status.active { |
||||||
|
background-color: var(--filter-notify-active-color); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.left-panel .filters .call-types { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
margin-left: .5rem; |
||||||
|
margin-right: .5rem; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type svg { |
||||||
|
height: 1.5rem; |
||||||
|
fill: var(--filter-calltype-color); |
||||||
|
transition: .15s fill; |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type span { |
||||||
|
margin-top: .25rem; |
||||||
|
font-size: 0.85rem; |
||||||
|
color: var(--filter-calltype-color); |
||||||
|
transition: .15s color; |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type.active svg { |
||||||
|
fill: var(--filter-calltype-active-color); |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type.active span { |
||||||
|
color: var(--filter-calltype-active-color); |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type:hover svg { |
||||||
|
fill: var(--filter-calltype-transition1-color); |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type:hover span { |
||||||
|
color: var(--filter-calltype-transition1-color); |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type.active:hover svg { |
||||||
|
fill: var(--filter-calltype-transition2-color); |
||||||
|
} |
||||||
|
.left-panel .filters .call-types .call-type.active:hover span { |
||||||
|
color: var(--filter-calltype-transition2-color); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.left-panel .filters .buttons { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
.left-panel .filters .buttons > .button { |
||||||
|
flex: 1; |
||||||
|
min-width: fit-content; |
||||||
|
font-size: .9rem; |
||||||
|
font-weight: 500; |
||||||
|
margin-left: .5rem; margin-right: .5rem; |
||||||
|
} |
||||||
|
.left-panel .filters .buttons > .button:first-child { |
||||||
|
margin-left: 0; |
||||||
|
} |
||||||
|
.left-panel .filters .buttons > .button:last-child { |
||||||
|
margin-right: 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.left-panel .settings { |
||||||
|
position: relative; |
||||||
|
padding-left: .8rem; |
||||||
|
margin-bottom: .75rem; |
||||||
|
} |
||||||
|
.left-panel .settings .component { |
||||||
|
margin-bottom: .5rem; |
||||||
|
font-size: .9rem; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
.left-panel .settings .component:last-child { |
||||||
|
margin-bottom: 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.left-panel .new-entries { |
||||||
|
margin-top: .5rem; |
||||||
|
margin-bottom: .75rem; |
||||||
|
display: flex; flex-direction: column; align-items: center; |
||||||
|
opacity: 0; visibility: hidden; |
||||||
|
transition: opacity .1s, visibility .1s; |
||||||
|
} |
||||||
|
.left-panel .new-entries.active { |
||||||
|
opacity: 1; visibility: visible; |
||||||
|
} |
||||||
|
.left-panel .new-entries .text { |
||||||
|
display: flex; justify-content: space-around; |
||||||
|
} |
||||||
|
.left-panel .new-entries span { |
||||||
|
font-size: 1.1rem; |
||||||
|
margin-bottom: .5rem; |
||||||
|
} |
||||||
|
.left-panel .new-entries .num { |
||||||
|
font-weight: 600; |
||||||
|
color: var(--important); |
||||||
|
} |
||||||
|
.left-panel .new-entries .update { |
||||||
|
min-width: 7.5rem; |
||||||
|
font-size: .9rem; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.right-panel .cdr { |
||||||
|
flex-grow: 1; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .table { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
min-width: 1000px; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header { |
||||||
|
display: flex; |
||||||
|
background-color: var(--cdr-header-color); |
||||||
|
border: 1px solid var(--cdr-header-border); |
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell { |
||||||
|
border-right: 1px solid var(--cdr-header-border); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell-group { |
||||||
|
padding: 0; |
||||||
|
flex-grow: 0; |
||||||
|
border: none; |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell-group_col { |
||||||
|
flex-direction: column; |
||||||
|
align-items: stretch; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell-group_col > .cell:not(:last-child) { |
||||||
|
border-bottom: 1px solid var(--cdr-header-border); |
||||||
|
padding-top: .25rem; |
||||||
|
padding-bottom: .25rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell-group_row > .cell:last-of-type { |
||||||
|
border-right: 1px solid var(--cdr-header-border); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell-clickable-area { |
||||||
|
padding-top: .25rem; |
||||||
|
padding-bottom: .25rem; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
flex: 1; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell.sortable .name { |
||||||
|
color: var(--link-normal-color); |
||||||
|
transition: .15s color; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell.sortable .cell-clickable-area:hover .name { |
||||||
|
color: var(--link-active-color); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell.sortable .sort-button { |
||||||
|
display: none; |
||||||
|
width: .7rem; |
||||||
|
height: .7rem; |
||||||
|
margin-left: .4rem; |
||||||
|
cursor: pointer; |
||||||
|
transition: .15s fill, .3s transform; |
||||||
|
transform-origin: center center; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell.sortable .sort-button.sort-asc { |
||||||
|
display: flex; |
||||||
|
transform: rotate(180deg); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell.sortable .sort-button.sort-desc { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .header .cell.sortable .cell-clickable-area:hover .sort-button { |
||||||
|
fill: var(--link-active-color); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.cdr .cell { |
||||||
|
flex-grow: 1; |
||||||
|
|
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
border-right: 1px solid var(--border); |
||||||
|
padding-left: .75rem; |
||||||
|
padding-right: .75rem; |
||||||
|
min-height: 1.35rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .cell:last-of-type { |
||||||
|
border-right: none; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .cell.cell_id { min-width: 3.5rem; max-width: 3.5rem; } |
||||||
|
.cdr .cell.cell_dailyct { min-width: 6rem; max-width: 6rem; } |
||||||
|
.cdr .cell.cell_type { min-width: 4.5rem; max-width: 4.5rem; } |
||||||
|
.cdr .cell.cell_date { min-width: 8.5%; max-width: 8.5%; } |
||||||
|
.cdr .cell.cell_source { min-width: 12.5%; flex-basis: 100%; } |
||||||
|
.cdr .cell.cell_destination { min-width: 12.5%; flex-basis: 100%; } |
||||||
|
.cdr .cell.cell_total-duration { min-width: 5rem; max-width: 5rem; } |
||||||
|
.cdr .cell.cell_call-duration { min-width: 5rem; max-width: 5rem; } |
||||||
|
.cdr .cell.cell_result { min-width: 8rem; max-width: 8rem; } |
||||||
|
.cdr .cell.cell_records { min-width: 7rem; max-width: 7rem; } |
||||||
|
|
||||||
|
|
||||||
|
.cdr .body { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
border: 1px solid var(--cdr-border); |
||||||
|
border-top: none; |
||||||
|
max-height: calc(100vh - 12rem); |
||||||
|
overflow-x: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .entry:nth-child(even) { |
||||||
|
background-color: var(--cdr-even-row); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .entry:nth-child(odd) { |
||||||
|
background-color: var(--cdr-odd-row); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .entry { |
||||||
|
display: flex; |
||||||
|
min-height: 2.5rem; |
||||||
|
transition: background-color .1s; |
||||||
|
} |
||||||
|
.cdr .body .entry.active { |
||||||
|
background-color: var(--cdr-active-row); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .cell { |
||||||
|
font-size: 0.95rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .cell .image { |
||||||
|
width: 1.5rem; |
||||||
|
height: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .cell .image.link { |
||||||
|
width: 1.25rem; |
||||||
|
height: 1.25rem; |
||||||
|
cursor: pointer; |
||||||
|
transition: fill .15s; |
||||||
|
margin-left: .4rem; |
||||||
|
margin-right: .4rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .cell .image.link:hover { |
||||||
|
fill: var(--control-selected); |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .body .entry.group { |
||||||
|
background-color: var(--cdr-group-row); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
padding-left: 1rem; padding-right: 1rem; |
||||||
|
} |
||||||
|
.cdr .body .entry.group:not(:nth-child(2)) { |
||||||
|
border-top: 1px solid var(--cdr-border); |
||||||
|
} |
||||||
|
.cdr .body .entry.group:not(:last-child) { |
||||||
|
border-bottom: 1px solid var(--cdr-border); |
||||||
|
} |
||||||
|
.cdr .body .entry.group .group-text { |
||||||
|
font-size: 1.2rem; |
||||||
|
font-weight: 500; |
||||||
|
min-width: 8rem; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
.cdr .body .entry.group .stats { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
margin-left: 1rem; |
||||||
|
} |
||||||
|
.cdr .body .entry.group .stats .image { |
||||||
|
width: 1.35rem; |
||||||
|
height: 1.35rem; |
||||||
|
margin-right: .5rem; |
||||||
|
margin-left: 2rem; |
||||||
|
} |
||||||
|
.cdr .body .entry.group .stats .num-calls { |
||||||
|
text-align: left; |
||||||
|
min-width: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.cdr .body .no-cdrs { |
||||||
|
display: none; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
padding-bottom: 2rem; |
||||||
|
} |
||||||
|
.cdr .body .no-cdrs.active { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.cdr .body .no-cdrs .text { |
||||||
|
font-size: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .controls { |
||||||
|
min-height: 2.5rem; |
||||||
|
max-height: 2.5rem; |
||||||
|
|
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
align-self: center; |
||||||
|
border-top: 1px solid var(--border); |
||||||
|
border-bottom: 1px solid var(--border); |
||||||
|
padding-left: 1rem; |
||||||
|
padding-right: 1rem; |
||||||
|
margin-top: 1rem; |
||||||
|
|
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .controls .button { |
||||||
|
width: 1.5rem; |
||||||
|
height: 1.5rem; |
||||||
|
margin-left: .75rem; |
||||||
|
margin-right: .75rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .controls .button.active { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .controls .button { fill: var(--control-disabled); transition: fill .15s; } |
||||||
|
.cdr .controls .button.active { fill: var(--control-enabled); } |
||||||
|
.cdr .controls .button.active:hover { fill: var(--control-selected); } |
||||||
|
|
||||||
|
.cdr .controls .status { |
||||||
|
margin-left: 2rem; |
||||||
|
margin-right: 2rem; |
||||||
|
font-size: 1.15rem; |
||||||
|
min-width: 5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.cdr .controls .goto-page { |
||||||
|
margin-left: 1rem; |
||||||
|
width: 3.5rem; |
||||||
|
} |
@ -0,0 +1,274 @@ |
|||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-Regular.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-Regular.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-Regular.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-Regular.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: normal; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-Italic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-Italic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-Italic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-Italic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: normal; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-Thin.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-Thin.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-Thin.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-Thin.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 100; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-ThinItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-ThinItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-ThinItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-ThinItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 100; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-ExtraLight.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-ExtraLight.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-ExtraLight.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-ExtraLight.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 200; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-ExtraLightItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-ExtraLightItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-ExtraLightItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-ExtraLightItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 200; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-Light.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-Light.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-Light.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-Light.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 300; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-LightItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-LightItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-LightItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-LightItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 300; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-Medium.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-Medium.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-Medium.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-Medium.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 500; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-MediumItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-MediumItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-MediumItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-MediumItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 500; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-SemiBold.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-SemiBold.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-SemiBold.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-SemiBold.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 600; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-SemiBoldItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-SemiBoldItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-SemiBoldItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-SemiBoldItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 600; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-Bold.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-Bold.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-Bold.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-Bold.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: bold; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-BoldItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-BoldItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-BoldItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-BoldItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: bold; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-ExtraBold.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-ExtraBold.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-ExtraBold.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-ExtraBold.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 800; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-ExtraBoldItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-ExtraBoldItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-ExtraBoldItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-ExtraBoldItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 800; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-Black.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-Black.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-Black.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-Black.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 900; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-BlackItalic.eot?") format("embedded-opentype"), |
||||||
|
url("/public/fonts/Exo2-BlackItalic.woff2") format("woff2"), |
||||||
|
url("/public/fonts/Exo2-BlackItalic.woff") format("woff"), |
||||||
|
url("/public/fonts/Exo2-BlackItalic.ttf") format("truetype"); |
||||||
|
|
||||||
|
font-weight: 900; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@supports (font-variation-settings: normal) { |
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-VF.woff2") format("woff2 supports variations"), |
||||||
|
url("/public/fonts/Exo2-VF.woff2") format("woff2-variations"); |
||||||
|
|
||||||
|
font-weight: 100 900; |
||||||
|
font-style: normal; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Exo 2"; |
||||||
|
src: url("/public/fonts/Exo2-VFI.woff2") format("woff2 supports variations"), |
||||||
|
url("/public/fonts/Exo2-VFI.woff2") format("woff2-variations"); |
||||||
|
|
||||||
|
font-weight: 100 900; |
||||||
|
font-style: italic; |
||||||
|
font-display: block; |
||||||
|
unicode-range: U+00??, U+04??, U+20??, U+226?; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,616 @@ |
|||||||
|
/* // TODO: fix all transition syntax |
||||||
|
// TODO: double-colon selectors */ |
||||||
|
|
||||||
|
*, ::after, ::before { |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
:root { |
||||||
|
--root-font-size: 14px; |
||||||
|
|
||||||
|
--text: #212121; |
||||||
|
--background: #fafafa; |
||||||
|
--border: #ccc; |
||||||
|
|
||||||
|
--action: #ff9000; |
||||||
|
--important: #ff4df1; |
||||||
|
|
||||||
|
--link-normal-color: #0d27fa; |
||||||
|
--link-normal-sub-color: #475bff; |
||||||
|
--link-active-color: #ff9000; |
||||||
|
|
||||||
|
--footer-background: #ddd; |
||||||
|
|
||||||
|
--left-panel-background: #c8e0cf; |
||||||
|
--left-panel-header-background: #b8e2ba; |
||||||
|
|
||||||
|
--right-panel-color: #f1f1f1; |
||||||
|
--divider-color: #a6a6a6; |
||||||
|
|
||||||
|
--textbox-border: #888; |
||||||
|
--textbox-selected-border: #ff9000; |
||||||
|
--textbox-selected-shadow: #ff810094; |
||||||
|
--textbox-background: #fafafa; |
||||||
|
|
||||||
|
--checkbox-color: #ff4df1; |
||||||
|
|
||||||
|
--control-disabled: #aaa; |
||||||
|
--control-enabled: #050505; |
||||||
|
--control-selected: #ff9000; |
||||||
|
|
||||||
|
--scrollbar-background: #ddd; |
||||||
|
--scrollbar-color: #aaa; |
||||||
|
|
||||||
|
--shadow-color: 128,128,128; |
||||||
|
|
||||||
|
--message-wrapper-background: rgba(190, 190, 190, 0.85); |
||||||
|
--message-shadow-background: rgb(190, 190, 190); |
||||||
|
--message-background: #fafafa; |
||||||
|
|
||||||
|
|
||||||
|
--button-background: #ffd96f; |
||||||
|
--button-selected-shadow: #ffd96f63; |
||||||
|
--button-selected-color: #070707; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
html { |
||||||
|
font-size: var(--root-font-size); |
||||||
|
} |
||||||
|
body { |
||||||
|
padding: 0; |
||||||
|
min-height: 100vh; |
||||||
|
display: flex; |
||||||
|
|
||||||
|
font-size: 1rem; |
||||||
|
font-family: 'Exo 2', 'Open Sans', 'Roboto', 'Segoe UI', Tahoma, sans-serif; |
||||||
|
font-weight: normal; |
||||||
|
letter-spacing: 0.02rem; |
||||||
|
text-align: center; |
||||||
|
|
||||||
|
background-color: var(--background); |
||||||
|
color: var(--text); |
||||||
|
} |
||||||
|
ol, ul { |
||||||
|
list-style-type: none; |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
a { |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
svg { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
label { |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:root { |
||||||
|
--accent-border: #ccc; |
||||||
|
|
||||||
|
--component-border: #999; |
||||||
|
--component-background: #fafafa; |
||||||
|
--component-focus: #cccccca9; |
||||||
|
--component-shadow-action: #ff8d0099; |
||||||
|
|
||||||
|
--button-inset-shadow: #555555aa; |
||||||
|
--cb-tick: #e31fe6; |
||||||
|
|
||||||
|
--window-wrapper-background: rgba(190, 190, 190, 0.85); |
||||||
|
--window-shadow-background: rgb(190, 190, 190); |
||||||
|
--window-background: #fafafa; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.component { |
||||||
|
display: flex; justify-content: flex-start; align-items: center; |
||||||
|
} |
||||||
|
.component.column { |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
.component > label { |
||||||
|
flex-basis: auto; |
||||||
|
width: max-content; |
||||||
|
} |
||||||
|
.component > input, .component > button { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
.component > input[type='checkbox'], .component > input[type='checkbox'] + label, .component > button { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
.component > input[type='checkbox'] { |
||||||
|
width: 1.25rem; height: 1.25rem; |
||||||
|
min-width: 1.25rem; min-height: 1.25rem; |
||||||
|
max-width: 1.25rem; max-height: 1.25rem; |
||||||
|
} |
||||||
|
.component > input + label, .component > label + input { |
||||||
|
margin-left: .5rem; |
||||||
|
} |
||||||
|
.component.column > input + label, .component.column > label + input { |
||||||
|
margin-left: 0; |
||||||
|
} |
||||||
|
.component > button { |
||||||
|
min-width: 5rem; |
||||||
|
min-height: 1.7rem; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
.component.column > label { |
||||||
|
margin-bottom: .25rem; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@supports (-webkit-appearance: none) or (-moz-appearance: none) or (appearance: none) { |
||||||
|
.component > input, .component > button { |
||||||
|
-webkit-appearance: none; -moz-appearance: none; appearance: none; |
||||||
|
outline: none; |
||||||
|
border: 1px solid var(--component-border-action, var(--component-border)); |
||||||
|
background: var(--component-background-action, var(--component-background)); |
||||||
|
border-radius: 3px; |
||||||
|
transition: border-color .15s, background-color .15s, box-shadow .15s; |
||||||
|
} |
||||||
|
.component > input[type='checkbox']:checked:not(:hover) { |
||||||
|
--component-border-action: #666; |
||||||
|
--component-background-action: #fff4ea; |
||||||
|
} |
||||||
|
.component > input:hover, .component > button:hover { |
||||||
|
--component-border-action: #ff8d00; |
||||||
|
box-shadow: 0 0 3px 1px var(--component-shadow-action); |
||||||
|
} |
||||||
|
.component > input:focus:not(:hover), .component > button:focus:not(:hover) { |
||||||
|
box-shadow: 0 0 1px 2px var(--component-focus); |
||||||
|
} |
||||||
|
.component > input[type='checkbox']::after { |
||||||
|
content: ''; |
||||||
|
display: block; |
||||||
|
left: 0; |
||||||
|
top: 0; |
||||||
|
position: absolute; |
||||||
|
width: 30%; |
||||||
|
height: 60%; |
||||||
|
border: 2px solid var(--cb-tick); |
||||||
|
border-top: 0; |
||||||
|
border-left: 0; |
||||||
|
left: 35%; |
||||||
|
top: 15%; |
||||||
|
opacity: 0; |
||||||
|
transform: rotate(45deg); |
||||||
|
transition: opacity .15s; |
||||||
|
} |
||||||
|
.component > input[type='checkbox']:checked::after { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.component > input[type='text'] { |
||||||
|
min-height: 1.6rem; |
||||||
|
padding: .15rem .3rem; |
||||||
|
min-width: 12.5rem; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.component > button::after { |
||||||
|
left: -1px; |
||||||
|
top: -1px; |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
width: calc(100% + 2px); |
||||||
|
height: calc(100% + 2px); |
||||||
|
opacity: 0; |
||||||
|
border-radius: 3px; |
||||||
|
border: 1px solid transparent; |
||||||
|
box-shadow: inset 0 0 2px 1px var(--button-inset-shadow); |
||||||
|
transition: opacity .15s; |
||||||
|
} |
||||||
|
.component > button:active::after { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.hidden { |
||||||
|
display: none !important; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.shadow-bottom-inset { |
||||||
|
box-shadow: 0 10px 5px -10px rgba(var(--shadow-color), 0.75) inset; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.textbox { |
||||||
|
padding: .25rem .5rem; |
||||||
|
background-color: var(--background); |
||||||
|
border: 1px solid var(--textbox-border); |
||||||
|
border-radius: .2rem; |
||||||
|
transition: box-shadow .2s ease-out; |
||||||
|
} |
||||||
|
|
||||||
|
.textbox:focus { |
||||||
|
border-color: var(--textbox-selected-border); |
||||||
|
box-shadow: 0 0 0 .1rem var(--textbox-selected-shadow); |
||||||
|
outline: .15rem solid transparent; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 3rd party css styling */ |
||||||
|
|
||||||
|
.pd-theme td:not(.is-selected):not(.is-today) [data-pika-month="2"][data-pika-day="8"] { |
||||||
|
background-color: var(--important-date-background); |
||||||
|
border-radius: 4px; |
||||||
|
} |
||||||
|
|
||||||
|
.pd-theme .pika-row td:nth-last-child(-n+2):not(.is-selected) .pika-button:not(:hover) { |
||||||
|
background-color: var(--weekend-background); |
||||||
|
} |
||||||
|
|
||||||
|
.simplebar-track.simplebar-vertical { |
||||||
|
width: 1rem !important; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.body-spacer { |
||||||
|
width: 5vw; |
||||||
|
max-width: 5vw; |
||||||
|
} |
||||||
|
|
||||||
|
.body-wrapper { |
||||||
|
max-width: 1500px; |
||||||
|
width: fit-content; |
||||||
|
|
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
margin: 0 auto; |
||||||
|
flex-grow: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.panel-wrapper { |
||||||
|
flex-grow: 1; |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel { |
||||||
|
max-width: 15.5rem; |
||||||
|
min-width: 12.5rem; |
||||||
|
flex-basis: 100%; |
||||||
|
background-color: var(--left-panel-background); |
||||||
|
border-right: 1px solid var(--border); |
||||||
|
|
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel > .header { |
||||||
|
height: 3rem; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
border-bottom: 1px solid var(--border); |
||||||
|
background-color: var(--left-panel-header-background); |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .header .text { |
||||||
|
font-size: 1.5rem; |
||||||
|
font-weight: 700; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .sections { |
||||||
|
padding: .5rem 0 .5rem 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .section { |
||||||
|
margin: .25rem 0; |
||||||
|
position: relative; |
||||||
|
display: flex; align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .section .clickable-area { |
||||||
|
width: 80%; max-width: 100%; |
||||||
|
padding: .5rem 1rem .5rem .5rem; |
||||||
|
cursor: pointer; |
||||||
|
display: flex; align-items: center; |
||||||
|
} |
||||||
|
.left-panel .section .image { |
||||||
|
min-width: 1.5rem; max-width: 1.5rem; width: 1.5rem; |
||||||
|
min-height: 1.5rem; max-height: 1.5rem; height: 1.5rem; |
||||||
|
margin-right: 1rem; |
||||||
|
transition: fill .15s; |
||||||
|
} |
||||||
|
.left-panel .section .text { |
||||||
|
font-size: 1.15rem; font-weight: 300; |
||||||
|
color: var(--link-normal-color); |
||||||
|
transition: color .15s; |
||||||
|
} |
||||||
|
.left-panel .clickable-area:hover .text, .left-panel .clickable-area:active .text { |
||||||
|
color: var(--link-active-color); |
||||||
|
} |
||||||
|
.left-panel .clickable-area:hover .image, .left-panel .clickable-area:active .image { |
||||||
|
fill: var(--link-active-color); |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .section .arrow { |
||||||
|
display: block; |
||||||
|
width: 1rem; |
||||||
|
height: 1rem; |
||||||
|
border-right: 1px solid var(--border); |
||||||
|
border-top: 1px solid var(--border); |
||||||
|
transform: rotate(-135deg); |
||||||
|
position: absolute; |
||||||
|
right: -.51rem; |
||||||
|
background-color: var(--right-panel-color); |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .section.sub .clickable-area { |
||||||
|
width: 100%; |
||||||
|
padding: .25rem 0; |
||||||
|
margin-right: .5rem; |
||||||
|
} |
||||||
|
.left-panel .section.sub .image { |
||||||
|
min-width: 1rem; max-width: 1rem; width: 1rem; |
||||||
|
min-height: 1rem; max-height: 1rem; height: 1rem; |
||||||
|
margin-right: .5rem; |
||||||
|
} |
||||||
|
.left-panel .section.sub .text { |
||||||
|
font-size: 1rem; |
||||||
|
color: var(--link-normal-sub-color); |
||||||
|
} |
||||||
|
.left-panel .section.sub .clickable-area:hover .image { |
||||||
|
fill: unset; |
||||||
|
} |
||||||
|
.left-panel .section.sub .clickable-area:hover .image.collapser { |
||||||
|
fill: var(--link-active-color); |
||||||
|
} |
||||||
|
.left-panel .section.sub .dot-filler { |
||||||
|
flex: 1; |
||||||
|
height: 2px; |
||||||
|
border-top: 2px dashed rgba(0,0,0,0.25); |
||||||
|
margin-left: .5rem; margin-right: .5rem; |
||||||
|
} |
||||||
|
.left-panel .section.sub .collapser { |
||||||
|
display: none; |
||||||
|
margin-right: 0; |
||||||
|
} |
||||||
|
.left-panel .section.sub .collapser.active { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .ss-line { |
||||||
|
position: absolute; |
||||||
|
height: 100%; |
||||||
|
width: 2px; |
||||||
|
border-left: 2px dashed rgba(0,0,0,0.25); |
||||||
|
left: 0; |
||||||
|
top: 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.left-panel .divider { display: none; } |
||||||
|
.left-panel .divider:not(:last-child) { |
||||||
|
display: block; |
||||||
|
width: 85%; |
||||||
|
border: 1px solid var(--divider-color); |
||||||
|
background-color: var(--divider-color); |
||||||
|
border-radius: 1px; |
||||||
|
align-self: center; |
||||||
|
margin-bottom: .25rem; |
||||||
|
} |
||||||
|
|
||||||
|
.left-panel .divider + div { |
||||||
|
padding: .5rem 1rem 1rem 1.5rem; |
||||||
|
display: flex; flex-direction: column; align-items: stretch; |
||||||
|
} |
||||||
|
|
||||||
|
.right-panel { |
||||||
|
flex-grow: 1; |
||||||
|
display: flex; |
||||||
|
background-color: var(--right-panel-color); |
||||||
|
padding: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.right-panel .no-section { |
||||||
|
font-size: 2rem; |
||||||
|
font-weight: 700; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.footer { |
||||||
|
min-height: 2rem; |
||||||
|
max-height: 2rem; |
||||||
|
height: 2rem; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
background-color: var(--footer-background); |
||||||
|
border-top: 1px solid var(--border); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.window-wrapper { |
||||||
|
position: fixed; |
||||||
|
width: 100%; height: 100%; |
||||||
|
background-color: var(--window-wrapper-background); |
||||||
|
z-index: 100; |
||||||
|
|
||||||
|
overflow: hidden; |
||||||
|
display: flex; justify-content: center; align-items: center; |
||||||
|
opacity: 0; |
||||||
|
visibility: hidden; |
||||||
|
transition: opacity .35s, visibility .35s; |
||||||
|
} |
||||||
|
.window-wrapper.active { |
||||||
|
opacity: 1; |
||||||
|
visibility: visible; |
||||||
|
} |
||||||
|
.window { |
||||||
|
min-width: min(400px, 80vw); max-width: 1200px; |
||||||
|
min-height: fit-content; max-height: 800px; |
||||||
|
position: relative; |
||||||
|
background-color: var(--window-background); |
||||||
|
border-radius: 1rem; |
||||||
|
display: flex; flex-direction: column; align-items: center; |
||||||
|
padding: 2rem; |
||||||
|
box-shadow: 0 0 100px 40px var(--window-shadow-background); |
||||||
|
opacity: 0; |
||||||
|
visibility: hidden; |
||||||
|
transition: opacity .35s, visibility .35s; |
||||||
|
} |
||||||
|
.window.active { |
||||||
|
opacity: 1; |
||||||
|
visibility: visible; |
||||||
|
} |
||||||
|
.window .close-button { |
||||||
|
position: absolute; |
||||||
|
right: 1.5rem; top: 1.5rem; |
||||||
|
width: 2rem; height: 2rem; |
||||||
|
opacity: .3; |
||||||
|
transition: opacity .2s; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
.window .close-button:hover { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
.window .close-button::before, .window .close-button::after { |
||||||
|
position: absolute; |
||||||
|
left: calc(50% - 1px); |
||||||
|
content: ' '; |
||||||
|
height: 100%; |
||||||
|
width: 2px; |
||||||
|
background-color: var(--text); |
||||||
|
} |
||||||
|
.window .close-button::before { transform: rotate(45deg); } |
||||||
|
.window .close-button::after { transform: rotate(-45deg); } |
||||||
|
|
||||||
|
.window > *:nth-child(n+2):not(:last-child) { |
||||||
|
margin-bottom: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* old stuff */ |
||||||
|
|
||||||
|
|
||||||
|
.message-wrapper { |
||||||
|
position: fixed; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background-color: var(--message-wrapper-background); |
||||||
|
z-index: 100; |
||||||
|
|
||||||
|
overflow: hidden; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
opacity: 0; |
||||||
|
visibility: hidden; |
||||||
|
transition: opacity .35s, visibility .35s; |
||||||
|
} |
||||||
|
.message-wrapper.active { |
||||||
|
opacity: 1; |
||||||
|
visibility: visible; |
||||||
|
} |
||||||
|
.message { |
||||||
|
min-width: min(400px, 80vw); max-width: 1200px; |
||||||
|
min-height: min(300px, 80vh); max-height: 800px; |
||||||
|
position: relative; |
||||||
|
background-color: var(--message-background); |
||||||
|
border-radius: 1rem; |
||||||
|
display: flex; flex-direction: column; align-items: center; |
||||||
|
padding: 2rem; |
||||||
|
box-shadow: 0 0 100px 40px var(--message-shadow-background); |
||||||
|
} |
||||||
|
.message .close-button { |
||||||
|
position: absolute; |
||||||
|
right: 1.5rem; top: 1.5rem; |
||||||
|
width: 2rem; height: 2rem; |
||||||
|
opacity: .3; |
||||||
|
transition: opacity .2s; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
.message .close-button:hover { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
.message .close-button:before, .message .close-button:after { |
||||||
|
position: absolute; |
||||||
|
left: calc(50% - 1px); |
||||||
|
content: ' '; |
||||||
|
height: 100%; |
||||||
|
width: 2px; |
||||||
|
background-color: var(--text); |
||||||
|
} |
||||||
|
.message .close-button:before { transform: rotate(45deg); } |
||||||
|
.message .close-button:after { transform: rotate(-45deg); } |
||||||
|
|
||||||
|
.message > *:nth-child(n+2):not(:last-child) { |
||||||
|
margin-bottom: 1.5rem; |
||||||
|
} |
||||||
|
.message .icon { |
||||||
|
width: 4rem; |
||||||
|
height: 4rem; |
||||||
|
display: flex; justify-content: center; align-items: center; |
||||||
|
} |
||||||
|
.message .header { |
||||||
|
font-size: 2.25rem; |
||||||
|
font-weight: 600; |
||||||
|
margin-left: 1rem; margin-right: 1rem; |
||||||
|
} |
||||||
|
.message .text { |
||||||
|
font-size: 1.25rem; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
.message .button { |
||||||
|
min-width: 10rem; |
||||||
|
min-height: 2.5rem; |
||||||
|
background-color: var(--button-background); |
||||||
|
border-radius: 2rem; |
||||||
|
display: flex; justify-content: center; align-items: center; |
||||||
|
cursor: pointer; |
||||||
|
user-select: none; |
||||||
|
transition: box-shadow .15s ease-out; |
||||||
|
color: var(--text); |
||||||
|
} |
||||||
|
.message .button:hover { |
||||||
|
box-shadow: 0 0 0 .1rem var(--button-selected-shadow); |
||||||
|
outline: .15rem solid transparent; |
||||||
|
color: var(--button-selected-color); |
||||||
|
} |
||||||
|
.message .button .button-text { |
||||||
|
font-size: 1.1rem; |
||||||
|
font-weight: 500; |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
:root { |
||||||
|
--player-background: #cfd7ff; |
||||||
|
--player-thumb: #212121; |
||||||
|
--player-track: #ccc; |
||||||
|
|
||||||
|
--player-playing-shadow: #f887ff; |
||||||
|
--player-playing-border: #ff00ff; |
||||||
|
} |
||||||
|
|
||||||
|
.player { |
||||||
|
position: fixed; |
||||||
|
z-index: 1; |
||||||
|
height: 2.5rem; |
||||||
|
min-width: 400px; |
||||||
|
max-width: 550px; |
||||||
|
width: 50vw; |
||||||
|
align-self: center; |
||||||
|
user-select: none; |
||||||
|
|
||||||
|
bottom: -3rem; |
||||||
|
opacity: 0; |
||||||
|
visibility: hidden; |
||||||
|
transition: bottom .5s ease-in-out, opacity .5s, visibility .5s; |
||||||
|
} |
||||||
|
.player.active { |
||||||
|
bottom: 0; |
||||||
|
opacity: 1; |
||||||
|
visibility: visible; |
||||||
|
} |
||||||
|
.player .controls { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
background-color: var(--player-background); |
||||||
|
border-radius: .5rem .5rem 0 0; |
||||||
|
padding: .2rem 1rem; |
||||||
|
|
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
.player .controls > * { |
||||||
|
margin-left: .5rem; |
||||||
|
margin-right: .5rem; |
||||||
|
} |
||||||
|
.player .controls > *:first-child { margin-left: 0; } |
||||||
|
.player .controls > *:last-child { margin-right: 0; } |
||||||
|
.player .time, .player.duration { |
||||||
|
min-width: 3rem; |
||||||
|
} |
||||||
|
.player .button { |
||||||
|
min-width: 1.3rem; |
||||||
|
min-height: 1.3rem; |
||||||
|
max-width: 1.3rem; |
||||||
|
max-height: 1.3rem; |
||||||
|
width: 1.3rem; |
||||||
|
height: 1.3rem; |
||||||
|
fill: var(--control-enabled); |
||||||
|
transition: fill .15s; |
||||||
|
cursor: pointer; |
||||||
|
display: block; |
||||||
|
} |
||||||
|
.player .button:hover { |
||||||
|
fill: var(--control-selected); |
||||||
|
} |
||||||
|
.player .button.disabled { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
.player .slider { |
||||||
|
outline: none; |
||||||
|
|
||||||
|
flex: 1; |
||||||
|
min-height: 2rem; |
||||||
|
height: 2rem; |
||||||
|
max-height: 2rem; |
||||||
|
min-width: 4rem; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.player .bar { |
||||||
|
position: absolute; |
||||||
|
z-index: -9; |
||||||
|
background-color: var(--player-background); |
||||||
|
border-radius: 1rem 1rem 0 0; |
||||||
|
margin: 0; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.player .bar.active { |
||||||
|
bottom: 2.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.player .volume-bar { |
||||||
|
left: 2.85rem; |
||||||
|
width: 2rem; |
||||||
|
height: 6rem; |
||||||
|
padding: .7rem .1rem .3rem .1rem; |
||||||
|
bottom: -3.5rem; |
||||||
|
|
||||||
|
flex-direction: column; |
||||||
|
transition: bottom .5s ease-in-out; |
||||||
|
} |
||||||
|
.player .volume-bar .volume-slider { |
||||||
|
transform: rotate(270deg); |
||||||
|
min-width: 5rem; |
||||||
|
width: 5rem; |
||||||
|
max-width: 5rem; |
||||||
|
margin-top: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.player .speed-bar { |
||||||
|
right: 3.3rem; |
||||||
|
width: 12.25rem; |
||||||
|
height: 2rem; |
||||||
|
padding: .5rem .8rem .2rem .8rem; |
||||||
|
bottom: 0; |
||||||
|
|
||||||
|
justify-content: center; |
||||||
|
transition: bottom .5s ease-in-out; |
||||||
|
} |
||||||
|
.player .speed-bar .current-speed { |
||||||
|
text-align: right; |
||||||
|
min-width: 3rem; width: 3rem; max-width: 3rem; |
||||||
|
margin-right: .5rem; |
||||||
|
} |
||||||
|
.player .speed-bar .speed-slider { |
||||||
|
margin-right: 1rem; |
||||||
|
min-width: 5rem; width: 5rem; max-width: 5rem; |
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
:root { |
||||||
|
--collection-background: #fafafa; |
||||||
|
--collection-border: #ccc; |
||||||
|
--collection-buttons-background: #e5e5e5; |
||||||
|
} |
||||||
|
|
||||||
|
.settings { |
||||||
|
flex-direction: column; |
||||||
|
text-align: left; |
||||||
|
align-self: flex-start; |
||||||
|
padding-left: .5rem; |
||||||
|
padding-top: 1rem; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
.settings .category { |
||||||
|
margin-bottom: 2.5rem; |
||||||
|
} |
||||||
|
.settings .category .header { |
||||||
|
font-size: 1.75rem; |
||||||
|
font-weight: 700; |
||||||
|
border-bottom: 1px solid var(--accent-border); |
||||||
|
padding-bottom: .5rem; |
||||||
|
margin: 0; |
||||||
|
margin-bottom: 1rem; |
||||||
|
min-width: 25rem; max-width: 35rem; |
||||||
|
} |
||||||
|
.settings .category > .component { |
||||||
|
margin-bottom: .75rem; |
||||||
|
max-width: 25rem; |
||||||
|
} |
||||||
|
|
||||||
|
.settings .category .collection { |
||||||
|
display: flex; flex-direction: column; |
||||||
|
margin-bottom: 1.25rem; |
||||||
|
} |
||||||
|
.settings .collection .name { |
||||||
|
font-size: 1.15rem; |
||||||
|
font-weight: 500; |
||||||
|
margin-bottom: .5rem; |
||||||
|
} |
||||||
|
.settings .collection .content { |
||||||
|
background-color: var(--collection-background); |
||||||
|
border-radius: .25rem; |
||||||
|
border: 1px solid var(--border); |
||||||
|
min-width: 25rem; width: 25rem; max-width: 50rem; |
||||||
|
min-height: 10rem; height: 10rem; max-height: 30rem; |
||||||
|
display: flex; |
||||||
|
resize: both; |
||||||
|
overflow: auto; |
||||||
|
} |
||||||
|
.settings .collection .content .items { |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
.settings .collection .content .buttons { |
||||||
|
min-width: 8rem; width: 8rem; max-width: 8rem; |
||||||
|
display: flex; flex-direction: column; |
||||||
|
border-left: 1px solid var(--border); |
||||||
|
background-color: var(--collection-buttons-background); |
||||||
|
padding: .5rem; |
||||||
|
} |
||||||
|
.settings .collection .content .buttons .component:not(:last-child) { |
||||||
|
margin-bottom: .25rem; |
||||||
|
} |
||||||
|
|
||||||
|
.settings .subcat { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.settings .subcat .subcat-line { |
||||||
|
background-color: var(--border); |
||||||
|
max-width: 1px; width: 1px; |
||||||
|
flex: 1; |
||||||
|
margin-bottom: .75rem; |
||||||
|
margin-right: .75rem; |
||||||
|
} |
||||||
|
.settings .subcat .content { |
||||||
|
display: flex; flex-direction: column; |
||||||
|
} |
||||||
|
.settings .subcat .content > .component { |
||||||
|
margin-bottom: .75rem; |
||||||
|
} |
||||||
|
|
||||||
|
.settings > .save { |
||||||
|
margin: 1rem 0; |
||||||
|
font-size: 1.2rem; |
||||||
|
width: 10rem; |
||||||
|
height: 2.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#call-category-form .header { |
||||||
|
font-size: 1.25rem; |
||||||
|
font-weight: 500; |
||||||
|
width: 80%; |
||||||
|
align-self: center; |
||||||
|
} |
||||||
|
#call-category-form .call-category { |
||||||
|
align-self: flex-start; |
||||||
|
align-items: flex-start; |
||||||
|
flex-direction: column; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
#call-category-form .buttons { |
||||||
|
display: flex; |
||||||
|
align-self: stretch; |
||||||
|
} |
||||||
|
#call-category-form .buttons .button { |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
#call-category-form .buttons .button:first-child { |
||||||
|
margin-right: 2rem; |
||||||
|
} |
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,634 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
let selectedRecord = null; |
||||||
|
let selectedElem = null; |
||||||
|
|
||||||
|
const prettifyThing = (name, value, record = null, globalParms = null) => { |
||||||
|
if (name === 'source' || name === 'destination' || name === 'any') { |
||||||
|
return common.prettifyPhone(value); |
||||||
|
} else if (name === 'totalDuration' || name === 'callDuration') { |
||||||
|
return (globalParms.timeInSeconds ? value : common.convertToDuration(value)); |
||||||
|
} else if (name === 'dateStart' || name === 'dateEnd') { |
||||||
|
return dayjs(value).format('DD.MM.YYYY'); |
||||||
|
} else if (name === 'callDate') { |
||||||
|
return (globalParms.disableGrouping ? dayjs(value).format('DD.MM.YYYY HH:mm:ss') : dayjs(value).format('HH:mm:ss')); |
||||||
|
} else if (name === 'callResult') { |
||||||
|
return ['Отвечен', 'Не отвечен', 'Занято', 'Ошибка', 'Другое', 'Отменен'][value]; |
||||||
|
} else if (name === 'dailyCT') { |
||||||
|
return ['ВХ', 'ИСХ', 'ВН'][record.callType] + '-' + value; |
||||||
|
} |
||||||
|
return value; |
||||||
|
}; |
||||||
|
|
||||||
|
const uglifyThing = (name, value) => { |
||||||
|
if (name === 'source' || name === 'destination' || name === 'any') { |
||||||
|
return common.uglifyPhone(value); |
||||||
|
} else if (name === 'dateStart' || name === 'dateEnd') { |
||||||
|
return value.replace(/(\d{1,2})\.(\d{1,2})\.(\d+)/, '$3-$2-$1'); |
||||||
|
} else if (name === 'dailyID' || name === 'dailyCT' || name === 'id') { |
||||||
|
return value.replace(/\D/g, ''); |
||||||
|
} |
||||||
|
return value; |
||||||
|
}; |
||||||
|
|
||||||
|
const preferableSortDirs = { |
||||||
|
callDate: 'desc', |
||||||
|
source: 'asc', destination: 'asc', |
||||||
|
totalDuration: 'desc', callDuration: 'desc' |
||||||
|
}; |
||||||
|
|
||||||
|
const filterProperties = ['source', 'destination', 'any', 'dateStart', 'dateEnd', 'id', 'callTypes', 'wcSource', 'wcDestination', 'wcAny']; |
||||||
|
const sortProperties = ['sort', 'dir']; |
||||||
|
|
||||||
|
const getCdrParams = () => { |
||||||
|
let parms = {}; |
||||||
|
|
||||||
|
// specify page if its not the 1st one
|
||||||
|
if (cdr.thisPage !== 1) { parms.page = cdr.thisPage; } |
||||||
|
|
||||||
|
// iterate over all known SORT and FILTER properties, add them if necessary
|
||||||
|
[...sortProperties, ...filterProperties].forEach(e => { |
||||||
|
if (cdr.filter[e]) { |
||||||
|
parms[e] = uglifyThing(e, cdr.filter[e]); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// remove default sort options
|
||||||
|
if (parms.sort === 'callDate' && parms.dir === 'desc') { |
||||||
|
delete parms.sort; |
||||||
|
delete parms.dir; |
||||||
|
} |
||||||
|
|
||||||
|
// check if grouping is disabled
|
||||||
|
if (document.querySelector('.left-panel .settings .checkbox.grouping > input').checked) { |
||||||
|
parms.disableGrouping = 1; |
||||||
|
} |
||||||
|
|
||||||
|
return Object.keys(parms).map(e => e + '=' + parms[e]); |
||||||
|
}; |
||||||
|
|
||||||
|
const getCdr = async () => { |
||||||
|
try { |
||||||
|
let url = [...getCdrParams()].join('&'); |
||||||
|
url = url ? ('?' + url) : '/'; |
||||||
|
|
||||||
|
// update browser url
|
||||||
|
if (history.pushState) { |
||||||
|
history.pushState({}, null, url); |
||||||
|
} |
||||||
|
|
||||||
|
const response = await fetch('/api/query' + url); |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error(response.status, ' ', response.statusText); |
||||||
|
} |
||||||
|
|
||||||
|
return (await response.json()); |
||||||
|
} catch (e) { |
||||||
|
showMessage({ |
||||||
|
icon: 'icon-error', |
||||||
|
header: 'Ошибка загрузки данных', |
||||||
|
text: 'Не удалось загрузить данные: ' + e |
||||||
|
}); |
||||||
|
console.warn('Failed to fetch data: ', e) |
||||||
|
return null; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const getCallsSinceLastUpdate = async () => { |
||||||
|
try { |
||||||
|
if (cdr && cdr.latest) { |
||||||
|
const response = await fetch('/api/check-new?lastUpdateAt=' + dayjs(cdr.latest).unix()); |
||||||
|
if (response.ok) { |
||||||
|
return (await response.json()).numSinceLastUpdate; |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
console.warn('Failed to query last update API: ' + e); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const performUpdate = async () => { |
||||||
|
const count = await getCallsSinceLastUpdate(); |
||||||
|
if (count > 0) { |
||||||
|
if (document.querySelector('.left-panel .settings .checkbox.auto-update > input').checked) { |
||||||
|
document.querySelector('.left-panel .new-entries').classList.remove('active'); |
||||||
|
populate(); |
||||||
|
} else { |
||||||
|
document.querySelector('.left-panel .new-entries .num').innerHTML = count; |
||||||
|
document.querySelector('.left-panel .new-entries').classList.add('active'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
updateTimerHandle = setTimeout(performUpdate, 5000); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
let updateTimerHandle = 0; |
||||||
|
|
||||||
|
const handlerDoUpdate = (ev, el) => { |
||||||
|
clearTimeout(updateTimerHandle); |
||||||
|
updateTimerHandle = setTimeout(performUpdate, 5000); |
||||||
|
document.querySelector('.left-panel .new-entries').classList.remove('active'); |
||||||
|
populate(); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const columnNameToId = name => { |
||||||
|
switch (name) { |
||||||
|
case 'dailyid': { return 'dailyID'; } |
||||||
|
case 'dailyct': { return 'dailyCT'; } |
||||||
|
case 'date': { return 'callDate'; } |
||||||
|
case 'result': { return 'callResult'; } |
||||||
|
case 'total-duration': { return 'totalDuration'; } |
||||||
|
case 'call-duration': { return 'callDuration'; } |
||||||
|
case 'id': { return 'uniqueID'; } |
||||||
|
default: { return name; } |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const addInfoToGroup = (group, cd, gp) => { |
||||||
|
const element = cdr.dailyStats.find( e => dayjs(e.day).isSame(cd, 'day')); |
||||||
|
|
||||||
|
if (element) { |
||||||
|
const stats = document.createElement('div'); |
||||||
|
stats.classList.add('stats'); |
||||||
|
|
||||||
|
const phone = createSVG(['image'], 'icon-phone'); |
||||||
|
const numCalls = document.createElement('span'); |
||||||
|
numCalls.classList.add('num-calls'); |
||||||
|
numCalls.innerHTML = element.totalCalls; |
||||||
|
|
||||||
|
const clock = createSVG(['image'], 'icon-clock'); |
||||||
|
const duration = document.createElement('span'); |
||||||
|
duration.innerHTML = prettifyThing('totalDuration', element.totalDuration, null, gp) |
||||||
|
+ ' / ' + prettifyThing('callDuration', element.callDuration, null, gp); |
||||||
|
|
||||||
|
stats.appendChild(phone); stats.appendChild(numCalls); |
||||||
|
stats.appendChild(clock); stats.appendChild(duration); |
||||||
|
group.appendChild(stats); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const tryNewGroup = (id, cdrBody, gp) => { |
||||||
|
if (id === 0 || !dayjs(cdr.records[id - 1].callDate).isSame(dayjs(cdr.records[id].callDate), 'day')) { |
||||||
|
const group = document.createElement('div'); |
||||||
|
group.classList.add('entry', 'group'); |
||||||
|
|
||||||
|
const groupText = document.createElement('span'); |
||||||
|
groupText.classList.add('group-text'); |
||||||
|
groupText.innerHTML = dayjs(cdr.records[id].callDate).format('DD.MM.YYYY'); |
||||||
|
|
||||||
|
if (cdr.isFirstCont && id === 0) { |
||||||
|
groupText.innerHTML += ' (продолжение)'; |
||||||
|
} |
||||||
|
|
||||||
|
group.appendChild(groupText); |
||||||
|
|
||||||
|
if (cdr.dailyStats) { |
||||||
|
addInfoToGroup(group, cdr.records[id].callDate, gp); |
||||||
|
} |
||||||
|
|
||||||
|
cdrBody.appendChild(group); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const draw = () => { |
||||||
|
if (!cdr) { |
||||||
|
throw new Error("Tried to draw without CDR data"); |
||||||
|
} |
||||||
|
|
||||||
|
// no-cdrs box
|
||||||
|
document.querySelector('.cdr .body .no-cdrs').classList.toggle('active', !cdr.records || cdr.records.length === 0); |
||||||
|
|
||||||
|
// pages
|
||||||
|
document.querySelector('.cdr .controls .status').innerHTML = `${cdr.thisPage} из ${cdr.maxPages}`; |
||||||
|
document.querySelector('.cdr .controls .goto-page').value = cdr.thisPage; |
||||||
|
document.querySelector('.cdr .controls .goto-page').max = cdr.maxPages; |
||||||
|
|
||||||
|
// page buttons
|
||||||
|
document.querySelector('.cdr .controls .button_begin').classList.toggle('active', cdr.thisPage > 1); |
||||||
|
document.querySelector('.cdr .controls .button_left').classList.toggle('active', cdr.thisPage > 1); |
||||||
|
document.querySelector('.cdr .controls .button_right').classList.toggle('active', cdr.thisPage < cdr.maxPages); |
||||||
|
document.querySelector('.cdr .controls .button_end').classList.toggle('active', cdr.thisPage < cdr.maxPages); |
||||||
|
document.querySelector('.cdr .controls .button_goto').classList.toggle('active', cdr.maxPages > 1); |
||||||
|
document.querySelector('.cdr .controls .goto-page').classList.toggle('active', cdr.maxPages > 1); |
||||||
|
|
||||||
|
// filters
|
||||||
|
document.querySelectorAll('.left-panel .filters .filter').forEach( e => { |
||||||
|
let id = e.getAttribute('filter-id'); |
||||||
|
if (id) { |
||||||
|
e.querySelector('.value').value = cdr.filter[id] ? prettifyThing(id, cdr.filter[id]) : ""; |
||||||
|
e.querySelector('.status').classList.toggle('active', !!cdr.filter[id]); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// call type filters
|
||||||
|
document.querySelector('.left-panel .call-type.call-type_incoming').classList.toggle('active', cdr.filter.callTypes & (1 << common.ctIncoming)); |
||||||
|
document.querySelector('.left-panel .call-type.call-type_outgoing').classList.toggle('active', cdr.filter.callTypes & (1 << common.ctOutgoing)); |
||||||
|
document.querySelector('.left-panel .call-type.call-type_internal').classList.toggle('active', cdr.filter.callTypes & (1 << common.ctInternal)); |
||||||
|
|
||||||
|
// wildcards
|
||||||
|
document.querySelector('.left-panel .filters .filter .wildcard_source').classList.toggle('active', cdr.filter.wcSource); |
||||||
|
document.querySelector('.left-panel .filters .filter .wildcard_destination').classList.toggle('active', cdr.filter.wcDestination); |
||||||
|
document.querySelector('.left-panel .filters .filter .wildcard_any').classList.toggle('active', cdr.filter.wcAny); |
||||||
|
|
||||||
|
|
||||||
|
// sortable fields
|
||||||
|
document.querySelectorAll('.cdr .header .cell.sortable').forEach( e => { |
||||||
|
let id = "" + e.getAttribute('sort-id'); |
||||||
|
if (id) { |
||||||
|
e.querySelector('.sort-button').classList.toggle('sort-none', cdr.filter.sort !== id); |
||||||
|
e.querySelector('.sort-button').classList.toggle('sort-asc', cdr.filter.sort === id && cdr.filter.dir === 'asc'); |
||||||
|
e.querySelector('.sort-button').classList.toggle('sort-desc', cdr.filter.sort === id && cdr.filter.dir === 'desc'); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// records
|
||||||
|
|
||||||
|
// cache CDR body descriptor
|
||||||
|
// (not actually cdr body due to scrollbars)
|
||||||
|
const cdrBody = document.querySelector('.cdr .body .no-cdrs').parentElement; |
||||||
|
|
||||||
|
// cache some settings
|
||||||
|
const globalParms = { |
||||||
|
timeInSeconds: document.querySelector('.left-panel .settings .checkbox.time-in-seconds > input').checked, |
||||||
|
disableGrouping: document.querySelector('.left-panel .settings .checkbox.grouping > input').checked |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
// change date header if grouping is enabled
|
||||||
|
document.querySelector('.cdr .header .cell.cell_date .name').innerHTML = |
||||||
|
globalParms.disableGrouping ? 'Дата' : 'Время'; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// remove all records first
|
||||||
|
cdrBody.querySelectorAll('.entry').forEach( e => { e.remove(); }) |
||||||
|
|
||||||
|
// iterate over records if there are any
|
||||||
|
let idx = (cdr.thisPage - 1) * cdr.filter.limit + 1; |
||||||
|
let localIdx = 0; |
||||||
|
|
||||||
|
// clear out selectedElem
|
||||||
|
selectedElem = null; |
||||||
|
|
||||||
|
cdr.records.forEach( record => { |
||||||
|
// if grouping is enabled, attempt to place a group header
|
||||||
|
if (!globalParms.disableGrouping) { |
||||||
|
tryNewGroup(localIdx, cdrBody, globalParms); |
||||||
|
} |
||||||
|
|
||||||
|
let entry = document.createElement('div'); |
||||||
|
entry.classList.add('entry', 'record'); |
||||||
|
entry.addEventListener('click', wrapListener(handlerSelectRecord, entry, record.uniqueID)); |
||||||
|
|
||||||
|
// check if this entry has been previously selected
|
||||||
|
if (selectedRecord === record.uniqueID) { |
||||||
|
entry.classList.add('active'); |
||||||
|
selectedElem = entry; |
||||||
|
} |
||||||
|
|
||||||
|
['id', 'dailyct', 'type', 'date', 'source', 'destination', 'total-duration', 'call-duration', 'result', 'records'].forEach( name => { |
||||||
|
const cell = document.createElement('div'); |
||||||
|
cell.classList.add('cell', 'cell_' + name); |
||||||
|
|
||||||
|
switch (name) { |
||||||
|
case 'id': { |
||||||
|
cell.innerHTML = idx; |
||||||
|
break; |
||||||
|
} |
||||||
|
case 'type': { |
||||||
|
const ct = common.callTypeToString(record.callType); |
||||||
|
cell.appendChild(createSVG(['image', 'type_' + ct], 'icon-' + ct + '-call')) |
||||||
|
break; |
||||||
|
} |
||||||
|
case 'records': { |
||||||
|
let play = createSVG(['image', 'link', 'play'], 'icon-play'); |
||||||
|
play.addEventListener('click', wrapListener(handlerPlay, record.uniqueID)); |
||||||
|
cell.appendChild(play); |
||||||
|
|
||||||
|
let save = createSVG(['image', 'link', 'save'], 'icon-save'); |
||||||
|
save.addEventListener('click', wrapListener(handlerSave, record.uniqueID)); |
||||||
|
cell.appendChild(save); |
||||||
|
break; |
||||||
|
} |
||||||
|
default: { |
||||||
|
const colName = columnNameToId(name); |
||||||
|
cell.innerHTML = prettifyThing(colName, record[colName], record, globalParms); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
entry.appendChild(cell); |
||||||
|
}); |
||||||
|
|
||||||
|
cdrBody.appendChild(entry); |
||||||
|
idx++; |
||||||
|
localIdx++; |
||||||
|
}); |
||||||
|
|
||||||
|
// TODO group view
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const populate = async () => { |
||||||
|
const newCdr = await getCdr(); |
||||||
|
if (!newCdr) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
cdr = newCdr; |
||||||
|
draw(); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function handlerSave(ev, uid) { |
||||||
|
if (uid) { |
||||||
|
window.location.assign('/api/recording/' + uid + '?asFile=true'); |
||||||
|
} |
||||||
|
|
||||||
|
ev.stopPropagation(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function handlerSelectRecord(ev, el, id) { |
||||||
|
if (selectedRecord !== id && selectedElem) { |
||||||
|
selectedElem.classList.remove('active'); |
||||||
|
} |
||||||
|
|
||||||
|
selectedRecord = (selectedRecord === id) ? null : id; |
||||||
|
selectedElem = (selectedRecord !== null) ? el : null; |
||||||
|
el.classList.toggle('active'); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function handlerFilterNotify(ev, el) { |
||||||
|
el.parentElement.querySelector('.status').classList.toggle('active', el.value && el.value.length > 0); |
||||||
|
} |
||||||
|
document.querySelectorAll('.left-panel .filters .filter .value').forEach(e => e.addEventListener('change', wrapListener(handlerFilterNotify, e))); |
||||||
|
document.querySelectorAll('.left-panel .filters .filter .value').forEach(e => e.addEventListener('input', wrapListener(handlerFilterNotify, e))); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function handlerSort(ev, el) { |
||||||
|
const id = "" + el.getAttribute('sort-id'); |
||||||
|
|
||||||
|
if (id) { |
||||||
|
if (cdr.filter.sort === id) { |
||||||
|
cdr.filter.dir = (cdr.filter.dir === 'asc') ? 'desc' : 'asc'; |
||||||
|
} else { |
||||||
|
cdr.thisPage = 1; |
||||||
|
cdr.filter.sort = id; |
||||||
|
cdr.filter.dir = preferableSortDirs[cdr.filter.sort] ? preferableSortDirs[cdr.filter.sort] : 'desc'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
populate(); |
||||||
|
} |
||||||
|
|
||||||
|
function handlerBegin() { |
||||||
|
if (cdr.thisPage !== 1) { |
||||||
|
cdr.thisPage = 1; |
||||||
|
populate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handlerLeft() { |
||||||
|
if (cdr.thisPage > 1) { |
||||||
|
--cdr.thisPage; |
||||||
|
populate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handlerRight() { |
||||||
|
if (cdr.thisPage < cdr.maxPages) { |
||||||
|
++cdr.thisPage; |
||||||
|
populate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handlerEnd() { |
||||||
|
if (cdr.thisPage !== cdr.maxPages) { |
||||||
|
cdr.thisPage = cdr.maxPages; |
||||||
|
populate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handlerGoto() { |
||||||
|
const page = parseInt(document.querySelector('.cdr .controls .goto-page').value, 10); |
||||||
|
|
||||||
|
if (!isNaN(page) && page !== cdr.thisPage && page >= 1 && page <= cdr.maxPages) { |
||||||
|
cdr.thisPage = page; |
||||||
|
populate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// apply all filters and redraw
|
||||||
|
function handlerApply() { |
||||||
|
let filters = {}; |
||||||
|
|
||||||
|
// iterate over filters, uglify and apply
|
||||||
|
document.querySelectorAll('.left-panel .filters .filter .value').forEach( e => { |
||||||
|
const id = "" + e.getAttribute('filter-id'); |
||||||
|
if (id) { |
||||||
|
filters[id] = uglifyThing(id, e.value); |
||||||
|
|
||||||
|
if (!filters[id] || filters[id].length === 0) { |
||||||
|
delete filters[id]; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// iterate over callType filters, combine into bitmask
|
||||||
|
filters.callTypes = 0; |
||||||
|
['internal', 'outgoing', 'incoming'].forEach( e => { |
||||||
|
filters.callTypes = (filters.callTypes << 1) + |
||||||
|
(document.querySelector('.left-panel .call-type.call-type_' + e).classList.contains('active') ? 1 : 0); |
||||||
|
}); |
||||||
|
|
||||||
|
// do not specify callfilters if none or all are selected
|
||||||
|
if (filters.callTypes === 0 || filters.callTypes === 7) { |
||||||
|
delete filters.callTypes; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// iterate over wildcards
|
||||||
|
if (document.querySelector('.left-panel .filters .filter .wildcard_source').classList.contains('active')) { filters.wcSource = true; } |
||||||
|
if (document.querySelector('.left-panel .filters .filter .wildcard_destination').classList.contains('active')) { filters.wcDestination = true; } |
||||||
|
if (document.querySelector('.left-panel .filters .filter .wildcard_any').classList.contains('active')) { filters.wcAny = true; } |
||||||
|
|
||||||
|
|
||||||
|
// compare new filters with old filters
|
||||||
|
let shouldPopulate = false; |
||||||
|
for (let prop in filters) { |
||||||
|
if (filters[prop] !== cdr.filter[prop]) { |
||||||
|
shouldPopulate = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// compare old filters with new filters
|
||||||
|
for (let prop in cdr.filter) { |
||||||
|
if (typeof filters[prop] === 'undefined') { |
||||||
|
shouldPopulate = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// should re-filter?
|
||||||
|
if (shouldPopulate) { |
||||||
|
cdr.thisPage = 1; |
||||||
|
cdr.filter = filters; |
||||||
|
populate(); |
||||||
|
} |
||||||
|
} |
||||||
|
document.querySelector('.left-panel .filters .button.apply').addEventListener('click', wrapListener(handlerApply)); |
||||||
|
|
||||||
|
|
||||||
|
// reset all filters and redraw
|
||||||
|
function handlerReset() { |
||||||
|
if (cdr.filter) { |
||||||
|
filterProperties.forEach(e => { delete cdr.filter[e]; }); |
||||||
|
} |
||||||
|
|
||||||
|
document.querySelectorAll('.left-panel .filters .filter .value').forEach( e => e.value = "" ); |
||||||
|
document.querySelectorAll('.left-panel .filters .filter .status').forEach( e => e.classList.remove('active') ); |
||||||
|
document.querySelectorAll('.left-panel .filters .call-type').forEach( e => e.classList.remove('active') ); |
||||||
|
populate(); |
||||||
|
} |
||||||
|
document.querySelector('.left-panel .filters .button.reset').addEventListener('click', wrapListener(handlerReset)); |
||||||
|
document.querySelector('.cdr .no-cdrs .button.reset').addEventListener('click', wrapListener(handlerReset)); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// handle click on calltypes
|
||||||
|
document.querySelectorAll('.left-panel .filters .call-types .call-type').forEach(e => e.addEventListener('click', wrapListener((ev, el) => { |
||||||
|
el.classList.toggle('active'); |
||||||
|
}, e))); |
||||||
|
|
||||||
|
// wildcard clicks
|
||||||
|
document.querySelectorAll('.left-panel .filters .filter .wildcard').forEach(e => e.addEventListener('click', wrapListener((ev, el) => { |
||||||
|
el.classList.toggle('active'); |
||||||
|
}, e))); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// handle click on settings and set initial values
|
||||||
|
document.querySelectorAll('.left-panel .settings .checkbox').forEach(e => { |
||||||
|
const state = storageGet('setting-' + e.getAttribute('component-id')); |
||||||
|
if (state === 'on' || state === 'off') { |
||||||
|
e.querySelector('input').checked = (state === 'on'); |
||||||
|
} |
||||||
|
|
||||||
|
e.querySelector('input').addEventListener('click', wrapListener((ev, el) => { |
||||||
|
storageSet('setting-' + el.getAttribute('component-id'), el.querySelector('input').checked ? 'on' : 'off'); |
||||||
|
}, e)); |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// handle settings
|
||||||
|
document.querySelector('.left-panel .settings .checkbox.grouping > input').addEventListener('click', populate); |
||||||
|
document.querySelector('.left-panel .settings .checkbox.time-in-seconds > input').addEventListener('click', draw); |
||||||
|
|
||||||
|
// handle page buttons
|
||||||
|
document.querySelector('.cdr .controls .button_begin').addEventListener('click', wrapListener(handlerBegin)); |
||||||
|
document.querySelector('.cdr .controls .button_left').addEventListener('click', wrapListener(handlerLeft)); |
||||||
|
document.querySelector('.cdr .controls .button_right').addEventListener('click', wrapListener(handlerRight)); |
||||||
|
document.querySelector('.cdr .controls .button_end').addEventListener('click', wrapListener(handlerEnd)); |
||||||
|
document.querySelector('.cdr .controls .button_goto').addEventListener('click', wrapListener(handlerGoto)); |
||||||
|
|
||||||
|
|
||||||
|
// handle sorting
|
||||||
|
document.querySelectorAll('.cdr .header .cell.sortable .cell-clickable-area').forEach( e => e.addEventListener('click', wrapListener(handlerSort, e))); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
createListener(document.querySelector('.left-panel .new-entries .button.update'), 'click', handlerDoUpdate); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
window.addEventListener('resize', ev => { |
||||||
|
let vh = window.innerHeight; |
||||||
|
let lph = Array.prototype.slice.call(document.querySelectorAll('.body-wrapper > .panel-wrapper > .left-panel > *')) |
||||||
|
.map(e => e.offsetHeight) |
||||||
|
.reduce((acc, cv) => acc + cv); |
||||||
|
let fh = document.querySelector('.body-wrapper > .footer').offsetHeight; |
||||||
|
|
||||||
|
let bh = 0; |
||||||
|
if (vh > lph + fh) { |
||||||
|
bh = vh - 165; |
||||||
|
} else { |
||||||
|
bh = lph + fh - 165; |
||||||
|
} |
||||||
|
|
||||||
|
document.querySelector('.cdr .table .body').style.maxHeight = bh + 'px'; |
||||||
|
}); |
||||||
|
|
||||||
|
document.onreadystatechange = e => { |
||||||
|
if (document.readyState === 'complete') { |
||||||
|
draw(); |
||||||
|
|
||||||
|
updateTimerHandle = setTimeout(performUpdate, 5000); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// 3rd party stuff
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => { |
||||||
|
OverlayScrollbars(document.querySelector('.cdr .body'), {}); |
||||||
|
}); |
||||||
|
|
||||||
|
const commonPikadayOptions = { |
||||||
|
format: 'dd.mm.yyyy', |
||||||
|
i18n: { |
||||||
|
previousMonth : 'Предыдущий месяц', |
||||||
|
nextMonth : 'Следующий месяц', |
||||||
|
months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'], |
||||||
|
weekdays: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'], |
||||||
|
weekdaysShort: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'] |
||||||
|
}, |
||||||
|
toString(date, format) { |
||||||
|
return date.toLocaleDateString('ru-ru', {}); |
||||||
|
}, |
||||||
|
theme: 'pd-theme', |
||||||
|
firstDay: 1 |
||||||
|
}; |
||||||
|
|
||||||
|
const startPicker = new Pikaday({ field: document.querySelector('.filters .filter_date-start .value'), ...commonPikadayOptions}); |
||||||
|
const endPicker = new Pikaday({ field: document.querySelector('.filters .filter_date-end .value'), ...commonPikadayOptions}); |
@ -0,0 +1,112 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const wrapListener = function(callback, ...args) { |
||||||
|
return function cf(ev) { |
||||||
|
if (ev && callback) { |
||||||
|
callback(ev, ...args); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const createListener = function(element, event, callback, ...args) { |
||||||
|
element.addEventListener(event, wrapListener(callback, element, ...args)); |
||||||
|
}; |
||||||
|
|
||||||
|
const createSVG = (classList, symbolId) => { |
||||||
|
const div = document.createElement('div'); |
||||||
|
div.classList.add(...classList); |
||||||
|
|
||||||
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); |
||||||
|
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); |
||||||
|
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#' + symbolId); |
||||||
|
|
||||||
|
svg.appendChild(use); |
||||||
|
div.appendChild(svg); |
||||||
|
|
||||||
|
return div; |
||||||
|
}; |
||||||
|
|
||||||
|
const changeSVGImage = (element, newSymbolId) => { |
||||||
|
element.querySelector('svg > use').setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "#" + newSymbolId); |
||||||
|
}; |
||||||
|
|
||||||
|
const storageGet = name => { |
||||||
|
try { |
||||||
|
return localStorage.getItem(name); |
||||||
|
} catch (e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const storageSet = (name, value) => { |
||||||
|
try { |
||||||
|
localStorage.setItem(name, value); |
||||||
|
} catch (e) { } |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handlerSubsection = function(ev, el, shouldOpen) { |
||||||
|
const selector = el.getAttribute('ts'); |
||||||
|
const name = el.getAttribute('cn'); |
||||||
|
|
||||||
|
if (selector && name) { |
||||||
|
const target = document.querySelector(selector); |
||||||
|
|
||||||
|
if (target) { |
||||||
|
const isOpen = (shouldOpen === true || shouldOpen === false) ? !shouldOpen : !target.classList.contains('hidden'); |
||||||
|
|
||||||
|
changeSVGImage(el.querySelector('.collapser'), isOpen ? 'icon-plus' : 'icon-minus'); |
||||||
|
target.classList.toggle('hidden', isOpen); |
||||||
|
storageSet('ss-' + name, isOpen ? 'closed' : 'open'); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const initializeSubsections = function() { |
||||||
|
document.querySelectorAll('.left-panel .section.sub').forEach( e => { |
||||||
|
const state = storageGet('ss-' + e.getAttribute('cn')); |
||||||
|
if (state === 'open' || state === 'closed') { |
||||||
|
handlerSubsection(null, e, !!(state === 'open')); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
initializeSubsections(); |
||||||
|
|
||||||
|
document.querySelectorAll('.left-panel .section.sub .clickable-area').forEach( |
||||||
|
e => e.addEventListener('click', wrapListener(handlerSubsection, e.parentElement)) |
||||||
|
); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const showMessage = function(parms) { |
||||||
|
if (!parms || !parms.header) { |
||||||
|
throw new Error('Invalid message parameters'); |
||||||
|
} |
||||||
|
|
||||||
|
const msg = document.querySelector('.message-wrapper'); |
||||||
|
|
||||||
|
if (msg.querySelector('.icon .image')) { |
||||||
|
msg.querySelector('.icon .image').remove(); |
||||||
|
} |
||||||
|
if (parms.icon) { |
||||||
|
msg.querySelector('.icon').appendChild(createSVG(['image'], parms.icon)); |
||||||
|
} |
||||||
|
|
||||||
|
msg.querySelector('.header').innerHTML = parms.header; |
||||||
|
msg.querySelector('.text').innerHTML = parms.text || ''; |
||||||
|
msg.querySelector('.button .button-text').innerHTML = parms.button || 'Закрыть'; |
||||||
|
|
||||||
|
msg.classList.add('active'); |
||||||
|
}; |
||||||
|
|
||||||
|
[document.querySelector('.message-wrapper .message .close-button'), document.querySelector('.message .button')].forEach( e => e.addEventListener('click', () => { |
||||||
|
document.querySelector('.message-wrapper').classList.remove('active'); |
||||||
|
})); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
document.querySelector('.message-wrapper').style.display = ""; |
@ -0,0 +1,170 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const player = document.querySelector('.body-wrapper > .player'); |
||||||
|
const audio = player.querySelector('.core'); |
||||||
|
|
||||||
|
const handlerPlay = function(ev, id) { |
||||||
|
player.classList.add('active'); |
||||||
|
|
||||||
|
changeSVGImage(player.querySelector('.button.play-pause'), 'icon-pause'); |
||||||
|
|
||||||
|
player.querySelector('.time-slider').value = 0; |
||||||
|
player.querySelector('.time-slider').max = 0; |
||||||
|
player.querySelector('.time').innerHTML = '00:00'; |
||||||
|
player.querySelector('.duration').innerHTML = '00:00'; |
||||||
|
|
||||||
|
audio.src = '/api/recording/' + id + '?asFile=false'; |
||||||
|
audio.volume = storageGet('player-volume') || 0.25; |
||||||
|
player.querySelector('.volume-slider').value = audio.volume; |
||||||
|
|
||||||
|
audio.load(); |
||||||
|
audio.play(); |
||||||
|
|
||||||
|
ev.stopPropagation(); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
(() => { |
||||||
|
let isDragging = false; |
||||||
|
|
||||||
|
const onLoadedMetadata = () => { |
||||||
|
if (audio.duration && !isNaN(audio.duration)) { |
||||||
|
player.querySelector('.duration').innerHTML = common.convertToDuration(Math.ceil(audio.duration)); |
||||||
|
player.querySelector('.time-slider').max = audio.duration; |
||||||
|
|
||||||
|
audio.playbackRate = storageGet('player-speed') || 1; |
||||||
|
player.querySelector('.speed-slider').value = audio.playbackRate; |
||||||
|
player.querySelector('.current-speed').innerHTML = audio.playbackRate * 100 + "%"; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const onEnded = () => { |
||||||
|
changeSVGImage(player.querySelector('.button.play-pause'), 'icon-play-fill'); |
||||||
|
|
||||||
|
player.querySelector('.time-slider').value = audio.duration; |
||||||
|
player.querySelector('.time').innerHTML = common.convertToDuration(Math.ceil(audio.duration)); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const onError = e => { |
||||||
|
let errorText = ""; |
||||||
|
switch (e.target.error.code) { |
||||||
|
case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED: |
||||||
|
errorText = "Файл записи не найден на сервере"; |
||||||
|
break; |
||||||
|
case e.target.error.MEDIA_ERR_NETWORK: |
||||||
|
errorText = "Что-то произошло с сетевым подключением"; |
||||||
|
break; |
||||||
|
case e.target.error.MEDIA_ERR_DECODE: |
||||||
|
errorText = "Проблема с декодированием аудиофайла"; |
||||||
|
break; |
||||||
|
default: |
||||||
|
errorText = "Неизвестная ошибка: " + e.target.error.code; |
||||||
|
} |
||||||
|
|
||||||
|
showMessage({ |
||||||
|
icon: 'icon-error', |
||||||
|
header: 'Ошибка!', |
||||||
|
text: errorText |
||||||
|
}); |
||||||
|
|
||||||
|
player.classList.remove('active'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerPlayPause = (ev, el) => { |
||||||
|
if (!audio.paused) { |
||||||
|
changeSVGImage(el, 'icon-play-fill'); |
||||||
|
audio.pause(); |
||||||
|
} else { |
||||||
|
changeSVGImage(el, 'icon-pause'); |
||||||
|
audio.play(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerVolume = () => { |
||||||
|
player.querySelector('.volume-bar').classList.toggle('active'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerSpeed = () => { |
||||||
|
player.querySelector('.speed-bar').classList.toggle('active'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerClose = () => { |
||||||
|
player.classList.remove('active'); |
||||||
|
audio.pause(); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const handlerChangeVolume = (ev, el) => { |
||||||
|
if (el.value >= 0 && el.value <= 1) { |
||||||
|
audio.volume = el.value; |
||||||
|
} |
||||||
|
}; |
||||||
|
const handlerSaveVolume = (ev, el) => { |
||||||
|
if (el.value >= 0 && el.value <= 1) { |
||||||
|
storageSet('player-volume', el.value); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const handlerChangeSpeed = (ev, el) => { |
||||||
|
audio.playbackRate = el.value; |
||||||
|
player.querySelector('.current-speed').innerHTML = audio.playbackRate * 100 + "%"; |
||||||
|
}; |
||||||
|
const handlerSaveSpeed = (ev, el) => { |
||||||
|
if (el.value >= 0) { |
||||||
|
storageSet('player-speed', el.value); |
||||||
|
} |
||||||
|
}; |
||||||
|
const handlerResetSpeed = (ev, el) => { |
||||||
|
el.value = 1; |
||||||
|
audio.playbackRate = 1; |
||||||
|
player.querySelector('.speed-slider').value = 1; |
||||||
|
player.querySelector('.current-speed').innerHTML = "100%"; |
||||||
|
storageSet('player-speed', 1); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handlerProgress = () => { |
||||||
|
if (!isNaN(audio.currentTime) && !isNaN(audio.duration)) { |
||||||
|
if (!isDragging) { |
||||||
|
player.querySelector('.time-slider').value = audio.currentTime; |
||||||
|
} |
||||||
|
player.querySelector('.time').innerHTML = common.convertToDuration(audio.currentTime); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerChangeTime = e => { |
||||||
|
if (!isNaN(audio.duration)) { |
||||||
|
const pb = player.querySelector('.time-slider'); |
||||||
|
audio.currentTime = pb.value; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
createListener(player.querySelector('.button.play-pause'), 'click', handlerPlayPause); |
||||||
|
|
||||||
|
createListener(player.querySelector('.volume-slider'), 'input', handlerChangeVolume); |
||||||
|
createListener(player.querySelector('.volume-slider'), 'change', handlerSaveVolume); |
||||||
|
createListener(player.querySelector('.speed-slider'), 'input', handlerChangeSpeed); |
||||||
|
createListener(player.querySelector('.speed-slider'), 'change', handlerSaveSpeed); |
||||||
|
createListener(player.querySelector('.button.reset-speed'), 'click', handlerResetSpeed); |
||||||
|
|
||||||
|
|
||||||
|
player.querySelector('.button.volume').addEventListener('click', handlerVolume); |
||||||
|
player.querySelector('.button.speed').addEventListener('click', handlerSpeed); |
||||||
|
player.querySelector('.button.close').addEventListener('click', handlerClose); |
||||||
|
|
||||||
|
player.querySelector('.time-slider').addEventListener('change', handlerChangeTime); |
||||||
|
player.querySelector('.time-slider').addEventListener('mousedown', () => {isDragging = true}); |
||||||
|
player.querySelector('.time-slider').addEventListener('mouseup', () => {isDragging = false}); |
||||||
|
|
||||||
|
|
||||||
|
audio.addEventListener('timeupdate', handlerProgress); |
||||||
|
audio.addEventListener('loadedmetadata', onLoadedMetadata); |
||||||
|
audio.addEventListener('ended', onEnded); |
||||||
|
audio.addEventListener('error', onError); |
||||||
|
})(); |
@ -0,0 +1 @@ |
|||||||
|
'use strict'; |
@ -0,0 +1,75 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
// document.querySelectorAll('.body-wrapper input').forEach( e => e.disabled = true);
|
||||||
|
// document.querySelectorAll('.body-wrapper a').forEach( e => e.tabIndex = -1);
|
||||||
|
|
||||||
|
// TODO: this
|
||||||
|
(() => { |
||||||
|
|
||||||
|
})(); |
||||||
|
|
||||||
|
let stack = []; |
||||||
|
let maxZIndex = 100; |
||||||
|
|
||||||
|
const wrapper = document.querySelector('.window-wrapper'); |
||||||
|
if (!wrapper) { |
||||||
|
throw new Error('window wrapper is missing'); |
||||||
|
} |
||||||
|
|
||||||
|
const getWindowById = id => { |
||||||
|
if (typeof id !== 'string') { |
||||||
|
throw new Error('className is not a string'); |
||||||
|
} |
||||||
|
|
||||||
|
const window = document.getElementById(id); |
||||||
|
if (!window) { |
||||||
|
throw new Error('Window with ID ' + className + ' not found'); |
||||||
|
} |
||||||
|
|
||||||
|
return window; |
||||||
|
}; |
||||||
|
const openWindow = id => { |
||||||
|
const window = getWindowById(id); |
||||||
|
window.classList.add('active'); |
||||||
|
wrapper.classList.add('active'); |
||||||
|
|
||||||
|
if (!stack.includes(window)) { |
||||||
|
// hide all other windows
|
||||||
|
if (stack.length) { |
||||||
|
stack.forEach( e => e.classList.remove('active')); |
||||||
|
} |
||||||
|
stack.push(window); |
||||||
|
|
||||||
|
++maxZIndex; |
||||||
|
window.style.zIndex = maxZIndex; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const closeWindow = id => { |
||||||
|
const window = getWindowById(id); |
||||||
|
window.classList.remove('active'); |
||||||
|
|
||||||
|
if (stack.length && stack[stack.length - 1] === window) { |
||||||
|
stack.pop(); |
||||||
|
} |
||||||
|
|
||||||
|
if (!stack.length) { |
||||||
|
wrapper.classList.remove('active'); |
||||||
|
maxZIndex = 100; |
||||||
|
} else { |
||||||
|
stack.forEach( e => e.classList.add('active')); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
wrapper.style.display = ""; |
||||||
|
|
||||||
|
//
|
||||||
|
// document.querySelector('.window-wrapper .window .close-button').forEach( e => {
|
||||||
|
// createListener(e, 'click', (ev, el, wrapper) => {
|
||||||
|
// e
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const createListener = function(element, event, callback, ...args) {
|
||||||
|
// element.addEventListener(event, wrapListener(callback, element, ...args));
|
||||||
|
// };
|
||||||
|
// });
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,26 @@ |
|||||||
|
{ |
||||||
|
"name": "ast-cdr", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "", |
||||||
|
"private": true, |
||||||
|
"main": "app.js", |
||||||
|
"scripts": { |
||||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||||
|
}, |
||||||
|
"author": "", |
||||||
|
"license": "ISC", |
||||||
|
"dependencies": { |
||||||
|
"@hapi/hapi": "latest", |
||||||
|
"@hapi/inert": "latest", |
||||||
|
"@hapi/vision": "latest", |
||||||
|
"asterisk-manager": "^0.1.16", |
||||||
|
"dayjs": "latest", |
||||||
|
"dotenv": "latest", |
||||||
|
"hapi-favicon": "^2.1.2", |
||||||
|
"joi": "latest", |
||||||
|
"knex": "latest", |
||||||
|
"pg": "latest", |
||||||
|
"pug": "latest", |
||||||
|
"rfr": "^1.2.3" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const Assert = require('assert'); |
||||||
|
const Joi = require('joi'); |
||||||
|
const AsteriskManager = require('asterisk-manager'); |
||||||
|
|
||||||
|
let ami = null; |
||||||
|
|
||||||
|
const initAMI = () => { |
||||||
|
Assert.ok(!ami, 'tried to double-initialize AMI'); |
||||||
|
|
||||||
|
if (!process.env.AMI_ENABLE) { |
||||||
|
console.log('AMI is disabled, skipping AMI initialization') |
||||||
|
} else { |
||||||
|
if (!process.env.AMI_HOST || !process.env.AMI_USER || !process.env.AMI_PASS) { |
||||||
|
console.warn('Some AMI parameters are missing, skipping AMI initialization') |
||||||
|
} else { |
||||||
|
ami = new AsteriskManager(process.env.AMI_PORT, process.env.AMI_HOST, process.env.AMI_USER, process.env.AMI_PASS, true); |
||||||
|
ami.keepConnected(); |
||||||
|
ami.on('error', ev => { |
||||||
|
console.warn('AMI error: ' + ev); |
||||||
|
}) |
||||||
|
console.log('AMI initialized'); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const sendAMIAction = (action, callback) => { |
||||||
|
if (!ami) { |
||||||
|
throw new Error('AMI is not initialized') |
||||||
|
} |
||||||
|
|
||||||
|
return ami.action(action, callback); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
initAMI: initAMI, |
||||||
|
action: sendAMIAction, |
||||||
|
|
||||||
|
config: Joi.object({ |
||||||
|
AMI_ENABLE: Joi.boolean().optional().default(false), |
||||||
|
AMI_HOST: Joi.alternatives().try(Joi.string().hostname(), Joi.string().ip()).when( |
||||||
|
'AMI_ENABLE', {is: Joi.boolean().valid(true), then: Joi.required(), otherwise: Joi.optional()}), |
||||||
|
AMI_PORT: Joi.number().port().when( |
||||||
|
'AMI_ENABLE', {is: Joi.boolean().valid(true), then: Joi.optional().default(5038), otherwise: Joi.optional()}), |
||||||
|
AMI_USER: Joi.string().min(1).when( |
||||||
|
'AMI_ENABLE', {is: Joi.boolean().valid(true), then: Joi.required(), otherwise: Joi.optional()}), |
||||||
|
AMI_PASS: Joi.string().min(1).when( |
||||||
|
'AMI_ENABLE', {is: Joi.boolean().valid(true), then: Joi.required(), otherwise: Joi.optional()}) |
||||||
|
}) |
||||||
|
}; |
@ -0,0 +1,17 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
isDebugMode: () => process.env.NODE_ENV === 'development', |
||||||
|
|
||||||
|
debug: (data, ...args) => { |
||||||
|
if (process.env.NODE_ENV === 'development') { |
||||||
|
console.log(data, ...args); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
debugIf: (cond, data, ...args) => { |
||||||
|
if (cond) { |
||||||
|
this.debug(data, ...args); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const Assert = require('assert'); |
||||||
|
const Joi = require('joi'); |
||||||
|
|
||||||
|
|
||||||
|
const initMainConfig = () => { |
||||||
|
const config = require('dotenv').config(); |
||||||
|
|
||||||
|
if (config.error) { |
||||||
|
console.error('Failed to read config file: ' + config.error); |
||||||
|
} else { |
||||||
|
console.log(`Config file loaded with ${config.parsed ? Object.keys(config.parsed).length : 0} options`); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const initConfigs = (configs = []) => { |
||||||
|
Assert.ok(configs && Array.isArray(configs), '\'configs\' must be an array'); |
||||||
|
|
||||||
|
initMainConfig(); |
||||||
|
|
||||||
|
configs.forEach( c => { |
||||||
|
try { |
||||||
|
if (c) { |
||||||
|
Assert.ok(Joi.isSchema(c), 'config must be a Joi schema'); |
||||||
|
|
||||||
|
const result = c.validate(process.env, { |
||||||
|
allowUnknown: true, |
||||||
|
presence: 'required' |
||||||
|
}); |
||||||
|
|
||||||
|
if (result.error) { |
||||||
|
throw result.error; |
||||||
|
} |
||||||
|
|
||||||
|
if (result.value) { |
||||||
|
process.env = {...process.env, ...result.value}; |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
throw new Joi.ValidationError(`Failed to validate config: ${e}`); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = initConfigs; |
@ -0,0 +1,77 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const Joi = require('joi'); |
||||||
|
const Knex = require('knex'); |
||||||
|
const Rfr = require('rfr'); |
||||||
|
|
||||||
|
const ComSrv = Rfr('/server/comsrv'); |
||||||
|
|
||||||
|
let db = null; |
||||||
|
|
||||||
|
const connectToDB = async () => { |
||||||
|
ComSrv.debug(`Connecting to ${process.env.DB_HOST}:${process.env.DB_PORT} as ` + |
||||||
|
`${process.env.DB_USER} (type: ${process.env.DB_TYPE}, ` + |
||||||
|
`DB: ${process.env.DB_DATABASE})`); |
||||||
|
|
||||||
|
let db = Knex({ |
||||||
|
client: process.env.DB_TYPE, |
||||||
|
connection: { |
||||||
|
host: process.env.DB_HOST, |
||||||
|
port: process.env.DB_PORT, |
||||||
|
user: process.env.DB_USER, |
||||||
|
password: process.env.DB_PASS, |
||||||
|
database: process.env.DB_DATABASE |
||||||
|
}, |
||||||
|
log: { |
||||||
|
warn(message) { console.warn(message) }, |
||||||
|
error(message) { console.error(message) }, |
||||||
|
debug(message) { ComSrv.debug(message) } |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
await db.raw('select 1 as connected'); |
||||||
|
console.log('Connected to DB'); |
||||||
|
|
||||||
|
return db; |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
config: Joi.object({ |
||||||
|
DB_TYPE: Joi.string().valid('pg', 'mysql'), |
||||||
|
DB_HOST: Joi.alternatives().try(Joi.string().hostname(), Joi.string().ip()), |
||||||
|
DB_USER: Joi.string().min(1), |
||||||
|
DB_PASS: Joi.string().min(1), |
||||||
|
DB_DATABASE: Joi.string().min(1), |
||||||
|
DB_TABLE: Joi.string().min(1), |
||||||
|
|
||||||
|
DB_PORT: Joi.number().port().optional().when('DB_TYPE', { |
||||||
|
is: Joi.string().valid('pg'), |
||||||
|
then: Joi.number().default(5432), |
||||||
|
otherwise: Joi.number().default(3306) |
||||||
|
}), |
||||||
|
|
||||||
|
DB_RETRIES: Joi.number().integer().min(0).optional().default(3) |
||||||
|
}), |
||||||
|
|
||||||
|
initDB: async () => { |
||||||
|
let retries = process.env.DB_RETRIES; |
||||||
|
|
||||||
|
do { |
||||||
|
try { |
||||||
|
db = await connectToDB(); |
||||||
|
return; |
||||||
|
} catch (e) { |
||||||
|
console.error(`Failed to connect to DB: ${e}`) |
||||||
|
if (retries >= 2 || process.env.DB_RETRIES == 0) { |
||||||
|
console.log(`Attempting another connection after 3s`); |
||||||
|
await new Promise(res => setTimeout(res, 3000)); |
||||||
|
} |
||||||
|
} |
||||||
|
} while (process.env.DB_RETRIES == 0 || --retries > 0); |
||||||
|
|
||||||
|
throw new Error("Failed to connect to DB"); |
||||||
|
}, |
||||||
|
|
||||||
|
query: (table = process.env.DB_TABLE) => db(table), |
||||||
|
db: () => db |
||||||
|
}; |
@ -0,0 +1,62 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const Boom = require('@hapi/boom'); |
||||||
|
const Joi = require('joi'); |
||||||
|
const Rfr = require('rfr'); |
||||||
|
|
||||||
|
const Calls = Rfr('/server/sections/calls'); |
||||||
|
|
||||||
|
|
||||||
|
const mainHandler = async (request, h) => { |
||||||
|
let options = { |
||||||
|
activeSection: request.params.section |
||||||
|
}; |
||||||
|
|
||||||
|
switch (request.params.section) { |
||||||
|
case 'calls': |
||||||
|
try { |
||||||
|
options.filter = await Calls.getFilter(request.query); |
||||||
|
} catch (e) { |
||||||
|
throw Boom.badRequest(`CDR query failed validation: ${e}`); |
||||||
|
} |
||||||
|
|
||||||
|
options.cdr = await Calls.getCdr(options.filter); |
||||||
|
options.title = 'Звонки'; |
||||||
|
|
||||||
|
break; |
||||||
|
case 'settings': |
||||||
|
options.title = 'Настройки'; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (!options.pageCard) { |
||||||
|
options.pageCard = options.title; |
||||||
|
} |
||||||
|
|
||||||
|
return h.view(request.params.section, options); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
routes: [{ |
||||||
|
method: 'GET', |
||||||
|
path: '/public/{file*}', |
||||||
|
handler: { |
||||||
|
directory: { |
||||||
|
path: ['./client', './shared'] |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
method: 'GET', |
||||||
|
path: '/{section?}', |
||||||
|
handler: mainHandler, |
||||||
|
options: { |
||||||
|
validate: { |
||||||
|
params: Joi.object({ |
||||||
|
section: Joi.string().optional().valid('calls', 'reports', 'status', 'settings').default('calls') |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
}] |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue