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}