diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..77c099c7e67185aa3d244160e7b7e53866105f8a
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "cellframe-sdk"]
+	path = cellframe-sdk
+	url = https://gitlab.demlabs.net/cellframe/cellframe-sdk.git
diff --git a/CellframeNodeDiagtool/AbstractDiagnostic.cpp b/CellframeNodeDiagtool/AbstractDiagnostic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3f0c8fb932a67c75c8c3b8c98fe0039ecfb5614a
--- /dev/null
+++ b/CellframeNodeDiagtool/AbstractDiagnostic.cpp
@@ -0,0 +1,422 @@
+#include "AbstractDiagnostic.h"
+
+#ifdef Q_OS_LINUX
+
+#elif defined(Q_OS_WIN)
+#include "registry.h"
+#elif defined(Q_OS_MACOS)
+#include "dap_common.h"
+#endif
+
+AbstractDiagnostic::AbstractDiagnostic(QObject *parent)
+    :QObject(parent)
+{
+#ifdef Q_OS_LINUX
+    s_nodeDataPath = "/opt/cellframe-node";
+    s_sendedDataFilePath = "/tmp/lastSendedData.json";
+#elif defined(Q_OS_WIN)
+    s_nodeDataPath = QString("%1/cellframe-node").arg(regGetUsrPath());
+    s_sendedDataFilePath = QString("%1/cellframe-node/lastSendedDiagData.json").arg(regGetUsrPath());
+#elif defined(Q_OS_MACOS)
+    s_nodeDataPath = QString("Applications/CellframeNode.app/Contents/Resources/");
+    s_sendedDataFilePath = "/tmp/lastSendedData.json";
+#endif
+
+    s_jsonFilePath = QString("%1/etc/diagdata.json").arg(s_nodeDataPath);
+
+    QFile config_file(s_jsonFilePath);
+    if(!config_file.exists())
+    {
+        config_file.open(QIODevice::WriteOnly);
+        QJsonObject obj{{"category","other"}};
+        config_file.write(QJsonDocument(obj).toJson());
+        config_file.close();
+    }
+
+    s_timer_update = new QTimer();
+    s_mac = get_mac();
+}
+
+AbstractDiagnostic::~AbstractDiagnostic()
+{
+    delete s_timer_update;
+}
+
+void AbstractDiagnostic::set_timeout(int timeout){
+    s_timer_update->stop();
+    s_timeout = timeout;
+    s_timer_update->start(s_timeout);
+}
+
+void AbstractDiagnostic::start_diagnostic()
+{
+    s_timer_update->start(s_timeout);
+}
+
+void AbstractDiagnostic::stop_diagnostic()
+{
+    s_timer_update->stop();
+}
+
+QJsonValue AbstractDiagnostic::get_mac()
+{
+    QString MAC{"unknown"};
+    foreach(QNetworkInterface netInterface, QNetworkInterface::allInterfaces())
+    {
+        // Return only the first non-loopback MAC Address
+        if (!(netInterface.flags() & QNetworkInterface::IsLoopBack))
+        {
+            qDebug()<<netInterface.hardwareAddress();
+            if(!netInterface.hardwareAddress().isEmpty())
+            {
+                MAC = netInterface.hardwareAddress();
+                break;
+            }
+        }
+    }
+
+    return MAC;
+}
+
+QString AbstractDiagnostic::get_uptime_string(long sec)
+{
+    QTime time(0, 0);
+    time = time.addSecs(sec);
+    int fullHours = sec/3600;
+
+    QString uptime = QString("%1:").arg(fullHours) + time.toString("mm:ss");
+
+    return uptime;
+}
+
+quint64 AbstractDiagnostic::get_file_size (QString flag, QString path ) {
+
+    if(flag == "log")
+        path += "/var/log";
+    else
+    if (flag == "DB")
+        path += "/var/lib/global_db";
+    else
+    if (flag == "chain")
+        path += "/var/lib/network";
+    else
+        path += "";
+
+    QDir currentFolder( path );
+
+    quint64 totalsize = 0;
+
+    currentFolder.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks );
+    currentFolder.setSorting( QDir::Name );
+
+    QFileInfoList folderitems( currentFolder.entryInfoList() );
+
+    foreach ( QFileInfo i, folderitems ) {
+        QString iname( i.fileName() );
+        if ( iname == "." || iname == ".." || iname.isEmpty() )
+            continue;
+        if(flag == "log" && i.suffix() != "log" && !i.isDir())
+            continue;
+        else
+        if(flag == "DB" && (i.suffix() != "dat" && !i.suffix().isEmpty()) && !i.isDir())
+            continue;
+        else
+        if(flag == "chain" && i.suffix() != "dchaincell" && !i.isDir())
+            continue;
+
+        if ( i.isDir() )
+            totalsize += get_file_size("", path+"/"+iname);
+        else
+            totalsize += i.size();
+    }
+
+    return totalsize;
+}
+
+QString AbstractDiagnostic::get_memory_string(long num)
+{
+    QString result = QString::number(num);
+    return result;
+
+    int gb = (num / 1024) / 1024;
+    int mb = (num-gb*1024*1024) /1024;
+    int kb = (num - (gb*1024*1024+mb*1024));
+    if (gb > 0)
+       result = QString::number(gb) + QString(" Gb ");
+    else
+       result = QString("");
+    if (mb > 0)
+       result += QString::number(mb) + QString(" Mb ");
+    if (kb > 0)
+       result += QString::number(kb) + QString(" Kb ");
+
+    return result;
+}
+
+QJsonObject AbstractDiagnostic::roles_processing()
+{
+    QJsonObject rolesObject;
+
+    QDir currentFolder(s_nodeDataPath + "/etc/network");
+
+    currentFolder.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks );
+    currentFolder.setSorting( QDir::Name );
+
+    QFileInfoList folderitems( currentFolder.entryInfoList() );
+
+    foreach ( QFileInfo i, folderitems ) {
+        QString iname( i.fileName() );
+        if ( iname == "." || iname == ".." || iname.isEmpty() )
+            continue;
+        if(i.suffix() == "cfg" && !i.isDir())
+        {
+            QSettings config(i.absoluteFilePath(), QSettings::IniFormat);
+
+            rolesObject.insert(i.completeBaseName(), config.value("node-role", "unknown").toString());
+        }
+    }
+
+    return rolesObject;
+}
+
+/// ---------------------------------------------------------------
+///        Cli info
+/// ---------------------------------------------------------------
+QJsonObject AbstractDiagnostic::get_cli_info()
+{
+    QStringList networks = get_networks();
+
+    QJsonObject netObj;
+
+    for(QString net : networks)
+    {
+        QJsonObject dataObj;
+
+        dataObj.insert("net_info", get_net_info(net));
+        dataObj.insert("mempool" , get_mempool_count(net));
+//        dataObj.insert("ledger"  , get_ledger_count(net));
+        dataObj.insert("blocks"  , get_blocks_count(net));
+        dataObj.insert("events"  , get_events_count(net));
+        dataObj.insert("nodelist"  , get_nodelist(net));
+
+        netObj.insert(net, dataObj);
+    }
+
+    return netObj;
+}
+
+QStringList AbstractDiagnostic::get_networks()
+{
+    QProcess proc;
+    proc.start(QString(CLI_PATH), QStringList()<<"net"<<"list");
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QStringList listNetworks;
+    result.remove(' ');
+    result.remove("\r");
+    result.remove("\n");
+    result.remove("networks:");
+    if(!(result.isEmpty() || result.isNull() || result.contains('\'') || result.contains("error") || result.contains("Error") || result.contains("err")))
+    {
+        listNetworks = result.split(",", QString::SkipEmptyParts);
+    }
+    
+    return listNetworks;
+}
+
+QJsonObject AbstractDiagnostic::get_net_info(QString net)
+{
+    QProcess proc;
+    proc.start(QString(CLI_PATH),
+               QStringList()<<"net"<<"-net"<<QString(net)<<"get"<<"status");
+    proc.waitForFinished(5000);
+    
+    QString result = proc.readAll();
+    
+    QRegularExpression rx_state(R"(.*current: ([A-Z,_]+).*)");
+    QRegularExpression rx_addr(R"(.*current_addr: (.+))");
+    
+    QJsonObject resultObj;
+    
+    QRegularExpressionMatch match_addr = rx_addr.match(result);
+    if (match_addr.hasMatch()) {
+        resultObj.insert("node_address", match_addr.captured(1));
+    }
+
+    QRegularExpressionMatch match_state = rx_state.match(result);
+    if (match_state.hasMatch()) {
+        resultObj.insert("state", match_state.captured(1));
+    }
+
+    // ---------- Links count ----------------
+    QRegularExpression rxLinksActive(R"(.*active: (.*))");
+    QRegularExpression rxLinksRequired(R"(.*required: (.*))");
+    
+    QRegularExpressionMatch match_active = rxLinksActive.match(result);
+    if (match_active.hasMatch()) {
+        resultObj.insert("active_links_count", match_active.captured(1));
+    }
+    
+    QRegularExpressionMatch match_req = rxLinksRequired.match(result);
+    if (match_req.hasMatch()) {
+        resultObj.insert("links_count", match_req.captured(1));
+    }
+    
+    
+    resultObj.insert("balancer", get_balancer_links(net));
+
+    return resultObj;
+}
+
+QJsonObject AbstractDiagnostic::get_mempool_count(QString net)
+{
+    QProcess proc;
+    proc.start(QString(CLI_PATH),
+               QStringList()<<"mempool_list"<<"-net"<<QString(net));
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QRegularExpression rx(R"(\.*total: .*\.(.*): (\d+))");
+
+    QJsonObject resultObj;
+
+    QRegularExpressionMatchIterator matchItr = rx.globalMatch(result);
+
+    while (matchItr.hasNext())
+    {
+        QRegularExpressionMatch match = matchItr.next();
+        resultObj.insert(match.captured(1), match.captured(2));
+    }
+    return resultObj;
+}
+
+QJsonObject AbstractDiagnostic::get_ledger_count(QString net)
+{
+    //TODO: legder tx -all -net   NOT WORKING
+    return {};
+}
+
+QJsonObject AbstractDiagnostic::get_blocks_count(QString net)
+{
+    QProcess proc;
+    proc.start(QString(CLI_PATH),
+               QStringList()<<"block"<<"last"<<"-net"<<QString(net) <<"-chain" << "main");
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+    
+    QRegularExpression rx_block_count(R"(\.(.+) has blocks: (.+))");
+    QRegularExpression rx_creation(R"(.*ts_created: (.*))");
+    QRegularExpression rx_hash(R"(.*Last block hash: (.*))");
+    
+
+    QRegularExpressionMatch block_count_match = rx_block_count.match(result);
+    QRegularExpressionMatch timestamp_match = rx_creation.match(result);
+    QRegularExpressionMatch hash_match = rx_hash.match(result);
+
+    if (!block_count_match.hasMatch()) {
+        return {};
+    }
+
+    QJsonObject resultObj;
+    resultObj.insert(block_count_match.captured(1), block_count_match.captured(2));
+
+    QJsonObject last_block;
+    last_block.insert("hash", hash_match.captured(1));
+    
+    qDebug() << "TS: "<<timestamp_match.captured(1);
+    QDateTime dt = QDateTime::fromString(timestamp_match.captured(1), Qt::RFC2822Date);
+    qint64 timestamp = dt.toSecsSinceEpoch();
+    last_block.insert("timestamp", QString::number(timestamp));
+
+    resultObj.insert("last_block", last_block);
+
+    qDebug() << "Block last "<<net<<":";
+    qDebug() << resultObj;
+    return resultObj;
+
+}
+
+QJsonObject AbstractDiagnostic::get_events_count(QString net)
+{
+    QProcess proc;
+    proc.start(QString(CLI_PATH),
+               QStringList()<<"dag"<<"event"<<"last"<<"-net"<<QString(net) <<"-chain" << "zerochain");
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QRegularExpression rx_event_count(R"(\.(.+) has events: (.+))");
+    QRegularExpression rx_creation(R"(.*ts_created: (.*))");
+    QRegularExpression rx_hash(R"(.*Last event hash: (.*))");
+    
+
+    QRegularExpressionMatch event_count_match = rx_event_count.match(result);
+    QRegularExpressionMatch timestamp_match = rx_creation.match(result);
+    QRegularExpressionMatch hash_match = rx_hash.match(result);
+
+    if (!event_count_match.hasMatch()) {
+        return {};
+    }
+
+    QJsonObject resultObj;
+    resultObj.insert(event_count_match.captured(1), event_count_match.captured(2));
+
+    QJsonObject last_event;
+    last_event.insert("hash", hash_match.captured(1));
+    
+    qDebug() << "TS: "<<timestamp_match.captured(1);
+    QDateTime dt = QDateTime::fromString(timestamp_match.captured(1), Qt::RFC2822Date);
+    qint64 timestamp = dt.toSecsSinceEpoch();
+    last_event.insert("timestamp", QString::number(timestamp));
+
+    resultObj.insert("last_event", last_event);
+
+    last_event.insert("timestamp", QString::number(timestamp));
+
+    qDebug() << "Event last "<<net<<":";
+    qDebug() << resultObj;
+    
+    return resultObj;
+}
+
+
+QJsonArray AbstractDiagnostic::get_nodelist(QString net)
+{
+    QProcess proc;
+    proc.start(QString(CLI_PATH),
+               QStringList()<<"node"<<"dump"<<"-net"<<QString(net));
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QJsonArray res;
+    for (auto l : result.split("\n"))
+        res.push_back(QJsonValue(l));
+
+    return res;
+}
+
+QJsonObject AbstractDiagnostic::get_balancer_links(QString net)
+{
+    QProcess proc;
+    proc.start(QString(CLI_PATH),
+                QStringList()<<"node"<<"connections"<<"-net"<<QString(net));
+
+
+    proc.waitForFinished(5000);
+    QString result = proc.readAll();
+
+    QJsonObject resultObj;
+
+    QRegularExpression rx(R"(Total links: \d+ \| Uplinks: (\d+) \| Downlinks: (\d+))");
+    QRegularExpressionMatch match = rx.match(result);
+
+    if (!match.hasMatch()) {
+        return {};
+    }
+
+    resultObj.insert("uplinks", match.captured(1));
+    resultObj.insert("downlinks", match.captured(2));
+
+
+    return resultObj;
+}
diff --git a/CellframeNodeDiagtool/AbstractDiagnostic.h b/CellframeNodeDiagtool/AbstractDiagnostic.h
new file mode 100644
index 0000000000000000000000000000000000000000..3440e76cf5d14aa1d2138632463a85912b6ac4e2
--- /dev/null
+++ b/CellframeNodeDiagtool/AbstractDiagnostic.h
@@ -0,0 +1,70 @@
+#ifndef ABSTRACTDIAGNOSTIC_H
+#define ABSTRACTDIAGNOSTIC_H
+
+#include <QObject>
+#include "qtimer.h"
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QDebug>
+#include <QUrl>
+#include <QFileInfo>
+#include <QCoreApplication>
+#include <QProcess>
+#include <QString>
+#include <QRegularExpression>
+#include <QSettings>
+#include <QTime>
+#include <QFile>
+
+#include <QDir>
+#include <QNetworkInterface>
+
+class AbstractDiagnostic : public QObject
+{
+    Q_OBJECT
+public:
+    explicit AbstractDiagnostic(QObject * parent = nullptr);
+    ~AbstractDiagnostic();
+
+public:
+    void start_diagnostic();
+    void stop_diagnostic();
+    void set_timeout(int timeout);
+    quint64 get_file_size(QString flag, QString path);
+    QString get_uptime_string(long sec);
+    QString get_memory_string(long num);
+    QJsonValue get_mac();
+
+    QJsonDocument get_full_info(){return s_full_info;}
+
+    QJsonObject roles_processing();
+
+    //CLI
+    QJsonObject get_cli_info();
+
+    QStringList get_networks();
+    QJsonObject get_net_info(QString net);
+    QJsonObject get_mempool_count(QString net);
+    QJsonObject get_ledger_count(QString net);
+    QJsonObject get_blocks_count(QString net);
+    QJsonObject get_events_count(QString net);
+    QJsonArray get_nodelist(QString net);
+    QJsonObject get_balancer_links(QString net);
+
+public:
+    QTimer * s_timer_update;
+    int s_timeout{1000};
+    QJsonDocument s_full_info;
+    QJsonValue s_mac;
+    bool s_node_status{false};
+    QString s_nodeDataPath{""};
+    QString s_jsonFilePath{""};
+    QString s_sendedDataFilePath{""};
+
+signals:
+    void data_updated(QJsonDocument);
+
+};
+
+#endif // ABSTRACTDIAGNOSTIC_H
diff --git a/CellframeNodeDiagtool/CMakeLists.txt b/CellframeNodeDiagtool/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ad329d3a55847fc077ba92baa22fbf812870d2f7
--- /dev/null
+++ b/CellframeNodeDiagtool/CMakeLists.txt
@@ -0,0 +1,51 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(cellframe-diagtool)
+
+find_package(Qt5 5.15 REQUIRED COMPONENTS
+    Core
+    Network
+)
+
+set(CMAKE_AUTOMOC ON)
+
+
+
+if(UNIX)
+    if(DARWIN)
+        add_definitions(-DCLI_PATH="./cellframe-node-cli")
+        add_executable(${PROJECT_NAME}
+            main.cpp
+            DiagnosticWorker.cpp
+            AbstractDiagnostic.cpp
+            MacDiagnostic.cpp
+        )
+    endif()
+
+    if(LINUX)
+        add_definitions(-DCLI_PATH="/opt/cellframe-node/bin/cellframe-node-cli")
+        add_executable(${PROJECT_NAME}
+            main.cpp
+            DiagnosticWorker.cpp
+            AbstractDiagnostic.cpp
+            LinuxDiagnostic.cpp
+        )
+    endif()
+
+endif()
+
+if(WIN32)
+    add_definitions(-DCLI_PATH="cellframe-node-cli.exe")
+    add_executable(${PROJECT_NAME}
+        main.cpp
+        DiagnosticWorker.cpp
+        AbstractDiagnostic.cpp
+        WinDiagnostic.cpp
+    )
+endif()
+
+
+target_link_libraries(${PROJECT_NAME}
+    Qt5::Core Qt5::Network
+)
+
diff --git a/CellframeNodeDiagtool/DiagnosticWorker.cpp b/CellframeNodeDiagtool/DiagnosticWorker.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..72ca0b0f0ead8ad9eba054d7c8e54d2548bd3ec1
--- /dev/null
+++ b/CellframeNodeDiagtool/DiagnosticWorker.cpp
@@ -0,0 +1,151 @@
+#include "DiagnosticWorker.h"
+#include <QNetworkAccessManager>
+#include <QHttpPart>
+#include <QHttpMultiPart>
+#include <QNetworkReply>
+static QString group = "global.users.statistic";
+
+DiagnosticWorker::DiagnosticWorker(QObject * parent)
+    : QObject{parent}
+{
+
+}
+
+DiagnosticWorker::~DiagnosticWorker()
+{
+    delete s_uptime_timer;
+    delete s_elapsed_timer;
+    delete m_diagnostic;
+}
+
+void DiagnosticWorker::init()
+{
+    s_uptime_timer = new QTimer(this);
+    connect(s_uptime_timer, &QTimer::timeout,
+            this, &DiagnosticWorker::slot_uptime,
+            Qt::QueuedConnection);
+    s_uptime_timer->start(1000);
+
+    s_elapsed_timer = new QElapsedTimer();
+    s_elapsed_timer->start();
+
+#ifdef Q_OS_LINUX
+    m_diagnostic = new LinuxDiagnostic();
+#elif defined Q_OS_WIN
+    m_diagnostic = new WinDiagnostic();
+#elif defined Q_OS_MAC
+    m_diagnostic = new MacDiagnostic();
+#endif
+
+    connect(m_diagnostic, &AbstractDiagnostic::data_updated,
+            this, &DiagnosticWorker::slot_diagnostic_data,
+            Qt::QueuedConnection);
+
+    m_diagnostic->start_diagnostic();
+    m_diagnostic->set_timeout(3000); //30 sec
+
+    m_isSendData = QSettings().value("diag_send_data", "1").toInt();
+
+    m_initStatus = true;
+
+    emit sigInitStatusChanged(m_initStatus);
+}
+
+void DiagnosticWorker::switchSendData()
+{
+    m_isSendData = !m_isSendData;
+
+    if(!m_isSendData)
+        m_diagnostic->stop_diagnostic();
+    else
+        m_diagnostic->start_diagnostic();
+
+    QSettings().setValue("diag_send_data", m_isSendData);
+
+    emit sigSendDataStatusChanged(m_isSendData);
+}
+
+void DiagnosticWorker::openConfigFile()
+{
+    QDesktopServices::openUrl(m_diagnostic->s_jsonFilePath);
+}
+
+void DiagnosticWorker::getSendedData()
+{
+    QFile sndData(m_diagnostic->s_sendedDataFilePath);
+    sndData.open(QIODevice::WriteOnly);
+    sndData.write(m_lastSendedPack.toJson());
+
+    QDesktopServices::openUrl(m_diagnostic->s_sendedDataFilePath);
+}
+
+DiagnosticWorker &DiagnosticWorker::getInstance()
+{
+    static DiagnosticWorker instance;
+    return instance;
+}
+
+void DiagnosticWorker::slot_diagnostic_data(QJsonDocument data)
+{
+    //insert uptime dashboard and more into system info
+    QJsonObject obj = data.object();
+    QJsonObject system = data["system"].toObject();
+    QJsonObject proc = data["process"].toObject();
+
+    system.insert("uptime_diagtool", s_uptime);
+    system.insert("time_update_unix", QDateTime::currentSecsSinceEpoch());
+
+//    system.insert("time_update", QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm"));
+    obj.insert("system",system);
+
+    if(proc["status"].toString() == "Offline") //if node offline - clear version
+        m_node_version = "";
+    else
+    if(m_node_version.isEmpty())
+    {
+        QProcess proc;
+        proc.start(QString(CLI_PATH), QStringList()<<"version");
+        proc.waitForFinished(5000);
+        QString result = proc.readAll();
+        if(result.contains("version"))
+        {
+            result = result.split("version")[1];
+            m_node_version = result.split('\n', QString::SkipEmptyParts).first().trimmed();
+        }
+    }
+
+    proc.insert("version", m_node_version);
+    obj.insert("process",proc);
+    data.setObject(obj);
+    m_lastSendedPack = data;
+
+    write_data(data);
+}
+
+void DiagnosticWorker::slot_uptime()
+{
+    s_uptime = m_diagnostic->get_uptime_string(s_elapsed_timer->elapsed()/1000);
+}
+
+void DiagnosticWorker::write_data(QJsonDocument data)
+{
+    QStringList urls = {"https://telemetry.cellframe.net/diag_report", "https://engine-minkowski.kelvpn.com/diag_report"};
+    for (auto url_s : urls)
+    {
+        QUrl url = QUrl(url_s);
+
+        QNetworkAccessManager * mgr = new QNetworkAccessManager(this);
+
+        connect(mgr, &QNetworkAccessManager::finished, this, [=](QNetworkReply*r) {qDebug() << "data sent " << urls << " " << r->error();});
+        connect(mgr,SIGNAL(finished(QNetworkReply*)),mgr,  SLOT(deleteLater()));
+
+        auto req = QNetworkRequest(url);
+        req.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
+        QString key = m_diagnostic->s_mac.toString();
+        QJsonObject obj = data.object();
+        obj.insert("mac",key);
+        data.setObject(obj);
+        mgr->post(req, data.toJson());
+    }
+
+}
diff --git a/CellframeNodeDiagtool/DiagnosticWorker.h b/CellframeNodeDiagtool/DiagnosticWorker.h
new file mode 100644
index 0000000000000000000000000000000000000000..51c1d3b79a416168152090ef7b36a1387ac2cfcb
--- /dev/null
+++ b/CellframeNodeDiagtool/DiagnosticWorker.h
@@ -0,0 +1,72 @@
+#ifndef DIAGNOSTICWORKER_H
+#define DIAGNOSTICWORKER_H
+
+#include <QObject>
+#include <QTimer>
+#include <QElapsedTimer>
+#include <QSettings>
+#include <QThread>
+#include <QDesktopServices>
+#include <QSettings>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef Q_OS_LINUX
+    #include "LinuxDiagnostic.h"
+#elif defined Q_OS_WIN
+    #include "WinDiagnostic.h"
+#elif defined Q_OS_MAC
+    #include "MacDiagnostic.h"
+#endif
+
+
+class DiagnosticWorker : public QObject
+{
+    Q_OBJECT
+    Q_DISABLE_COPY(DiagnosticWorker)
+
+    explicit DiagnosticWorker(QObject *parent = nullptr);
+    ~DiagnosticWorker();
+
+public:
+    static DiagnosticWorker &getInstance();
+
+    void init();
+
+    Q_INVOKABLE bool getInitStatus(){return m_initStatus;}
+    Q_INVOKABLE bool getSendDataStatus(){return m_isSendData;}
+    Q_INVOKABLE void switchSendData();
+    Q_INVOKABLE void openConfigFile();
+    Q_INVOKABLE void getSendedData();
+
+private:
+    QString m_node_version{""};
+    QSettings m_settings;
+    AbstractDiagnostic* m_diagnostic;
+
+    QTimer *s_uptime_timer;
+    QElapsedTimer *s_elapsed_timer;
+    QString s_uptime{"00:00:00"};
+
+    QJsonDocument m_lastSendedPack;
+
+    bool m_initStatus{false};
+    bool m_isSendData{true};
+
+private slots:
+    void slot_diagnostic_data(QJsonDocument);
+    void slot_uptime();
+
+private:
+    void write_data(QJsonDocument);
+
+signals:
+    Q_INVOKABLE void sigSendDataStatusChanged(bool status);
+    Q_INVOKABLE void sigInitStatusChanged(bool status);
+
+};
+
+#endif // DIAGNOSTICWORKER_H
diff --git a/CellframeNodeDiagtool/LinuxDiagnostic.cpp b/CellframeNodeDiagtool/LinuxDiagnostic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d3c031c8061eef1c0441b9a8753056d43f319ff8
--- /dev/null
+++ b/CellframeNodeDiagtool/LinuxDiagnostic.cpp
@@ -0,0 +1,346 @@
+#include "LinuxDiagnostic.h"
+#include <sys/vfs.h>
+#include <sys/stat.h>
+
+LinuxDiagnostic::LinuxDiagnostic(AbstractDiagnostic *parent)
+    : AbstractDiagnostic{parent}
+{
+    connect(s_timer_update, &QTimer::timeout,
+            this, &LinuxDiagnostic::info_update,
+            Qt::QueuedConnection);
+}
+
+
+void LinuxDiagnostic::info_update(){
+
+    // QSettings config("/opt/cellframe-node/etc/cellframe-node.cfg",
+    //                    QSettings::IniFormat);
+
+    // bool isEnabled = config.value("Diagnostic/enabled",false).toBool();
+
+    if(true) //isEnabled
+    {
+        QJsonObject proc_info;
+        QJsonObject sys_info;
+        QJsonObject cli_info;
+        QJsonObject full_info;
+
+        sys_info = get_sys_info();
+        sys_info.insert("mac", s_mac);
+        sys_info.insert("disk", get_disk_info());
+
+        QFile file(s_jsonFilePath);
+        if(file.open(QIODevice::ReadOnly | QIODevice::Text))
+        {
+            QJsonParseError err;
+            QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err);
+            file.close();
+
+            if(err.error == QJsonParseError::NoError && !doc.isEmpty())
+                sys_info.insert("data", doc.object());
+            else
+                qWarning()<<err.errorString();
+        }
+
+        QJsonObject obj = sys_info["memory"].toObject();
+        int mem = obj["total"].toInt();
+
+        proc_info = get_process_info(get_pid(), mem);
+        proc_info.insert("roles", roles_processing());
+
+        if(s_node_status)
+        {
+            cli_info = get_cli_info();
+            full_info.insert("cli_data", cli_info);
+        }
+
+
+        full_info.insert("system", sys_info);
+        full_info.insert("process", proc_info);
+
+        s_full_info.setObject(full_info);
+
+        emit data_updated(s_full_info);
+    }
+}
+
+QJsonObject LinuxDiagnostic::get_disk_info()
+{
+    QString path = "/opt/cellframe-node";
+
+    QString apath = QDir(path).absolutePath();
+
+    struct statfs stfs;
+
+    if (::statfs(apath.toLocal8Bit(),&stfs) == -1)
+    {
+        QJsonObject diskObj;
+        diskObj.insert("total",     -1);
+        diskObj.insert("free",      -1);
+        diskObj.insert("available", -1);
+        diskObj.insert("used",      -1);
+
+        return diskObj;
+    }
+
+    quint64 total     = stfs.f_blocks * stfs.f_bsize;
+    quint64 free      = stfs.f_bfree  * stfs.f_bsize;
+    quint64 available = stfs.f_bavail * stfs.f_bsize;
+    quint64 used      = total - free;
+
+    QJsonObject diskObj;
+    diskObj.insert("total",     QString::number(total));
+    diskObj.insert("free",      QString::number(free));
+    diskObj.insert("available", QString::number(available));
+    diskObj.insert("used",      QString::number(used));
+
+    return diskObj;
+}
+
+
+long LinuxDiagnostic::get_pid()
+{
+    long pid;
+    QString path = QString("%1/var/run/cellframe-node.pid").arg("/opt/cellframe-node/");
+
+    QFile file(path);
+    QByteArray data;
+    if (!file.open(QIODevice::ReadOnly))
+        return 0;
+    data = file.readAll();
+    pid = data.toLong();
+
+    return pid;
+}
+
+/// ---------------------------------------------------------------
+///        Sys info
+/// ---------------------------------------------------------------
+QJsonObject LinuxDiagnostic::get_sys_info()
+{
+    QJsonObject obj_sys_data, obj_cpu, obj_memory;
+
+    ifstream stat_stream;
+    string buff;
+
+    //-------
+
+    //get cpu info
+    size_t idle_time, total_time;
+    get_cpu_times(idle_time, total_time);
+
+    const float idle_time_delta = idle_time - previous_idle_time;
+    const float total_time_delta = total_time - previous_total_time;
+    const float utilization = 100.0 * (1.0 - idle_time_delta / total_time_delta);
+
+    previous_idle_time = idle_time;
+    previous_total_time = total_time;
+
+    stat_stream.open("/proc/cpuinfo");
+    for(int i = 0; i < 16;i++) stat_stream >> buff;
+    getline(stat_stream,buff);
+
+    obj_cpu.insert("load", (int)utilization);
+    obj_cpu.insert("model", QString::fromStdString(buff));
+    stat_stream.close();
+
+    //get uptime system
+    stat_stream.open("/proc/uptime");
+    stat_stream >> buff;
+    QString uptime = get_uptime_string(atoi(buff.c_str()));
+    stat_stream.close();
+
+    //get memory data
+    stat_stream.open("/proc/meminfo");
+    QString memory, memory_used, memory_free;
+    string total,free,available;
+    stat_stream >> buff >> total >> buff >> buff >> free >> buff >> buff >> available;
+    stat_stream.close();
+
+    int total_value = atoi(total.c_str());
+    int available_value = atoi(available.c_str());
+
+    memory = get_memory_string(total_value);
+    memory_used = QString::number((total_value - available_value) *100 / total_value);
+    memory_free = get_memory_string(available_value);
+
+//    obj_memory.insert("total", memory);
+    obj_memory.insert("total", total_value);
+//    obj_memory.insert("total_value", total_value);
+    obj_memory.insert("free", memory_free);
+    obj_memory.insert("load", memory_used);
+
+    //-------
+
+    obj_sys_data.insert("uptime", uptime);
+    obj_sys_data.insert("CPU", obj_cpu);
+    obj_sys_data.insert("memory", obj_memory);
+
+    return obj_sys_data;
+}
+
+QString LinuxDiagnostic::get_running(char* pid)
+{
+    char tbuf[32];
+    char *cp;
+    int fd;
+    char c;
+
+    sprintf(tbuf, "/proc/%s/stat", pid);
+    fd = open(tbuf, O_RDONLY, 0);
+    if (fd == -1) return QString("no open");
+
+    memset(tbuf, '\0', sizeof tbuf); // didn't feel like checking read()
+    read(fd, tbuf, sizeof tbuf - 1);
+
+    cp = strrchr(tbuf, ')');
+    if(!cp) return QString("no read");
+
+    c = cp[2];
+    close(fd);
+    qDebug()<<c;
+
+    if (c=='R') {
+      return "running";
+    }else
+    if (c=='D') {
+      return "blocked";
+    }
+    return QString("blocked");
+}
+
+
+QString LinuxDiagnostic::get_proc_path(long pid) // work with root
+{
+    char exePath[PATH_MAX];
+    char arg1[20];
+    sprintf( arg1, "/proc/%ld/exe", pid );
+    ssize_t len = ::readlink(arg1, exePath, sizeof(exePath));
+    if (len == -1 || len == sizeof(exePath))
+        len = 0;
+    exePath[len] = '\0';
+    return QString::fromUtf8(exePath);
+}
+
+std::vector<size_t> LinuxDiagnostic::get_cpu_times() {
+    std::ifstream proc_stat("/proc/stat");
+    proc_stat.ignore(5, ' '); // Skip the 'cpu' prefix.
+    std::vector<size_t> times;
+    for (size_t time; proc_stat >> time; times.push_back(time));
+    return times;
+}
+
+bool LinuxDiagnostic::get_cpu_times(size_t &idle_time, size_t &total_time) {
+    const std::vector<size_t> cpu_times = get_cpu_times();
+    if (cpu_times.size() < 4)
+        return false;
+    idle_time = cpu_times[3];
+    total_time = std::accumulate(cpu_times.begin(), cpu_times.end(), 0);
+    return true;
+}
+
+
+/// ---------------------------------------------------------------
+///        Process info
+/// ---------------------------------------------------------------
+QJsonObject LinuxDiagnostic::get_process_info(long proc_id, int totalRam)
+{
+   using std::ios_base;
+   using std::ifstream;
+   using std::string;
+
+//   vm_usage     = 0.0;
+//   resident_set = 0.0;
+
+   // 'file' stat seems to give the most reliable results
+   char arg1[20];
+   sprintf( arg1, "/proc/%ld/stat", proc_id );
+   ifstream stat_stream(arg1,ios_base::in);
+
+   // dummy vars for leading entries in stat that we don't care about
+   //
+   string pid, comm, state, ppid, pgrp, session, tty_nr;
+   string tpgid, flags, minflt, cminflt, majflt, cmajflt;
+   string utime, stime, cutime, cstime, priority, nice;
+   string O, itrealvalue;
+
+   // the two fields we want
+   //
+   unsigned long vsize;
+   long rss;
+   double starttime;
+
+   stat_stream >> pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr
+               >> tpgid >> flags >> minflt >> cminflt >> majflt >> cmajflt
+               >> utime >> stime >> cutime >> cstime >> priority >> nice
+               >> O >> itrealvalue >> starttime >> vsize >> rss; // don't care about the rest
+
+   stat_stream.close();
+
+   long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages
+//   vm_usage     = (vsize/1024.0);
+   long resident_set = rss * page_size_kb;
+
+   int precentUseRss = (resident_set * 100) / totalRam;
+
+   QProcess proc;
+   QString program = "ps";
+   QStringList arguments;
+   arguments << "-p" << pid.c_str() << "-o" << "etimes";
+   proc.start(program, arguments);
+   proc.waitForFinished(1000);
+   QString res = proc.readAll();
+
+   static QRegularExpression rx(R"([0-9]+)");
+
+   QJsonObject process_info;
+   QString status = "Offline";
+   process_info.insert("status", "Offline");
+
+   QString node_dir = "/opt/cellframe-node/";
+
+   QString log_size, db_size, chain_size;
+   log_size = get_memory_string(get_file_size("log", node_dir) / 1024);
+   db_size = get_memory_string(get_file_size("DB", node_dir) / 1024);
+   chain_size = get_memory_string(get_file_size("chain", node_dir) / 1024);
+
+   if(log_size.isEmpty()) log_size = "0";
+   if(db_size.isEmpty()) db_size = "0";
+   if(chain_size.isEmpty()) chain_size = "0";
+
+   process_info.insert("log_size", log_size);
+   process_info.insert("DB_size", db_size);
+   process_info.insert("chain_size", chain_size);
+
+   if(QString::fromLocal8Bit(comm.c_str()).contains("cellframe-node"))
+   {
+       int uptime_sec = rx.match(res).captured(0).toInt();
+
+       QString uptime= get_uptime_string(uptime_sec);
+
+       QString memory_use_value = get_memory_string(resident_set);
+
+//       process_info.insert("PPID",QString::fromLocal8Bit(ppid.c_str()));
+//       process_info.insert("priory",QString::fromLocal8Bit(priority.c_str()));
+//       process_info.insert("start_time",QString::number(((starttime/100)/60)));
+       process_info.insert("memory_use",precentUseRss);
+       process_info.insert("memory_use_value",memory_use_value);
+       process_info.insert("uptime",uptime);
+       process_info.insert("name","cellframe-node");
+
+       status = "Online";
+   }
+
+   if(status == "Offline")
+   {
+       process_info.insert("memory_use","0");
+       process_info.insert("memory_use_value","0 Kb");
+       process_info.insert("uptime","00:00:00");
+   }
+
+   s_node_status = status == "Online" ? true : false;
+
+   process_info.insert("status", status);
+
+   return process_info;
+}
diff --git a/CellframeNodeDiagtool/LinuxDiagnostic.h b/CellframeNodeDiagtool/LinuxDiagnostic.h
new file mode 100644
index 0000000000000000000000000000000000000000..37d89f52a0a691d57c8b942145071590f22cdd34
--- /dev/null
+++ b/CellframeNodeDiagtool/LinuxDiagnostic.h
@@ -0,0 +1,53 @@
+#ifndef LINUXDIAGNOSTIC_H
+#define LINUXDIAGNOSTIC_H
+
+
+#include "AbstractDiagnostic.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <vector>
+
+#include <fstream>
+#include <numeric>
+#include <vector>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <locale.h>
+#include <signal.h>
+#include <dirent.h>
+
+#include <sys/sysinfo.h>
+
+using namespace std;
+
+
+class LinuxDiagnostic : public AbstractDiagnostic
+{
+    Q_OBJECT
+public:
+    explicit LinuxDiagnostic(AbstractDiagnostic * parent = nullptr);
+
+private:
+    QJsonObject get_sys_info();
+    bool get_cpu_times(size_t &idle_time, size_t &total_time);
+    std::vector<size_t> get_cpu_times();
+    long get_pid();
+    QString get_running(char* pid);
+    QString get_proc_path(long pid);
+    QJsonObject get_process_info(long pid, int totalRam);
+    QJsonObject get_disk_info();
+
+
+private slots:
+    void info_update();
+
+private:
+    size_t previous_idle_time{0}, previous_total_time{0};
+
+
+};
+
+#endif // LINUXDIAGNOSTIC_H
diff --git a/CellframeNodeDiagtool/MacDiagnostic.cpp b/CellframeNodeDiagtool/MacDiagnostic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..761addce75d8bcb23102412475e2c479ec216c39
--- /dev/null
+++ b/CellframeNodeDiagtool/MacDiagnostic.cpp
@@ -0,0 +1,242 @@
+#include "MacDiagnostic.h"
+
+static unsigned long long _previousTotalTicks = 0;
+static unsigned long long _previousIdleTicks = 0;
+
+MacDiagnostic::MacDiagnostic(AbstractDiagnostic *parent)
+    : AbstractDiagnostic{parent}
+{
+    connect(s_timer_update, &QTimer::timeout,
+            this, &MacDiagnostic::info_update,
+            Qt::QueuedConnection);
+}
+
+void MacDiagnostic::info_update()
+{
+//    qInfo()<<"MacDiagnostic::info_update ";
+
+    QJsonObject proc_info;
+    QJsonObject sys_info;
+    QJsonObject cli_info;
+    QJsonObject full_info;
+
+
+    sys_info = get_sys_info();
+    sys_info.insert("mac", s_mac);
+
+    QFile file(s_jsonFilePath);
+    if(file.open(QIODevice::ReadOnly | QIODevice::Text))
+    {
+        QJsonParseError err;
+        QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err);
+        file.close();
+
+        if(err.error == QJsonParseError::NoError && !doc.isEmpty())
+            sys_info.insert("data", doc.object());
+        else
+            qWarning()<<err.errorString();
+    }
+
+    QJsonObject obj = sys_info["memory"].toObject();
+    int mem = obj["total"].toString().toUInt();;
+
+    proc_info = get_process_info(mem);
+    proc_info.insert("roles", roles_processing());
+
+    if(s_node_status)
+    {
+        cli_info = get_cli_info();
+        full_info.insert("cli_data", cli_info);
+    }
+
+    full_info.insert("system", sys_info);
+    full_info.insert("process", proc_info);
+
+    s_full_info.setObject(full_info);
+
+    emit data_updated(s_full_info);
+}
+
+QJsonObject MacDiagnostic::get_sys_info()
+{
+//    qInfo()<<"MacDiagnostic::get_sys_info ";
+    QJsonObject obj_sys_data, obj_cpu, obj_memory;
+
+    //get memory data
+    //total mem
+    int mib[2];
+    int64_t physical_memory;
+    mib[0] = CTL_HW;
+    mib[1] = HW_MEMSIZE;
+    size_t length = sizeof(int64_t);
+    sysctl(mib, 2, &physical_memory, &length, NULL, 0);
+
+
+
+    //use mem
+
+    vm_size_t page_size;
+    mach_port_t mach_port;
+    mach_msg_type_number_t count;
+    vm_statistics64_data_t vm_stats;
+    long long free_memory = 0;
+//    long long used_memory = 0;
+
+    mach_port = mach_host_self();
+    count = sizeof(vm_stats) / sizeof(natural_t);
+    if (KERN_SUCCESS == host_page_size(mach_port, &page_size) &&
+        KERN_SUCCESS == host_statistics64(mach_port, HOST_VM_INFO,
+                                        (host_info64_t)&vm_stats, &count))
+    {
+        free_memory = (int64_t)vm_stats.free_count * (int64_t)page_size;
+
+//        used_memory = ((int64_t)vm_stats.active_count +
+//                                 (int64_t)vm_stats.inactive_count +
+//                                 (int64_t)vm_stats.wire_count) *  (int64_t)page_size;
+    }
+
+    QString memtotal = QString::number(physical_memory/1024);
+    QString memfree  = QString::number(free_memory/1024);
+//    QString memused = get_memory_string(used_memory/1024);
+
+    QString memory_used = QString::number((physical_memory - free_memory) *100 / physical_memory);
+
+    obj_memory.insert("total", memtotal);
+    obj_memory.insert("free", memfree);
+    obj_memory.insert("load", memory_used);
+
+    //get cpu info
+
+    host_cpu_load_info_data_t cpuinfo;
+    float res = -1.0f;
+    mach_msg_type_number_t counts = HOST_CPU_LOAD_INFO_COUNT;
+    if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpuinfo, &count) == KERN_SUCCESS)
+    {
+       unsigned long long totalTicks = 0;
+       for(int i=0; i<CPU_STATE_MAX; i++) totalTicks += cpuinfo.cpu_ticks[i];
+       res =  calculate_cpu_load(cpuinfo.cpu_ticks[CPU_STATE_IDLE], totalTicks);
+    }
+    obj_cpu.insert("load", int(res*100));
+
+    //get uptime system
+
+    enum { NANOSECONDS_IN_SEC = 1000 * 1000 * 1000 };
+    double multiply = 0;
+    QString uptime = "00:00:00";
+    if (multiply == 0)
+    {
+        mach_timebase_info_data_t s_timebase_info;
+        if(mach_timebase_info(&s_timebase_info) == KERN_SUCCESS)
+        {
+            // multiply to get value in the nano seconds
+            multiply = (double)s_timebase_info.numer / (double)s_timebase_info.denom;
+            // multiply to get value in the seconds
+            multiply /= NANOSECONDS_IN_SEC;
+            uptime = get_uptime_string(mach_absolute_time() * multiply);
+        }
+    }
+
+
+//    //-------
+
+    obj_sys_data.insert("uptime", uptime);
+    obj_sys_data.insert("CPU", obj_cpu);
+    obj_sys_data.insert("memory", obj_memory);
+
+    return obj_sys_data;
+}
+
+float MacDiagnostic::calculate_cpu_load(unsigned long long idleTicks, unsigned long long totalTicks)
+{
+//    qInfo()<<"MacDiagnostic::calculate_cpu_load ";
+    unsigned long long totalTicksSinceLastTime = totalTicks-_previousTotalTicks;
+    unsigned long long idleTicksSinceLastTime  = idleTicks-_previousIdleTicks;
+    float ret = 1.0f-((totalTicksSinceLastTime > 0) ? ((float)idleTicksSinceLastTime)/totalTicksSinceLastTime : 0);
+    _previousTotalTicks = totalTicks;
+    _previousIdleTicks  = idleTicks;
+    return ret;
+
+}
+
+/// ---------------------------------------------------------------
+///        Process info
+/// ---------------------------------------------------------------
+QJsonObject MacDiagnostic::get_process_info(int totalRam)
+{
+//    qInfo()<<"MacDiagnostic::get_process_info ";
+    QJsonObject process_info;
+
+    QProcess proc;
+    QString program = "ps";
+    QStringList arguments;
+    arguments << "-axm" << "-o" << "rss,pid,etime,comm";
+    proc.start(program, arguments);
+    proc.waitForFinished(1000);
+    QString res = proc.readAll();
+
+    QString string="";
+
+    for(QString str : res.split("\n"))
+    {
+        if(str.contains("cellframe-node"))
+        {
+            string = str;
+            break;
+        }
+    }
+
+    QString status = "Offline";
+    QString node_dir = s_nodeDataPath;
+
+    QString log_size, db_size, chain_size;
+    log_size   = QString::number(get_file_size("log", node_dir) / 1024);
+    db_size    = QString::number(get_file_size("DB", node_dir) / 1024);
+    chain_size = QString::number(get_file_size("chain", node_dir) / 1024);
+
+    if(log_size.isEmpty()) log_size = "0";
+    if(db_size.isEmpty()) db_size = "0";
+    if(chain_size.isEmpty()) chain_size = "0";
+
+    process_info.insert("log_size", log_size);
+    process_info.insert("DB_size", db_size);
+    process_info.insert("chain_size", chain_size);
+
+
+    if(string.isEmpty())
+    {
+        process_info.insert("memory_use","0");
+        process_info.insert("memory_use_value","0 Kb");
+        process_info.insert("uptime","00:00:00");
+    }
+    else
+    {
+        status = "Online";
+
+        int pid, rss;
+        QString uptime, path;
+
+        QStringList parseString = string.split(" ");
+        parseString.removeAll("");
+
+        rss = parseString[0].toInt();
+        pid = parseString[1].toInt();
+        uptime = parseString[2];
+        path = parseString[3];
+
+        QString memory_use_value = QString::number(rss);
+        int precentUseRss = rss *100 / totalRam;
+
+        process_info.insert("memory_use",precentUseRss);
+        process_info.insert("memory_use_value",memory_use_value);
+        process_info.insert("uptime",uptime);
+        process_info.insert("name","cellframe-node");
+        process_info.insert("path", path);
+    }
+
+    s_node_status = status == "Online" ? true : false;
+
+    process_info.insert("status", status);
+
+
+   return process_info;
+}
diff --git a/CellframeNodeDiagtool/MacDiagnostic.h b/CellframeNodeDiagtool/MacDiagnostic.h
new file mode 100644
index 0000000000000000000000000000000000000000..540a1b88b540cce5833b70e0efd1e6645626cebb
--- /dev/null
+++ b/CellframeNodeDiagtool/MacDiagnostic.h
@@ -0,0 +1,36 @@
+#ifndef MACDIAGNOSTIC_H
+#define MACDIAGNOSTIC_H
+
+#include "AbstractDiagnostic.h"
+
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <mach/vm_statistics.h>
+#include <mach/mach_types.h>
+#include <mach/mach_init.h>
+#include <mach/mach_host.h>
+#include <time.h>
+#include <mach/mach_time.h>
+
+using namespace std;
+
+
+class MacDiagnostic : public AbstractDiagnostic
+{
+    Q_OBJECT
+public:
+    explicit MacDiagnostic(AbstractDiagnostic * parent = nullptr);
+
+private:
+    QJsonObject get_sys_info();
+    QJsonObject get_process_info(int totalRam);
+
+    //cpu
+    float calculate_cpu_load(unsigned long long idleTick, unsigned long long totakTicks );
+
+private slots:
+    void info_update();
+
+};
+
+#endif // MACDIAGNOSTIC_H
diff --git a/CellframeNodeDiagtool/NodeDiagnostic.pro b/CellframeNodeDiagtool/NodeDiagnostic.pro
new file mode 100644
index 0000000000000000000000000000000000000000..ee1416aaa153aed147c51acd0df25f9c324116a7
--- /dev/null
+++ b/CellframeNodeDiagtool/NodeDiagnostic.pro
@@ -0,0 +1,43 @@
+QT -= gui
+QT += core network
+
+CONFIG += c++17 console
+CONFIG -= app_bundle
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+#LIBS += -L$$NODE_BUILD_PATH/dap-sdk/core/ -ldap_core
+include (../dap-sdk/core/libdap.pri)
+
+SOURCES += \
+        AbstractDiagnostic.cpp \
+        DiagnosticWorker.cpp \
+        main.cpp
+HEADERS += \
+    AbstractDiagnostic.h \
+    DiagnosticWorker.h
+
+win32 {
+    DEFINES += CLI_PATH=\\\"cellframe-node-cli.exe\\\"
+    HEADERS += WinDiagnostic.h
+    SOURCES += WinDiagnostic.cpp
+}
+
+mac {
+    DEFINES += CLI_PATH=\\\"./cellframe-node-cli\\\"
+    HEADERS += MacDiagnostic.h
+    SOURCES += MacDiagnostic.cpp
+}
+
+else: !win32 {
+    DEFINES += CLI_PATH=\\\"/opt/cellframe-node/bin/cellframe-node-cli\\\"
+    HEADERS += LinuxDiagnostic.h
+    SOURCES += LinuxDiagnostic.cpp
+}
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
diff --git a/CellframeNodeDiagtool/WinDiagnostic.cpp b/CellframeNodeDiagtool/WinDiagnostic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f1ddd7dcee9744f17bde983860698c76ee64c800
--- /dev/null
+++ b/CellframeNodeDiagtool/WinDiagnostic.cpp
@@ -0,0 +1,273 @@
+#include "WinDiagnostic.h"
+
+WinDiagnostic::WinDiagnostic(AbstractDiagnostic *parent)
+    : AbstractDiagnostic{parent}
+{
+    connect(s_timer_update, &QTimer::timeout,
+            this, &WinDiagnostic::info_update,
+            Qt::QueuedConnection);
+
+    GetSystemTimes(&s_prev_idle, &s_prev_kernel, &s_prev_user);
+}
+
+void WinDiagnostic::info_update()
+{
+    QJsonObject proc_info;
+    QJsonObject sys_info;
+    QJsonObject cli_info;
+    QJsonObject full_info;
+
+    sys_info = get_sys_info();
+    sys_info.insert("mac", s_mac);
+//    sys_info.insert("disk", get_disk_info());
+
+    QFile file(s_jsonFilePath);
+    if(file.open(QIODevice::ReadOnly | QIODevice::Text))
+    {
+        QJsonParseError err;
+        QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err);
+        file.close();
+
+        if(err.error == QJsonParseError::NoError && !doc.isEmpty())
+            sys_info.insert("data", doc.object());
+        else
+            qWarning()<<err.errorString();
+    }
+
+    QJsonObject obj = sys_info["memory"].toObject();
+    int mem = obj["total"].toString().toInt();;
+
+    proc_info = get_process_info(mem);
+    proc_info.insert("roles", roles_processing());
+
+    if(s_node_status)
+    {
+        cli_info = get_cli_info();
+        full_info.insert("cli_data", cli_info);
+    }
+
+    full_info.insert("system", sys_info);
+    full_info.insert("process", proc_info);
+
+    s_full_info.setObject(full_info);
+
+    emit data_updated(s_full_info);
+}
+
+QJsonObject WinDiagnostic::get_sys_info()
+{
+    QJsonObject obj_sys_data, obj_cpu, obj_memory;
+
+    // get CPU load
+    FILETIME idle;
+    FILETIME kernel;
+    FILETIME user;
+
+    GetSystemTimes(&idle, &kernel, &user);
+
+    ULONGLONG sys = (ft2ull(user) - ft2ull(s_prev_user)) +
+        (ft2ull(kernel) - ft2ull(s_prev_kernel));
+
+    QString cpu_load = QString::number(int((sys - ft2ull(idle) + ft2ull(s_prev_idle)) * 100.0 / sys));
+    obj_cpu.insert("load", cpu_load);
+
+    s_prev_idle = idle;
+    s_prev_kernel = kernel;
+    s_prev_user = user;
+
+    MEMORYSTATUSEX memory_status;
+    ZeroMemory(&memory_status, sizeof(MEMORYSTATUSEX));
+    memory_status.dwLength = sizeof(MEMORYSTATUSEX);
+    GlobalMemoryStatusEx(&memory_status);
+
+    QString memory, memory_used, memory_free;
+
+    memory = QString::number(memory_status.ullTotalPhys / 1024);
+    size_t total_value = memory_status.ullTotalPhys / 1024;
+    size_t available_value = memory_status.ullAvailPhys / 1024;
+    memory_free = QString::number(memory_status.ullAvailPhys / 1024);
+
+    memory_used = QString::number((total_value - available_value) *100 / total_value);
+
+    obj_memory.insert("total", memory);
+    obj_memory.insert("free", memory_free);
+    obj_memory.insert("load", memory_used);
+
+    DWORD currentTime = GetTickCount();
+
+    QString uptime = get_uptime_string(currentTime/1000);
+
+
+    obj_sys_data.insert("uptime", uptime);
+    obj_sys_data.insert("CPU", obj_cpu);
+    obj_sys_data.insert("memory", obj_memory);
+    return obj_sys_data;
+
+}
+
+long WinDiagnostic::get_memory_size(Qt::HANDLE hProc)
+{
+    PROCESS_MEMORY_COUNTERS pmcInfo;
+
+    if (GetProcessMemoryInfo(hProc, &pmcInfo, sizeof(pmcInfo)))
+        return pmcInfo.WorkingSetSize/1024;
+    else return 0;
+
+}
+
+ULONGLONG WinDiagnostic::ft2ull(FILETIME &ft) {
+    ULARGE_INTEGER ul;
+    ul.HighPart = ft.dwHighDateTime;
+    ul.LowPart = ft.dwLowDateTime;
+    return ul.QuadPart;
+}
+
+void WinDiagnostic::refresh_win_snapshot()
+{
+    // Get the process list snapshot.
+    hProcessSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
+
+    ProcessEntry.dwSize = sizeof( ProcessEntry );
+
+
+    // Get the first process info.
+    BOOL Return = FALSE;
+    Return = Process32First( hProcessSnapShot,&ProcessEntry );
+
+    // Getting process info failed.
+    if( !Return )
+    {
+        qDebug()<<"Getting process info failed";
+    }
+}
+
+QJsonObject WinDiagnostic::get_process_info(int totalRam)
+{
+    refresh_win_snapshot();
+
+    hProcessSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
+
+    // vars for get process time
+    FILETIME lpCreation, lpExit, lpKernel, lpUser;
+    SYSTEMTIME stCreation, stExit, stKernel, stUser;
+    long memory_size;
+
+    HANDLE hSnapshot;
+    PROCESSENTRY32 pe;
+    long pid = 0;
+    BOOL hResult;
+
+    // snapshot of all processes in the system
+    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+
+    // initializing size: needed for using Process32First
+    pe.dwSize = sizeof(PROCESSENTRY32);
+
+    // info about first process encountered in a system snapshot
+    hResult = Process32First(hSnapshot, &pe);
+
+
+    QString proc_name = "cellframe-node.exe";
+    while (hResult) {
+        // if we find the process: return process ID
+
+        std::wstring string(pe.szExeFile);
+        std::string str(string.begin(), string.end());
+        QString s = QString::fromStdString(str);
+
+
+        if(!proc_name.compare(s)){
+            pid = pe.th32ProcessID;
+
+            // get process descriptor
+            HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION , FALSE, pe.th32ProcessID);
+
+            if(hProcess)
+            {
+                // get process times info
+                if(GetProcessTimes(hProcess,&lpCreation, &lpExit, &lpKernel, &lpUser))
+                {
+                      FileTimeToSystemTime(&lpCreation, &stCreation);
+                      FileTimeToSystemTime(&lpExit, &stExit);
+                      FileTimeToSystemTime(&lpUser, &stUser);
+                      FileTimeToSystemTime(&lpKernel, &stKernel);
+                }
+
+                memory_size = get_memory_size(hProcess);
+
+                //TODO CPU processing
+
+                CloseHandle(hProcess);
+            }
+
+            break;
+        }
+        hResult = Process32Next(hSnapshot, &pe);
+    }
+
+    CloseHandle( hProcessSnapShot );
+
+    QJsonObject process_info;
+
+    QString status = "Offline";
+//    QString path = NODE_PATH;
+    QString node_dir = QString("%1/cellframe-node/").arg(regGetUsrPath());
+
+    QString log_size, db_size, chain_size;
+    log_size = QString::number(get_file_size("log", node_dir) / 1024);
+    db_size = QString::number(get_file_size("DB", node_dir) / 1024);
+    chain_size = QString::number(get_file_size("chain", node_dir) / 1024);
+
+    if(log_size.isEmpty()) log_size = "0";
+    if(db_size.isEmpty()) db_size = "0";
+    if(chain_size.isEmpty()) chain_size = "0";
+
+    process_info.insert("log_size", log_size);
+    process_info.insert("DB_size", db_size);
+    process_info.insert("chain_size", chain_size);
+
+
+    if(pid){
+
+        QDateTime dateStart;
+        dateStart.setMSecsSinceEpoch(0);
+        dateStart = dateStart.addYears(stCreation.wYear - 1970);
+        dateStart = dateStart.addMonths(stCreation.wMonth - 1);
+        dateStart = dateStart.addDays(stCreation.wDay - 1);
+        dateStart = dateStart.addSecs(stCreation.wHour * 60 * 60 + stCreation.wMinute * 60 + stCreation.wSecond);
+
+        QDateTime dateNow = QDateTime::currentDateTime();
+
+        quint64 sec_start = dateStart.toSecsSinceEpoch();
+        quint64 sec_now = dateNow.toSecsSinceEpoch();
+
+        long result_uptime = sec_now - sec_start;
+
+        QString uptime = get_uptime_string(result_uptime);
+
+        int precentUseRss = (memory_size * 100) / totalRam;
+        QString memory_use_value = QString::number(memory_size);
+
+        process_info.insert("memory_use",precentUseRss);
+        process_info.insert("memory_use_value",memory_use_value);
+        process_info.insert("uptime",uptime);
+        process_info.insert("name","cellframe-node");
+
+        status = "Online";
+
+    }else{
+
+        process_info.insert("memory_use",0);
+        process_info.insert("memory_use_value","0 Kb");
+        process_info.insert("uptime","00:00:00");
+    }
+
+    s_node_status = status == "Online" ? true : false;
+
+    process_info.insert("status", status);
+//    process_info.insert("path", path);
+
+
+    return process_info;
+}
+
diff --git a/CellframeNodeDiagtool/WinDiagnostic.h b/CellframeNodeDiagtool/WinDiagnostic.h
new file mode 100644
index 0000000000000000000000000000000000000000..769452fa6065392943825f504a9ad27c4e54e58a
--- /dev/null
+++ b/CellframeNodeDiagtool/WinDiagnostic.h
@@ -0,0 +1,38 @@
+#ifndef WINDIAGNOSTIC_H
+#define WINDIAGNOSTIC_H
+
+#include "AbstractDiagnostic.h"
+
+#include <windows.h>
+#include <psapi.h>
+#include <tlhelp32.h>
+#include "registry.h"
+#include "pdh.h"
+
+
+enum PLATFORM { WINNT1, WIN2K_XP1, WIN9X1, UNKNOWN1 };
+
+class WinDiagnostic : public AbstractDiagnostic {
+    Q_OBJECT
+public:
+    explicit WinDiagnostic(AbstractDiagnostic* parent = nullptr);
+
+private:
+    QJsonObject get_sys_info();
+    QJsonObject get_process_info(int totalRam);
+
+    long get_memory_size(HANDLE hProc);
+    ULONGLONG ft2ull(FILETIME &ft);
+
+private:
+
+    HANDLE hProcessSnapShot;
+    PROCESSENTRY32 ProcessEntry;
+    FILETIME s_prev_idle, s_prev_kernel, s_prev_user;
+
+private slots:
+    void refresh_win_snapshot();
+    void info_update();
+};
+
+#endif  // WINDIAGNOSTIC_H
diff --git a/CellframeNodeDiagtool/main.cpp b/CellframeNodeDiagtool/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c4db95221cd35ace629ce42b6333dba533428f57
--- /dev/null
+++ b/CellframeNodeDiagtool/main.cpp
@@ -0,0 +1,12 @@
+#include <QCoreApplication>
+#include "DiagnosticWorker.h"
+
+int main(int argc, char *argv[])
+{
+    qSetMessagePattern("%{type} %{if-category}%{category}: %{endif}%{function}: %{message}");
+    QCoreApplication a(argc, argv);
+
+    DiagnosticWorker * wrkr = new DiagnosticWorker();
+
+    return a.exec();
+}
diff --git a/CellframeNodeTray/NodeTrayCommandController.cpp b/CellframeNodeTray/NodeTrayCommandController.cpp
index 56964d73f2bffda4167387ee4517dd7e4cd427c3..631ec09567bfbe1398d23b61a75bb083302d8cd1 100644
--- a/CellframeNodeTray/NodeTrayCommandController.cpp
+++ b/CellframeNodeTray/NodeTrayCommandController.cpp
@@ -1,11 +1,38 @@
 #include "NodeTrayCommandController.h"
 
 NodeTrayCommandController::NodeTrayCommandController(QObject *parent)
