Разработка:
- БД скрипт с начальными данными
- вывод списка номеров телефонов по группам
This commit is contained in:
Alexander Zhirov 2023-05-31 02:04:03 +03:00
parent ca5259c638
commit 815b5a6b6a
34 changed files with 1229 additions and 49 deletions

19
.gitignore vendored
View File

@ -1 +1,20 @@
/database/volumes-data
.dub
docs.json
__dummy.html
docs/
/web
web.so
web.dylib
web.dll
web.a
web.lib
web-test-*
*.exe
*.o
*.obj
*.lst
bin
web.log
settings.conf
.vscode

33
certs/test.local.crt Normal file
View File

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFnjCCA4agAwIBAgIBATANBgkqhkiG9w0BAQsFADCBkjELMAkGA1UEBhMCUlUx
DzANBgNVBAgMBlJ1c3NpYTEWMBQGA1UEBwwNS3JpdmVua292c2tvZTEPMA0GA1UE
CgwGWmhpcm92MRIwEAYDVQQLDAlBbGV4YW5kZXIxDzANBgNVBAMMBnpoaXJvdjEk
MCIGCSqGSIb3DQEJARYVYXpoaXJvdjE5OTFAZ21haWwuY29tMB4XDTIzMDQwNTA4
NTYyN1oXDTI0MDQwNDA4NTYyN1owUTELMAkGA1UEBhMCUlUxDzANBgNVBAgMBlJ1
c3NpYTERMA8GA1UEBwwIQmVsZ29yb2QxETAPBgNVBAoMCE1pcmF0b3JnMQswCQYD
VQQLDAJJVDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALkJl6TwLPax
A35IWIpq8Z8hPbbK9qTOr5EGwzh1/WCfO5bidhGG/UhZV7JA9Slh+w/PbjMq4qyE
fi654ODmi3dcJgMEoWb3RP+CBn/1A3jRBMBTXGyD7aKNgi7vPuJN51wyVUtQ1P6s
Oc4rF/ob/vArXHS0q9sLJoaOr9DOZxCjycMX/k7GaeoEluf7Z/g1kgOU4wqYxeJk
avinh3NQyuixZ0fiPBi5c9WmXLvzP3cb+BM2aybXZlb4bsRQBWsPy0NDVrApkAk7
guTrXPnwaU0y7XfGMr3nFpJddmGBy3T86k9xZQtdWCoDE4ie/GzDBw1837TPUre/
IV4z1hM7/CO1YWjVE/4JCaG+0TBnDNfiHjzPu0jCpfkX+B8YsTBSRHZd9Geyn4Zh
L/Yz3bPnbvWgs9VS/MU0kgNZ+kCvDanuiJhw12WTtbkx0ZCk1YPP77V9zR0Se5w8
O94vaNpJ4Yav/y7bG8kG4uIJR6r/cQbJYgi3SZN2aEVX7F/bjC29Op5abAr6jz7J
eiu4iKcxXOH9KZQeH+Kwkg2KEGMKbcnaIctjtz3A/f/3MUVVgFnvf9/VB0VOEg3x
sEtHc4WMsWDmYS9Kk25rEfkL3eImZSXRkVCMU4AjhHeN/8eUgJuV9kIE0kpzJgUR
8wxqj02MxOqalDY2dpPR3FPpjb+aydDZAgMBAAGjPzA9MAkGA1UdEwQCMAAwCwYD
VR0PBAQDAgXgMCMGA1UdEQQcMBqCCnRlc3QubG9jYWyCDCoudGVzdC5sb2NhbDAN
BgkqhkiG9w0BAQsFAAOCAgEAVhigTzgvhGE5uyJPaBzAFjNzLqgQuEqKkvPFNvdI
/mXoO1IAtJ6GZS+3MlT3jORj7kric3ipaTd7+zdpMVjoFfSCnFoBcd+NKs0lehIW
rJdvDb9WMP2kQJzTSxMRz0qpYGpEWgmDjxPlq8HaLFTIE9XuWwfIk+noY18Ovhcm
TB1zYA7rwZ26Cf9pfugCw167sRzw4KYy9suX57pNrxhwMSOIawmZeou4bkfMk100
u2wSnFeJZW1hn4Cy4n3HsWiclyAv79SrygRNa4ljKNFnoJ1G9UZf+9dnmn8SDQn2
sMytIf+ouwwYPzCrOjH+9NwC5MqtwSx8EcGc8nF/J6ArWc2fCGNdszaa1NyhP2Lz
SYZg4NdxysywpnwKgby3MmIs7yXxIE8cAaSPLBS2PpqLmohur1eqsMj1XBLk5YpO
LOVR6xzcTqeB8Twt6D/rpw61lBg7lDrmrXD+PMi6yANVS6Yq2wgioT7VjT6jTXi+
SPTKA7Cj1R6UPvwC+XUwIzKXNjhjdTagI7/ePV9Ntc113Wkvb+WECyNPmi2Jbp8e
S19BFiBApvynTEuhq/tuMNSvjiQnHr9Vy7orU+HaGNc4L2KlZ6aE/33XAKgrYyPx
nVNBWX+wciBuiVPmAs2vsc6j3mUreZTvUT5G3UiwafxHbVrHO43gB4p1QuDvEvKp
WwU=
-----END CERTIFICATE-----

