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 f78f532..e220943 100644 Binary files a/images/favicon.png and b/images/favicon.png differ diff --git a/js/script.js b/js/script.js index d6c60f0..1a444f2 100644 --- a/js/script.js +++ b/js/script.js @@ -1,36 +1,50 @@ var numbers = []; var sms = []; +var ussd = []; $(document).ready(function () { noticer = new Noticer; let tabs = { + 0: () => { 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}