-    : QObject{parent}
+    : QObject(parent)
 {
     m_configDir = getConfigPath();
     m_statusInitConftool = initConfTool();
 
+    initEngine();
+}
+
+void NodeTrayCommandController::initEngine()
+{
+    QQmlContext * context = m_engine.rootContext();
+    context->setContextProperty("trayCommandController", this);
+    context->setContextProperty("diagnosticWorker", &diagnosticWorker);
+
+    const QUrl url(QStringLiteral("qrc:/main.qml"));
+    QObject::connect(&m_engine, &QQmlApplicationEngine::objectCreated,
+        this, [url](QObject *obj, const QUrl &objUrl) {
+            if (!obj && url == objUrl)
+                QCoreApplication::exit(-1);
+        }, Qt::QueuedConnection);
+
+    m_engine.load(url);
+}
+
+NodeTrayCommandController::~NodeTrayCommandController()
+{
+
+}
+
+QQmlApplicationEngine *NodeTrayCommandController::qmlEngine()
+{
+    return &m_engine;
 }
 
 bool NodeTrayCommandController::initConfTool()
diff --git a/CellframeNodeTray/NodeTrayCommandController.h b/CellframeNodeTray/NodeTrayCommandController.h
index bff1ed9193423c94c00dc879ad907293a73317e3..8daf9be583438e81ff9bbea51c2935139afddd57 100644
--- a/CellframeNodeTray/NodeTrayCommandController.h
+++ b/CellframeNodeTray/NodeTrayCommandController.h
@@ -2,6 +2,9 @@
 #define NODETRAYCOMMANDCONTROLLER_H
 
 #include <QObject>
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
 #include <QDebug>
 #include <QProcess>
 #include <QDir>