51
certs/test.local.key Normal file
View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -9,9 +9,15 @@ services:
POSTGRES_PASSWORD: asterisk
PGTZ: Europe/Moscow
TZ: Europe/Moscow
networks:
- db_net
ports:
- 5432:5432
volumes:
- ./volumes-data:/var/lib/postgresql/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
db_net:
name: db_net

View File

@ -1,62 +1,62 @@
create table if not exists "groups" (
"name" varchar(20) not null,
"comment" varchar(100) default null,
constraint groups_pk primary key ("name")
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)
);
insert into "groups" ("name", "comment")
insert into da_groups (da_name, da_comment)
values
('general', 'Общие контакты'),
('work', 'Рабочие контакты'),
('personal', 'Личные контакты');
create table if not exists lists (
"name" varchar(20) not null,
"comment" varchar(100) default null,
constraint lists_pk primary key ("name")
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)
);
insert into lists ("name", "comment")
insert into da_lists (da_name, da_comment)
values
('general', 'Общий список'),
('whitelist', 'Белый список'),
('blacklist', 'Черный список');
('general', 'Общий'),
('whitelist', 'Белый'),
('blacklist', 'Черный');
create table if not exists numbers (
"number" varchar(12) not null,
"group" varchar(20) not null default 'general',
list varchar(20) not null default 'general',
all_cc int not null default 0,
white_cc int not null default 0,
black_cc int not null default 0,
"comment" varchar(100) default null,
constraint numbers_pk primary key ("number"),
foreign key ("group") references "groups" ("name") on delete set null on update cascade,
foreign key (list) references lists ("name") on delete set null on update cascade
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
);
create table if not exists sms (
id bigserial not null,
"date" timestamp not null default NOW(),
"to" varchar(12) not null,
"from" varchar(12) not null,
"text" text not null,
constraint sms_pk primary key (id)
create table if not exists da_sms (
da_id bigserial not null,
da_date timestamp not null default NOW(),
da_to varchar(12) not null,
da_from varchar(12) not null,
da_text text not null,
constraint da_sms_pk primary key (da_id)
);
create table if not exists ussd (
id bigserial not null,
"date" timestamp not null default NOW(),
"to" varchar(12) not null,
"type" smallint not null,
"text" text not null,
constraint ussd_pk primary key (id)
create table if not exists da_ussd (
da_id bigserial not null,
da_date timestamp not null default NOW(),
da_to varchar(12) not null,
da_type smallint not null,
da_text text not null,
constraint da_ussd_pk primary key (da_id)
);
create table if not exists "server" (
address varchar(50) not null,
transparent_mode bool not null default false,
internal_number varchar(12) not null,
external_number varchar(12) not null,
constraint server_pk primary key (address)
create table if not exists da_server (
da_address varchar(50) not null,
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)
);

33
dub.json Normal file
View File

@ -0,0 +1,33 @@
{
"authors": [
"Alexander Zhirov"
],
"copyright": "Copyright © 2023, Alexander Zhirov",
"dependencies": {
"vibe-d": "~>0.9",
"ldap": "~>0.4",
"singlog": "~>0.3.1",
"arsd-official:postgres": "~>10.9.10",
"readconf": "~>0.3.1"
},
"buildTypes": {
"debug": {
"buildOptions": [
"debugMode",
"debugInfo"
]
},
"release": {
"buildOptions": [
"releaseMode",
"inline",
"optimize"
]
}
},
"description": "Dialplan Asterisk - веб-сервер для управления обработкой вызовов Asterisk",
"license": "proprietary",
"name": "daster",
"targetPath": "bin",
"targetType": "executable"
}

22
dub.selections.json Normal file
View File

@ -0,0 +1,22 @@
{
"fileVersion": 1,
"versions": {
"arsd-official": "10.9.10",
"datefmt": "1.0.4",
"diet-ng": "1.8.1",
"eventcore": "0.9.25",
"ldap": "0.4.0",
"libasync": "0.8.6",
"memutils": "1.0.9",
"mir-linux-kernel": "1.0.1",
"openssl": "3.3.0",
"openssl-static": "1.0.2+3.0.8",
"readconf": "0.3.1",
"silly": "1.1.1",
"singlog": "0.3.1",
"stdx-allocator": "2.77.5",
"taggedalgebraic": "0.11.22",
"vibe-core": "2.2.0",
"vibe-d": "0.9.6"
}
}

4
dub.settings.json Normal file
View File

@ -0,0 +1,4 @@
{
"defaultArchitecture": "x86_64",
"defaultCompiler": "ldc2"
}

BIN
images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

2
jq/jquery-3.7.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
jq/jquery-ui.min.css vendored Normal file

File diff suppressed because one or more lines are too long

6
jq/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

100
js/message.js Normal file
View File

@ -0,0 +1,100 @@
class Message {
timer;
constructor() {
this.div = $('<div id="div-message"></div>');
this.div.css({
"position": "absolute",
"top": "10px",
"right": "20px",
"z-index": "1000"
});
$('body').append(this.div);
}
success(message, delay = 6000) {
this.print(message, delay, '#52b818', '#bffdc0');
}
warning(message, delay = 6000) {
this.print(message, delay, '#b8ae18', '#f8fdbf');
}
error(message, delay = 6000) {
this.print(message, delay, '#b96161', '#fddede');
}
print = function(message, delay, border, background) {
if (delay < 6000) delay = 6000;
let Timer = function(callback, delay) {
let timerId, start, remaining = delay;
this.pause = function() {
clearTimeout(timerId);
remaining -= new Date() - start;
};
this.resume = function() {
start = new Date();
clearTimeout(timerId);
timerId = setTimeout(callback, remaining);
};
this.dead = function() {
clearTimeout(timerId);
};
this.resume();
}
let newMessage = $(`<div class="message">${message}</div>`);
newMessage.css({
"border": `1px solid ${border}`,
"background-color": `${background}`,
"color": "#333",
"padding": "10px 30px",
"text-align": "center",
"display": "none",
"margin": "10px 0 0 0",
"width": "350px",
"opacity": "1",
"cursor": "pointer"
});
newMessage.hover(function(){
$(this).css({
"opacity": "1"
});
}, function(){
$(this).css({
"opacity": "0.3"
});
});
this.div.append(newMessage);
let opacityTimeout = setTimeout(function() {
newMessage.fadeTo(1000, 0.3);
}, 2500);
newMessage.fadeIn(500).mouseenter(() => {
clearTimeout(opacityTimeout);
this.timer.pause();
}).mouseleave(() => {
this.timer.resume();
}).click(() => {
this.timer.dead();
newMessage.fadeOut(0, function(){
this.remove();
});
});
this.timer = new Timer(() => {
newMessage.fadeOut(500, function(){
this.remove();
});
}, delay);
}
}

90
js/script.js Normal file
View File

@ -0,0 +1,90 @@
$(document).ready(function () {
message = new Message;
// (new divNotFoundNumbers).push("Загрузка...");
$("button").button();
$("#tabs").tabs();
// $("#accordion-numbers").accordion();
// $(".addNumber").click(() => {
// numberAdd()
// });
$("body").fadeTo(500, 1);
// getData("Список номеров успешно загружен");
$(".search").on("input", function () {
// showNumbers(numbers.filter(e => e.id.includes($(this).val())))
}).keydown(function (e) {
// e.key == "Escape" && ($(this).val(""), showNumbers())
});
loadData();
})
async function request(query, type, queryData = {}) {
let response = await fetch('.', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({
...queryData,
query: query
})
});
if (!response.ok)
throw new Error(`Произошла неизвестаня ошибка: ${response.status}`);
const data = await response[type]();
return data;
}
function isJSON(str) {
try {
return (JSON.parse(str) && !!str);
} catch (e) {
return false;
}
}
function loadData() {
request('listsgroups', 'json').then(data => {
data.error ? message.error(data.message) : generateListsGroups(data);
}).catch(error => {
message.error(error.message);
});
}
function generateListsGroups(data) {
let numbers = $("#tabs-numbers");
let group = $('<div id="accordion-numbers"></div>');
$(data).each((i, j) => {
group.append(`<h3>${j.comment}</h3><div class="group-content" data-group-name="${j.name}"></div>`);
});
numbers.append(group);
$("#accordion-numbers").accordion({
heightStyle: "content",
create: function( event, ui ) {
generateGroupNumbers(ui.panel);
},
beforeActivate: function( event, ui ) {
generateGroupNumbers(ui.newPanel);
}
});
}
async function generateGroupNumbers(panel) {
request('groupnumbers', 'text', { group: panel.data("group-name") }).then(data => {
if (isJSON(data) && JSON.parse(data).error)
message.error(JSON.parse(data).message);
else {
panel.html(data);
}
}).catch(error => {
message.error(error.message);
});
}

BIN
public/Scada-Regular.ttf Normal file

Binary file not shown.

107
public/style.css Normal file
View File

@ -0,0 +1,107 @@
@font-face {
font-family: Scada;
src: url(Scada-Regular.ttf);
}
body {
display: flex;
align-items: center;
height: 100vh;
overflow: hidden;
flex-direction: column;
color: #333;
margin: 0;
opacity: 0;
font-family: Scada;
}
div.div-header {
display: flex;
align-items: center;
width: 60%;
justify-content: center;
margin-top: 30px
}
/* HEADER */
/* div.div-add {
margin-right: 20px
} */
div.div-search {
display: flex;
height: 100%
}
div.div-user {
flex-grow: 1;
text-align: center;
color: #333;
}
input {
text-align: center;
color: #333;
}
.input-focus {
border: 1px solid#c5c5c5;
}
.input-focus:hover {
border: 1px solid #ccc
}
.input-focus::placeholder {
color: #333
}
.input-focus:focus {
outline: none;
box-shadow: 1px 1px 10px 1px #007fff;
border: 1px solid #003eff;
}
/* BODY */
.content {
width: 60%;
margin-top: 20px
}
.content table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed;
}
div.body-rows {
max-height: 55vh;
overflow-x: auto;
border: 1px solid #c5c5c5;
border-top: 0;
}
th {
color: #333;
height: 50px;
background-color: #f6f6f6;
border: 1px solid #c5c5c5;
font-weight: normal;
}
td {
text-align: center;
}
.body-rows tbody tr:nth-child(even){
background: #fff;
}
tr.row:hover, tr.row:nth-child(even):hover {
background-color: #c5c5c5;
color: #fff;
cursor: pointer;
}

