From cf3962d71d42ab594b71441839105469f9cef6d7 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Wed, 31 May 2023 17:48:05 +0300 Subject: [PATCH 01/10] v0.0.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Раздельная динамическая загрузка групп и номеров по каждой группе - Реализован поиск номера телефона по активной (раскрытой) группе --- README.md | 4 +- images/mainpage.png | Bin 0 -> 89057 bytes js/script.js | 77 ++++++++++++++++++++++++++------- source/requests/groupnumbers.d | 3 +- source/requests/listsgroups.d | 5 +-- source/version_.d | 2 +- views/group-numbers-list.dt | 36 +++++++-------- 7 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 images/mainpage.png 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 - управление диалпланом + +![mainpage](images/mainpage.png) diff --git a/images/mainpage.png b/images/mainpage.png new file mode 100644 index 0000000000000000000000000000000000000000..d5cf9bfa6f32ed6f677dfc7cc0982c820e09eeeb GIT binary patch literal 89057 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
'); - $(data).each((i, j) => { - group.append(`

${j.comment}

`); - }); - numbers.append(group); + $("#tabs-numbers").html(data); $("#accordion-numbers").accordion({ heightStyle: "content", create: function( event, ui ) { @@ -78,13 +79,57 @@ function generateListsGroups(data) { } async function generateGroupNumbers(panel) { - request('groupnumbers', 'text', { group: panel.data("group-name") }).then(data => { + request('groupnumbers', 'json', { group: panel.data("group-name") }).then(data => { if (isJSON(data) && JSON.parse(data).error) message.error(JSON.parse(data).message); else { - panel.html(data); + numbers = data; + showNumbers(panel); } }).catch(error => { message.error(error.message); }); } + +function showNumbers(panel, data = numbers) { + (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(){ + // numberEdit($(this).data('number')); + }); + }); + + if (!body.children().length) + (new divNotFoundNumbers).push(); +} + + +function divNotFoundNumbers() { + let notFound = $('.failNumbers'); + let divTable = $('.body-rows'); + let divFound = $('
'); + + divFound.css({ + "color": "#333", + "text-align": "center", + "padding": "20px 0 20px 0" + }); + + this.push = function(text = 'Нет номеров') { + divTable.append(divFound.html(text)); + } + + this.remove = function() { + notFound.remove(); + } +} diff --git a/source/requests/groupnumbers.d b/source/requests/groupnumbers.d index 4634a38..62f6288 100644 --- a/source/requests/groupnumbers.d +++ b/source/requests/groupnumbers.d @@ -7,6 +7,5 @@ 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); + res.writeJsonBody(getListNumbers(jsr["group"].get!string).serializeToJson()); } diff --git a/source/requests/listsgroups.d b/source/requests/listsgroups.d index be1504c..4d6bec3 100644 --- a/source/requests/listsgroups.d +++ b/source/requests/listsgroups.d @@ -5,7 +5,6 @@ import response; import data; void listsgroups(HTTPServerRequest req, HTTPServerResponse res) { - // auto jsr = req.json; - - res.writeJsonBody(getListGroups().serializeToJson()); + auto listGroups = getListGroups(); + render!("group-numbers-list.dt", listGroups)(res); } diff --git a/source/version_.d b/source/version_.d index ea9a3a6..7164ef0 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.1"; +enum dasterVersion = "v0.0.2"; diff --git a/views/group-numbers-list.dt b/views/group-numbers-list.dt index 46d56eb..d5cc975 100644 --- a/views/group-numbers-list.dt +++ b/views/group-numbers-list.dt @@ -1,22 +1,18 @@ - 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} +div#accordion-numbers + - foreach (group; listGroups) + h3 #{group.comment} + div.group-content(data-group-name='#{group.name}') + table + thead.head + tr + th Номер + th Список + th Общие звонки + th Белые звонки + th Черные звонки + th Комментарий + div.body-rows + table + tbody.body From c0290cd75316d6d29a6820ab464207bc50a19bb3 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Thu, 1 Jun 2023 01:10:08 +0300 Subject: [PATCH 02/10] v0.0.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлено модальное окно редактирования номера --- js/message.js | 100 --------------------------------- js/noticer.min.js | 4 ++ js/script.js | 90 ++++++++++++++++++++++++++--- public/style.css | 62 ++++++++++++++++++-- source/daster.d | 11 ++-- source/data.d | 86 ++++++++++++++++++++++++++-- source/requests/editnumber.d | 15 +++++ source/requests/groupnumbers.d | 2 +- source/requests/listsgroups.d | 2 +- source/structures.d | 5 ++ views/edit-number.dt | 43 ++++++++++++++ views/index.dt | 2 +- 12 files changed, 294 insertions(+), 128 deletions(-) delete mode 100644 js/message.js create mode 100644 js/noticer.min.js create mode 100644 source/requests/editnumber.d create mode 100644 views/edit-number.dt 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..e9e9957 --- /dev/null +++ b/js/noticer.min.js @@ -0,0 +1,4 @@ +/*! noticer - v0.1.0 - 2023-05-31 +* https://git.zhirov.kz/alexander/noticer +* Copyright Alexander Zhirov; Licensed GPL-2.0 */ +class Noticer{timer;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 i=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 a=setTimeout(function(){o.fadeTo(1e3,.3)},2500);o.fadeIn(500).mouseenter(()=>{clearTimeout(a),this.timer.pause()}).mouseleave(()=>{this.timer.resume()}).click(()=>{this.timer.dead(),o.fadeOut(0,function(){this.remove()})}),this.timer=new i(()=>{o.fadeOut(500,function(){this.remove()})},t)}} diff --git a/js/script.js b/js/script.js index 711df54..845d167 100644 --- a/js/script.js +++ b/js/script.js @@ -1,7 +1,7 @@ var numbers = []; $(document).ready(function () { - message = new Message; + noticer = new Noticer; $("button").button(); $("#tabs").tabs(); @@ -59,9 +59,9 @@ function isJSON(str) { function loadData() { request('listsgroups', 'text').then(data => { - data.error ? message.error(data.message) : generateListsGroups(data); + data.error ? noticer.error(data.message) : generateListsGroups(data); }).catch(error => { - message.error(error.message); + noticer.error(error.message); }); } @@ -78,16 +78,16 @@ function generateListsGroups(data) { }); } -async function generateGroupNumbers(panel) { +function generateGroupNumbers(panel) { request('groupnumbers', 'json', { group: panel.data("group-name") }).then(data => { if (isJSON(data) && JSON.parse(data).error) - message.error(JSON.parse(data).message); + noticer.error(JSON.parse(data).message); else { numbers = data; showNumbers(panel); } }).catch(error => { - message.error(error.message); + noticer.error(error.message); }); } @@ -95,7 +95,7 @@ function showNumbers(panel, data = numbers) { (new divNotFoundNumbers).remove(); let body = panel.find('.body').html(''); $(data).each((i, j) => { - let row = $(``); + let row = $(``); row.append(`${j.number}`); row.append(`${j.list}`); row.append(`${j.all_cc}`); @@ -104,8 +104,8 @@ function showNumbers(panel, data = numbers) { row.append(`${j.comment}`); body.append(row); - row.click(function(){ - // numberEdit($(this).data('number')); + row.click(function() { + numberEdit($(this).data('number')); }); }); @@ -133,3 +133,75 @@ function divNotFoundNumbers() { notFound.remove(); } } + +function numberEdit(number) { + request('editnumber', 'text', {number: number}).then(data => { + if (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(data).message); + else { + showEditNumber(data, [ + { + id: "btn-save", + text: "Сохранить", + icon: "ui-icon-check", + click: function() { + actionNumber($(this), 'update'); + } + }, + { + id: "btn-delete", + text: "Удалить", + icon: "ui-icon-trash", + click: function() { + // removeNumber($(this)); + } + } + ], `Редактирование номера ${number}`); + } + }).catch(error => { + noticer.error(error.message); + }); +} + +function showEditNumber(data, actionButton, title) { + let form = $(data); + + form.appendTo('body').dialog({ + title: title, + height: 'auto', + width: 'auto', + maxHeight: 500, + minHeight: 50, + 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(currentWindow, query) { + 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(); +} + diff --git a/public/style.css b/public/style.css index 3f86680..767e236 100644 --- a/public/style.css +++ b/public/style.css @@ -49,18 +49,19 @@ 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 */ @@ -96,6 +97,12 @@ td { text-align: center; } +.body-rows td { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .body-rows tbody tr:nth-child(even){ background: #fff; } @@ -105,3 +112,48 @@ tr.row:hover, tr.row:nth-child(even):hover { color: #fff; cursor: pointer; } + +/* EDIT NUMBER */ + +.number-label { + color: #333; + text-align: right; +} + +.number-value { + height: 30px; +} + +.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-content { + width: 100%; + height: 100%; +} + +#number-comment { + width: calc(100% - 6px); + resize: none; +} diff --git a/source/daster.d b/source/daster.d index 7c2c52a..14a7121 100644 --- a/source/daster.d +++ b/source/daster.d @@ -18,6 +18,7 @@ import structures; import requests.listsgroups; import requests.groupnumbers; +import requests.editnumber; static ServerInfo serverInfo; @@ -169,10 +170,10 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { switch (query) { case "listsgroups": - listsgroups(req, res); + listsGroups(req, res); break; case "groupnumbers": - groupnumbers(req, res); + groupNumbers(req, res); break; // case "authorization": // authorization(req, res); @@ -189,9 +190,9 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { // case "write": // writeNumber(req, res); // break; - // case "edit": - // editNumber(req, res); - // break; + case "editnumber": + editNumber(req, res); + break; // case "update": // updateNumber(req, res); // break; diff --git a/source/data.d b/source/data.d index b333388..0b6eaaa 100644 --- a/source/data.d +++ b/source/data.d @@ -11,10 +11,10 @@ GroupDB[] getListGroups() { 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" + 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; @@ -43,8 +43,8 @@ NumberDB[] getListNumbers(string group) { 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 = ?", + left join da_lists dal on dal.da_name = dan.da_list + where dan.da_group = ?", group ); foreach (row; queryResult) { @@ -65,3 +65,77 @@ NumberDB[] getListNumbers(string group) { return numbers; } + +NumberDB getDataNumber(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[] getGroups() { + 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[] getLists() { + 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; +} diff --git a/source/requests/editnumber.d b/source/requests/editnumber.d new file mode 100644 index 0000000..4d8be89 --- /dev/null +++ b/source/requests/editnumber.d @@ -0,0 +1,15 @@ +module requests.editnumber; + +import vibe.vibe; +import response; +import data; +import singlog; + +void editNumber(HTTPServerRequest req, HTTPServerResponse res) { + auto jsr = req.json; + bool edit = true; + auto dataNumber = getDataNumber(jsr["number"].get!string); + auto groups = getGroups(); + auto lists = getLists(); + render!("edit-number.dt", edit, dataNumber, groups, lists)(res); +} diff --git a/source/requests/groupnumbers.d b/source/requests/groupnumbers.d index 62f6288..1d7f73a 100644 --- a/source/requests/groupnumbers.d +++ b/source/requests/groupnumbers.d @@ -5,7 +5,7 @@ import response; import data; import singlog; -void groupnumbers(HTTPServerRequest req, HTTPServerResponse res) { +void groupNumbers(HTTPServerRequest req, HTTPServerResponse res) { auto jsr = req.json; res.writeJsonBody(getListNumbers(jsr["group"].get!string).serializeToJson()); } diff --git a/source/requests/listsgroups.d b/source/requests/listsgroups.d index 4d6bec3..94e6dbb 100644 --- a/source/requests/listsgroups.d +++ b/source/requests/listsgroups.d @@ -4,7 +4,7 @@ import vibe.vibe; import response; import data; -void listsgroups(HTTPServerRequest req, HTTPServerResponse res) { +void listsGroups(HTTPServerRequest req, HTTPServerResponse res) { auto listGroups = getListGroups(); render!("group-numbers-list.dt", listGroups)(res); } diff --git a/source/structures.d b/source/structures.d index 1f04a77..a115318 100644 --- a/source/structures.d +++ b/source/structures.d @@ -22,6 +22,11 @@ struct GroupDB { string comment; } +struct ListDB { + string name; + string comment; +} + struct NumberDB { string number; string group; diff --git a/views/edit-number.dt b/views/edit-number.dt new file mode 100644 index 0000000..fd8c788 --- /dev/null +++ b/views/edit-number.dt @@ -0,0 +1,43 @@ +- import structures; +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/index.dt b/views/index.dt index 69887bd..e32e093 100644 --- a/views/index.dt +++ b/views/index.dt @@ -6,7 +6,7 @@ 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 From de17e88d373500588737045675314f0bdca996d9 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Fri, 2 Jun 2023 00:36:21 +0300 Subject: [PATCH 03/10] v0.0.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Реализовано: обновление/добавление/удаление номера + Добавлена кнопка обновления активной вкладки - Доработать проверки на обновление/добавление/удаление номера --- js/script.js | 116 ++++++++++++++++++++++++++++----- public/style.css | 18 +++-- source/daster.d | 31 ++++----- source/data.d | 73 +++++++++++++++++++-- source/requests/addnumber.d | 18 +++++ source/requests/delnumber.d | 24 +++++++ source/requests/editnumber.d | 6 +- source/requests/groupnumbers.d | 2 +- source/requests/listsgroups.d | 2 +- source/requests/updatenumber.d | 16 +++++ source/requests/writenumber.d | 16 +++++ source/version_.d | 2 +- views/index.dt | 15 +++-- 13 files changed, 283 insertions(+), 56 deletions(-) create mode 100644 source/requests/addnumber.d create mode 100644 source/requests/delnumber.d create mode 100644 source/requests/updatenumber.d create mode 100644 source/requests/writenumber.d diff --git a/js/script.js b/js/script.js index 845d167..ca55e13 100644 --- a/js/script.js +++ b/js/script.js @@ -6,23 +6,32 @@ $(document).ready(function () { $("button").button(); $("#tabs").tabs(); - // $(".addNumber").click(() => { - // numberAdd() - // }); + $("#update").button("option", "icon", "ui-icon-refresh"); + $("#add-number").button("option", "icon", "ui-icon-plusthick"); + $("#logout").button("option", "icon", "ui-icon-power"); + + // За каждым индексом закреплена функция для выполнения действия + // на активной вкладке при нажатии на кнопку обновления + $("#update").click(() => { + ({ + 0: () => { generateGroupNumbers($("#accordion-numbers .ui-accordion-content-active")) }, + 1: () => { noticer.success('Вкладка "SMS"') }, + 2: () => { noticer.success('Вкладка "USSD"') }, + 3: () => { noticer.success('Вкладка "Сервер"') } + })[$("#tabs").tabs( "option", "active" )]() + }); + + $("#add-number").click(() => { + addNumber($("#accordion-numbers .ui-accordion-content-active")) + }); $("body").fadeTo(500, 1); - $(".search").on("input", function () { + $("#search").on("input", function () { showNumbers( $("#accordion-numbers .ui-accordion-content-active"), numbers.filter(e => e.number.includes($(this).val())) ) - }).on("focus", function () { - if ($(this).val()) - showNumbers( - $("#accordion-numbers .ui-accordion-content-active"), - numbers.filter(e => e.number.includes($(this).val())) - ); }).keydown(function (e) { e.key == "Escape" && ($(this).val(""), showNumbers($("#accordion-numbers .ui-accordion-content-active"))) }); @@ -91,7 +100,7 @@ function generateGroupNumbers(panel) { }); } -function showNumbers(panel, data = numbers) { +function showNumbers(panel, data = numbers.filter(e => e.number.includes($("#search").val()))) { (new divNotFoundNumbers).remove(); let body = panel.find('.body').html(''); $(data).each((i, j) => { @@ -105,7 +114,7 @@ function showNumbers(panel, data = numbers) { body.append(row); row.click(function() { - numberEdit($(this).data('number')); + editNumber(panel, $(this).data('number')); }); }); @@ -134,7 +143,7 @@ function divNotFoundNumbers() { } } -function numberEdit(number) { +function editNumber(panel, number) { request('editnumber', 'text', {number: number}).then(data => { if (isJSON(data) && JSON.parse(data).error) noticer.error(JSON.parse(data).message); @@ -145,7 +154,7 @@ function numberEdit(number) { text: "Сохранить", icon: "ui-icon-check", click: function() { - actionNumber($(this), 'update'); + actionNumber(panel, $(this), 'updatenumber'); } }, { @@ -153,7 +162,7 @@ function numberEdit(number) { text: "Удалить", icon: "ui-icon-trash", click: function() { - // removeNumber($(this)); + delNumber(panel, $(this)); } } ], `Редактирование номера ${number}`); @@ -170,8 +179,6 @@ function showEditNumber(data, actionButton, title) { title: title, height: 'auto', width: 'auto', - maxHeight: 500, - minHeight: 50, resizable: false, modal: true, show: { effect: "fade", duration: 500 }, @@ -195,7 +202,10 @@ function showEditNumber(data, actionButton, title) { }); } -function actionNumber(currentWindow, query) { +function actionNumber(panel, currentWindow, query) { + // Только числа, начинающие с >0 или только 0 + let regexp = /^(?=\d)(\d|([^0]\d+))$/g; + let number = $('#number-number').val(); let group = $('#number-group').val(); let list = $('#number-list').val(); @@ -203,5 +213,75 @@ function actionNumber(currentWindow, query) { let white_cc = $('#number-white-cc').val(); let black_cc = $('#number-black-cc').val(); let comment = $('#number-comment').val(); + + let error = false; + + if (!number.length) { noticer.warning('Номер не может быть пуст'); error = true; } + if (all_cc.match(regexp) === null) { noticer.warning("Не верно указано общее количество звонков"); error = true; } + if (white_cc.match(regexp) === null) { noticer.warning("Не верно указано белое количество звонков"); error = true; } + if (black_cc.match(regexp) === null) { 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} был обновлен`); + generateGroupNumbers(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 { + showEditNumber(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} был удален`); + generateGroupNumbers(panel); + currentWindow.dialog("close"); + } + }).catch(error => { + noticer.error(error.message); + }); +} diff --git a/public/style.css b/public/style.css index 767e236..a1de265 100644 --- a/public/style.css +++ b/public/style.css @@ -25,9 +25,9 @@ div.div-header { /* HEADER */ -/* div.div-add { +div.main-button { margin-right: 20px -} */ +} div.div-search { display: flex; @@ -107,10 +107,16 @@ td { background: #fff; } +tr.row { + border-top: 1px solid #fff; +} + tr.row:hover, tr.row:nth-child(even):hover { - background-color: #c5c5c5; - color: #fff; + background-color: #f6f6f6; + color: #003eff; cursor: pointer; + border-bottom: 1px solid #c5c5c5; + border-top: 1px solid #c5c5c5; } /* EDIT NUMBER */ @@ -148,6 +154,10 @@ tr.row:hover, tr.row:nth-child(even):hover { justify-content: center; } +.comment-name { + padding: 15px 0 5px 0; +} + .comment-content { width: 100%; height: 100%; diff --git a/source/daster.d b/source/daster.d index 14a7121..f93f834 100644 --- a/source/daster.d +++ b/source/daster.d @@ -19,6 +19,10 @@ import structures; import requests.listsgroups; import requests.groupnumbers; import requests.editnumber; +import requests.updatenumber; +import requests.addnumber; +import requests.delnumber; +import requests.writenumber; static ServerInfo serverInfo; @@ -181,24 +185,21 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { // 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 "addnumber": + addNumber(req, res); + break; + case "writenumber": + writeNumber(req, res); + break; case "editnumber": editNumber(req, res); break; - // case "update": - // updateNumber(req, res); - // break; - // case "remove": - // removeNumber(req, res); - // break; + case "updatenumber": + updateNumber(req, res); + break; + case "delnumber": + delNumber(req, res); + break; default: res.redirect("/"); } diff --git a/source/data.d b/source/data.d index 0b6eaaa..e5fe6a2 100644 --- a/source/data.d +++ b/source/data.d @@ -6,7 +6,7 @@ import structures; import std.conv; -GroupDB[] getListGroups() { +GroupDB[] sqlGetListGroups() { GroupDB[] groups; try { auto queryResult = pgsql.sql( @@ -31,7 +31,7 @@ GroupDB[] getListGroups() { return groups; } -NumberDB[] getListNumbers(string group) { +NumberDB[] sqlGetListNumbers(string group) { NumberDB[] numbers; try { auto queryResult = pgsql.sql( @@ -44,7 +44,8 @@ NumberDB[] getListNumbers(string group) { dan.da_comment from da_numbers dan left join da_lists dal on dal.da_name = dan.da_list - where dan.da_group = ?", + where dan.da_group = ? + order by dan.da_number", group ); foreach (row; queryResult) { @@ -66,7 +67,7 @@ NumberDB[] getListNumbers(string group) { return numbers; } -NumberDB getDataNumber(string number) { +NumberDB sqlGetDataNumber(string number) { NumberDB data; try { auto queryResult = pgsql.sql( @@ -98,7 +99,7 @@ NumberDB getDataNumber(string number) { return data; } -GroupDB[] getGroups() { +GroupDB[] sqlGetGroups() { GroupDB[] groups; try { auto queryResult = pgsql.sql( @@ -119,7 +120,7 @@ GroupDB[] getGroups() { return groups; } -ListDB[] getLists() { +ListDB[] sqlGetLists() { ListDB[] lists; try { auto queryResult = pgsql.sql( @@ -139,3 +140,63 @@ ListDB[] getLists() { 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; +} diff --git a/source/requests/addnumber.d b/source/requests/addnumber.d new file mode 100644 index 0000000..2117b22 --- /dev/null +++ b/source/requests/addnumber.d @@ -0,0 +1,18 @@ +module requests.addnumber; + +import vibe.vibe; +import response; +import structures; +import data; +import singlog; + +void addNumber(HTTPServerRequest req, HTTPServerResponse res) { + auto jsr = req.json; + bool edit = false; + NumberDB dataNumber; + dataNumber.group = jsr["group"].get!string; + // auto dataNumber = sqlGetDataNumber(jsr["number"].get!string); + auto groups = sqlGetGroups(); + auto lists = sqlGetLists(); + render!("edit-number.dt", edit, dataNumber, groups, lists)(res); +} diff --git a/source/requests/delnumber.d b/source/requests/delnumber.d new file mode 100644 index 0000000..1f26183 --- /dev/null +++ b/source/requests/delnumber.d @@ -0,0 +1,24 @@ +module requests.delnumber; + +import vibe.vibe; +import response; +import structures; +import data; +import singlog; + +void delNumber(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(); +} diff --git a/source/requests/editnumber.d b/source/requests/editnumber.d index 4d8be89..719027b 100644 --- a/source/requests/editnumber.d +++ b/source/requests/editnumber.d @@ -8,8 +8,8 @@ import singlog; void editNumber(HTTPServerRequest req, HTTPServerResponse res) { auto jsr = req.json; bool edit = true; - auto dataNumber = getDataNumber(jsr["number"].get!string); - auto groups = getGroups(); - auto lists = getLists(); + auto dataNumber = sqlGetDataNumber(jsr["number"].get!string); + auto groups = sqlGetGroups(); + auto lists = sqlGetLists(); render!("edit-number.dt", edit, dataNumber, groups, lists)(res); } diff --git a/source/requests/groupnumbers.d b/source/requests/groupnumbers.d index 1d7f73a..6163e2b 100644 --- a/source/requests/groupnumbers.d +++ b/source/requests/groupnumbers.d @@ -7,5 +7,5 @@ import singlog; void groupNumbers(HTTPServerRequest req, HTTPServerResponse res) { auto jsr = req.json; - res.writeJsonBody(getListNumbers(jsr["group"].get!string).serializeToJson()); + res.writeJsonBody(sqlGetListNumbers(jsr["group"].get!string).serializeToJson()); } diff --git a/source/requests/listsgroups.d b/source/requests/listsgroups.d index 94e6dbb..2512743 100644 --- a/source/requests/listsgroups.d +++ b/source/requests/listsgroups.d @@ -5,6 +5,6 @@ import response; import data; void listsGroups(HTTPServerRequest req, HTTPServerResponse res) { - auto listGroups = getListGroups(); + auto listGroups = sqlGetListGroups(); render!("group-numbers-list.dt", listGroups)(res); } diff --git a/source/requests/updatenumber.d b/source/requests/updatenumber.d new file mode 100644 index 0000000..edfdebb --- /dev/null +++ b/source/requests/updatenumber.d @@ -0,0 +1,16 @@ +module requests.updatenumber; + +import vibe.vibe; +import response; +import data; +import singlog; +import structures; + +void updateNumber(HTTPServerRequest req, HTTPServerResponse res) { + NumberDB number = deserializeJson!NumberDB(req.json); + if (!sqlUpdateNumber(number)) { + res.send(true, "Не удалось обновить номер"); + return; + } + res.send(); +} diff --git a/source/requests/writenumber.d b/source/requests/writenumber.d new file mode 100644 index 0000000..7ffa747 --- /dev/null +++ b/source/requests/writenumber.d @@ -0,0 +1,16 @@ +module requests.writenumber; + +import vibe.vibe; +import response; +import structures; +import data; +import singlog; + +void writeNumber(HTTPServerRequest req, HTTPServerResponse res) { + NumberDB number = deserializeJson!NumberDB(req.json); + if (!sqlInsertNumber(number)) { + res.send(true, "Не удалось записать номер"); + return; + } + res.send(); +} diff --git a/source/version_.d b/source/version_.d index 7164ef0..263b019 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.2"; +enum dasterVersion = "v0.0.4"; diff --git a/views/index.dt b/views/index.dt index e32e093..5b3f98f 100644 --- a/views/index.dt +++ b/views/index.dt @@ -10,15 +10,16 @@ head script(src='script.js') body div.div-header - // div.div-add - // button.addNumber Добавить номер + div.div-update.main-button + button#update + div.div-add.main-button + button#add-number Добавить номер div.div-search - input.input-focus.search(name='search', type='text', value='', placeholder='Найти номер') + 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-user + div.div-logout + button#logout Выход div.content div#tabs ul From ab4b8c6badfb5798a86e42b623ad5e617f3083e9 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sat, 3 Jun 2023 02:28:18 +0300 Subject: [PATCH 04/10] v0.0.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Добавлены проверки на обновление/добавление/удаление номера + Реализовано: просмотр и удаление SMS - Необходим рефакторинг (убрать повторяющийся код в JS), оптимизировать - Объединить запросы в D в модули (подпапки) --- dub.json | 6 +- dub.selections.json | 3 +- js/noticer.min.js | 4 +- js/script.js | 200 ++++++++++++++++++++++++++++++--- public/style.css | 28 ++++- settings.conf.sample | 6 +- source/daster.d | 16 +++ source/data.d | 93 +++++++++++++++ source/requests/delsms.d | 19 ++++ source/requests/listsms.d | 10 ++ source/requests/smsnumbers.d | 15 +++ source/requests/updatenumber.d | 26 +++++ source/requests/viewsms.d | 12 ++ source/requests/writenumber.d | 26 +++++ source/structures.d | 8 ++ source/version_.d | 2 +- views/edit-number.dt | 1 - views/index.dt | 1 - views/sms-numbers.dt | 13 +++ views/view-sms.dt | 11 ++ 20 files changed, 467 insertions(+), 33 deletions(-) create mode 100644 source/requests/delsms.d create mode 100644 source/requests/listsms.d create mode 100644 source/requests/smsnumbers.d create mode 100644 source/requests/viewsms.d create mode 100644 views/sms-numbers.dt create mode 100644 views/view-sms.dt diff --git a/dub.json b/dub.json index 5814e7b..4575027 100644 --- a/dub.json +++ b/dub.json @@ -5,10 +5,10 @@ "copyright": "Copyright © 2023, Alexander Zhirov", "dependencies": { "vibe-d": "~>0.9", - "ldap": "~>0.4", - "singlog": "~>0.3.1", + "singlog": "~>0.3.2", "arsd-official:postgres": "~>10.9.10", - "readconf": "~>0.3.1" + "readconf": "~>0.3.1", + "datefmt": "1.0.4" }, "buildTypes": { "debug": { diff --git a/dub.selections.json b/dub.selections.json index f10278b..a6ee273 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -5,7 +5,6 @@ "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", @@ -13,7 +12,7 @@ "openssl-static": "1.0.2+3.0.8", "readconf": "0.3.1", "silly": "1.1.1", - "singlog": "0.3.1", + "singlog": "0.3.2", "stdx-allocator": "2.77.5", "taggedalgebraic": "0.11.22", "vibe-core": "2.2.0", diff --git a/js/noticer.min.js b/js/noticer.min.js index e9e9957..e7604a1 100644 --- a/js/noticer.min.js +++ b/js/noticer.min.js @@ -1,4 +1,4 @@ -/*! noticer - v0.1.0 - 2023-05-31 +/*! noticer - v0.1.1 - 2023-06-02 * https://git.zhirov.kz/alexander/noticer * Copyright Alexander Zhirov; Licensed GPL-2.0 */ -class Noticer{timer;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 i=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 a=setTimeout(function(){o.fadeTo(1e3,.3)},2500);o.fadeIn(500).mouseenter(()=>{clearTimeout(a),this.timer.pause()}).mouseleave(()=>{this.timer.resume()}).click(()=>{this.timer.dead(),o.fadeOut(0,function(){this.remove()})}),this.timer=new i(()=>{o.fadeOut(500,function(){this.remove()})},t)}} +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 ca55e13..663b217 100644 --- a/js/script.js +++ b/js/script.js @@ -1,21 +1,31 @@ var numbers = []; +var sms = []; $(document).ready(function () { noticer = new Noticer; $("button").button(); - $("#tabs").tabs(); + $("#tabs").tabs({ + activate: function( event, ui ) { + ({ + 0: () => { showNumbers($("#accordion-numbers .ui-accordion-content-active")) }, + 1: () => { showSMS($("#accordion-sms .ui-accordion-content-active")) }, + 2: () => {}, + 3: () => {} + })[$(this).tabs( "option", "active" )](); + + $("#add-number").button( "option", "disabled", $(this).tabs( "option", "active" ) > 0 ); + } + }); $("#update").button("option", "icon", "ui-icon-refresh"); $("#add-number").button("option", "icon", "ui-icon-plusthick"); $("#logout").button("option", "icon", "ui-icon-power"); - // За каждым индексом закреплена функция для выполнения действия - // на активной вкладке при нажатии на кнопку обновления $("#update").click(() => { ({ 0: () => { generateGroupNumbers($("#accordion-numbers .ui-accordion-content-active")) }, - 1: () => { noticer.success('Вкладка "SMS"') }, + 1: () => { generateListSMS($("#accordion-sms .ui-accordion-content-active")) }, 2: () => { noticer.success('Вкладка "USSD"') }, 3: () => { noticer.success('Вкладка "Сервер"') } })[$("#tabs").tabs( "option", "active" )]() @@ -28,15 +38,24 @@ $(document).ready(function () { $("body").fadeTo(500, 1); $("#search").on("input", function () { - showNumbers( - $("#accordion-numbers .ui-accordion-content-active"), - numbers.filter(e => e.number.includes($(this).val())) - ) + ({ + 0: () => { showNumbers($("#accordion-numbers .ui-accordion-content-active")) }, + 1: () => { showSMS($("#accordion-sms .ui-accordion-content-active")) }, + 2: () => {}, + 3: () => {} + })[$("#tabs").tabs( "option", "active" )]() }).keydown(function (e) { - e.key == "Escape" && ($(this).val(""), showNumbers($("#accordion-numbers .ui-accordion-content-active"))) + e.key == "Escape" && ($(this).val(""), ({ + 0: () => { showNumbers($("#accordion-numbers .ui-accordion-content-active")) }, + 1: () => { showSMS($("#accordion-sms .ui-accordion-content-active")) }, + 2: () => {}, + 3: () => {} + })[$("#tabs").tabs( "option", "active" )]() + ) }); - loadData(); + loadNumbers(); + loadSMS(); }) async function request(query, type, queryData = {}) { @@ -66,7 +85,11 @@ function isJSON(str) { } } -function loadData() { +function isNumeric(value) { + return /^-?\d+$/.test(value); +} + +function loadNumbers() { request('listsgroups', 'text').then(data => { data.error ? noticer.error(data.message) : generateListsGroups(data); }).catch(error => { @@ -74,7 +97,142 @@ function loadData() { }); } +function loadSMS() { + request('smsnumbers', 'text').then(data => { + data.error ? noticer.error(data.message) : generateListsSMSNumber(data); + }).catch(error => { + noticer.error(error.message); + }); +} + +function generateListsSMSNumber(data) { + if (!$(data).children().length) { + $("#tabs-sms").html('

SMS отсутствуют

'); + return; + } + + $("#tabs-sms").html(data); + $("#accordion-sms").accordion({ + heightStyle: "content", + create: function( event, ui ) { + generateListSMS(ui.panel); + }, + beforeActivate: function( event, ui ) { + generateListSMS(ui.newPanel); + } + }) +} + +function generateListSMS(panel) { + if (!$("#accordion-sms").children().length) { + noticer.warning("SMS отсутствуют"); + $("#tabs-sms").html('

SMS отсутствуют

'); + return; + } + + request('listsms', 'json', { to: panel.data("to") }).then(data => { + if (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(data).message); + else { + sms = data; + showSMS(panel); + } + }).catch(error => { + noticer.error(error.message); + }); +} + +function showSMS(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).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} было удалено`); + generateListSMS(panel); + currentWindow.dialog("close"); + } + }).catch(error => { + noticer.error(error.message); + }); +} + function generateListsGroups(data) { + if (!$(data).children().length) { + $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); + return; + } + $("#tabs-numbers").html(data); $("#accordion-numbers").accordion({ heightStyle: "content", @@ -88,6 +246,12 @@ function generateListsGroups(data) { } function generateGroupNumbers(panel) { + if (!$("#accordion-numbers").children().length) { + noticer.warning("Номера телефонов отсутствуют"); + $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); + return; + } + request('groupnumbers', 'json', { group: panel.data("group-name") }).then(data => { if (isJSON(data) && JSON.parse(data).error) noticer.error(JSON.parse(data).message); @@ -203,8 +367,7 @@ function showEditNumber(data, actionButton, title) { } function actionNumber(panel, currentWindow, query) { - // Только числа, начинающие с >0 или только 0 - let regexp = /^(?=\d)(\d|([^0]\d+))$/g; + let pattern_number = /^\+7\d{10}$/g; let number = $('#number-number').val(); let group = $('#number-group').val(); @@ -216,10 +379,13 @@ function actionNumber(panel, currentWindow, query) { let error = false; - if (!number.length) { noticer.warning('Номер не может быть пуст'); error = true; } - if (all_cc.match(regexp) === null) { noticer.warning("Не верно указано общее количество звонков"); error = true; } - if (white_cc.match(regexp) === null) { noticer.warning("Не верно указано белое количество звонков"); error = true; } - if (black_cc.match(regexp) === null) { noticer.warning("Не верно указано черное количество звонков"); error = true; } + 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; diff --git a/public/style.css b/public/style.css index a1de265..e790245 100644 --- a/public/style.css +++ b/public/style.css @@ -121,7 +121,7 @@ tr.row:hover, tr.row:nth-child(even):hover { /* EDIT NUMBER */ -.number-label { +.number-label, .sms-label { color: #333; text-align: right; } @@ -130,6 +130,15 @@ tr.row:hover, tr.row:nth-child(even):hover { height: 30px; } +.sms-label-text { + vertical-align:top +} + +.sms-value { + width: 300px; + text-align: left; +} + .number-input-main { height: 25px; width: 194px; @@ -158,12 +167,25 @@ tr.row:hover, tr.row:nth-child(even):hover { padding: 15px 0 5px 0; } -.comment-content { +.comment-content, .sms-content { width: 100%; height: 100%; } -#number-comment { +#number-comment, #sms-content { width: calc(100% - 6px); resize: none; } + +#sms-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 { + width: 155px; +} diff --git a/settings.conf.sample b/settings.conf.sample index 8a98fe3..2b48a48 100644 --- a/settings.conf.sample +++ b/settings.conf.sample @@ -1,14 +1,14 @@ [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 [daster-db] host => 127.0.0.1 diff --git a/source/daster.d b/source/daster.d index f93f834..5958e03 100644 --- a/source/daster.d +++ b/source/daster.d @@ -23,6 +23,10 @@ import requests.updatenumber; import requests.addnumber; import requests.delnumber; import requests.writenumber; +import requests.smsnumbers; +import requests.listsms; +import requests.viewsms; +import requests.delsms; static ServerInfo serverInfo; @@ -200,6 +204,18 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { case "delnumber": delNumber(req, res); break; + case "smsnumbers": + smsNumbers(req, res); + break; + case "listsms": + listSMS(req, res); + break; + case "viewsms": + viewSMS(req, res); + break; + case "delsms": + delSMS(req, res); + break; default: res.redirect("/"); } diff --git a/source/data.d b/source/data.d index e5fe6a2..4121a59 100644 --- a/source/data.d +++ b/source/data.d @@ -200,3 +200,96 @@ bool sqlInsertNumber(NumberDB number) { } return true; } + +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; +} diff --git a/source/requests/delsms.d b/source/requests/delsms.d new file mode 100644 index 0000000..02a2cce --- /dev/null +++ b/source/requests/delsms.d @@ -0,0 +1,19 @@ +module requests.delsms; + +import vibe.vibe; +import response; +import structures; +import data; +import singlog; + +void delSMS(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/listsms.d b/source/requests/listsms.d new file mode 100644 index 0000000..07af810 --- /dev/null +++ b/source/requests/listsms.d @@ -0,0 +1,10 @@ +module requests.listsms; + +import vibe.vibe; +import response; +import data; + +void listSMS(HTTPServerRequest req, HTTPServerResponse res) { + auto jsr = req.json; + res.writeJsonBody(sqlGetListSMS(jsr["to"].get!string).serializeToJson()); +} \ No newline at end of file diff --git a/source/requests/smsnumbers.d b/source/requests/smsnumbers.d new file mode 100644 index 0000000..1ead955 --- /dev/null +++ b/source/requests/smsnumbers.d @@ -0,0 +1,15 @@ +module requests.smsnumbers; + +import vibe.vibe; +import response; +import data; +import singlog; + +void smsNumbers(HTTPServerRequest req, HTTPServerResponse res) { + // auto jsr = req.json; + // bool edit = true; + // auto dataNumber = sqlGetDataNumber(jsr["number"].get!string); + // auto groups = sqlGetGroups(); + auto numbers = sqlGetSMSNumbers(); + render!("sms-numbers.dt", numbers)(res); +} diff --git a/source/requests/updatenumber.d b/source/requests/updatenumber.d index edfdebb..a2c8338 100644 --- a/source/requests/updatenumber.d +++ b/source/requests/updatenumber.d @@ -6,8 +6,34 @@ import data; import singlog; import structures; +import std.regex; + void updateNumber(HTTPServerRequest req, HTTPServerResponse res) { NumberDB number = deserializeJson!NumberDB(req.json); + + // const string pattern_number = r"^\+7\d{10}$"; + // auto regular_number = regex(pattern_number, "g"); + + if (!number.number.matchFirst(regex(r"^\+7\d{10}$", "g"))) { + res.send(true, "Номер не соответствует формату +7XXXXXXXXXX"); + return; + } + + if (number.all_cc < 0) { + res.send(true, "Общее количество звонков не может быть отрицательным"); + return; + } + + if (number.white_cc < 0) { + res.send(true, "Белое количество звонков не может быть отрицательным"); + return; + } + + if (number.black_cc < 0) { + res.send(true, "Черное количество звонков не может быть отрицательным"); + return; + } + if (!sqlUpdateNumber(number)) { res.send(true, "Не удалось обновить номер"); return; diff --git a/source/requests/viewsms.d b/source/requests/viewsms.d new file mode 100644 index 0000000..414af84 --- /dev/null +++ b/source/requests/viewsms.d @@ -0,0 +1,12 @@ +module requests.viewsms; + +import vibe.vibe; +import response; +import data; +import singlog; + +void viewSMS(HTTPServerRequest req, HTTPServerResponse res) { + auto jsr = req.json; + auto dataSMS = sqlGetSMS(jsr["id"].to!int); + render!("view-sms.dt", dataSMS)(res); +} diff --git a/source/requests/writenumber.d b/source/requests/writenumber.d index 7ffa747..44f67c5 100644 --- a/source/requests/writenumber.d +++ b/source/requests/writenumber.d @@ -6,8 +6,34 @@ import structures; import data; import singlog; +import std.regex; + void writeNumber(HTTPServerRequest req, HTTPServerResponse res) { NumberDB number = deserializeJson!NumberDB(req.json); + + // const string pattern_number = r"^\+7\d{10}$"; + // auto regular_number = regex(r"^\+7\d{10}$", "g"); + + if (!number.number.matchFirst(regex(r"^\+7\d{10}$", "g"))) { + res.send(true, "Номер не соответствует формату +7XXXXXXXXXX"); + return; + } + + if (number.all_cc < 0) { + res.send(true, "Общее количество звонков не может быть отрицательным"); + return; + } + + if (number.white_cc < 0) { + res.send(true, "Белое количество звонков не может быть отрицательным"); + return; + } + + if (number.black_cc < 0) { + res.send(true, "Черное количество звонков не может быть отрицательным"); + return; + } + if (!sqlInsertNumber(number)) { res.send(true, "Не удалось записать номер"); return; diff --git a/source/structures.d b/source/structures.d index a115318..10ccba1 100644 --- a/source/structures.d +++ b/source/structures.d @@ -36,3 +36,11 @@ struct NumberDB { int black_cc; string comment; } + +struct SMSDB { + int id; + string date; + string to; + string from; + string text; +} diff --git a/source/version_.d b/source/version_.d index 263b019..d33ae86 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.4"; +enum dasterVersion = "v0.0.5"; diff --git a/views/edit-number.dt b/views/edit-number.dt index fd8c788..36efe5e 100644 --- a/views/edit-number.dt +++ b/views/edit-number.dt @@ -1,4 +1,3 @@ -- import structures; div#number-data table tbody diff --git a/views/index.dt b/views/index.dt index 5b3f98f..7c82457 100644 --- a/views/index.dt +++ b/views/index.dt @@ -33,7 +33,6 @@ body a(href='#tabs-server') Сервер div#tabs-numbers div#tabs-sms - p Список SMS сообщений div#tabs-ussd p Список результатов USSD запросов div#tabs-server diff --git a/views/sms-numbers.dt b/views/sms-numbers.dt new file mode 100644 index 0000000..a511995 --- /dev/null +++ b/views/sms-numbers.dt @@ -0,0 +1,13 @@ +div#accordion-sms + - foreach (number; numbers) + h3 На номер #{number.to} + div.group-content(data-to='#{number.to}') + table + thead.head + tr + th.sms-content-width Дата + th.sms-content-width От кого + th Текст сообщения + div.body-rows + table + tbody.body diff --git a/views/view-sms.dt b/views/view-sms.dt new file mode 100644 index 0000000..bc57579 --- /dev/null +++ b/views/view-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} From 2ba510adac8e3fc30eaa81167680904573955984 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sat, 3 Jun 2023 21:27:07 +0300 Subject: [PATCH 05/10] v0.0.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Запросы объеденины в соответствующие модули + Оптимизация кода --- dub.json | 5 +- js/script.js | 545 +++++++++--------- public/style.css | 6 +- source/daster.d | 60 +- source/requests/addnumber.d | 18 - source/requests/delnumber.d | 24 - source/requests/delsms.d | 19 - source/requests/editnumber.d | 15 - source/requests/groupnumbers.d | 11 - source/requests/listsgroups.d | 10 - source/requests/listsms.d | 10 - source/requests/numbers.d | 113 ++++ source/requests/sms.d | 39 ++ source/requests/smsnumbers.d | 15 - source/requests/updatenumber.d | 42 -- source/requests/viewsms.d | 12 - source/requests/writenumber.d | 42 -- source/{data.d => sql.d} | 2 +- source/version_.d | 2 +- ...-numbers-list.dt => list_number_groups.dt} | 2 - views/{sms-numbers.dt => list_sms_groups.dt} | 0 views/{edit-number.dt => number.dt} | 0 views/{view-sms.dt => sms.dt} | 0 23 files changed, 462 insertions(+), 530 deletions(-) delete mode 100644 source/requests/addnumber.d delete mode 100644 source/requests/delnumber.d delete mode 100644 source/requests/delsms.d delete mode 100644 source/requests/editnumber.d delete mode 100644 source/requests/groupnumbers.d delete mode 100644 source/requests/listsgroups.d delete mode 100644 source/requests/listsms.d create mode 100644 source/requests/numbers.d create mode 100644 source/requests/sms.d delete mode 100644 source/requests/smsnumbers.d delete mode 100644 source/requests/updatenumber.d delete mode 100644 source/requests/viewsms.d delete mode 100644 source/requests/writenumber.d rename source/{data.d => sql.d} (99%) rename views/{group-numbers-list.dt => list_number_groups.dt} (96%) rename views/{sms-numbers.dt => list_sms_groups.dt} (100%) rename views/{edit-number.dt => number.dt} (100%) rename views/{view-sms.dt => sms.dt} (100%) diff --git a/dub.json b/dub.json index 4575027..dcc38e8 100644 --- a/dub.json +++ b/dub.json @@ -7,8 +7,7 @@ "vibe-d": "~>0.9", "singlog": "~>0.3.2", "arsd-official:postgres": "~>10.9.10", - "readconf": "~>0.3.1", - "datefmt": "1.0.4" + "readconf": "~>0.3.1" }, "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/js/script.js b/js/script.js index 663b217..d6c60f0 100644 --- a/js/script.js +++ b/js/script.js @@ -4,58 +4,50 @@ var sms = []; $(document).ready(function () { noticer = new Noticer; + let tabs = { + 0: () => { showListNumbers($("#accordion-numbers .ui-accordion-content-active")) }, + 1: () => { showListSMS($("#accordion-sms .ui-accordion-content-active")) }, + 2: () => {}, + 3: () => {} + }; + + let groups = { + 0: () => { generateListGroupNumbers($("#accordion-numbers .ui-accordion-content-active")) }, + 1: () => { generateListGroupSMS($("#accordion-sms .ui-accordion-content-active")) }, + 2: () => { noticer.success('Вкладка "USSD"') }, + 3: () => { noticer.success('Вкладка "Сервер"') } + }; + $("button").button(); - $("#tabs").tabs({ - activate: function( event, ui ) { - ({ - 0: () => { showNumbers($("#accordion-numbers .ui-accordion-content-active")) }, - 1: () => { showSMS($("#accordion-sms .ui-accordion-content-active")) }, - 2: () => {}, - 3: () => {} - })[$(this).tabs( "option", "active" )](); - - $("#add-number").button( "option", "disabled", $(this).tabs( "option", "active" ) > 0 ); - } - }); - $("#update").button("option", "icon", "ui-icon-refresh"); $("#add-number").button("option", "icon", "ui-icon-plusthick"); $("#logout").button("option", "icon", "ui-icon-power"); + $("#tabs").tabs({ + activate: function( event, ui ) { + tabs[$(this).tabs( "option", "active" )](); + $("#add-number").button( "option", "disabled", $(this).tabs( "option", "active" ) > 0 ); + } + }); + $("#update").click(() => { - ({ - 0: () => { generateGroupNumbers($("#accordion-numbers .ui-accordion-content-active")) }, - 1: () => { generateListSMS($("#accordion-sms .ui-accordion-content-active")) }, - 2: () => { noticer.success('Вкладка "USSD"') }, - 3: () => { noticer.success('Вкладка "Сервер"') } - })[$("#tabs").tabs( "option", "active" )]() + groups[$("#tabs").tabs( "option", "active" )]() }); $("#add-number").click(() => { addNumber($("#accordion-numbers .ui-accordion-content-active")) }); - $("body").fadeTo(500, 1); - $("#search").on("input", function () { - ({ - 0: () => { showNumbers($("#accordion-numbers .ui-accordion-content-active")) }, - 1: () => { showSMS($("#accordion-sms .ui-accordion-content-active")) }, - 2: () => {}, - 3: () => {} - })[$("#tabs").tabs( "option", "active" )]() + tabs[$("#tabs").tabs( "option", "active" )]() }).keydown(function (e) { - e.key == "Escape" && ($(this).val(""), ({ - 0: () => { showNumbers($("#accordion-numbers .ui-accordion-content-active")) }, - 1: () => { showSMS($("#accordion-sms .ui-accordion-content-active")) }, - 2: () => {}, - 3: () => {} - })[$("#tabs").tabs( "option", "active" )]() - ) + e.key == "Escape" && ($(this).val(""), tabs[$("#tabs").tabs( "option", "active" )]()) }); loadNumbers(); loadSMS(); + + $("body").fadeTo(500, 1); }) async function request(query, type, queryData = {}) { @@ -89,23 +81,259 @@ function isNumeric(value) { return /^-?\d+$/.test(value); } +function divNotFoundNumbers() { + let notFound = $('.notFoundNumbers'); + let divTable = $('.body-rows'); + let divNotFound = $('
'); + + divNotFound.css({ + "color": "#333", + "text-align": "center", + "padding": "20px 0 20px 0" + }); + + this.push = function(text = 'Нет номеров') { + divTable.append(divNotFound.html(text)); + } + + this.remove = function() { + notFound.remove(); + } +} + +/************************************************************************************ + + Обработка таблицы с номерами телефонов + +************************************************************************************/ + function loadNumbers() { - request('listsgroups', 'text').then(data => { - data.error ? noticer.error(data.message) : generateListsGroups(data); + request('listnumbergroups', 'text').then(data => { + data.error ? noticer.error(data.message) : generateListNumberGroups(data); }).catch(error => { noticer.error(error.message); }); } +function generateListNumberGroups(data) { + if (!$(data).children().length) { + $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); + return; + } + + $("#tabs-numbers").html(data); + $("#accordion-numbers").accordion({ + heightStyle: "content", + create: function( event, ui ) { + generateListGroupNumbers(ui.panel); + }, + beforeActivate: function( event, ui ) { + generateListGroupNumbers(ui.newPanel); + } + }); +} + +function generateListGroupNumbers(panel) { + if (!$("#accordion-numbers").children().length) { + noticer.warning("Номера телефонов отсутствуют"); + $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); + return; + } + + request('listgroupnumbers', 'json', { group: panel.data("group-name") }).then(data => { + if (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(data).message); + else { + numbers = data; + showListNumbers(panel); + } + }).catch(error => { + 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('smsnumbers', 'text').then(data => { - data.error ? noticer.error(data.message) : generateListsSMSNumber(data); + request('listsmsgroups', 'text').then(data => { + data.error ? noticer.error(data.message) : generateListSMSGroups(data); }).catch(error => { noticer.error(error.message); }); } -function generateListsSMSNumber(data) { +function generateListSMSGroups(data) { if (!$(data).children().length) { $("#tabs-sms").html('

SMS отсутствуют

'); return; @@ -115,34 +343,34 @@ function generateListsSMSNumber(data) { $("#accordion-sms").accordion({ heightStyle: "content", create: function( event, ui ) { - generateListSMS(ui.panel); + generateListGroupSMS(ui.panel); }, beforeActivate: function( event, ui ) { - generateListSMS(ui.newPanel); + generateListGroupSMS(ui.newPanel); } }) } -function generateListSMS(panel) { +function generateListGroupSMS(panel) { if (!$("#accordion-sms").children().length) { noticer.warning("SMS отсутствуют"); $("#tabs-sms").html('

SMS отсутствуют

'); return; } - request('listsms', 'json', { to: panel.data("to") }).then(data => { + request('listgroupsms', 'json', { to: panel.data("to") }).then(data => { if (isJSON(data) && JSON.parse(data).error) noticer.error(JSON.parse(data).message); else { sms = data; - showSMS(panel); + showListSMS(panel); } }).catch(error => { noticer.error(error.message); }); } -function showSMS(panel, data = sms.filter(e => e.from.includes($("#search").val()))) { +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) => { @@ -219,232 +447,7 @@ function delSMS(panel, currentWindow) { noticer.error(data.message); else { noticer.success(`SMS от ${from} было удалено`); - generateListSMS(panel); - currentWindow.dialog("close"); - } - }).catch(error => { - noticer.error(error.message); - }); -} - -function generateListsGroups(data) { - if (!$(data).children().length) { - $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); - return; - } - - $("#tabs-numbers").html(data); - $("#accordion-numbers").accordion({ - heightStyle: "content", - create: function( event, ui ) { - generateGroupNumbers(ui.panel); - }, - beforeActivate: function( event, ui ) { - generateGroupNumbers(ui.newPanel); - } - }); -} - -function generateGroupNumbers(panel) { - if (!$("#accordion-numbers").children().length) { - noticer.warning("Номера телефонов отсутствуют"); - $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); - return; - } - - request('groupnumbers', 'json', { group: panel.data("group-name") }).then(data => { - if (isJSON(data) && JSON.parse(data).error) - noticer.error(JSON.parse(data).message); - else { - numbers = data; - showNumbers(panel); - } - }).catch(error => { - noticer.error(error.message); - }); -} - -function showNumbers(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() { - editNumber(panel, $(this).data('number')); - }); - }); - - if (!body.children().length) - (new divNotFoundNumbers).push(); -} - - -function divNotFoundNumbers() { - let notFound = $('.failNumbers'); - let divTable = $('.body-rows'); - let divFound = $('
'); - - divFound.css({ - "color": "#333", - "text-align": "center", - "padding": "20px 0 20px 0" - }); - - this.push = function(text = 'Нет номеров') { - divTable.append(divFound.html(text)); - } - - this.remove = function() { - notFound.remove(); - } -} - -function editNumber(panel, number) { - request('editnumber', 'text', {number: number}).then(data => { - if (isJSON(data) && JSON.parse(data).error) - noticer.error(JSON.parse(data).message); - else { - showEditNumber(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 showEditNumber(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} был обновлен`); - generateGroupNumbers(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 { - showEditNumber(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} был удален`); - generateGroupNumbers(panel); + generateListGroupSMS(panel); currentWindow.dialog("close"); } }).catch(error => { diff --git a/public/style.css b/public/style.css index e790245..fe99a79 100644 --- a/public/style.css +++ b/public/style.css @@ -78,8 +78,12 @@ input { 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; diff --git a/source/daster.d b/source/daster.d index 5958e03..c4b5181 100644 --- a/source/daster.d +++ b/source/daster.d @@ -16,17 +16,8 @@ import verinfo; import pgdb; import structures; -import requests.listsgroups; -import requests.groupnumbers; -import requests.editnumber; -import requests.updatenumber; -import requests.addnumber; -import requests.delnumber; -import requests.writenumber; -import requests.smsnumbers; -import requests.listsms; -import requests.viewsms; -import requests.delsms; +import requests.numbers; +import requests.sms; static ServerInfo serverInfo; @@ -82,15 +73,16 @@ 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(); + auto router = new URLRouter; router.post("/", &postReq); router.get("/", &getReq); @@ -176,45 +168,47 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { // return; // } + log.d("json request: " ~ jsr.to!string); + switch (query) { - case "listsgroups": - listsGroups(req, res); - break; - case "groupnumbers": - groupNumbers(req, res); - break; // case "authorization": // authorization(req, res); // break; // 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": - addNumber(req, res); + getAddNumber(req, res); break; case "writenumber": - writeNumber(req, res); - break; - case "editnumber": - editNumber(req, res); + sendWriteNumber(req, res); break; case "updatenumber": - updateNumber(req, res); + sendUpdateNumber(req, res); break; case "delnumber": - delNumber(req, res); + sendDelNumber(req, res); break; - case "smsnumbers": - smsNumbers(req, res); + case "listsmsgroups": + getListSMSGroups(req, res); break; - case "listsms": - listSMS(req, res); + case "listgroupsms": + getListGroupSMS(req, res); break; case "viewsms": - viewSMS(req, res); + getViewSMS(req, res); break; case "delsms": - delSMS(req, res); + sendDelSMS(req, res); break; default: res.redirect("/"); @@ -260,7 +254,7 @@ void rcAsteriskDB() { " password=" ~ asteriskDB["password"] ); } catch (Exception e) { - log.c(e); + log.c(e.msg); exit(1); } } diff --git a/source/requests/addnumber.d b/source/requests/addnumber.d deleted file mode 100644 index 2117b22..0000000 --- a/source/requests/addnumber.d +++ /dev/null @@ -1,18 +0,0 @@ -module requests.addnumber; - -import vibe.vibe; -import response; -import structures; -import data; -import singlog; - -void addNumber(HTTPServerRequest req, HTTPServerResponse res) { - auto jsr = req.json; - bool edit = false; - NumberDB dataNumber; - dataNumber.group = jsr["group"].get!string; - // auto dataNumber = sqlGetDataNumber(jsr["number"].get!string); - auto groups = sqlGetGroups(); - auto lists = sqlGetLists(); - render!("edit-number.dt", edit, dataNumber, groups, lists)(res); -} diff --git a/source/requests/delnumber.d b/source/requests/delnumber.d deleted file mode 100644 index 1f26183..0000000 --- a/source/requests/delnumber.d +++ /dev/null @@ -1,24 +0,0 @@ -module requests.delnumber; - -import vibe.vibe; -import response; -import structures; -import data; -import singlog; - -void delNumber(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(); -} diff --git a/source/requests/delsms.d b/source/requests/delsms.d deleted file mode 100644 index 02a2cce..0000000 --- a/source/requests/delsms.d +++ /dev/null @@ -1,19 +0,0 @@ -module requests.delsms; - -import vibe.vibe; -import response; -import structures; -import data; -import singlog; - -void delSMS(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/editnumber.d b/source/requests/editnumber.d deleted file mode 100644 index 719027b..0000000 --- a/source/requests/editnumber.d +++ /dev/null @@ -1,15 +0,0 @@ -module requests.editnumber; - -import vibe.vibe; -import response; -import data; -import singlog; - -void editNumber(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!("edit-number.dt", edit, dataNumber, groups, lists)(res); -} diff --git a/source/requests/groupnumbers.d b/source/requests/groupnumbers.d deleted file mode 100644 index 6163e2b..0000000 --- a/source/requests/groupnumbers.d +++ /dev/null @@ -1,11 +0,0 @@ -module requests.groupnumbers; - -import vibe.vibe; -import response; -import data; -import singlog; - -void groupNumbers(HTTPServerRequest req, HTTPServerResponse res) { - auto jsr = req.json; - res.writeJsonBody(sqlGetListNumbers(jsr["group"].get!string).serializeToJson()); -} diff --git a/source/requests/listsgroups.d b/source/requests/listsgroups.d deleted file mode 100644 index 2512743..0000000 --- a/source/requests/listsgroups.d +++ /dev/null @@ -1,10 +0,0 @@ -module requests.listsgroups; - -import vibe.vibe; -import response; -import data; - -void listsGroups(HTTPServerRequest req, HTTPServerResponse res) { - auto listGroups = sqlGetListGroups(); - render!("group-numbers-list.dt", listGroups)(res); -} diff --git a/source/requests/listsms.d b/source/requests/listsms.d deleted file mode 100644 index 07af810..0000000 --- a/source/requests/listsms.d +++ /dev/null @@ -1,10 +0,0 @@ -module requests.listsms; - -import vibe.vibe; -import response; -import data; - -void listSMS(HTTPServerRequest req, HTTPServerResponse res) { - auto jsr = req.json; - res.writeJsonBody(sqlGetListSMS(jsr["to"].get!string).serializeToJson()); -} \ No newline at end of file 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/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/smsnumbers.d b/source/requests/smsnumbers.d deleted file mode 100644 index 1ead955..0000000 --- a/source/requests/smsnumbers.d +++ /dev/null @@ -1,15 +0,0 @@ -module requests.smsnumbers; - -import vibe.vibe; -import response; -import data; -import singlog; - -void smsNumbers(HTTPServerRequest req, HTTPServerResponse res) { - // auto jsr = req.json; - // bool edit = true; - // auto dataNumber = sqlGetDataNumber(jsr["number"].get!string); - // auto groups = sqlGetGroups(); - auto numbers = sqlGetSMSNumbers(); - render!("sms-numbers.dt", numbers)(res); -} diff --git a/source/requests/updatenumber.d b/source/requests/updatenumber.d deleted file mode 100644 index a2c8338..0000000 --- a/source/requests/updatenumber.d +++ /dev/null @@ -1,42 +0,0 @@ -module requests.updatenumber; - -import vibe.vibe; -import response; -import data; -import singlog; -import structures; - -import std.regex; - -void updateNumber(HTTPServerRequest req, HTTPServerResponse res) { - NumberDB number = deserializeJson!NumberDB(req.json); - - // const string pattern_number = r"^\+7\d{10}$"; - // auto regular_number = regex(pattern_number, "g"); - - if (!number.number.matchFirst(regex(r"^\+7\d{10}$", "g"))) { - res.send(true, "Номер не соответствует формату +7XXXXXXXXXX"); - return; - } - - if (number.all_cc < 0) { - res.send(true, "Общее количество звонков не может быть отрицательным"); - return; - } - - if (number.white_cc < 0) { - res.send(true, "Белое количество звонков не может быть отрицательным"); - return; - } - - if (number.black_cc < 0) { - res.send(true, "Черное количество звонков не может быть отрицательным"); - return; - } - - if (!sqlUpdateNumber(number)) { - res.send(true, "Не удалось обновить номер"); - return; - } - res.send(); -} diff --git a/source/requests/viewsms.d b/source/requests/viewsms.d deleted file mode 100644 index 414af84..0000000 --- a/source/requests/viewsms.d +++ /dev/null @@ -1,12 +0,0 @@ -module requests.viewsms; - -import vibe.vibe; -import response; -import data; -import singlog; - -void viewSMS(HTTPServerRequest req, HTTPServerResponse res) { - auto jsr = req.json; - auto dataSMS = sqlGetSMS(jsr["id"].to!int); - render!("view-sms.dt", dataSMS)(res); -} diff --git a/source/requests/writenumber.d b/source/requests/writenumber.d deleted file mode 100644 index 44f67c5..0000000 --- a/source/requests/writenumber.d +++ /dev/null @@ -1,42 +0,0 @@ -module requests.writenumber; - -import vibe.vibe; -import response; -import structures; -import data; -import singlog; - -import std.regex; - -void writeNumber(HTTPServerRequest req, HTTPServerResponse res) { - NumberDB number = deserializeJson!NumberDB(req.json); - - // const string pattern_number = r"^\+7\d{10}$"; - // auto regular_number = regex(r"^\+7\d{10}$", "g"); - - if (!number.number.matchFirst(regex(r"^\+7\d{10}$", "g"))) { - res.send(true, "Номер не соответствует формату +7XXXXXXXXXX"); - return; - } - - if (number.all_cc < 0) { - res.send(true, "Общее количество звонков не может быть отрицательным"); - return; - } - - if (number.white_cc < 0) { - res.send(true, "Белое количество звонков не может быть отрицательным"); - return; - } - - if (number.black_cc < 0) { - res.send(true, "Черное количество звонков не может быть отрицательным"); - return; - } - - if (!sqlInsertNumber(number)) { - res.send(true, "Не удалось записать номер"); - return; - } - res.send(); -} diff --git a/source/data.d b/source/sql.d similarity index 99% rename from source/data.d rename to source/sql.d index 4121a59..f4ccd69 100644 --- a/source/data.d +++ b/source/sql.d @@ -1,4 +1,4 @@ -module data; +module sql; import pgdb; import singlog; diff --git a/source/version_.d b/source/version_.d index d33ae86..fd2e40b 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.5"; +enum dasterVersion = "v0.0.6"; diff --git a/views/group-numbers-list.dt b/views/list_number_groups.dt similarity index 96% rename from views/group-numbers-list.dt rename to views/list_number_groups.dt index d5cc975..67052e8 100644 --- a/views/group-numbers-list.dt +++ b/views/list_number_groups.dt @@ -1,5 +1,3 @@ -- import structures; - div#accordion-numbers - foreach (group; listGroups) h3 #{group.comment} diff --git a/views/sms-numbers.dt b/views/list_sms_groups.dt similarity index 100% rename from views/sms-numbers.dt rename to views/list_sms_groups.dt diff --git a/views/edit-number.dt b/views/number.dt similarity index 100% rename from views/edit-number.dt rename to views/number.dt diff --git a/views/view-sms.dt b/views/sms.dt similarity index 100% rename from views/view-sms.dt rename to views/sms.dt From 69ab43a4ec2e72e1b7464cefaa8f70711f8f2c18 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 4 Jun 2023 02:00:19 +0300 Subject: [PATCH 06/10] v0.0.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Обновлен скрипт SQL - добавлена таблица с описанием типов USSD + Реализовано: просмотр и удаление USSD + Кнопка перезагрузки вкладки; кнопка обновления обновляет данные группы во вкладке --- database/script.sql | 15 +++ images/favicon.png | Bin 11271 -> 24788 bytes js/script.js | 190 ++++++++++++++++++++++++++++++++++---- public/style.css | 18 ++-- source/daster.d | 13 +++ source/requests/ussd.d | 39 ++++++++ source/sql.d | 115 +++++++++++++++++++++++ source/structures.d | 9 ++ views/index.dt | 9 +- views/list_ussd_groups.dt | 13 +++ views/ussd.dt | 11 +++ 11 files changed, 403 insertions(+), 29 deletions(-) create mode 100644 source/requests/ussd.d create mode 100644 views/list_ussd_groups.dt create mode 100644 views/ussd.dt diff --git a/database/script.sql b/database/script.sql index 4789ce4..1e4a1cd 100644 --- a/database/script.sql +++ b/database/script.sql @@ -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(), diff --git a/images/favicon.png b/images/favicon.png index f78f53225624e2604bf290107b54fb6d163cdd01..e22094349b706416adf6d5aab7c5ff1ffa046801 100644 GIT binary patch literal 24788 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 o_Tj*H0OkKY!Nt { loadNumbers() }, + 1: () => { loadSMS() }, + 2: () => { loadUSSD() }, + 3: () => {} + }; + + let lists = { 0: () => { showListNumbers($("#accordion-numbers .ui-accordion-content-active")) }, 1: () => { showListSMS($("#accordion-sms .ui-accordion-content-active")) }, - 2: () => {}, + 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: () => { noticer.success('Вкладка "USSD"') }, + 2: () => { generateListGroupUSSD($("#accordion-ussd .ui-accordion-content-active")) }, 3: () => { noticer.success('Вкладка "Сервер"') } }; $("button").button(); - $("#update").button("option", "icon", "ui-icon-refresh"); + $("#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"); $("#tabs").tabs({ activate: function( event, ui ) { - tabs[$(this).tabs( "option", "active" )](); + lists[$(this).tabs( "option", "active" )](); $("#add-number").button( "option", "disabled", $(this).tabs( "option", "active" ) > 0 ); + // $("#search").attr( "disabled", $("#search").val("") && $(this).tabs( "option", "active" ) > 1 ); } }); - $("#update").click(() => { + $("#update-tab").click(() => { + tabs[$("#tabs").tabs( "option", "active" )]() + }); + + $("#update-group").click(() => { groups[$("#tabs").tabs( "option", "active" )]() }); @@ -39,13 +53,14 @@ $(document).ready(function () { }); $("#search").on("input", function () { - tabs[$("#tabs").tabs( "option", "active" )]() + lists[$("#tabs").tabs( "option", "active" )]() }).keydown(function (e) { - e.key == "Escape" && ($(this).val(""), tabs[$("#tabs").tabs( "option", "active" )]()) + e.key == "Escape" && ($(this).val(""), lists[$("#tabs").tabs( "option", "active" )]()) }); loadNumbers(); loadSMS(); + loadUSSD(); $("body").fadeTo(500, 1); }) @@ -81,10 +96,10 @@ function isNumeric(value) { return /^-?\d+$/.test(value); } -function divNotFoundNumbers() { +function divNotFoundNumbers(text = '') { let notFound = $('.notFoundNumbers'); let divTable = $('.body-rows'); - let divNotFound = $('
'); + let divNotFound = $(`
${text}
`); divNotFound.css({ "color": "#333", @@ -92,8 +107,8 @@ function divNotFoundNumbers() { "padding": "20px 0 20px 0" }); - this.push = function(text = 'Нет номеров') { - divTable.append(divNotFound.html(text)); + this.push = function() { + divTable.append(divNotFound); } this.remove = function() { @@ -101,6 +116,10 @@ function divNotFoundNumbers() { } } +function pEmpty(text) { + return $(`

${text}

`).css('text-align', 'center'); +} + /************************************************************************************ Обработка таблицы с номерами телефонов @@ -117,7 +136,7 @@ function loadNumbers() { function generateListNumberGroups(data) { if (!$(data).children().length) { - $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); + $("#tabs-numbers").html(pEmpty('Номера телефонов отсутствуют')); return; } @@ -136,7 +155,7 @@ function generateListNumberGroups(data) { function generateListGroupNumbers(panel) { if (!$("#accordion-numbers").children().length) { noticer.warning("Номера телефонов отсутствуют"); - $("#tabs-numbers").html('

Номера телефонов отсутствуют

'); + $("#tabs-numbers").html(pEmpty('Номера телефонов отсутствуют')); return; } @@ -171,7 +190,7 @@ function showListNumbers(panel, data = numbers.filter(e => e.number.includes($(" }); if (!body.children().length) - (new divNotFoundNumbers).push(); + (new divNotFoundNumbers('Нет номеров')).push(); } function viewNumber(panel, number) { @@ -321,7 +340,7 @@ function delNumber(panel, currentWindow) { /************************************************************************************ - Обработка таблицы с SMS + Обработка таблицы с SMS ************************************************************************************/ @@ -335,7 +354,7 @@ function loadSMS() { function generateListSMSGroups(data) { if (!$(data).children().length) { - $("#tabs-sms").html('

SMS отсутствуют

'); + $("#tabs-sms").html(pEmpty('SMS отсутствуют')); return; } @@ -354,7 +373,7 @@ function generateListSMSGroups(data) { function generateListGroupSMS(panel) { if (!$("#accordion-sms").children().length) { noticer.warning("SMS отсутствуют"); - $("#tabs-sms").html('

SMS отсутствуют

'); + $("#tabs-sms").html(pEmpty('SMS отсутствуют')); return; } @@ -386,7 +405,7 @@ function showListSMS(panel, data = sms.filter(e => e.from.includes($("#search"). }); if (!body.children().length) - (new divNotFoundNumbers).push(); + (new divNotFoundNumbers('Нет SMS')).push(); } function viewSMS(panel, id, number) { @@ -454,3 +473,138 @@ function delSMS(panel, currentWindow) { noticer.error(error.message); }); } + +/************************************************************************************ + + Обработка таблицы с USSD + +************************************************************************************/ + +function loadUSSD() { + request('listussdgroups', 'text').then(data => { + data.error ? noticer.error(data.message) : 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 (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(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); + }); +} diff --git a/public/style.css b/public/style.css index fe99a79..79134bf 100644 --- a/public/style.css +++ b/public/style.css @@ -125,7 +125,7 @@ tr.row:hover, tr.row:nth-child(even):hover { /* EDIT NUMBER */ -.number-label, .sms-label { +.number-label, .sms-label, .ussd-label { color: #333; text-align: right; } @@ -134,11 +134,11 @@ tr.row:hover, tr.row:nth-child(even):hover { height: 30px; } -.sms-label-text { +.sms-label-text, .ussd-label-text { vertical-align:top } -.sms-value { +.sms-value, .ussd-value { width: 300px; text-align: left; } @@ -171,17 +171,17 @@ tr.row:hover, tr.row:nth-child(even):hover { padding: 15px 0 5px 0; } -.comment-content, .sms-content { +.comment-content, .sms-content, .ussd-content { width: 100%; height: 100%; } -#number-comment, #sms-content { +#number-comment, #sms-content, #ussd-content { width: calc(100% - 6px); resize: none; } -#sms-content { +#sms-content, #ussd-content { height: 150px; outline: none; border: 1px solid#c5c5c5; @@ -190,6 +190,10 @@ tr.row:hover, tr.row:nth-child(even):hover { padding: 5px 10px 5px 10px; } -th.sms-content-width, td.sms-content-width { +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/source/daster.d b/source/daster.d index c4b5181..9f55970 100644 --- a/source/daster.d +++ b/source/daster.d @@ -18,6 +18,7 @@ import structures; import requests.numbers; import requests.sms; +import requests.ussd; static ServerInfo serverInfo; @@ -210,6 +211,18 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { 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; default: res.redirect("/"); } 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 index f4ccd69..5f14461 100644 --- a/source/sql.d +++ b/source/sql.d @@ -6,6 +6,12 @@ import structures; import std.conv; +/* + + Запросы для таблицы номеров телефонов + +*/ + GroupDB[] sqlGetListGroups() { GroupDB[] groups; try { @@ -201,6 +207,12 @@ bool sqlInsertNumber(NumberDB number) { return true; } +/* + + Запросы для таблицы SMS + +*/ + SMSDB[] sqlGetSMSNumbers() { SMSDB[] numbers; try { @@ -293,3 +305,106 @@ bool sqlDeleteSMS(int idsms) { } 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; +} diff --git a/source/structures.d b/source/structures.d index 10ccba1..ec79c5a 100644 --- a/source/structures.d +++ b/source/structures.d @@ -44,3 +44,12 @@ struct SMSDB { string from; string text; } + +struct USSDDB { + int id; + string date; + string to; + int type; + string type_comment; + string text; +} diff --git a/views/index.dt b/views/index.dt index 7c82457..c6e2f47 100644 --- a/views/index.dt +++ b/views/index.dt @@ -10,13 +10,14 @@ head script(src='script.js') body div.div-header - div.div-update.main-button - button#update div.div-add.main-button button#add-number Добавить номер - div.div-search + div.div-search.main-button input.input-focus#search(name='search', type='text', value='', placeholder='Найти номер') - // div.div-user Вы вошли как #{user.name} + 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 Выход diff --git a/views/list_ussd_groups.dt b/views/list_ussd_groups.dt new file mode 100644 index 0000000..249fa78 --- /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 + thead.head + tr + th.ussd-content-width Дата + th.ussd-content-width-type Тип + th Текст сообщения + div.body-rows + table + tbody.body 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} From 7a71bb7e015f92e0b71574f719c1cec335e81c50 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 4 Jun 2023 21:50:36 +0300 Subject: [PATCH 07/10] v0.0.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Реализовано: просмотр параметров сервера и возможность редактирования --- database/script.sql | 67 ++++++++++++++++----------------- js/script.js | 67 +++++++++++++++++++++++++++++++-- public/style.css | 19 ++++++++-- source/daster.d | 7 ++++ source/requests/server.d | 30 +++++++++++++++ source/sql.d | 74 +++++++++++++++++++++++++++++-------- source/structures.d | 7 ++++ source/version_.d | 2 +- views/index.dt | 2 - views/list_number_groups.dt | 4 +- views/list_sms_groups.dt | 4 +- views/list_ussd_groups.dt | 4 +- views/server.dt | 23 ++++++++++++ 13 files changed, 247 insertions(+), 63 deletions(-) create mode 100644 source/requests/server.d create mode 100644 views/server.dt diff --git a/database/script.sql b/database/script.sql index 1e4a1cd..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 ( @@ -51,13 +51,13 @@ create table if not exists da_ussd_type ( ); insert into da_ussd_type (da_id, da_comment) - values - ('0', 'Уведомление'), - ('1', 'Запрос'), - ('2', 'Прервано сетью'), - ('3', 'Ответ другого локального клиента'), - ('4', 'Операция не поддерживается'), - ('5', 'Тайм-аут сети'); + values + ('0', 'Уведомление'), + ('1', 'Запрос'), + ('2', 'Прервано сетью'), + ('3', 'Ответ другого локального клиента'), + ('4', 'Операция не поддерживается'), + ('5', 'Тайм-аут сети'); create table if not exists da_ussd ( da_id bigserial not null, @@ -69,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/js/script.js b/js/script.js index 1a444f2..583147f 100644 --- a/js/script.js +++ b/js/script.js @@ -9,7 +9,7 @@ $(document).ready(function () { 0: () => { loadNumbers() }, 1: () => { loadSMS() }, 2: () => { loadUSSD() }, - 3: () => {} + 3: () => { loadServerInfo() } }; let lists = { @@ -23,7 +23,7 @@ $(document).ready(function () { 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: () => { noticer.success('Вкладка "Сервер"') } + 3: () => {} }; $("button").button(); @@ -36,7 +36,7 @@ $(document).ready(function () { activate: function( event, ui ) { lists[$(this).tabs( "option", "active" )](); $("#add-number").button( "option", "disabled", $(this).tabs( "option", "active" ) > 0 ); - // $("#search").attr( "disabled", $("#search").val("") && $(this).tabs( "option", "active" ) > 1 ); + $("#update-group").button( "option", "disabled", $(this).tabs( "option", "active" ) > 2 ); } }); @@ -61,6 +61,7 @@ $(document).ready(function () { loadNumbers(); loadSMS(); loadUSSD(); + loadServerInfo(); $("body").fadeTo(500, 1); }) @@ -608,3 +609,63 @@ function delUSSD(panel, currentWindow) { noticer.error(error.message); }); } + +/************************************************************************************ + + Обработка таблицы информации о сервере + +************************************************************************************/ + +function loadServerInfo() { + request('serverinfo', 'text').then(data => { + data.error ? noticer.error(data.message) : 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/style.css b/public/style.css index 79134bf..71ea28c 100644 --- a/public/style.css +++ b/public/style.css @@ -71,7 +71,7 @@ input { margin-top: 20px } -.content table { +.table-content { width: 100%; border-collapse: collapse; border-spacing: 0; @@ -125,15 +125,28 @@ tr.row:hover, tr.row:nth-child(even):hover { /* EDIT NUMBER */ -.number-label, .sms-label, .ussd-label { +.number-label, .sms-label, .ussd-label, .server-label { color: #333; text-align: right; } -.number-value { +.number-value, .server-value { height: 30px; } +.server-value { + text-align: left; + padding-left: 10px; +} + +.server-input { + height: 100%; +} + +.server-button { + margin-top: 30px; +} + .sms-label-text, .ussd-label-text { vertical-align:top } diff --git a/source/daster.d b/source/daster.d index 9f55970..29609de 100644 --- a/source/daster.d +++ b/source/daster.d @@ -19,6 +19,7 @@ import structures; import requests.numbers; import requests.sms; import requests.ussd; +import requests.server; static ServerInfo serverInfo; @@ -223,6 +224,12 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { case "delussd": sendDelUSSD(req, res); break; + case "serverinfo": + getServerInfo(req, res); + break; + case "writeserverinfo": + sendWriteServerInfo(req, res); + break; default: res.redirect("/"); } 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/sql.d b/source/sql.d index 5f14461..a488407 100644 --- a/source/sql.d +++ b/source/sql.d @@ -6,11 +6,9 @@ import structures; import std.conv; -/* - - Запросы для таблицы номеров телефонов - -*/ +/*********************************************************** + Запросы для таблицы номеров телефонов +***********************************************************/ GroupDB[] sqlGetListGroups() { GroupDB[] groups; @@ -207,11 +205,9 @@ bool sqlInsertNumber(NumberDB number) { return true; } -/* - - Запросы для таблицы SMS - -*/ +/*********************************************************** + Запросы для таблицы SMS +***********************************************************/ SMSDB[] sqlGetSMSNumbers() { SMSDB[] numbers; @@ -306,11 +302,9 @@ bool sqlDeleteSMS(int idsms) { return true; } -/* - - Запросы для таблицы USSD - -*/ +/*********************************************************** + Запросы для таблицы USSD +***********************************************************/ USSDDB[] sqlGetUSSDNumbers() { USSDDB[] numbers; @@ -408,3 +402,53 @@ bool sqlDeleteUSSD(int idussd) { } 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 ec79c5a..04c2a95 100644 --- a/source/structures.d +++ b/source/structures.d @@ -53,3 +53,10 @@ struct USSDDB { 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 fd2e40b..296864d 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.6"; +enum dasterVersion = "v0.0.8"; diff --git a/views/index.dt b/views/index.dt index c6e2f47..60cf645 100644 --- a/views/index.dt +++ b/views/index.dt @@ -35,6 +35,4 @@ body div#tabs-numbers div#tabs-sms div#tabs-ussd - p Список результатов USSD запросов div#tabs-server - p Информация о сервере diff --git a/views/list_number_groups.dt b/views/list_number_groups.dt index 67052e8..9a907a8 100644 --- a/views/list_number_groups.dt +++ b/views/list_number_groups.dt @@ -2,7 +2,7 @@ div#accordion-numbers - foreach (group; listGroups) h3 #{group.comment} div.group-content(data-group-name='#{group.name}') - table + table.table-content thead.head tr th Номер @@ -12,5 +12,5 @@ div#accordion-numbers th Черные звонки th Комментарий div.body-rows - table + table.table-content tbody.body diff --git a/views/list_sms_groups.dt b/views/list_sms_groups.dt index a511995..c4f7270 100644 --- a/views/list_sms_groups.dt +++ b/views/list_sms_groups.dt @@ -2,12 +2,12 @@ div#accordion-sms - foreach (number; numbers) h3 На номер #{number.to} div.group-content(data-to='#{number.to}') - table + table.table-content thead.head tr th.sms-content-width Дата th.sms-content-width От кого th Текст сообщения div.body-rows - table + table.table-content tbody.body diff --git a/views/list_ussd_groups.dt b/views/list_ussd_groups.dt index 249fa78..4cd5b44 100644 --- a/views/list_ussd_groups.dt +++ b/views/list_ussd_groups.dt @@ -2,12 +2,12 @@ div#accordion-ussd - foreach (number; numbers) h3 На номер #{number.to} div.group-content(data-to='#{number.to}') - table + table.table-content thead.head tr th.ussd-content-width Дата th.ussd-content-width-type Тип th Текст сообщения div.body-rows - table + table.table-content tbody.body 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 Сохранить изменения From 639118f7818e0a3f748ab462ad3fffa970039110 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Mon, 5 Jun 2023 00:35:45 +0300 Subject: [PATCH 08/10] v0.0.9 + 404 page --- source/daster.d | 6 ++++++ source/version_.d | 2 +- views/404.dt | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 views/404.dt diff --git a/source/daster.d b/source/daster.d index 29609de..01789f6 100644 --- a/source/daster.d +++ b/source/daster.d @@ -27,6 +27,10 @@ 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); @@ -99,12 +103,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; diff --git a/source/version_.d b/source/version_.d index 296864d..0492867 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.8"; +enum dasterVersion = "v0.0.9"; diff --git a/views/404.dt b/views/404.dt new file mode 100644 index 0000000..ae9c976 --- /dev/null +++ b/views/404.dt @@ -0,0 +1,6 @@ +doctype html +head + title 404 +body + div + p Страница не найдена From e74c0a1af02b5d243e647931148fed57e707ff22 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Mon, 5 Jun 2023 19:48:33 +0300 Subject: [PATCH 09/10] v0.0.10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Добавлена авторизация как отдельная страница + Небольшие исправления в JS при ответе от сервера --- dub.json | 4 +- dub.selections.json | 6 +-- js/authorization.js | 48 ++++++++++++++++++++++ js/script.js | 40 +++++++++++++----- public/authorization.css | 64 +++++++++++++++++++++++++++++ settings.conf.sample | 4 ++ source/daster.d | 73 ++++++++++++++++++++++----------- source/requests/authorization.d | 47 +++++++++++++++++++++ source/structures.d | 9 ++++ source/version_.d | 2 +- views/authorization.dt | 26 ++++++++++++ 11 files changed, 283 insertions(+), 40 deletions(-) create mode 100644 js/authorization.js create mode 100644 public/authorization.css create mode 100644 source/requests/authorization.d create mode 100644 views/authorization.dt diff --git a/dub.json b/dub.json index dcc38e8..d1e1943 100644 --- a/dub.json +++ b/dub.json @@ -5,9 +5,9 @@ "copyright": "Copyright © 2023, Alexander Zhirov", "dependencies": { "vibe-d": "~>0.9", - "singlog": "~>0.3.2", + "singlog": "~>0.4.0", "arsd-official:postgres": "~>10.9.10", - "readconf": "~>0.3.1" + "readconf": "~>0.4.0" }, "buildTypes": { "debug": { diff --git a/dub.selections.json b/dub.selections.json index a6ee273..08bea5a 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -10,9 +10,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.2", + "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/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/script.js b/js/script.js index 583147f..7a00ef9 100644 --- a/js/script.js +++ b/js/script.js @@ -58,6 +58,14 @@ $(document).ready(function () { 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(); @@ -129,7 +137,10 @@ function pEmpty(text) { function loadNumbers() { request('listnumbergroups', 'text').then(data => { - data.error ? noticer.error(data.message) : generateListNumberGroups(data); + if (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(data).message); + else + generateListNumberGroups(data); }).catch(error => { noticer.error(error.message); }); @@ -161,8 +172,8 @@ function generateListGroupNumbers(panel) { } request('listgroupnumbers', 'json', { group: panel.data("group-name") }).then(data => { - if (isJSON(data) && JSON.parse(data).error) - noticer.error(JSON.parse(data).message); + if (data.error) + noticer.error(data.message); else { numbers = data; showListNumbers(panel); @@ -347,7 +358,10 @@ function delNumber(panel, currentWindow) { function loadSMS() { request('listsmsgroups', 'text').then(data => { - data.error ? noticer.error(data.message) : generateListSMSGroups(data); + if (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(data).message); + else + generateListSMSGroups(data); }).catch(error => { noticer.error(error.message); }); @@ -379,8 +393,8 @@ function generateListGroupSMS(panel) { } request('listgroupsms', 'json', { to: panel.data("to") }).then(data => { - if (isJSON(data) && JSON.parse(data).error) - noticer.error(JSON.parse(data).message); + if (data.error) + noticer.error(data.message); else { sms = data; showListSMS(panel); @@ -483,7 +497,10 @@ function delSMS(panel, currentWindow) { function loadUSSD() { request('listussdgroups', 'text').then(data => { - data.error ? noticer.error(data.message) : generateListUSSDGroups(data); + if (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(data).message); + else + generateListUSSDGroups(data); }).catch(error => { noticer.error(error.message); }); @@ -515,8 +532,8 @@ function generateListGroupUSSD(panel) { } request('listgroupussd', 'json', { to: panel.data("to") }).then(data => { - if (isJSON(data) && JSON.parse(data).error) - noticer.error(JSON.parse(data).message); + if (data.error) + noticer.error(data.message); else { ussd = data; showListUSSD(panel); @@ -618,7 +635,10 @@ function delUSSD(panel, currentWindow) { function loadServerInfo() { request('serverinfo', 'text').then(data => { - data.error ? noticer.error(data.message) : showServerInfo(data); + if (isJSON(data) && JSON.parse(data).error) + noticer.error(JSON.parse(data).message); + else + showServerInfo(data); }).catch(error => { noticer.error(error.message); }); diff --git a/public/authorization.css b/public/authorization.css new file mode 100644 index 0000000..2b1cf94 --- /dev/null +++ b/public/authorization.css @@ -0,0 +1,64 @@ +@font-face { + font-family: Scada; + src: url(Scada-Regular.ttf); +} + +body { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + overflow: hidden; + 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; + color:#333333 +} + +.label { + text-align: right; + padding-right: 10px; +} diff --git a/settings.conf.sample b/settings.conf.sample index 2b48a48..538d199 100644 --- a/settings.conf.sample +++ b/settings.conf.sample @@ -10,6 +10,10 @@ loglevel => 0 ; 0 - debug, 1 - crit, 2 logoutput => 1, 4 ; 1 - syslog, 2 - stout, 4 - file => example: 1,2 or 1,2,4 logfile => /var/log/daster.log ; if log-output set with 4 +[auth] +login => +password => + [daster-db] host => 127.0.0.1 port => 5432 diff --git a/source/daster.d b/source/daster.d index 01789f6..020c75f 100644 --- a/source/daster.d +++ b/source/daster.d @@ -15,13 +15,16 @@ import std.array; import verinfo; import pgdb; import structures; +import response; 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__); @@ -32,8 +35,9 @@ void page404(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo } 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; @@ -88,6 +92,7 @@ int main(string[] args) { if (webHost.logfile.length) log.file(webHost.logfile); rcAsteriskDB(); + rcAuth(); auto router = new URLRouter; router.post("/", &postReq); @@ -144,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) { @@ -168,23 +172,23 @@ 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("json request: " ~ jsr.to!string); switch (query) { - // case "authorization": - // authorization(req, res); - // break; - // case "logout": - // logout(req, res); - // break; + case "login": + login(req, res, serverAuthData); + break; + case "logout": + logout(req, res); + break; case "listnumbergroups": getListNumberGroups(req, res); break; @@ -441,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/requests/authorization.d b/source/requests/authorization.d new file mode 100644 index 0000000..e046886 --- /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(("Данные авторизации не верны: %s").format(req.json)); + res.send( + true, + "Данные авторизации не верны" + ); + return; + } + + auto user = UserData(true); + + req.session = res.startSession(); + req.session.set!UserData("user", user); + + log.i("Авторизация успешно пройдена"); + + res.send(); +} + +void logout(HTTPServerRequest req, HTTPServerResponse res) { + req.session.set!UserData("user", UserData.init); + res.terminateSession(); + + log.i("Выход из системы"); + + res.send(); +} + +bool checkAuth(HTTPServerRequest req) { + if (req.session) + return req.session.get!UserData("user").login; + + log.d("Отсутствует авторизация"); + + return false; +} diff --git a/source/structures.d b/source/structures.d index 04c2a95..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; diff --git a/source/version_.d b/source/version_.d index 0492867..e8028ea 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.9"; +enum dasterVersion = "v0.0.10"; 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 Войти From 082a55ea9f6e8f3442ff8b9f37ed2c5d270b997f Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Thu, 8 Jun 2023 16:56:05 +0300 Subject: [PATCH 10/10] v0.1.0-beta.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + Реализованы основные компоненты для БД + Страница авторизации + Страница 404 + Обработка ошибок + Логирование событий --- images/404.png | Bin 0 -> 35484 bytes public/404.css | 18 ++++++++++++++++++ public/authorization.css | 2 -- public/style.css | 6 +++--- source/daster.d | 2 +- source/requests/authorization.d | 8 ++++---- source/version_.d | 2 +- views/404.dt | 8 +++++--- 8 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 images/404.png create mode 100644 public/404.css diff --git a/images/404.png b/images/404.png new file mode 100644 index 0000000000000000000000000000000000000000..116c3e7fbad7b7704c857e578f3944de7d4d2577 GIT binary patch literal 35484 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 literal 0 HcmV?d00001 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 index 2b1cf94..8137a7a 100644 --- a/public/authorization.css +++ b/public/authorization.css @@ -8,7 +8,6 @@ body { align-items: center; justify-content: center; height: 100vh; - overflow: hidden; margin-top: -5%; color: #333; opacity: 0; @@ -55,7 +54,6 @@ input:hover { .title { margin: 30px; - color:#333333 } .label { diff --git a/public/style.css b/public/style.css index 71ea28c..8cd066b 100644 --- a/public/style.css +++ b/public/style.css @@ -18,7 +18,7 @@ body { div.div-header { display: flex; align-items: center; - width: 60%; + width: 70%; justify-content: center; margin-top: 30px } @@ -67,7 +67,7 @@ input { /* BODY */ .content { - width: 60%; + width: 70%; margin-top: 20px } @@ -136,7 +136,7 @@ tr.row:hover, tr.row:nth-child(even):hover { .server-value { text-align: left; - padding-left: 10px; + padding: 2px 0 2px 10px; } .server-input { diff --git a/source/daster.d b/source/daster.d index 020c75f..0f4876c 100644 --- a/source/daster.d +++ b/source/daster.d @@ -180,7 +180,7 @@ void postReq(HTTPServerRequest req, HTTPServerResponse res) { return; } - log.d("json request: " ~ jsr.to!string); + log.d("%s: json request %s".format(req.clientAddress.toAddressString(), jsr.to!string)); switch (query) { case "login": diff --git a/source/requests/authorization.d b/source/requests/authorization.d index e046886..5456424 100644 --- a/source/requests/authorization.d +++ b/source/requests/authorization.d @@ -10,7 +10,7 @@ void login(HTTPServerRequest req, HTTPServerResponse res, AuthData serverAuthDat if (!(serverAuthData.login == userAuthData.login && serverAuthData.password == userAuthData.password)) { - log.i(("Данные авторизации не верны: %s").format(req.json)); + log.i(req.clientAddress.toAddressString() ~ ": Данные авторизации не верны"); res.send( true, "Данные авторизации не верны" @@ -23,7 +23,7 @@ void login(HTTPServerRequest req, HTTPServerResponse res, AuthData serverAuthDat req.session = res.startSession(); req.session.set!UserData("user", user); - log.i("Авторизация успешно пройдена"); + log.i(req.clientAddress.toAddressString() ~ ": Вход в систему"); res.send(); } @@ -32,7 +32,7 @@ void logout(HTTPServerRequest req, HTTPServerResponse res) { req.session.set!UserData("user", UserData.init); res.terminateSession(); - log.i("Выход из системы"); + log.i(req.clientAddress.toAddressString() ~ ": Выход из системы"); res.send(); } @@ -41,7 +41,7 @@ bool checkAuth(HTTPServerRequest req) { if (req.session) return req.session.get!UserData("user").login; - log.d("Отсутствует авторизация"); + log.d(req.clientAddress.toAddressString() ~ ": Отсутствует авторизация"); return false; } diff --git a/source/version_.d b/source/version_.d index e8028ea..fa80cc9 100644 --- a/source/version_.d +++ b/source/version_.d @@ -1,3 +1,3 @@ module version_; -enum dasterVersion = "v0.0.10"; +enum dasterVersion = "v0.1.0-beta.1"; diff --git a/views/404.dt b/views/404.dt index ae9c976..0d4d34b 100644 --- a/views/404.dt +++ b/views/404.dt @@ -1,6 +1,8 @@ doctype html head - title 404 + title Страница не найдена + link(rel='icon', type='image/png', sizes='128x128', href='favicon.png') + link(rel='stylesheet', type='text/css', href='404.css') body - div - p Страница не найдена + div.page + div.pic

PWfvc%K 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 H+minRac-S) literal 0 HcmV?d00001 diff --git a/js/script.js b/js/script.js index 2dc6f90..711df54 100644 --- a/js/script.js +++ b/js/script.js @@ -1,11 +1,10 @@ +var numbers = []; + $(document).ready(function () { message = new Message; - // (new divNotFoundNumbers).push("Загрузка..."); - $("button").button(); $("#tabs").tabs(); - // $("#accordion-numbers").accordion(); // $(".addNumber").click(() => { // numberAdd() @@ -13,12 +12,19 @@ $(document).ready(function () { $("body").fadeTo(500, 1); - // getData("Список номеров успешно загружен"); - $(".search").on("input", function () { - // showNumbers(numbers.filter(e => e.id.includes($(this).val()))) + showNumbers( + $("#accordion-numbers .ui-accordion-content-active"), + numbers.filter(e => e.number.includes($(this).val())) + ) + }).on("focus", function () { + if ($(this).val()) + showNumbers( + $("#accordion-numbers .ui-accordion-content-active"), + numbers.filter(e => e.number.includes($(this).val())) + ); }).keydown(function (e) { - // e.key == "Escape" && ($(this).val(""), showNumbers()) + e.key == "Escape" && ($(this).val(""), showNumbers($("#accordion-numbers .ui-accordion-content-active"))) }); loadData(); @@ -52,7 +58,7 @@ function isJSON(str) { } function loadData() { - request('listsgroups', 'json').then(data => { + request('listsgroups', 'text').then(data => { data.error ? message.error(data.message) : generateListsGroups(data); }).catch(error => { message.error(error.message); @@ -60,12 +66,7 @@ function loadData() { } function generateListsGroups(data) { - let numbers = $("#tabs-numbers"); - let group = $('