@@ -11,6 +14,9 @@
 
 #include <QQmlEngine>
 
+
+#include "DiagnosticWorker.h"
+
 #ifdef WIN32
 #include <windows.h>
 #endif
@@ -20,7 +26,9 @@ class NodeTrayCommandController : public QObject
     Q_OBJECT
 public:
     explicit NodeTrayCommandController(QObject *parent = nullptr);
+    ~NodeTrayCommandController();
 
+    QQmlApplicationEngine *qmlEngine();
 
     enum TypeServiceCommands{
         Status = 0,
@@ -39,16 +47,21 @@ public:
     void showMessage(const QString msg = "Could not find cellframe-node-config",
                      const QMessageBox::Icon icn = QMessageBox::Warning);
 
+    DiagnosticWorker &diagnosticWorker = DiagnosticWorker::getInstance();
+
 private:
     bool m_statusInitConftool{false};
     QString m_confToolPath{""};
     QString m_configDir{""};
 
+    QQmlApplicationEngine m_engine;
+
 private:
     bool initConfTool();
     QString getConfigPath();
     QString sendRequest(QString req);
 
+    void initEngine();
 
 signals:
     Q_INVOKABLE void sigResultStatus(bool status);
diff --git a/CellframeNodeTray/main.cpp b/CellframeNodeTray/main.cpp
deleted file mode 100644
index 930c93ad68b566531a6512610decb59d999f92da..0000000000000000000000000000000000000000
--- a/CellframeNodeTray/main.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-#include <QApplication>
-#include <QQmlApplicationEngine>
-#include <QIcon>
-#include <QQuickWidget>
-#include <QSystemTrayIcon>
-#include <QQmlContext>
-#include <QDebug>
-
-#include <QProcess>
-
-#include "NodeTrayCommandController.h"
-
-int main(int argc, char *argv[])
-{
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
-    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
-#endif
-    QApplication app(argc, argv);
-
-
-
-    QQmlApplicationEngine engine;
-    const QUrl url(QStringLiteral("qrc:/main.qml"));
-    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
-        &app, [url](QObject *obj, const QUrl &objUrl) {
-            if (!obj && url == objUrl)
-                QCoreApplication::exit(-1);
-        }, Qt::QueuedConnection);
-
-    NodeTrayCommandController *trayCommandController = new NodeTrayCommandController();
-    QQmlContext * context = engine.rootContext();
-
-    context->setContextProperty("trayCommandController",trayCommandController);
-
-    engine.load(url);
-
-    return app.exec();
-}
diff --git a/CellframeNodeTray/main.qml b/CellframeNodeTray/main.qml
index fd77405ee9c81ac7f7c3626eac417574ba7cab20..7061d0f137a11dadd3bc153f617c54c5146bcbb3 100644
--- a/CellframeNodeTray/main.qml
+++ b/CellframeNodeTray/main.qml
@@ -8,7 +8,8 @@ ApplicationWindow {
     id: systemTrayApplicationWindow
     visible: false
 
-    property bool flagAutoCheck:false
+    property bool diagStatusInit:false
+    property bool diagStatusSendData:true
 
     Menu {
         id: menu
@@ -24,7 +25,6 @@ ApplicationWindow {
 //        }
 
         Menu{
-            id: dirsMenu
             title: qsTr("Open dir")
 
             MenuItem {
@@ -44,17 +44,16 @@ ApplicationWindow {
         Menu{
             title: qsTr("Service")
             MenuItem {
+                property bool status: false
                 id: autostartItemMenu
-                text: qsTr("Set autostart cellframe-node")
+                text: status ? qsTr("Autostart cellframe-node: enabled"):
+                               qsTr("Autostart cellframe-node: disabled")
 
-                checkable: true
-
-                onCheckedChanged:
-                {
-                    if(checked && !flagAutoCheck)
-                        trayCommandController.serviceCommand(1)
-                    else if(!checked && !flagAutoCheck)
+                onTriggered: {
+                    if(status)
                         trayCommandController.serviceCommand(2)
+                    else
+                        trayCommandController.serviceCommand(1)
                 }
             }
             MenuItem {
@@ -70,6 +69,27 @@ ApplicationWindow {
                 onTriggered: trayCommandController.serviceCommand(5)
             }
         }
+
+        Menu{
+            title: qsTr("Diagnostic")
+            visible: diagStatusInit
+
+            MenuItem {
+
+                text: diagStatusSendData? qsTr("Send diagnostic data: enabled")
+                                        : qsTr("Send diagnostic data: disabled")
+                onTriggered: diagnosticWorker.switchSendData()
+            }
+            MenuItem {
+                text: qsTr("Sended data list")
+                onTriggered: diagnosticWorker.getSendedData()
+            }
+            MenuItem {
+                text: qsTr("Open diagnostic config")
+                onTriggered: diagnosticWorker.openConfigFile()
+            }
+
+        }
     }
 
     SystemTrayIcon {
@@ -83,12 +103,11 @@ ApplicationWindow {
 
         onActivated:
         {
-            flagAutoCheck = true
             trayCommandController.serviceCommand(0)
         }
         Component.onCompleted:
         {
-            flagAutoCheck = true
+            diagStatusInit = diagnosticWorker.getInitStatus();
             trayCommandController.serviceCommand(0)
         }
     }
@@ -98,11 +117,7 @@ ApplicationWindow {
         function onSigResultStatus(status)
         {
 //            console.log(status)
-            autostartItemMenu.checked = status
-
-            if(flagAutoCheck)
-                flagAutoCheck = false
-
+            autostartItemMenu.status = status
         }
 
         function onSigShowMessage(title, message)
@@ -110,5 +125,17 @@ ApplicationWindow {
             systemTrayIcon.showMessage(title, message, "qrc:/cellframe-node-logo.svg", 5000)
         }
     }
+
+    Connections{
+        target: diagnosticWorker
+        function onSigInitStatusChanged(status)
+        {
+            diagStatusInit = status
+        }
+        function onSigSendDataStatusChanged(status)
+        {
+            diagStatusSendData = status
+        }
+    }
 }
 
diff --git a/cellfram-node-diagtool.pro b/cellfram-node-diagtool.pro
new file mode 100644
index 0000000000000000000000000000000000000000..e97454f8268b4be624bd99d39a8e4ecba2a2cb8f
--- /dev/null
+++ b/cellfram-node-diagtool.pro
@@ -0,0 +1,59 @@
+QT += qml quick widgets quickwidgets
+
+CONFIG += c++17
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+INCLUDEPATH += CellframeNodeTray \
+               CellframeNodeDiagtool
+
+include (cellframe-sdk/dap-sdk/core/libdap.pri)
+
+SOURCES += \
+        CellframeNodeTray/NodeTrayCommandController.cpp \
+        main.cpp \
+        CellframeNodeDiagtool/AbstractDiagnostic.cpp \
+        CellframeNodeDiagtool/DiagnosticWorker.cpp
+
+HEADERS += \
+    CellframeNodeTray/NodeTrayCommandController.h \
+    CellframeNodeDiagtool/AbstractDiagnostic.h \
+    CellframeNodeDiagtool/DiagnosticWorker.h
+
+win32 {
+    DEFINES += CLI_PATH=\\\"cellframe-node-cli.exe\\\"
+    HEADERS += CellframeNodeDiagtool/WinDiagnostic.h
+    SOURCES += CellframeNodeDiagtool/WinDiagnostic.cpp
+}
+
+mac {
+    DEFINES += CLI_PATH=\\\"./cellframe-node-cli\\\"
+    HEADERS +=  CellframeNodeDiagtool/MacDiagnostic.h
+    SOURCES +=  CellframeNodeDiagtool/MacDiagnostic.cpp
+}
+
+else: !win32 {
+    DEFINES += CLI_PATH=\\\"/opt/cellframe-node/bin/cellframe-node-cli\\\"
+    HEADERS +=  CellframeNodeDiagtool/LinuxDiagnostic.h
+    SOURCES +=  CellframeNodeDiagtool/LinuxDiagnostic.cpp
+}
+
+RESOURCES += CellframeNodeTray/qml.qrc \
+    CellframeNodeTray/Resources.qrc
+
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH =
+
+# Additional import path used to resolve QML modules just for Qt Quick Designer
+QML_DESIGNER_IMPORT_PATH =
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+
+DISTFILES +=
diff --git a/cellfram-node-tray.pro b/cellfram-node-tray.pro
deleted file mode 100644
index 8443aab533c01474d9995667031dcfbb17245adc..0000000000000000000000000000000000000000
--- a/cellfram-node-tray.pro
+++ /dev/null
@@ -1,30 +0,0 @@
-QT += qml quick widgets quickwidgets
-
-# You can make your code fail to compile if it uses deprecated APIs.
-# In order to do so, uncomment the following line.
-#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
-
-INCLUDEPATH += CellframeNodeTray
-
-SOURCES += \
-        CellframeNodeTray/NodeTrayCommandController.cpp \
-        CellframeNodeTray/main.cpp
-
-RESOURCES += CellframeNodeTray/qml.qrc \
-    CellframeNodeTray/Resources.qrc
-
-# Additional import path used to resolve QML modules in Qt Creator's code model
-QML_IMPORT_PATH =
-
-# Additional import path used to resolve QML modules just for Qt Quick Designer
-QML_DESIGNER_IMPORT_PATH =
-
-# Default rules for deployment.
-qnx: target.path = /tmp/$${TARGET}/bin
-else: unix:!android: target.path = /opt/$${TARGET}/bin
-!isEmpty(target.path): INSTALLS += target
-
-HEADERS += \
-    CellframeNodeTray/NodeTrayCommandController.h
-
-DISTFILES +=
diff --git a/cellframe-sdk b/cellframe-sdk
new file mode 160000
index 0000000000000000000000000000000000000000..45751058d77a4a7501009ba3942b6aa507f722dc
--- /dev/null
+++ b/cellframe-sdk
@@ -0,0 +1 @@
+Subproject commit 45751058d77a4a7501009ba3942b6aa507f722dc
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..26f158626c0de977cda7d860286909f18c7537cb
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,38 @@
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QIcon>
+#include <QQuickWidget>
+#include <QSystemTrayIcon>
+#include <QQmlContext>
+#include <QDebug>
+
+#include <QProcess>
+
+#include "NodeTrayCommandController.h"
+#include "DiagnosticWorker.h"
+
+bool findArg(char** begin, char** end, const std::string& option)
+{
+    return std::find(begin, end, option) != end;
+}
+
+
+int main(int argc, char *argv[])
+{
+
+qSetMessagePattern("%{type} %{if-category}%{category}: %{endif}%{function}: %{message}");
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+#endif
+    QApplication app(argc, argv);
+
+    bool start_diag = !findArg(argv, argv+argc,"--no-diag");
+    bool start_tray = !findArg(argv, argv+argc,"--no-tray");
+
+    if(start_diag) DiagnosticWorker::getInstance().init();
+    if(start_tray) NodeTrayCommandController *trayCommandController = new NodeTrayCommandController();
+
+
+    return app.exec();
+}