18
settings.conf.sample Normal file
View File

@ -0,0 +1,18 @@
[web-host]
title => "Управление диалпланом"
addresses => 127.0.0.1
http => 8080
https => 443
cert => certs/test.local.crt
key => certs/test.local.key
data => ./ ; Путь к каталогу, в котором расположены каталоги public, 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
[daster-db]
host => 127.0.0.1
port => 5432
dbname => daster
user => daster
password => daster

405
source/daster.d Normal file
View File

@ -0,0 +1,405 @@
module daster;
import vibe.vibe;
import singlog;
import readconf;
import core.stdc.stdlib : exit, EXIT_SUCCESS, EXIT_FAILURE;
import std.stdio: writefln;
import std.getopt;
import std.file;
import std.path;
import std.algorithm;
import std.array;
import verinfo;
import pgdb;
import structures;
import requests.listsgroups;
import requests.groupnumbers;
static ServerInfo serverInfo;
private void showVersion() {
writefln("daster версия %s, собрано %s", getDasterVersion(), __DATE__);
}
int main(string[] args) {
log.level(log.INFORMATION);
log.output(log.SYSLOG);
bool flagVersion;
string flagSettings;
try {
auto opt = getopt(
args,
config.bundling,
config.caseSensitive,
"settings|s",
"<файл> Путь к файлу конфигурации settings.conf",
&flagSettings,
"version|v",
"Текущая версия программы",
&flagVersion
);
if (opt.helpWanted) {
showVersion();
defaultGetoptPrinter(
"Использование: daster [ОПЦИИ]...\n",
opt.options
);
return EXIT_SUCCESS;
}
if (flagVersion) {
showVersion();
return EXIT_SUCCESS;
}
} catch (GetOptException e) {
showVersion();
log.c(e.msg);
log.i("Попробуйте 'daster -h' для дополнительной информации");
log.i("https://git.zhirov.kz/alexander/daster");
return EXIT_FAILURE;
}
if (!flagSettings.length)
flagSettings = "./settings.conf";
if (!flagSettings.exists) {
log.c("Файл конфигурации не найден: " ~ flagSettings);
exit(1);
}
rc.read(flagSettings);
rcAsteriskDB();
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);
auto router = new URLRouter;
router.post("/", &postReq);
router.get("/", &getReq);
router.get("*", serveStaticFiles(buildPath(webHost.data, "public")));
router.get("*", serveStaticFiles(buildPath(webHost.data, "images")));
router.get("*", serveStaticFiles(buildPath(webHost.data, "js")));
router.get("*", serveStaticFiles(buildPath(webHost.data, "jq")));
auto memorySessionStore = new MemorySessionStore;
auto settingsHTTP = new HTTPServerSettings;
auto settingsHTTPS = new HTTPServerSettings;
if (webHost.http) {
settingsHTTP.sessionStore = memorySessionStore;
settingsHTTP.port = webHost.http;
settingsHTTP.bindAddresses = ["::1"] ~ webHost.addresses;
}
if (webHost.https) {
settingsHTTPS.sessionStore = memorySessionStore;
settingsHTTPS.port = webHost.https;
settingsHTTPS.bindAddresses = ["::1"] ~ webHost.addresses;
settingsHTTPS.tlsContext = createTLSContext(TLSContextKind.server);
settingsHTTPS.tlsContext.useCertificateChainFile(webHost.cert);
settingsHTTPS.tlsContext.usePrivateKeyFile(webHost.key);
}
startWebServer(webHost, settingsHTTP, settingsHTTPS, router);
return EXIT_SUCCESS;
}
void startWebServer(WebHost wh, HTTPServerSettings http, HTTPServerSettings https, URLRouter router)
{
if (wh.http && wh.https) {
auto listenerHTTP = listenHTTP(http, router);
auto listenerHTTPS = listenHTTP(https, router);
scope (exit) { listenerHTTP.stopListening(); }
scope (exit) { listenerHTTPS.stopListening(); }
runApplication();
} else if (wh.http) {
auto listenerHTTP = listenHTTP(http, router);
scope (exit) { listenerHTTP.stopListening(); }
runApplication();
} else if (wh.https) {
auto listenerHTTPS = listenHTTP(https, router);
scope (exit) { listenerHTTPS.stopListening(); }
runApplication();
}
}
void getReq(HTTPServerRequest req, HTTPServerResponse res) {
// if (req.session) {
// auto user = req.session.get!UserData("userData");
// if (user.loggedIn) {
// renderMainPage(req, res);
// return;
// }
// }
// render!("index.dt", serverInfo)(res);
renderMainPage(req, res);
}
void renderMainPage(HTTPServerRequest req, HTTPServerResponse res) {
render!("index.dt", serverInfo)(res);
}
void postReq(HTTPServerRequest req, HTTPServerResponse res) {
if (req.method != HTTPMethod.POST) return;
auto jsr = req.json;
string query = jsr["query"].get!string;
if (query.empty) return;
// if (query != "authorization" && !checkAuth(req)) {
// res.send(
// true,
// "Сессия не существует. Перезагрузите страницу"
// );
// return;
// }
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 "numbers":
// numbers(req, res);
// break;
// case "add":
// addNumber(req, res);
// break;
// case "write":
// writeNumber(req, res);
// break;
// case "edit":
// editNumber(req, res);
// break;
// case "update":
// updateNumber(req, res);
// break;
// case "remove":
// removeNumber(req, res);
// break;
default:
res.redirect("/");
}
}
void rcAsteriskDB() {
ConfigSection asteriskDB;
try {
asteriskDB = rc[]["daster-db"];
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки daster-db");
exit(1);
}
if (asteriskDB["host"].empty) {
log.c("В конфигурационном файле не верны настройки daster-db.host");
exit(1);
}
if (asteriskDB["port"].empty) {
log.c("В конфигурационном файле не верны настройки daster-db.port");
exit(1);
}
if (asteriskDB["dbname"].empty) {
log.c("В конфигурационном файле не верны настройки daster-db.dbname");
exit(1);
}
if (asteriskDB["user"].empty) {
log.c("В конфигурационном файле не верны настройки daster-db.user");
exit(1);
}
try {
pgsql(
"hostaddr=" ~ asteriskDB["host"] ~
" port=" ~ asteriskDB["port"] ~
" dbname=" ~ asteriskDB["dbname"] ~
" user=" ~ asteriskDB["user"] ~
" password=" ~ asteriskDB["password"]
);
} catch (Exception e) {
log.c(e);
exit(1);
}
}
WebHost rcWebHost() {
WebHost wh;
ConfigSection webHost;
try {
webHost = rc[]["web-host"];
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host");
exit(1);
}
if (webHost["addresses"].empty) {
log.c("В конфигурационном файле не верны настройки web-host.addresses");
exit(1);
}
try {
wh.addresses = webHost["addresses"].to!string.split(',').map!(a => a.strip).array;
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.addresses");
exit(1);
}
if (webHost["http"].empty && webHost["https"].empty) {
log.c("В конфигурационном файле не верны настройки web-host.[http|https]:
должен быть указан хотя бы один протокол");
exit(1);
}
if (!webHost["http"].empty) {
try {
wh.http = webHost["http"].to!ushort;
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.http");
exit(1);
}
}
if (!webHost["https"].empty) {
try {
wh.https = webHost["https"].to!ushort;
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.https");
exit(1);
}
}
if (!webHost["https"].empty) {
if (webHost["cert"].empty || webHost["key"].empty) {
log.c("В конфигурационном файле не верны настройки web-host.[cert|key]:
необходимо указать сертификат и ключ для использования https протокола"
);
exit(1);
}
}
try {
wh.cert = webHost["cert"];
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.cert");
exit(1);
}
try {
wh.key = webHost["key"];
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.key");
exit(1);
}
if (!webHost["data"].empty) {
string data;
try {
data = webHost["data"];
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.data");
exit(1);
}
if (data.exists) {
if (!buildPath(data, "public").exists) {
log.c("В конфигурационном файле не верны настройки web-host.data - каталог не существует: public");
exit(1);
}
if (!buildPath(data, "js").exists) {
log.c("В конфигурационном файле не верны настройки web-host.data - каталог не существует: js");
exit(1);
}
if (!buildPath(data, "images").exists) {
log.c("В конфигурационном файле не верны настройки web-host.data - каталог не существует: images");
exit(1);
}
} else {
log.c("В конфигурационном файле не верны настройки web-host.data - каталог не существует: " ~ data);
exit(1);
}
wh.data = data;
}
if (!webHost["title"].empty) {
try {
wh.title = webHost["title"];
} catch (Exception e) {
log.w("Заголовок не был установлен - web-host.title");
}
}
if (!webHost["loglevel"].empty) {
try {
wh.loglevel = webHost["loglevel"].to!int;
if (wh.loglevel < 0 || wh.loglevel > 6)
throw new Exception("несуществующий уровень");
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.loglevel: " ~ e.msg);
exit(1);
}
}
if (!webHost["logoutput"].empty) {
try {
wh.logoutput = webHost["logoutput"]
.to!string.split(',')
.map!((a) {
auto flag = a.strip.to!int;
if ([1, 2, 4].canFind(flag))
return flag;
log.c("В конфигурационном файле не верны настройки web-host.logoutput");
exit(1);
})
.fold!((a, b) => a | b);
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.logoutput");
exit(1);
}
if (wh.logoutput & 4) {
if (webHost["logfile"].empty) {
log.c("В конфигурационном файле не верны настройки web-host.logfile");
exit(1);
}
try {
wh.logfile = webHost["logfile"];
} catch (Exception e) {
log.c("В конфигурационном файле не верны настройки web-host.logfile");
exit(1);
}
}
}
return wh;
}

67
source/data.d Normal file
View File

@ -0,0 +1,67 @@
module data;
import pgdb;
import singlog;
import structures;
import std.conv;
GroupDB[] getListGroups() {
GroupDB[] groups;
try {
auto queryResult = pgsql.sql(
"select distinct
n.da_group,
g.da_comment
from da_numbers n
left join da_groups g ON g.da_name = n.da_group"
);
foreach (row; queryResult) {
GroupDB data;
data.name = row["da_group"];
data.comment = row["da_comment"];
groups ~= data;
}
} catch (Exception e) {
log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
}
return groups;
}
NumberDB[] getListNumbers(string group) {
NumberDB[] numbers;
try {
auto queryResult = pgsql.sql(
"select
dan.da_number,
dal.da_comment da_list,
dan.da_all_cc,
dan.da_white_cc,
dan.da_black_cc,
dan.da_comment
from da_numbers dan
left join da_lists dal on dal.da_name = dan.da_list
where da_group = ?",
group
);
foreach (row; queryResult) {
NumberDB data;
data.number = row["da_number"];
data.list = row["da_list"];
data.all_cc = row["da_all_cc"].to!int;
data.white_cc = row["da_white_cc"].to!int;
data.black_cc = row["da_black_cc"].to!int;
data.comment = row["da_comment"];
numbers ~= data;
}
} catch (Exception e) {
log.e("Не удалось выполнить запрос к БД. " ~ e.msg);
}
return numbers;
}

38
source/pgdb.d Normal file
View File

@ -0,0 +1,38 @@
module pgdb;
import arsd.postgres;
import std.stdio;
alias pgsql = PG.getConnection;
class PG : PostgreSql {
private:
static PG _pgsql;
this(string config) {
super(config);
}
public:
@property static PG getConnection(string config = null) {
if (this._pgsql is null) {
try {
this._pgsql = new PG(config);
} catch (Exception e) {
throw new Exception(e.msg);
}
}
return this._pgsql;
}
PostgresResult sql(T...)(string query, T t) {
return cast(PostgresResult)this.query(query, t);
}
void commit() {
this.query("COMMIT");
}
void rollback() {
this.query("ROLLBACK");
}
}

View File

@ -0,0 +1,12 @@
module requests.groupnumbers;
import vibe.vibe;
import response;
import data;
import singlog;
void groupnumbers(HTTPServerRequest req, HTTPServerResponse res) {
auto jsr = req.json;
auto listNumbers = getListNumbers(jsr["group"].get!string);
render!("group-numbers-list.dt", listNumbers)(res);
}

View File

@ -0,0 +1,11 @@
module requests.listsgroups;
import vibe.vibe;
import response;
import data;
void listsgroups(HTTPServerRequest req, HTTPServerResponse res) {
// auto jsr = req.json;
res.writeJsonBody(getListGroups().serializeToJson());
}

15
source/response.d Normal file
View File

@ -0,0 +1,15 @@
module response;
import vibe.vibe;
struct Res {
bool error;
string message;
string data;
}
void send(HTTPServerResponse res, bool error = false, string message = "", string data = "") {
res.writeJsonBody(Res(
error, message, data
).serializeToJson());
}

33
source/structures.d Normal file
View File

@ -0,0 +1,33 @@
module structures;
struct ServerInfo {
string name;
}
struct WebHost {
string[] addresses;
ushort http = 0;
ushort https = 0;
string cert;
string key;
string data;
string title;
int loglevel = -1;
int logoutput = 0;
string logfile;
}
struct GroupDB {
string name;
string comment;
}
struct NumberDB {
string number;
string group;
string list;
int all_cc;
int white_cc;
int black_cc;
string comment;
}

37
source/verinfo.d Normal file
View File

@ -0,0 +1,37 @@
module verinfo;
import std.algorithm: startsWith;
import version_;
import std.array : split, join;
string getDasterVersion() {
auto verstr = dasterVersion;
if (verstr.startsWith("v"))
verstr = verstr[1 .. $];
auto parts = verstr.split("-");
if (parts.length >= 3) {
if (parts[$-1].length == 8 && parts[$-1][1 .. $].isHexNumber() && parts[$-2].isNumber())
verstr = parts[0 .. $-2].join("-") ~ "+" ~ parts[$-2 .. $].join("-");
}
return verstr;
}
private bool isHexNumber(string str) {
foreach (ch; str)
switch (ch) {
case '0': .. case '9': break;
case 'a': .. case 'f': break;
case 'A': .. case 'F': break;
default: return false;
}
return true;
}
private bool isNumber(string str) {
foreach (ch; str)
switch (ch) {
case '0': .. case '9': break;
default: return false;
}
return true;
}

3
source/version_.d Normal file
View File

@ -0,0 +1,3 @@
module version_;
enum dasterVersion = "v0.0.1";

View File

@ -0,0 +1,22 @@
- 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}

39
views/index.dt Normal file
View File

@ -0,0 +1,39 @@
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='style.css')
script(src='jquery-3.7.0.min.js')
script(src='jquery-ui.min.js')
script(src='message.js')
script(src='script.js')
body
div.div-header
// div.div-add
// button.addNumber Добавить номер
div.div-search
input.input-focus.search(name='search', type='text', value='', placeholder='Найти номер')
// div.div-user Вы вошли как #{user.name}
div.div-user Вы вошли как Александр
div.div-button
button Выход
// button(onclick='logout()') Выход
div.content
div#tabs
ul
li
a(href='#tabs-numbers') Номера телефонов
li
a(href='#tabs-sms') SMS
li
a(href='#tabs-ussd') USSD
li
a(href='#tabs-server') Сервер
div#tabs-numbers
div#tabs-sms
p Список SMS сообщений
div#tabs-ussd
p Список результатов USSD запросов
div#tabs-server
p Информация о сервере