diff --git a/README.md b/README.md
index 0f1fc2b..4d897e2 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
# daster
-Управление диалпланом
+Dialplan Asterisk - управление диалпланом
+
+
diff --git a/database/script.sql b/database/script.sql
index 4789ce4..beecc0f 100644
--- a/database/script.sql
+++ b/database/script.sql
@@ -1,38 +1,38 @@
create table if not exists da_groups (
- da_name varchar(20) not null,
- da_comment varchar(100) default null,
- constraint da_groups_pk primary key (da_name)
+ da_name varchar(20) not null,
+ da_comment varchar(100) default null,
+ constraint da_groups_pk primary key (da_name)
);
insert into da_groups (da_name, da_comment)
- values
- ('general', 'Общие контакты'),
- ('work', 'Рабочие контакты'),
- ('personal', 'Личные контакты');
+ values
+ ('general', 'Общие контакты'),
+ ('work', 'Рабочие контакты'),
+ ('personal', 'Личные контакты');
create table if not exists da_lists (
- da_name varchar(20) not null,
- da_comment varchar(100) default null,
- constraint da_lists_pk primary key (da_name)
+ da_name varchar(20) not null,
+ da_comment varchar(100) default null,
+ constraint da_lists_pk primary key (da_name)
);
insert into da_lists (da_name, da_comment)
- values
- ('general', 'Общий'),
- ('whitelist', 'Белый'),
- ('blacklist', 'Черный');
+ values
+ ('general', 'Общий'),
+ ('whitelist', 'Белый'),
+ ('blacklist', 'Черный');
create table if not exists da_numbers (
- da_number varchar(12) not null,
- da_group varchar(20) not null default 'general',
- da_list varchar(20) not null default 'general',
- da_all_cc int not null default 0,
- da_white_cc int not null default 0,
- da_black_cc int not null default 0,
- da_comment varchar(100) default null,
- constraint da_numbers_pk primary key (da_number),
- foreign key (da_group) references da_groups (da_name) on delete set null on update cascade,
- foreign key (da_list) references da_lists (da_name) on delete set null on update cascade
+ da_number varchar(12) not null,
+ da_group varchar(20) not null default 'general',
+ da_list varchar(20) not null default 'general',
+ da_all_cc int not null default 0,
+ da_white_cc int not null default 0,
+ da_black_cc int not null default 0,
+ da_comment varchar(100) default null,
+ constraint da_numbers_pk primary key (da_number),
+ foreign key (da_group) references da_groups (da_name) on delete set null on update cascade,
+ foreign key (da_list) references da_lists (da_name) on delete set null on update cascade
);
create table if not exists da_sms (
@@ -44,6 +44,21 @@ create table if not exists da_sms (
constraint da_sms_pk primary key (da_id)
);
+create table if not exists da_ussd_type (
+ da_id bigserial not null,
+ da_comment varchar(100) not null,
+ constraint da_ussd_type_pk primary key (da_id)
+);
+
+insert into da_ussd_type (da_id, da_comment)
+ values
+ ('0', 'Уведомление'),
+ ('1', 'Запрос'),
+ ('2', 'Прервано сетью'),
+ ('3', 'Ответ другого локального клиента'),
+ ('4', 'Операция не поддерживается'),
+ ('5', 'Тайм-аут сети');
+
create table if not exists da_ussd (
da_id bigserial not null,
da_date timestamp not null default NOW(),
@@ -54,9 +69,10 @@ create table if not exists da_ussd (
);
create table if not exists da_server (
- da_address varchar(50) not null,
+ da_id int not null default 1,
da_transparent_mode bool not null default false,
da_internal_number varchar(12) not null,
da_external_number varchar(12) not null,
- constraint da_server_pk primary key (da_address)
+ da_external_number_on bool not null default true,
+ constraint da_server_pk primary key (da_id)
);
diff --git a/dub.json b/dub.json
index 5814e7b..d1e1943 100644
--- a/dub.json
+++ b/dub.json
@@ -5,10 +5,9 @@
"copyright": "Copyright © 2023, Alexander Zhirov",
"dependencies": {
"vibe-d": "~>0.9",
- "ldap": "~>0.4",
- "singlog": "~>0.3.1",
+ "singlog": "~>0.4.0",
"arsd-official:postgres": "~>10.9.10",
- "readconf": "~>0.3.1"
+ "readconf": "~>0.4.0"
},
"buildTypes": {
"debug": {
@@ -26,7 +25,7 @@
}
},
"description": "Dialplan Asterisk - веб-сервер для управления обработкой вызовов Asterisk",
- "license": "proprietary",
+ "license": "GPL-2.0",
"name": "daster",
"targetPath": "bin",
"targetType": "executable"
diff --git a/dub.selections.json b/dub.selections.json
index f10278b..08bea5a 100644
--- a/dub.selections.json
+++ b/dub.selections.json
@@ -5,15 +5,14 @@
"datefmt": "1.0.4",
"diet-ng": "1.8.1",
"eventcore": "0.9.25",
- "ldap": "0.4.0",
"libasync": "0.8.6",
"memutils": "1.0.9",
"mir-linux-kernel": "1.0.1",
"openssl": "3.3.0",
"openssl-static": "1.0.2+3.0.8",
- "readconf": "0.3.1",
- "silly": "1.1.1",
- "singlog": "0.3.1",
+ "readconf": "0.4.0",
+ "silly": "1.2.0-dev.2",
+ "singlog": "0.4.0",
"stdx-allocator": "2.77.5",
"taggedalgebraic": "0.11.22",
"vibe-core": "2.2.0",
diff --git a/images/404.png b/images/404.png
new file mode 100644
index 0000000..116c3e7
Binary files /dev/null and b/images/404.png differ
diff --git a/images/favicon.png b/images/favicon.png
index f78f532..e220943 100644
Binary files a/images/favicon.png and b/images/favicon.png differ
diff --git a/images/mainpage.png b/images/mainpage.png
new file mode 100644
index 0000000..d5cf9bf
Binary files /dev/null and b/images/mainpage.png differ
diff --git a/js/authorization.js b/js/authorization.js
new file mode 100644
index 0000000..4edab3f
--- /dev/null
+++ b/js/authorization.js
@@ -0,0 +1,48 @@
+$(document).ready(function () {
+ noticer = new Noticer;
+
+ $("#authorization").button({ icon: "ui-icon-home" });
+
+ $("#login, #password").on('keypress',function(e) {
+ if(e.which == 13) {
+ authorization()
+ }
+ });
+
+ $("#authorization").click(() => {
+ authorization()
+ });
+
+ $("body").fadeTo(500, 1);
+});
+
+function authorization() {
+ request().then((data) => {
+ data.error ? noticer.error(data.message) : (window.location.href = ".");
+ }).catch((e) => {
+ noticer.error(e.message);
+ });
+}
+
+async function request() {
+ let login = $("#login").val();
+ let password = $("#password").val();
+
+ let response = await fetch('.', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=utf-8'
+ },
+ body: JSON.stringify({
+ login: login,
+ password: password,
+ query: "login"
+ })
+ });
+
+ if (!response.ok)
+ throw new Error(`Произошла неизвестаня ошибка: ${response.status}`);
+
+ const data = await response.json();
+ return data;
+}
diff --git a/js/message.js b/js/message.js
deleted file mode 100644
index e064ac5..0000000
--- a/js/message.js
+++ /dev/null
@@ -1,100 +0,0 @@
-class Message {
- timer;
-
- constructor() {
- this.div = $('
');
- this.div.css({
- "position": "absolute",
- "top": "10px",
- "right": "20px",
- "z-index": "1000"
- });
-
- $('body').append(this.div);
- }
-
- success(message, delay = 6000) {
- this.print(message, delay, '#52b818', '#bffdc0');
- }
-
- warning(message, delay = 6000) {
- this.print(message, delay, '#b8ae18', '#f8fdbf');
- }
-
- error(message, delay = 6000) {
- this.print(message, delay, '#b96161', '#fddede');
- }
-
- print = function(message, delay, border, background) {
- if (delay < 6000) delay = 6000;
- let Timer = function(callback, delay) {
- let timerId, start, remaining = delay;
-
- this.pause = function() {
- clearTimeout(timerId);
- remaining -= new Date() - start;
- };
-
- this.resume = function() {
- start = new Date();
- clearTimeout(timerId);
- timerId = setTimeout(callback, remaining);
- };
-
- this.dead = function() {
- clearTimeout(timerId);
- };
-
- this.resume();
- }
-
- let newMessage = $(`${message}
`);
-
- newMessage.css({
- "border": `1px solid ${border}`,
- "background-color": `${background}`,
- "color": "#333",
- "padding": "10px 30px",
- "text-align": "center",
- "display": "none",
- "margin": "10px 0 0 0",
- "width": "350px",
- "opacity": "1",
- "cursor": "pointer"
- });
-
- newMessage.hover(function(){
- $(this).css({
- "opacity": "1"
- });
- }, function(){
- $(this).css({
- "opacity": "0.3"
- });
- });
-
- this.div.append(newMessage);
-
- let opacityTimeout = setTimeout(function() {
- newMessage.fadeTo(1000, 0.3);
- }, 2500);
-
- newMessage.fadeIn(500).mouseenter(() => {
- clearTimeout(opacityTimeout);
- this.timer.pause();
- }).mouseleave(() => {
- this.timer.resume();
- }).click(() => {
- this.timer.dead();
- newMessage.fadeOut(0, function(){
- this.remove();
- });
- });
-
- this.timer = new Timer(() => {
- newMessage.fadeOut(500, function(){
- this.remove();
- });
- }, delay);
- }
-}
diff --git a/js/noticer.min.js b/js/noticer.min.js
new file mode 100644
index 0000000..e7604a1
--- /dev/null
+++ b/js/noticer.min.js
@@ -0,0 +1,4 @@
+/*! noticer - v0.1.1 - 2023-06-02
+* https://git.zhirov.kz/alexander/noticer
+* Copyright Alexander Zhirov; Licensed GPL-2.0 */
+class Noticer{constructor(){this.div=$(''),this.div.css({position:"absolute",top:"10px",right:"20px","z-index":"1000"}),$("body").append(this.div)}success(e,t=6e3){this.print(e,t,"#52b818","#bffdc0")}warning(e,t=6e3){this.print(e,t,"#b8ae18","#f8fdbf")}error(e,t=6e3){this.print(e,t,"#b96161","#fddede")}print=function(e,t,n,s){t<6e3&&(t=6e3);let a=function(e,t){let n,s,o=t;this.pause=function(){clearTimeout(n),o-=new Date-s},this.resume=function(){s=new Date,clearTimeout(n),n=setTimeout(e,o)},this.dead=function(){clearTimeout(n)},this.resume()},o=$(`${e}
`);o.css({border:`1px solid ${n}`,"background-color":`${s}`,color:"#333",padding:"10px 30px","text-align":"center",display:"none",margin:"10px 0 0 0",width:"350px",opacity:"1",cursor:"pointer"}),o.hover(function(){$(this).css({opacity:"1"})},function(){$(this).css({opacity:"0.3"})}),this.div.append(o);let r=setTimeout(function(){o.fadeTo(1e3,.3)},2500);o.fadeIn(500).mouseenter(()=>{clearTimeout(r),i.pause()}).mouseleave(()=>{i.resume()}).click(()=>{i.dead(),o.fadeOut(0,function(){this.remove()})});let i=new a(()=>{o.fadeOut(500,function(){this.remove()})},t)}}
diff --git a/js/script.js b/js/script.js
index 2dc6f90..7a00ef9 100644
--- a/js/script.js
+++ b/js/script.js
@@ -1,27 +1,77 @@
-$(document).ready(function () {
- message = new Message;
+var numbers = [];
+var sms = [];
+var ussd = [];
- // (new divNotFoundNumbers).push("Загрузка...");
+$(document).ready(function () {
+ noticer = new Noticer;
+
+ let tabs = {
+ 0: () => { loadNumbers() },
+ 1: () => { loadSMS() },
+ 2: () => { loadUSSD() },
+ 3: () => { loadServerInfo() }
+ };
+
+ let lists = {
+ 0: () => { showListNumbers($("#accordion-numbers .ui-accordion-content-active")) },
+ 1: () => { showListSMS($("#accordion-sms .ui-accordion-content-active")) },
+ 2: () => { showListUSSD($("#accordion-ussd .ui-accordion-content-active")) },
+ 3: () => {}
+ };
+
+ let groups = {
+ 0: () => { generateListGroupNumbers($("#accordion-numbers .ui-accordion-content-active")) },
+ 1: () => { generateListGroupSMS($("#accordion-sms .ui-accordion-content-active")) },
+ 2: () => { generateListGroupUSSD($("#accordion-ussd .ui-accordion-content-active")) },
+ 3: () => {}
+ };
$("button").button();
- $("#tabs").tabs();
- // $("#accordion-numbers").accordion();
+ $("#update-tab").button("option", "icon", "ui-icon-refresh");
+ $("#update-group").button("option", "icon", "ui-icon-arrowrefresh-1-s");
+ $("#add-number").button("option", "icon", "ui-icon-plusthick");
+ $("#logout").button("option", "icon", "ui-icon-power");
- // $(".addNumber").click(() => {
- // numberAdd()
- // });
-
- $("body").fadeTo(500, 1);
-
- // getData("Список номеров успешно загружен");
-
- $(".search").on("input", function () {
- // showNumbers(numbers.filter(e => e.id.includes($(this).val())))
- }).keydown(function (e) {
- // e.key == "Escape" && ($(this).val(""), showNumbers())
+ $("#tabs").tabs({
+ activate: function( event, ui ) {
+ lists[$(this).tabs( "option", "active" )]();
+ $("#add-number").button( "option", "disabled", $(this).tabs( "option", "active" ) > 0 );
+ $("#update-group").button( "option", "disabled", $(this).tabs( "option", "active" ) > 2 );
+ }
});
- loadData();
+ $("#update-tab").click(() => {
+ tabs[$("#tabs").tabs( "option", "active" )]()
+ });
+
+ $("#update-group").click(() => {
+ groups[$("#tabs").tabs( "option", "active" )]()
+ });
+
+ $("#add-number").click(() => {
+ addNumber($("#accordion-numbers .ui-accordion-content-active"))
+ });
+
+ $("#search").on("input", function () {
+ lists[$("#tabs").tabs( "option", "active" )]()
+ }).keydown(function (e) {
+ e.key == "Escape" && ($(this).val(""), lists[$("#tabs").tabs( "option", "active" )]())
+ });
+
+ $("#logout").click(() => {
+ request('logout', 'json').then(data => {
+ data.error ? noticer.error(data.message) : (window.location.href = ".");
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+ });
+
+ loadNumbers();
+ loadSMS();
+ loadUSSD();
+ loadServerInfo();
+
+ $("body").fadeTo(500, 1);
})
async function request(query, type, queryData = {}) {
@@ -51,40 +101,591 @@ function isJSON(str) {
}
}
-function loadData() {
- request('listsgroups', 'json').then(data => {
- data.error ? message.error(data.message) : generateListsGroups(data);
+function isNumeric(value) {
+ return /^-?\d+$/.test(value);
+}
+
+function divNotFoundNumbers(text = '') {
+ let notFound = $('.notFoundNumbers');
+ let divTable = $('.body-rows');
+ let divNotFound = $(`${text}
`);
+
+ divNotFound.css({
+ "color": "#333",
+ "text-align": "center",
+ "padding": "20px 0 20px 0"
+ });
+
+ this.push = function() {
+ divTable.append(divNotFound);
+ }
+
+ this.remove = function() {
+ notFound.remove();
+ }
+}
+
+function pEmpty(text) {
+ return $(`${text}
`).css('text-align', 'center');
+}
+
+/************************************************************************************
+
+ Обработка таблицы с номерами телефонов
+
+************************************************************************************/
+
+function loadNumbers() {
+ request('listnumbergroups', 'text').then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else
+ generateListNumberGroups(data);
}).catch(error => {
- message.error(error.message);
+ noticer.error(error.message);
});
}
-function generateListsGroups(data) {
- let numbers = $("#tabs-numbers");
- let group = $('');
- $(data).each((i, j) => {
- group.append(`${j.comment}
`);
- });
- numbers.append(group);
+function generateListNumberGroups(data) {
+ if (!$(data).children().length) {
+ $("#tabs-numbers").html(pEmpty('Номера телефонов отсутствуют'));
+ return;
+ }
+
+ $("#tabs-numbers").html(data);
$("#accordion-numbers").accordion({
heightStyle: "content",
create: function( event, ui ) {
- generateGroupNumbers(ui.panel);
+ generateListGroupNumbers(ui.panel);
},
beforeActivate: function( event, ui ) {
- generateGroupNumbers(ui.newPanel);
+ generateListGroupNumbers(ui.newPanel);
}
});
}
-async function generateGroupNumbers(panel) {
- request('groupnumbers', 'text', { group: panel.data("group-name") }).then(data => {
- if (isJSON(data) && JSON.parse(data).error)
- message.error(JSON.parse(data).message);
+function generateListGroupNumbers(panel) {
+ if (!$("#accordion-numbers").children().length) {
+ noticer.warning("Номера телефонов отсутствуют");
+ $("#tabs-numbers").html(pEmpty('Номера телефонов отсутствуют'));
+ return;
+ }
+
+ request('listgroupnumbers', 'json', { group: panel.data("group-name") }).then(data => {
+ if (data.error)
+ noticer.error(data.message);
else {
- panel.html(data);
+ numbers = data;
+ showListNumbers(panel);
}
}).catch(error => {
- message.error(error.message);
+ noticer.error(error.message);
+ });
+}
+
+function showListNumbers(panel, data = numbers.filter(e => e.number.includes($("#search").val()))) {
+ (new divNotFoundNumbers).remove();
+ let body = panel.find('.body').html('');
+ $(data).each((i, j) => {
+ let row = $(`
`);
+ row.append(`${j.number} | `);
+ row.append(`${j.list} | `);
+ row.append(`${j.all_cc} | `);
+ row.append(`${j.white_cc} | `);
+ row.append(`${j.black_cc} | `);
+ row.append(`${j.comment} | `);
+ body.append(row);
+
+ row.click(function() {
+ viewNumber(panel, $(this).data('number'));
+ });
+ });
+
+ if (!body.children().length)
+ (new divNotFoundNumbers('Нет номеров')).push();
+}
+
+function viewNumber(panel, number) {
+ request('viewnumber', 'text', {number: number}).then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else {
+ showViewNumber(data, [
+ {
+ id: "btn-save",
+ text: "Сохранить",
+ icon: "ui-icon-check",
+ click: function() {
+ actionNumber(panel, $(this), 'updatenumber');
+ }
+ },
+ {
+ id: "btn-delete",
+ text: "Удалить",
+ icon: "ui-icon-trash",
+ click: function() {
+ delNumber(panel, $(this));
+ }
+ }
+ ], `Редактирование номера ${number}`);
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function showViewNumber(data, actionButton, title) {
+ let form = $(data);
+
+ form.appendTo('body').dialog({
+ title: title,
+ height: 'auto',
+ width: 'auto',
+ resizable: false,
+ modal: true,
+ show: { effect: "fade", duration: 500 },
+ close: function(event, ui) {
+ $(this).dialog('destroy').remove()
+ },
+ buttons: [
+ ...actionButton,
+ {
+ text: "Отмена",
+ icon: "ui-icon-cancel",
+ click: function() {
+ $(this).dialog("close");
+ }
+ }
+ ]
+ });
+
+ $('#number-group, #number-list').selectmenu({
+ width: 200
+ });
+}
+
+function actionNumber(panel, currentWindow, query) {
+ let pattern_number = /^\+7\d{10}$/g;
+
+ let number = $('#number-number').val();
+ let group = $('#number-group').val();
+ let list = $('#number-list').val();
+ let all_cc = $('#number-all-cc').val();
+ let white_cc = $('#number-white-cc').val();
+ let black_cc = $('#number-black-cc').val();
+ let comment = $('#number-comment').val();
+
+ let error = false;
+
+ if (number.match(pattern_number) === null) { noticer.warning("Номер не соответствует формату +7XXXXXXXXXX"); error = true; }
+ if (!isNumeric(all_cc)) { noticer.warning("Общее количество звонков должно быть числом"); error = true; }
+ if (all_cc < 0) { noticer.warning("Общее количество звонков не может быть отрицательным"); error = true; }
+ if (!isNumeric(white_cc)) { noticer.warning("Белое количество звонков должно быть числом"); error = true; }
+ if (white_cc < 0) { noticer.warning("Белое количество звонков не может быть отрицательным"); error = true; }
+ if (!isNumeric(black_cc)) { noticer.warning("Черное количество звонков должно быть числом"); error = true; }
+ if (black_cc < 0) { noticer.warning("Черное количество звонков не может быть отрицательным"); error = true; }
+
+ if (error) return;
+
+ request(query, 'json', {
+ number: number,
+ group: group,
+ list: list,
+ all_cc: parseInt(all_cc),
+ white_cc: parseInt(white_cc),
+ black_cc: parseInt(black_cc),
+ comment: comment
+ }).then(data => {
+ if (data.error)
+ noticer.error(data.message);
+ else
+ {
+ query == 'write' ? noticer.success(`Номер ${number} был добавлен`) : noticer.success(`Номер ${number} был обновлен`);
+ generateListGroupNumbers(panel);
+ currentWindow.dialog("close");
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function addNumber(panel) {
+ request('addnumber', 'text', { group: panel.data("group-name") }).then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else {
+ showViewNumber(data, [{
+ id: "btnSave",
+ text: "Добавить",
+ icon: "ui-icon-check",
+ click: function() {
+ actionNumber(panel, $(this), 'writenumber');
+ }
+ }], "Добавление нового номера");
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function delNumber(panel, currentWindow) {
+ let number = $('#number-number').val();
+
+ let error = false;
+ if (!number.length) { noticer.warning('Номер не может быть пуст'); error = true; }
+ if (error) return;
+
+ request('delnumber', 'json', {
+ number: number
+ }).then(data => {
+ if (data.error)
+ noticer.error(data.message);
+ else {
+ noticer.success(`Номер ${number} был удален`);
+ generateListGroupNumbers(panel);
+ currentWindow.dialog("close");
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+/************************************************************************************
+
+ Обработка таблицы с SMS
+
+************************************************************************************/
+
+function loadSMS() {
+ request('listsmsgroups', 'text').then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else
+ generateListSMSGroups(data);
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function generateListSMSGroups(data) {
+ if (!$(data).children().length) {
+ $("#tabs-sms").html(pEmpty('SMS отсутствуют'));
+ return;
+ }
+
+ $("#tabs-sms").html(data);
+ $("#accordion-sms").accordion({
+ heightStyle: "content",
+ create: function( event, ui ) {
+ generateListGroupSMS(ui.panel);
+ },
+ beforeActivate: function( event, ui ) {
+ generateListGroupSMS(ui.newPanel);
+ }
+ })
+}
+
+function generateListGroupSMS(panel) {
+ if (!$("#accordion-sms").children().length) {
+ noticer.warning("SMS отсутствуют");
+ $("#tabs-sms").html(pEmpty('SMS отсутствуют'));
+ return;
+ }
+
+ request('listgroupsms', 'json', { to: panel.data("to") }).then(data => {
+ if (data.error)
+ noticer.error(data.message);
+ else {
+ sms = data;
+ showListSMS(panel);
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function showListSMS(panel, data = sms.filter(e => e.from.includes($("#search").val()))) {
+ (new divNotFoundNumbers).remove();
+ let body = panel.find('.body').html('');
+ $(data).each((i, j) => {
+ let row = $(`
`);
+ row.append(`${j.date} | `);
+ row.append(`${j.from} | `);
+ row.append(`${j.text} | `);
+ body.append(row);
+
+ row.click(function() {
+ viewSMS(panel, $(this).data('sms-id'), j.from);
+ });
+ });
+
+ if (!body.children().length)
+ (new divNotFoundNumbers('Нет SMS')).push();
+}
+
+function viewSMS(panel, id, number) {
+ request('viewsms', 'text', {id: id}).then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else {
+ showViewSMS(data, [
+ {
+ id: "btn-delete",
+ text: "Удалить",
+ icon: "ui-icon-trash",
+ click: function() {
+ delSMS(panel, $(this));
+ }
+ }
+ ], `SMS от ${number}`);
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function showViewSMS(data, actionButton, title) {
+ let form = $(data);
+
+ form.appendTo('body').dialog({
+ title: title,
+ height: 'auto',
+ width: 'auto',
+ resizable: false,
+ modal: true,
+ show: { effect: "fade", duration: 500 },
+ close: function(event, ui) {
+ $(this).dialog('destroy').remove()
+ },
+ buttons: [
+ ...actionButton,
+ {
+ text: "Отмена",
+ icon: "ui-icon-cancel",
+ click: function() {
+ $(this).dialog("close");
+ }
+ }
+ ]
+ });
+}
+
+function delSMS(panel, currentWindow) {
+ let id = $('#sms-content').data('id');
+ let from = $('#sms-content').data('from');
+
+ request('delsms', 'json', {
+ id: id
+ }).then(data => {
+ if (data.error)
+ noticer.error(data.message);
+ else {
+ noticer.success(`SMS от ${from} было удалено`);
+ generateListGroupSMS(panel);
+ currentWindow.dialog("close");
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+/************************************************************************************
+
+ Обработка таблицы с USSD
+
+************************************************************************************/
+
+function loadUSSD() {
+ request('listussdgroups', 'text').then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else
+ generateListUSSDGroups(data);
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function generateListUSSDGroups(data) {
+ if (!$(data).children().length) {
+ $("#tabs-ussd").html(pEmpty('USSD отсутствуют'));
+ return;
+ }
+
+ $("#tabs-ussd").html(data);
+ $("#accordion-ussd").accordion({
+ heightStyle: "content",
+ create: function( event, ui ) {
+ generateListGroupUSSD(ui.panel);
+ },
+ beforeActivate: function( event, ui ) {
+ generateListGroupUSSD(ui.newPanel);
+ }
+ })
+}
+
+function generateListGroupUSSD(panel) {
+ if (!$("#accordion-ussd").children().length) {
+ noticer.warning("USSD отсутствуют");
+ $("#tabs-ussd").html(pEmpty('USSD отсутствуют'));
+ return;
+ }
+
+ request('listgroupussd', 'json', { to: panel.data("to") }).then(data => {
+ if (data.error)
+ noticer.error(data.message);
+ else {
+ ussd = data;
+ showListUSSD(panel);
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function showListUSSD(panel, data = ussd) {
+ (new divNotFoundNumbers).remove();
+ let body = panel.find('.body').html('');
+ $(data).each((i, j) => {
+ let row = $(`
`);
+ row.append(`${j.date} | `);
+ row.append(`${j.type_comment} | `);
+ row.append(`${j.text} | `);
+ body.append(row);
+
+ row.click(function() {
+ viewUSSD(panel, $(this).data('ussd-id'), j.type_comment);
+ });
+ });
+
+ if (!body.children().length)
+ (new divNotFoundNumbers('Нет USSD')).push();
+}
+
+function viewUSSD(panel, id, type) {
+ request('viewussd', 'text', {id: id}).then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else {
+ showViewUSSD(data, [
+ {
+ id: "btn-delete",
+ text: "Удалить",
+ icon: "ui-icon-trash",
+ click: function() {
+ delUSSD(panel, $(this));
+ }
+ }
+ ], `USSD: ${type}`);
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function showViewUSSD(data, actionButton, title) {
+ let form = $(data);
+
+ form.appendTo('body').dialog({
+ title: title,
+ height: 'auto',
+ width: 'auto',
+ resizable: false,
+ modal: true,
+ show: { effect: "fade", duration: 500 },
+ close: function(event, ui) {
+ $(this).dialog('destroy').remove()
+ },
+ buttons: [
+ ...actionButton,
+ {
+ text: "Отмена",
+ icon: "ui-icon-cancel",
+ click: function() {
+ $(this).dialog("close");
+ }
+ }
+ ]
+ });
+}
+
+function delUSSD(panel, currentWindow) {
+ let id = $('#ussd-content').data('id');
+
+ request('delussd', 'json', {
+ id: id
+ }).then(data => {
+ if (data.error)
+ noticer.error(data.message);
+ else {
+ noticer.success(`USSD было удалено`);
+ generateListGroupUSSD(panel);
+ currentWindow.dialog("close");
+ }
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+/************************************************************************************
+
+ Обработка таблицы информации о сервере
+
+************************************************************************************/
+
+function loadServerInfo() {
+ request('serverinfo', 'text').then(data => {
+ if (isJSON(data) && JSON.parse(data).error)
+ noticer.error(JSON.parse(data).message);
+ else
+ showServerInfo(data);
+ }).catch(error => {
+ noticer.error(error.message);
+ });
+}
+
+function showServerInfo(data) {
+ $("#tabs-server").html(data);
+ $("#server-external-number-on").checkboxradio();
+ $("#server-transparent-mode").checkboxradio();
+ $("#server-button").button({ icon: "ui-icon-disk", disabled: true });
+
+ $(".server-input").on("change paste cut keydown", () => {
+ if ($("#server-button").button( "option", "disabled")) {
+ noticer.warning('Некоторые параметры сервера были изменены');
+ $("#server-button").button( "option", "disabled", false);
+ }
+ });
+
+ $("#server-button").click(() => {
+ writeServerInfo();
+ });
+}
+
+function writeServerInfo() {
+ let pattern_number = /^\+7\d{10}$/g;
+
+ let internal_number = $("#server-internal-number").val();
+ let external_number = $("#server-external-number").val();
+ let external_number_on = $("#server-external-number-on").is(":checked");
+ let transparent_mode = $("#server-transparent-mode").is(":checked");
+
+ let error = false;
+
+ if (external_number.match(pattern_number) === null) { noticer.warning("Внешний номер не соответствует формату +7XXXXXXXXXX"); error = true; }
+
+ if (error) return;
+
+ request('writeserverinfo', 'json', {
+ internal_number: internal_number,
+ external_number: external_number,
+ external_number_on: external_number_on,
+ transparent_mode: transparent_mode
+ }).then(data => {
+ data.error ?
+ noticer.error(data.message) :
+ $("#server-button").button( "option", "disabled", true) && noticer.success("Параметры сервера были сохранены")
+ }).catch(error => {
+ noticer.error(error.message);
});
}
diff --git a/public/404.css b/public/404.css
new file mode 100644
index 0000000..74f4081
--- /dev/null
+++ b/public/404.css
@@ -0,0 +1,18 @@
+body {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+}
+
+div.page {
+ display: flex;
+ align-items: center;
+}
+
+.pic {
+ background-image: url("404.png");
+ min-width: 512px;
+ min-height: 512px;
+ background-size: contain;
+}
diff --git a/public/authorization.css b/public/authorization.css
new file mode 100644
index 0000000..8137a7a
--- /dev/null
+++ b/public/authorization.css
@@ -0,0 +1,62 @@
+@font-face {
+ font-family: Scada;
+ src: url(Scada-Regular.ttf);
+}
+
+body {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ margin-top: -5%;
+ color: #333;
+ opacity: 0;
+ font-family: Scada;
+}
+
+div.form {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.div-button {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+input {
+ text-align: center;
+ color: #333;
+ border: 1px solid#c5c5c5;
+ height: 30px;
+}
+
+input:hover {
+ border: 1px solid #999;
+ box-shadow: 1px 1px 10px 1px #ccc;
+}
+
+.input-focus:focus {
+ outline: none;
+ box-shadow: 1px 1px 10px 1px #666;
+ border: 1px solid #555;
+}
+
+.logo {
+ background-image: url("favicon.png");
+ min-width: 128px;
+ min-height: 128px;
+ background-size: contain;
+ margin-bottom: 20px;
+}
+
+.title {
+ margin: 30px;
+}
+
+.label {
+ text-align: right;
+ padding-right: 10px;
+}
diff --git a/public/style.css b/public/style.css
index 3f86680..8cd066b 100644
--- a/public/style.css
+++ b/public/style.css
@@ -18,16 +18,16 @@ body {
div.div-header {
display: flex;
align-items: center;
- width: 60%;
+ width: 70%;
justify-content: center;
margin-top: 30px
}
/* HEADER */
-/* div.div-add {
+div.main-button {
margin-right: 20px
-} */
+}
div.div-search {
display: flex;
@@ -49,36 +49,41 @@ input {
border: 1px solid#c5c5c5;
}
-.input-focus:hover {
- border: 1px solid #ccc
+.input-focus:hover:not([disabled]) {
+ border: 1px solid #999;
+ box-shadow: 1px 1px 10px 1px #ccc;
}
.input-focus::placeholder {
color: #333
}
-.input-focus:focus {
+.input-focus:focus:not([disabled]) {
outline: none;
- box-shadow: 1px 1px 10px 1px #007fff;
- border: 1px solid #003eff;
+ box-shadow: 1px 1px 10px 1px #666;
+ border: 1px solid #555;
}
/* BODY */
.content {
- width: 60%;
+ width: 70%;
margin-top: 20px
}
-.content table {
+.table-content {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed;
}
+div.group-content {
+ height: 55vh;
+}
+
div.body-rows {
- max-height: 55vh;
+ height: calc(100% - 55px);
overflow-x: auto;
border: 1px solid #c5c5c5;
border-top: 0;
@@ -96,12 +101,112 @@ td {
text-align: center;
}
+.body-rows td {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.body-rows tbody tr:nth-child(even){
background: #fff;
}
-tr.row:hover, tr.row:nth-child(even):hover {
- background-color: #c5c5c5;
- color: #fff;
- cursor: pointer;
+tr.row {
+ border-top: 1px solid #fff;
+}
+
+tr.row:hover, tr.row:nth-child(even):hover {
+ background-color: #f6f6f6;
+ color: #003eff;
+ cursor: pointer;
+ border-bottom: 1px solid #c5c5c5;
+ border-top: 1px solid #c5c5c5;
+}
+
+/* EDIT NUMBER */
+
+.number-label, .sms-label, .ussd-label, .server-label {
+ color: #333;
+ text-align: right;
+}
+
+.number-value, .server-value {
+ height: 30px;
+}
+
+.server-value {
+ text-align: left;
+ padding: 2px 0 2px 10px;
+}
+
+.server-input {
+ height: 100%;
+}
+
+.server-button {
+ margin-top: 30px;
+}
+
+.sms-label-text, .ussd-label-text {
+ vertical-align:top
+}
+
+.sms-value, .ussd-value {
+ width: 300px;
+ text-align: left;
+}
+
+.number-input-main {
+ height: 25px;
+ width: 194px;
+ margin: 0 10px 0 10px;
+}
+
+.number-input-cc {
+ height: 25px;
+ width: 50px;
+ margin: 0 10px 0 10px;
+}
+
+.div-advanced {
+ display: flex;
+ flex-direction: column;
+}
+
+.comment {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.comment-name {
+ padding: 15px 0 5px 0;
+}
+
+.comment-content, .sms-content, .ussd-content {
+ width: 100%;
+ height: 100%;
+}
+
+#number-comment, #sms-content, #ussd-content {
+ width: calc(100% - 6px);
+ resize: none;
+}
+
+#sms-content, #ussd-content {
+ height: 150px;
+ outline: none;
+ border: 1px solid#c5c5c5;
+ color: #333;
+ width: 400px;
+ padding: 5px 10px 5px 10px;
+}
+
+th.sms-content-width, td.sms-content-width, th.ussd-content-width, td.ussd-content-width {
+ width: 155px;
+}
+
+th.ussd-content-width-type, td.ussd-content-width-type {
+ width: 240px;
}
diff --git a/settings.conf.sample b/settings.conf.sample
index 8a98fe3..538d199 100644
--- a/settings.conf.sample
+++ b/settings.conf.sample
@@ -1,14 +1,18 @@
[web-host]
title => "Управление диалпланом"
addresses => 127.0.0.1
-http => 8080
+http => 80
https => 443
cert => certs/test.local.crt
key => certs/test.local.key
-data => ./ ; Путь к каталогу, в котором расположены каталоги public, js, images
+data => ./ ; Путь к каталогу, в котором расположены каталоги public, jq, js, images
loglevel => 0 ; 0 - debug, 1 - crit, 2 - err, 3 - warn, 4 - notice, 5 - info, 6 - alert
logoutput => 1, 4 ; 1 - syslog, 2 - stout, 4 - file => example: 1,2 or 1,2,4
-logfile => /var/log/jaster.log ; if log-output set with 4
+logfile => /var/log/daster.log ; if log-output set with 4
+
+[auth]
+login =>
+password =>
[daster-db]
host => 127.0.0.1
diff --git a/source/daster.d b/source/daster.d
index 7c2c52a..0f4876c 100644
--- a/source/daster.d
+++ b/source/daster.d
@@ -15,19 +15,29 @@ import std.array;
import verinfo;
import pgdb;
import structures;
+import response;
-import requests.listsgroups;
-import requests.groupnumbers;
+import requests.numbers;
+import requests.sms;
+import requests.ussd;
+import requests.server;
+import requests.authorization;
static ServerInfo serverInfo;
+static AuthData serverAuthData;
private void showVersion() {
writefln("daster версия %s, собрано %s", getDasterVersion(), __DATE__);
}
+void page404(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
+ res.render!("404.dt", req, error);
+}
+
int main(string[] args) {
- log.level(log.INFORMATION);
- log.output(log.SYSLOG);
+ log.level(log.INFORMATION)
+ .output(log.SYSLOG)
+ .color(true);
bool flagVersion;
string flagSettings;
@@ -73,15 +83,17 @@ int main(string[] args) {
}
rc.read(flagSettings);
- rcAsteriskDB();
- auto webHost = rcWebHost();
+ auto webHost = rcWebHost();
serverInfo = ServerInfo(webHost.title);
if (webHost.loglevel != -1) log.level(webHost.loglevel);
if (webHost.logoutput) log.output(webHost.logoutput);
if (webHost.logfile.length) log.file(webHost.logfile);
+ rcAsteriskDB();
+ rcAuth();
+
auto router = new URLRouter;
router.post("/", &postReq);
router.get("/", &getReq);
@@ -96,12 +108,14 @@ int main(string[] args) {
auto settingsHTTPS = new HTTPServerSettings;
if (webHost.http) {
+ settingsHTTP.errorPageHandler = toDelegate(&page404);
settingsHTTP.sessionStore = memorySessionStore;
settingsHTTP.port = webHost.http;
settingsHTTP.bindAddresses = ["::1"] ~ webHost.addresses;
}
if (webHost.https) {
+ settingsHTTPS.errorPageHandler = toDelegate(&page404);
settingsHTTPS.sessionStore = memorySessionStore;
settingsHTTPS.port = webHost.https;
settingsHTTPS.bindAddresses = ["::1"] ~ webHost.addresses;
@@ -135,16 +149,15 @@ void startWebServer(WebHost wh, HTTPServerSettings http, HTTPServerSettings http
}
void getReq(HTTPServerRequest req, HTTPServerResponse res) {
- // if (req.session) {
- // auto user = req.session.get!UserData("userData");
- // if (user.loggedIn) {
- // renderMainPage(req, res);
- // return;
- // }
- // }
+ if (req.session) {
+ auto user = req.session.get!UserData("user");
+ if (user.login) {
+ renderMainPage(req, res);
+ return;
+ }
+ }
- // render!("index.dt", serverInfo)(res);
- renderMainPage(req, res);
+ render!("authorization.dt", serverInfo)(res);
}
void renderMainPage(HTTPServerRequest req, HTTPServerResponse res) {
@@ -159,45 +172,74 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) {
if (query.empty) return;
- // if (query != "authorization" && !checkAuth(req)) {
- // res.send(
- // true,
- // "Сессия не существует. Перезагрузите страницу"
- // );
- // return;
- // }
+ if (query != "login" && !checkAuth(req)) {
+ res.send(
+ true,
+ "Сессия не существует. Перезагрузите страницу"
+ );
+ return;
+ }
+
+ log.d("%s: json request %s".format(req.clientAddress.toAddressString(), jsr.to!string));
switch (query) {
- case "listsgroups":
- listsgroups(req, res);
+ case "login":
+ login(req, res, serverAuthData);
break;
- case "groupnumbers":
- groupnumbers(req, res);
+ case "logout":
+ logout(req, res);
+ break;
+ case "listnumbergroups":
+ getListNumberGroups(req, res);
+ break;
+ case "listgroupnumbers":
+ getListGroupNumbers(req, res);
+ break;
+ case "viewnumber":
+ getViewNumber(req, res);
+ break;
+ case "addnumber":
+ getAddNumber(req, res);
+ break;
+ case "writenumber":
+ sendWriteNumber(req, res);
+ break;
+ case "updatenumber":
+ sendUpdateNumber(req, res);
+ break;
+ case "delnumber":
+ sendDelNumber(req, res);
+ break;
+ case "listsmsgroups":
+ getListSMSGroups(req, res);
+ break;
+ case "listgroupsms":
+ getListGroupSMS(req, res);
+ break;
+ case "viewsms":
+ getViewSMS(req, res);
+ break;
+ case "delsms":
+ sendDelSMS(req, res);
+ break;
+ case "listussdgroups":
+ getListUSSDGroups(req, res);
+ break;
+ case "listgroupussd":
+ getListGroupUSSD(req, res);
+ break;
+ case "viewussd":
+ getViewUSSD(req, res);
+ break;
+ case "delussd":
+ sendDelUSSD(req, res);
+ break;
+ case "serverinfo":
+ getServerInfo(req, res);
+ break;
+ case "writeserverinfo":
+ sendWriteServerInfo(req, res);
break;
- // case "authorization":
- // authorization(req, res);
- // break;
- // case "logout":
- // logout(req, res);
- // break;
- // case "numbers":
- // numbers(req, res);
- // break;
- // case "add":
- // addNumber(req, res);
- // break;
- // case "write":
- // writeNumber(req, res);
- // break;
- // case "edit":
- // editNumber(req, res);
- // break;
- // case "update":
- // updateNumber(req, res);
- // break;
- // case "remove":
- // removeNumber(req, res);
- // break;
default:
res.redirect("/");
}
@@ -242,7 +284,7 @@ void rcAsteriskDB() {
" password=" ~ asteriskDB["password"]
);
} catch (Exception e) {
- log.c(e);
+ log.c(e.msg);
exit(1);
}
}
@@ -403,3 +445,24 @@ WebHost rcWebHost() {
return wh;
}
+
+void rcAuth() {
+ ConfigSection auth;
+
+ try {
+ auth = rc[]["auth"];
+ } catch (Exception e) {
+ log.c("В конфигурационном файле не верны настройки auth");
+ exit(1);
+ }
+
+ if (auth["login"].empty)
+ log.w("Логин не был установлен - auth.login");
+ else
+ serverAuthData.login = auth["login"];
+
+ if (auth["password"].empty)
+ log.w("Пароль не был установлен - auth.password");
+ else
+ serverAuthData.password = auth["password"];
+}
diff --git a/source/data.d b/source/data.d
deleted file mode 100644
index b333388..0000000
--- a/source/data.d
+++ /dev/null
@@ -1,67 +0,0 @@
-module data;
-
-import pgdb;
-import singlog;
-import structures;
-
-import std.conv;
-
-GroupDB[] getListGroups() {
- GroupDB[] groups;
- try {
- auto queryResult = pgsql.sql(
- "select distinct
- n.da_group,
- g.da_comment
- from da_numbers n
- left join da_groups g ON g.da_name = n.da_group"
- );
- foreach (row; queryResult) {
- GroupDB data;
-
- data.name = row["da_group"];
- data.comment = row["da_comment"];
-
- groups ~= data;
- }
- } catch (Exception e) {
- log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
- }
-
- return groups;
-}
-
-NumberDB[] getListNumbers(string group) {
- NumberDB[] numbers;
- try {
- auto queryResult = pgsql.sql(
- "select
- dan.da_number,
- dal.da_comment da_list,
- dan.da_all_cc,
- dan.da_white_cc,
- dan.da_black_cc,
- dan.da_comment
- from da_numbers dan
- left join da_lists dal on dal.da_name = dan.da_list
- where da_group = ?",
- group
- );
- foreach (row; queryResult) {
- NumberDB data;
-
- data.number = row["da_number"];
- data.list = row["da_list"];
- data.all_cc = row["da_all_cc"].to!int;
- data.white_cc = row["da_white_cc"].to!int;
- data.black_cc = row["da_black_cc"].to!int;
- data.comment = row["da_comment"];
-
- numbers ~= data;
- }
- } catch (Exception e) {
- log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
- }
-
- return numbers;
-}
diff --git a/source/requests/authorization.d b/source/requests/authorization.d
new file mode 100644
index 0000000..5456424
--- /dev/null
+++ b/source/requests/authorization.d
@@ -0,0 +1,47 @@
+module requests.authorization;
+
+import vibe.vibe;
+import response;
+import structures;
+import singlog;
+
+void login(HTTPServerRequest req, HTTPServerResponse res, AuthData serverAuthData) {
+ auto userAuthData = deserializeJson!AuthData(req.json);
+
+ if (!(serverAuthData.login == userAuthData.login &&
+ serverAuthData.password == userAuthData.password)) {
+ log.i(req.clientAddress.toAddressString() ~ ": Данные авторизации не верны");
+ res.send(
+ true,
+ "Данные авторизации не верны"
+ );
+ return;
+ }
+
+ auto user = UserData(true);
+
+ req.session = res.startSession();
+ req.session.set!UserData("user", user);
+
+ log.i(req.clientAddress.toAddressString() ~ ": Вход в систему");
+
+ res.send();
+}
+
+void logout(HTTPServerRequest req, HTTPServerResponse res) {
+ req.session.set!UserData("user", UserData.init);
+ res.terminateSession();
+
+ log.i(req.clientAddress.toAddressString() ~ ": Выход из системы");
+
+ res.send();
+}
+
+bool checkAuth(HTTPServerRequest req) {
+ if (req.session)
+ return req.session.get!UserData("user").login;
+
+ log.d(req.clientAddress.toAddressString() ~ ": Отсутствует авторизация");
+
+ return false;
+}
diff --git a/source/requests/groupnumbers.d b/source/requests/groupnumbers.d
deleted file mode 100644
index 4634a38..0000000
--- a/source/requests/groupnumbers.d
+++ /dev/null
@@ -1,12 +0,0 @@
-module requests.groupnumbers;
-
-import vibe.vibe;
-import response;
-import data;
-import singlog;
-
-void groupnumbers(HTTPServerRequest req, HTTPServerResponse res) {
- auto jsr = req.json;
- auto listNumbers = getListNumbers(jsr["group"].get!string);
- render!("group-numbers-list.dt", listNumbers)(res);
-}
diff --git a/source/requests/listsgroups.d b/source/requests/listsgroups.d
deleted file mode 100644
index be1504c..0000000
--- a/source/requests/listsgroups.d
+++ /dev/null
@@ -1,11 +0,0 @@
-module requests.listsgroups;
-
-import vibe.vibe;
-import response;
-import data;
-
-void listsgroups(HTTPServerRequest req, HTTPServerResponse res) {
- // auto jsr = req.json;
-
- res.writeJsonBody(getListGroups().serializeToJson());
-}
diff --git a/source/requests/numbers.d b/source/requests/numbers.d
new file mode 100644
index 0000000..24fa4d7
--- /dev/null
+++ b/source/requests/numbers.d
@@ -0,0 +1,113 @@
+module requests.numbers;
+
+import vibe.vibe;
+import response;
+import structures;
+import sql;
+import singlog;
+
+import std.regex;
+
+// Получить список всех групп номеров
+void getListNumberGroups(HTTPServerRequest req, HTTPServerResponse res) {
+ auto listGroups = sqlGetListGroups();
+ render!("list_number_groups.dt", listGroups)(res);
+}
+
+// Получить список номеров конкретной группы
+void getListGroupNumbers(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ res.writeJsonBody(sqlGetListNumbers(jsr["group"].get!string).serializeToJson());
+}
+
+// Добавление номера телефона
+void getAddNumber(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ bool edit = false;
+ NumberDB dataNumber;
+ dataNumber.group = jsr["group"].get!string;
+ auto groups = sqlGetGroups();
+ auto lists = sqlGetLists();
+ render!("number.dt", edit, dataNumber, groups, lists)(res);
+}
+
+// Просмотр номера телефона
+void getViewNumber(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ bool edit = true;
+ auto dataNumber = sqlGetDataNumber(jsr["number"].get!string);
+ auto groups = sqlGetGroups();
+ auto lists = sqlGetLists();
+ render!("number.dt", edit, dataNumber, groups, lists)(res);
+}
+
+// Обновить номер телефона
+void sendUpdateNumber(HTTPServerRequest req, HTTPServerResponse res) {
+ NumberDB number = deserializeJson!NumberDB(req.json);
+
+ if (!checkNumber(number, res))
+ return;
+
+ if (!sqlUpdateNumber(number)) {
+ res.send(true, "Не удалось обновить номер");
+ return;
+ }
+ res.send();
+}
+
+// Записать номер телефона
+void sendWriteNumber(HTTPServerRequest req, HTTPServerResponse res) {
+ NumberDB number = deserializeJson!NumberDB(req.json);
+
+ if (!checkNumber(number, res))
+ return;
+
+ if (!sqlInsertNumber(number)) {
+ res.send(true, "Не удалось записать номер");
+ return;
+ }
+ res.send();
+}
+
+// Удалить номера телефона
+void sendDelNumber(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ string number = jsr["number"].get!string;
+
+ if (!number.length) {
+ res.send(true, "Номер не может быть пуст");
+ return;
+ }
+
+ if (!sqlDeleteNumber(number)) {
+ res.send(true, "Не удалось удалить номер");
+ return;
+ }
+
+ res.send();
+}
+
+// Проверка номера перед изменением
+bool checkNumber(NumberDB number, HTTPServerResponse res) {
+ if (!number.number.matchFirst(regex(r"^\+7\d{10}$", "g"))) {
+ res.send(true, "Номер не соответствует формату +7XXXXXXXXXX");
+ return false;
+ }
+
+ if (number.all_cc < 0) {
+ res.send(true, "Общее количество звонков не может быть отрицательным");
+ return false;
+ }
+
+ if (number.white_cc < 0) {
+ res.send(true, "Белое количество звонков не может быть отрицательным");
+ return false;
+ }
+
+ if (number.black_cc < 0) {
+ res.send(true, "Черное количество звонков не может быть отрицательным");
+ return false;
+ }
+
+ return true;
+}
diff --git a/source/requests/server.d b/source/requests/server.d
new file mode 100644
index 0000000..7b66583
--- /dev/null
+++ b/source/requests/server.d
@@ -0,0 +1,30 @@
+module requests.server;
+
+import vibe.vibe;
+import response;
+import structures;
+import sql;
+import singlog;
+
+import std.regex;
+
+// Получить информацию о сервере
+void getServerInfo(HTTPServerRequest req, HTTPServerResponse res) {
+ auto dataServer = sqlGetServerInfo();
+ render!("server.dt", dataServer)(res);
+}
+
+void sendWriteServerInfo(HTTPServerRequest req, HTTPServerResponse res) {
+ ServerDB server = deserializeJson!ServerDB(req.json);
+
+ if (!server.external_number.matchFirst(regex(r"^\+7\d{10}$", "g"))) {
+ res.send(true, "Внешний номер не соответствует формату +7XXXXXXXXXX");
+ return;
+ }
+
+ if (!sqlUpdateServerInfo(server)) {
+ res.send(true, "Не удалось записать параметры сервера");
+ return;
+ }
+ res.send();
+}
diff --git a/source/requests/sms.d b/source/requests/sms.d
new file mode 100644
index 0000000..254793b
--- /dev/null
+++ b/source/requests/sms.d
@@ -0,0 +1,39 @@
+module requests.sms;
+
+import vibe.vibe;
+import response;
+import structures;
+import sql;
+import singlog;
+
+// Получить список всех групп SMS
+void getListSMSGroups(HTTPServerRequest req, HTTPServerResponse res) {
+ auto numbers = sqlGetSMSNumbers();
+ render!("list_sms_groups.dt", numbers)(res);
+}
+
+// Получить список SMS конкретной группы
+void getListGroupSMS(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ res.writeJsonBody(sqlGetListSMS(jsr["to"].get!string).serializeToJson());
+}
+
+// Просмотр SMS
+void getViewSMS(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ auto dataSMS = sqlGetSMS(jsr["id"].to!int);
+ render!("sms.dt", dataSMS)(res);
+}
+
+// Удалить SMS
+void sendDelSMS(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ int idsms = jsr["id"].get!int;
+
+ if (!sqlDeleteSMS(idsms)) {
+ res.send(true, "Не удалось удалить SMS");
+ return;
+ }
+
+ res.send();
+}
diff --git a/source/requests/ussd.d b/source/requests/ussd.d
new file mode 100644
index 0000000..c39a46e
--- /dev/null
+++ b/source/requests/ussd.d
@@ -0,0 +1,39 @@
+module requests.ussd;
+
+import vibe.vibe;
+import response;
+import structures;
+import sql;
+import singlog;
+
+// Получить список всех групп USSD
+void getListUSSDGroups(HTTPServerRequest req, HTTPServerResponse res) {
+ auto numbers = sqlGetUSSDNumbers();
+ render!("list_ussd_groups.dt", numbers)(res);
+}
+
+// Получить список USSD конкретной группы
+void getListGroupUSSD(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ res.writeJsonBody(sqlGetListUSSD(jsr["to"].get!string).serializeToJson());
+}
+
+// Просмотр USSD
+void getViewUSSD(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ auto dataUSSD = sqlGetUSSD(jsr["id"].to!int);
+ render!("ussd.dt", dataUSSD)(res);
+}
+
+// Удалить USSD
+void sendDelUSSD(HTTPServerRequest req, HTTPServerResponse res) {
+ auto jsr = req.json;
+ int idussd = jsr["id"].get!int;
+
+ if (!sqlDeleteUSSD(idussd)) {
+ res.send(true, "Не удалось удалить USSD");
+ return;
+ }
+
+ res.send();
+}
diff --git a/source/sql.d b/source/sql.d
new file mode 100644
index 0000000..a488407
--- /dev/null
+++ b/source/sql.d
@@ -0,0 +1,454 @@
+module sql;
+
+import pgdb;
+import singlog;
+import structures;
+
+import std.conv;
+
+/***********************************************************
+ Запросы для таблицы номеров телефонов
+***********************************************************/
+
+GroupDB[] sqlGetListGroups() {
+ GroupDB[] groups;
+ try {
+ auto queryResult = pgsql.sql(
+ "select distinct
+ dan.da_group,
+ dag.da_comment
+ from da_numbers dan
+ left join da_groups dag ON dag.da_name = dan.da_group"
+ );
+ foreach (row; queryResult) {
+ GroupDB data;
+
+ data.name = row["da_group"];
+ data.comment = row["da_comment"];
+
+ groups ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return groups;
+}
+
+NumberDB[] sqlGetListNumbers(string group) {
+ NumberDB[] numbers;
+ try {
+ auto queryResult = pgsql.sql(
+ "select
+ dan.da_number,
+ dal.da_comment da_list,
+ dan.da_all_cc,
+ dan.da_white_cc,
+ dan.da_black_cc,
+ dan.da_comment
+ from da_numbers dan
+ left join da_lists dal on dal.da_name = dan.da_list
+ where dan.da_group = ?
+ order by dan.da_number",
+ group
+ );
+ foreach (row; queryResult) {
+ NumberDB data;
+
+ data.number = row["da_number"];
+ data.list = row["da_list"];
+ data.all_cc = row["da_all_cc"].to!int;
+ data.white_cc = row["da_white_cc"].to!int;
+ data.black_cc = row["da_black_cc"].to!int;
+ data.comment = row["da_comment"];
+
+ numbers ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return numbers;
+}
+
+NumberDB sqlGetDataNumber(string number) {
+ NumberDB data;
+ try {
+ auto queryResult = pgsql.sql(
+ "select
+ da_number,
+ da_group,
+ da_list,
+ da_all_cc,
+ da_white_cc,
+ da_black_cc,
+ da_comment
+ from da_numbers
+ where da_number = ?",
+ number
+ );
+ foreach (row; queryResult) {
+ data.number = row["da_number"];
+ data.group = row["da_group"];
+ data.list = row["da_list"];
+ data.all_cc = row["da_all_cc"].to!int;
+ data.white_cc = row["da_white_cc"].to!int;
+ data.black_cc = row["da_black_cc"].to!int;
+ data.comment = row["da_comment"];
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return data;
+}
+
+GroupDB[] sqlGetGroups() {
+ GroupDB[] groups;
+ try {
+ auto queryResult = pgsql.sql(
+ "select da_name, da_comment from da_groups"
+ );
+ foreach (row; queryResult) {
+ GroupDB data;
+
+ data.name = row["da_name"];
+ data.comment = row["da_comment"];
+
+ groups ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return groups;
+}
+
+ListDB[] sqlGetLists() {
+ ListDB[] lists;
+ try {
+ auto queryResult = pgsql.sql(
+ "select da_name, da_comment from da_lists"
+ );
+ foreach (row; queryResult) {
+ ListDB data;
+
+ data.name = row["da_name"];
+ data.comment = row["da_comment"];
+
+ lists ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return lists;
+}
+
+bool sqlUpdateNumber(NumberDB number) {
+ try {
+ pgsql.sql(
+ "update da_numbers set
+ da_group = ?,
+ da_list = ?,
+ da_all_cc = ?,
+ da_white_cc = ?,
+ da_black_cc = ?,
+ da_comment = ?
+ where da_number = ?",
+ number.group,
+ number.list,
+ number.all_cc,
+ number.white_cc,
+ number.black_cc,
+ number.comment,
+ number.number
+ );
+ } catch (Exception e) {
+ log.e("Ошибка обновления номера в БД. " ~ e.msg);
+ return false;
+ }
+ return true;
+}
+
+bool sqlDeleteNumber(string number) {
+ try {
+ pgsql.sql(
+ "delete from da_numbers where da_number = ?", number
+ );
+ } catch (Exception e) {
+ log.e("Ошибка удаления номера в БД. " ~ e.msg);
+ return false;
+ }
+ return true;
+}
+
+bool sqlInsertNumber(NumberDB number) {
+ try {
+ pgsql.sql(
+ "insert into da_numbers
+ (da_number, da_group, da_list, da_all_cc, da_white_cc, da_black_cc, da_comment)
+ values
+ (?, ?, ?, ?, ?, ?, ?)",
+ number.number,
+ number.group,
+ number.list,
+ number.all_cc,
+ number.white_cc,
+ number.black_cc,
+ number.comment
+ );
+ } catch (Exception e) {
+ log.error("Ошибка добавления номера телефона в БД. " ~ e.msg);
+ return false;
+ }
+ return true;
+}
+
+/***********************************************************
+ Запросы для таблицы SMS
+***********************************************************/
+
+SMSDB[] sqlGetSMSNumbers() {
+ SMSDB[] numbers;
+ try {
+ auto queryResult = pgsql.sql(
+ "select distinct da_to from da_sms"
+ );
+ foreach (row; queryResult) {
+ SMSDB data;
+
+ data.to = row["da_to"];
+
+ numbers ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return numbers;
+}
+
+SMSDB[] sqlGetListSMS(string to) {
+ SMSDB[] sms;
+ try {
+ auto queryResult = pgsql.sql(
+ "select
+ da_id,
+ to_char(da_date, 'YYYY.MM.DD HH24:MI:SS') da_date,
+ da_to,
+ da_from,
+ da_text
+ from da_sms
+ where da_to = ?
+ order by da_date desc",
+ to
+ );
+ foreach (row; queryResult) {
+ SMSDB data;
+
+ data.id = row["da_id"].to!int;
+ data.date = row["da_date"];
+ data.to = row["da_to"];
+ data.from = row["da_from"];
+ data.text = row["da_text"];
+
+ sms ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return sms;
+}
+
+SMSDB sqlGetSMS(int idsms) {
+ SMSDB data;
+ try {
+ auto queryResult = pgsql.sql(
+ "select
+ da_id,
+ to_char(da_date, 'YYYY.MM.DD HH24:MI:SS') da_date,
+ da_to,
+ da_from,
+ da_text
+ from da_sms
+ where da_id = ?",
+ idsms
+ );
+ foreach (row; queryResult) {
+ data.id = row["da_id"].to!int;
+ data.date = row["da_date"];
+ data.to = row["da_to"];
+ data.from = row["da_from"];
+ data.text = row["da_text"];
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return data;
+}
+
+bool sqlDeleteSMS(int idsms) {
+ try {
+ pgsql.sql(
+ "delete from da_sms where da_id = ?", idsms
+ );
+ } catch (Exception e) {
+ log.e("Ошибка удаления SMS в БД. " ~ e.msg);
+ return false;
+ }
+ return true;
+}
+
+/***********************************************************
+ Запросы для таблицы USSD
+***********************************************************/
+
+USSDDB[] sqlGetUSSDNumbers() {
+ USSDDB[] numbers;
+ try {
+ auto queryResult = pgsql.sql(
+ "select distinct da_to from da_ussd"
+ );
+ foreach (row; queryResult) {
+ USSDDB data;
+
+ data.to = row["da_to"];
+
+ numbers ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return numbers;
+}
+
+USSDDB[] sqlGetListUSSD(string to) {
+ USSDDB[] ussd;
+ try {
+ auto queryResult = pgsql.sql(
+ "select
+ dau.da_id,
+ to_char(dau.da_date, 'YYYY.MM.DD HH24:MI:SS') da_date,
+ dau.da_to,
+ dau.da_type,
+ daut.da_comment da_type_comment,
+ dau.da_text
+ from da_ussd dau
+ inner join da_ussd_type daut
+ on dau.da_type = daut.da_id
+ where dau.da_to = ?
+ order by dau.da_date desc",
+ to
+ );
+ foreach (row; queryResult) {
+ USSDDB data;
+
+ data.id = row["da_id"].to!int;
+ data.date = row["da_date"];
+ data.to = row["da_to"];
+ data.type = row["da_type"].to!int;
+ data.type_comment = row["da_type_comment"];
+ data.text = row["da_text"];
+
+ ussd ~= data;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return ussd;
+}
+
+USSDDB sqlGetUSSD(int idussd) {
+ USSDDB data;
+ try {
+ auto queryResult = pgsql.sql(
+ "select
+ da_id,
+ to_char(da_date, 'YYYY.MM.DD HH24:MI:SS') da_date,
+ da_to,
+ da_type,
+ da_text
+ from da_ussd
+ where da_id = ?",
+ idussd
+ );
+ foreach (row; queryResult) {
+ data.id = row["da_id"].to!int;
+ data.date = row["da_date"];
+ data.to = row["da_to"];
+ data.type = row["da_type"].to!int;
+ data.text = row["da_text"];
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return data;
+}
+
+bool sqlDeleteUSSD(int idussd) {
+ try {
+ pgsql.sql(
+ "delete from da_ussd where da_id = ?", idussd
+ );
+ } catch (Exception e) {
+ log.e("Ошибка удаления USSD в БД. " ~ e.msg);
+ return false;
+ }
+ return true;
+}
+
+/***********************************************************
+ Запросы для таблицы информации о сервере
+***********************************************************/
+
+ServerDB sqlGetServerInfo() {
+ ServerDB server;
+ try {
+ auto queryResult = pgsql.sql(
+ "select
+ case when da_transparent_mode then 1 else 0 end da_transparent_mode,
+ da_internal_number,
+ da_external_number,
+ case when da_external_number_on then 1 else 0 end da_external_number_on
+ from da_server
+ where da_id = 1"
+ );
+ foreach (row; queryResult) {
+ server.transparent_mode = row["da_transparent_mode"].to!int.to!bool;
+ server.internal_number = row["da_internal_number"];
+ server.external_number = row["da_external_number"];
+ server.external_number_on = row["da_external_number_on"].to!int.to!bool;
+ }
+ } catch (Exception e) {
+ log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
+ }
+
+ return server;
+}
+
+bool sqlUpdateServerInfo(ServerDB server) {
+ try {
+ pgsql.sql(
+ "update da_server set
+ da_transparent_mode = ?,
+ da_internal_number = ?,
+ da_external_number = ?,
+ da_external_number_on = ?
+ where da_id = 1",
+ server.transparent_mode,
+ server.internal_number,
+ server.external_number,
+ server.external_number_on
+ );
+ } catch (Exception e) {
+ log.e("Ошибка обновления параметров сервера в БД. " ~ e.msg);
+ return false;
+ }
+ return true;
+}
diff --git a/source/structures.d b/source/structures.d
index 1f04a77..b38058d 100644
--- a/source/structures.d
+++ b/source/structures.d
@@ -4,6 +4,15 @@ struct ServerInfo {
string name;
}
+struct AuthData {
+ string login;
+ string password;
+}
+
+struct UserData {
+ bool login = false;
+}
+
struct WebHost {
string[] addresses;
ushort http = 0;
@@ -22,6 +31,11 @@ struct GroupDB {
string comment;
}
+struct ListDB {
+ string name;
+ string comment;
+}
+
struct NumberDB {
string number;
string group;
@@ -31,3 +45,27 @@ struct NumberDB {
int black_cc;
string comment;
}
+
+struct SMSDB {
+ int id;
+ string date;
+ string to;
+ string from;
+ string text;
+}
+
+struct USSDDB {
+ int id;
+ string date;
+ string to;
+ int type;
+ string type_comment;
+ string text;
+}
+
+struct ServerDB {
+ bool transparent_mode;
+ string internal_number;
+ string external_number;
+ bool external_number_on;
+}
diff --git a/source/version_.d b/source/version_.d
index ea9a3a6..fa80cc9 100644
--- a/source/version_.d
+++ b/source/version_.d
@@ -1,3 +1,3 @@
module version_;
-enum dasterVersion = "v0.0.1";
+enum dasterVersion = "v0.1.0-beta.1";
diff --git a/views/404.dt b/views/404.dt
new file mode 100644
index 0000000..0d4d34b
--- /dev/null
+++ b/views/404.dt
@@ -0,0 +1,8 @@
+doctype html
+head
+ title Страница не найдена
+ link(rel='icon', type='image/png', sizes='128x128', href='favicon.png')
+ link(rel='stylesheet', type='text/css', href='404.css')
+body
+ div.page
+ div.pic
diff --git a/views/authorization.dt b/views/authorization.dt
new file mode 100644
index 0000000..6eb8540
--- /dev/null
+++ b/views/authorization.dt
@@ -0,0 +1,26 @@
+doctype html
+head
+ title #{serverInfo.name}: авторизация
+ link(rel='icon', type='image/png', sizes='128x128', href='favicon.png')
+ link(rel='stylesheet', type='text/css', href='jquery-ui.min.css')
+ link(rel='stylesheet', type='text/css', href='authorization.css')
+ script(src='jquery-3.7.0.min.js')
+ script(src='jquery-ui.min.js')
+ script(src='noticer.min.js')
+ script(src='authorization.js')
+body
+ div.form
+ div.logo
+ div.title #{serverInfo.name}
+ table
+ tbody
+ tr
+ td.label Логин:
+ td
+ input.input-focus#login(type='text')
+ tr
+ td.label Пароль:
+ td
+ input.input-focus#password(type='password')
+ div.div-button
+ button#authorization Войти
diff --git a/views/group-numbers-list.dt b/views/group-numbers-list.dt
deleted file mode 100644
index 46d56eb..0000000
--- a/views/group-numbers-list.dt
+++ /dev/null
@@ -1,22 +0,0 @@
-- import structures;
-
-table
- thead.head
- tr
- th Номер
- th Список
- th Общие звонки
- th Белые звонки
- th Черные звонки
- th Комментарий
-div.body-rows
- table
- tbody.body
- - foreach (number; listNumbers)
- tr.row
- td #{number.number}
- td #{number.list}
- td #{number.all_cc}
- td #{number.white_cc}
- td #{number.black_cc}
- td #{number.comment}
diff --git a/views/index.dt b/views/index.dt
index 69887bd..60cf645 100644
--- a/views/index.dt
+++ b/views/index.dt
@@ -6,19 +6,21 @@ head
link(rel='stylesheet', type='text/css', href='style.css')
script(src='jquery-3.7.0.min.js')
script(src='jquery-ui.min.js')
- script(src='message.js')
+ script(src='noticer.min.js')
script(src='script.js')
body
div.div-header
- // div.div-add
- // button.addNumber Добавить номер
- div.div-search
- input.input-focus.search(name='search', type='text', value='', placeholder='Найти номер')
- // div.div-user Вы вошли как #{user.name}
- div.div-user Вы вошли как Александр
- div.div-button
- button Выход
- // button(onclick='logout()') Выход
+ div.div-add.main-button
+ button#add-number Добавить номер
+ div.div-search.main-button
+ input.input-focus#search(name='search', type='text', value='', placeholder='Найти номер')
+ div.div-update-tab.main-button
+ button#update-tab Перезагрузить
+ div.div-update-tab.main-button
+ button#update-group Обновить
+ div.div-user
+ div.div-logout
+ button#logout Выход
div.content
div#tabs
ul
@@ -32,8 +34,5 @@ body
a(href='#tabs-server') Сервер
div#tabs-numbers
div#tabs-sms
- p Список SMS сообщений
div#tabs-ussd
- p Список результатов USSD запросов
div#tabs-server
- p Информация о сервере
diff --git a/views/list_number_groups.dt b/views/list_number_groups.dt
new file mode 100644
index 0000000..9a907a8
--- /dev/null
+++ b/views/list_number_groups.dt
@@ -0,0 +1,16 @@
+div#accordion-numbers
+ - foreach (group; listGroups)
+ h3 #{group.comment}
+ div.group-content(data-group-name='#{group.name}')
+ table.table-content
+ thead.head
+ tr
+ th Номер
+ th Список
+ th Общие звонки
+ th Белые звонки
+ th Черные звонки
+ th Комментарий
+ div.body-rows
+ table.table-content
+ tbody.body
diff --git a/views/list_sms_groups.dt b/views/list_sms_groups.dt
new file mode 100644
index 0000000..c4f7270
--- /dev/null
+++ b/views/list_sms_groups.dt
@@ -0,0 +1,13 @@
+div#accordion-sms
+ - foreach (number; numbers)
+ h3 На номер #{number.to}
+ div.group-content(data-to='#{number.to}')
+ table.table-content
+ thead.head
+ tr
+ th.sms-content-width Дата
+ th.sms-content-width От кого
+ th Текст сообщения
+ div.body-rows
+ table.table-content
+ tbody.body
diff --git a/views/list_ussd_groups.dt b/views/list_ussd_groups.dt
new file mode 100644
index 0000000..4cd5b44
--- /dev/null
+++ b/views/list_ussd_groups.dt
@@ -0,0 +1,13 @@
+div#accordion-ussd
+ - foreach (number; numbers)
+ h3 На номер #{number.to}
+ div.group-content(data-to='#{number.to}')
+ table.table-content
+ thead.head
+ tr
+ th.ussd-content-width Дата
+ th.ussd-content-width-type Тип
+ th Текст сообщения
+ div.body-rows
+ table.table-content
+ tbody.body
diff --git a/views/number.dt b/views/number.dt
new file mode 100644
index 0000000..36efe5e
--- /dev/null
+++ b/views/number.dt
@@ -0,0 +1,42 @@
+div#number-data
+ table
+ tbody
+ tr
+ td.number-label Номер:
+ td.number-value
+ - if (edit)
+ input.number-input-main.input-focus#number-number(type='text', value='#{dataNumber.number}', disabled)
+ - else
+ input.number-input-main.input-focus#number-number(type='text', value='#{dataNumber.number}')
+ td.number-label Общие звонки:
+ td.number-value
+ input.number-input-cc.input-focus#number-all-cc(type='text', value='#{dataNumber.all_cc}')
+ tr
+ td.number-label Группа:
+ td.number-value
+ select#number-group
+ - foreach (group; groups)
+ - if (dataNumber.group == group.name)
+ option(selected, value='#{group.name}') #{group.comment}
+ - else
+ option(value='#{group.name}') #{group.comment}
+ td.number-label Белые звонки:
+ td.number-value
+ input.number-input-cc.input-focus#number-white-cc(type='text', value='#{dataNumber.white_cc}')
+ tr
+ td.number-label Список:
+ td.number-value
+ select#number-list
+ - foreach (list; lists)
+ - if (dataNumber.list == list.name)
+ option(selected, value='#{list.name}') #{list.comment}
+ - else
+ option(value='#{list.name}') #{list.comment}
+ td.number-label Черные звонки:
+ td.number-value
+ input.number-input-cc.input-focus#number-black-cc(type='text', value='#{dataNumber.black_cc}')
+ div.div-advanced
+ div.comment
+ div.comment-name Комментарий
+ div.comment-content
+ textarea.input-focus#number-comment #{dataNumber.comment}
diff --git a/views/server.dt b/views/server.dt
new file mode 100644
index 0000000..f17ecaf
--- /dev/null
+++ b/views/server.dt
@@ -0,0 +1,23 @@
+div#server-data
+ table
+ tbody
+ tr
+ td.server-label Внутренний номер:
+ td.server-value
+ input.input-focus.server-input#server-internal-number(type="text", value="#{dataServer.internal_number}")
+ td.server-value
+ tr
+ td.server-label Внешний номер:
+ td.server-value
+ input.input-focus.server-input#server-external-number(type="text", value="#{dataServer.external_number}")
+ td.server-value
+ label(for="server-external-number-on") Использовать
+ input.server-input#server-external-number-on(name="server-external-number-on", type="checkbox", checked="#{dataServer.external_number_on}")
+ tr
+ td.server-label Прозрачный режим:
+ td.server-value
+ label(for="server-transparent-mode") Включен
+ input.server-input#server-transparent-mode(name="server-transparent-mode", type="checkbox", checked="#{dataServer.transparent_mode}")
+ td.server-value
+div.server-button
+ button#server-button Сохранить изменения
diff --git a/views/sms.dt b/views/sms.dt
new file mode 100644
index 0000000..bc57579
--- /dev/null
+++ b/views/sms.dt
@@ -0,0 +1,11 @@
+div#sms-data
+ table
+ tbody
+ tr
+ td.sms-label Дата:
+ td.sms-value #{dataSMS.date}
+ tr
+ td.sms-label.sms-label-text Текст:
+ td.sms-value
+ div.sms-content
+ textarea#sms-content(readonly, data-id="#{dataSMS.id}", data-from="#{dataSMS.from}") #{dataSMS.text}
diff --git a/views/ussd.dt b/views/ussd.dt
new file mode 100644
index 0000000..8e80a4b
--- /dev/null
+++ b/views/ussd.dt
@@ -0,0 +1,11 @@
+div#ussd-data
+ table
+ tbody
+ tr
+ td.ussd-label Дата:
+ td.ussd-value #{dataUSSD.date}
+ tr
+ td.ussd-label.ussd-label-text Текст:
+ td.ussd-value
+ div.ussd-content
+ textarea#ussd-content(readonly, data-id="#{dataUSSD.id}") #{dataUSSD.text}