Compare commits
10 commits
815b5a6b6a
...
082a55ea9f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
082a55ea9f | ||
![]() |
e74c0a1af0 | ||
![]() |
639118f781 | ||
![]() |
7a71bb7e01 | ||
![]() |
69ab43a4ec | ||
![]() |
2ba510adac | ||
![]() |
ab4b8c6bad | ||
![]() |
de17e88d37 | ||
![]() |
c0290cd753 | ||
![]() |
cf3962d71d |
38 changed files with 1998 additions and 367 deletions
|
@ -1,3 +1,5 @@
|
|||
# daster
|
||||
|
||||
Управление диалпланом
|
||||
Dialplan Asterisk - управление диалпланом
|
||||
|
||||

|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
7
dub.json
7
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"
|
||||
|
|
|
@ -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",
|
||||
|
|
BIN
images/404.png
Normal file
BIN
images/404.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 24 KiB |
BIN
images/mainpage.png
Normal file
BIN
images/mainpage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
48
js/authorization.js
Normal file
48
js/authorization.js
Normal file
|
@ -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;
|
||||
}
|
100
js/message.js
100
js/message.js
|
@ -1,100 +0,0 @@
|
|||
class Message {
|
||||
timer;
|
||||
|
||||
constructor() {
|
||||
this.div = $('<div id="div-message"></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 = $(`<div class="message">${message}</div>`);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
4
js/noticer.min.js
vendored
Normal file
4
js/noticer.min.js
vendored
Normal file
|
@ -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=$('<div id="noticer"></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=$(`<div class="notice">${e}</div>`);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)}}
|
675
js/script.js
675
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 = $(`<div class="notFoundNumbers">${text}</div>`);
|
||||
|
||||
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 $(`<p>${text}</p>`).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 = $('<div id="accordion-numbers"></div>');
|
||||
$(data).each((i, j) => {
|
||||
group.append(`<h3>${j.comment}</h3><div class="group-content" data-group-name="${j.name}"></div>`);
|
||||
});
|
||||
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 = $(`<tr class="row" data-number="${j.number}"></tr>`);
|
||||
row.append(`<td>${j.number}</td>`);
|
||||
row.append(`<td>${j.list}</td>`);
|
||||
row.append(`<td>${j.all_cc}</td>`);
|
||||
row.append(`<td>${j.white_cc}</td>`);
|
||||
row.append(`<td>${j.black_cc}</td>`);
|
||||
row.append(`<td>${j.comment}</td>`);
|
||||
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 = $(`<tr class="row" data-sms-id="${j.id}"></tr>`);
|
||||
row.append(`<td class="sms-content-width">${j.date}</td>`);
|
||||
row.append(`<td class="sms-content-width">${j.from}</td>`);
|
||||
row.append(`<td>${j.text}</td>`);
|
||||
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 = $(`<tr class="row" data-ussd-id="${j.id}"></tr>`);
|
||||
row.append(`<td class="ussd-content-width">${j.date}</td>`);
|
||||
row.append(`<td class="ussd-content-width-type">${j.type_comment}</td>`);
|
||||
row.append(`<td>${j.text}</td>`);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
18
public/404.css
Normal file
18
public/404.css
Normal file
|
@ -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;
|
||||
}
|
62
public/authorization.css
Normal file
62
public/authorization.css
Normal file
|
@ -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;
|
||||
}
|
135
public/style.css
135
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
165
source/daster.d
165
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"];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
47
source/requests/authorization.d
Normal file
47
source/requests/authorization.d
Normal file
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
113
source/requests/numbers.d
Normal file
113
source/requests/numbers.d
Normal file
|
@ -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;
|
||||
}
|
30
source/requests/server.d
Normal file
30
source/requests/server.d
Normal file
|
@ -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();
|
||||
}
|
39
source/requests/sms.d
Normal file
39
source/requests/sms.d
Normal file
|
@ -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();
|
||||
}
|
39
source/requests/ussd.d
Normal file
39
source/requests/ussd.d
Normal file
|
@ -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();
|
||||
}
|
454
source/sql.d
Normal file
454
source/sql.d
Normal file
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module version_;
|
||||
|
||||
enum dasterVersion = "v0.0.1";
|
||||
enum dasterVersion = "v0.1.0-beta.1";
|
||||
|
|
8
views/404.dt
Normal file
8
views/404.dt
Normal file
|
@ -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
|
26
views/authorization.dt
Normal file
26
views/authorization.dt
Normal file
|
@ -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 Войти
|
|
@ -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}
|
|
@ -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 Информация о сервере
|
||||
|
|
16
views/list_number_groups.dt
Normal file
16
views/list_number_groups.dt
Normal file
|
@ -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
|
13
views/list_sms_groups.dt
Normal file
13
views/list_sms_groups.dt
Normal file
|
@ -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
|
13
views/list_ussd_groups.dt
Normal file
13
views/list_ussd_groups.dt
Normal file
|
@ -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
|
42
views/number.dt
Normal file
42
views/number.dt
Normal file
|
@ -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}
|
23
views/server.dt
Normal file
23
views/server.dt
Normal file
|
@ -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 Сохранить изменения
|
11
views/sms.dt
Normal file
11
views/sms.dt
Normal file
|
@ -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}
|
11
views/ussd.dt
Normal file
11
views/ussd.dt
Normal file
|
@ -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}
|
Loading…
Add table
Add a link
Reference in a new issue