diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h
index 2611a191..a2b22810 100644
--- a/client/core/servercontroller.h
+++ b/client/core/servercontroller.h
@@ -32,11 +32,11 @@ public:
// create initial config - generate passwords, etc
QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp);
+ ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
-
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &path, ErrorCode *errorCode = nullptr);
@@ -62,7 +62,6 @@ private:
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
- ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h
index c5f15d5b..1f890f4c 100644
--- a/client/protocols/protocols_defs.h
+++ b/client/protocols/protocols_defs.h
@@ -77,6 +77,7 @@ constexpr char defaultSubnetAddress[] = "10.8.0.0";
constexpr char defaultSubnetMask[] = "255.255.255.0";
constexpr char defaultSubnetCidr[] = "24";
+constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf";
constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt";
constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued";
constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key";
diff --git a/client/resources.qrc b/client/resources.qrc
index 8deef801..91830679 100644
--- a/client/resources.qrc
+++ b/client/resources.qrc
@@ -84,6 +84,10 @@
ui/qml/Pages/PageAbout.qml
ui/qml/Pages/PageQrDecoderIos.qml
ui/qml/Pages/PageViewConfig.qml
+ ui/qml/Pages/PageClientManagement.qml
+ ui/qml/Pages/ClientInfo/PageClientInfoBase.qml
+ ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml
+ ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml
ui/qml/Pages/Protocols/PageProtoCloak.qml
ui/qml/Pages/Protocols/PageProtoOpenVPN.qml
ui/qml/Pages/Protocols/PageProtoShadowSocks.qml
diff --git a/client/server_scripts/openvpn/configure_container.sh b/client/server_scripts/openvpn/configure_container.sh
index 0499b6e8..838088cf 100644
--- a/client/server_scripts/openvpn/configure_container.sh
+++ b/client/server_scripts/openvpn/configure_container.sh
@@ -18,6 +18,7 @@ user nobody
group nobody
persist-key
persist-tun
+crl-verify crl.pem
status openvpn-status.log
verb 1
tls-server
diff --git a/client/server_scripts/openvpn/template.ovpn b/client/server_scripts/openvpn/template.ovpn
index c0b4a044..8ab0c9bf 100644
--- a/client/server_scripts/openvpn/template.ovpn
+++ b/client/server_scripts/openvpn/template.ovpn
@@ -5,6 +5,7 @@ resolv-retry infinite
nobind
persist-key
persist-tun
+crl-verify crl.pem
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
auth $OPENVPN_HASH
diff --git a/client/server_scripts/openvpn_cloak/configure_container.sh b/client/server_scripts/openvpn_cloak/configure_container.sh
index 5e2deb45..94d9610b 100644
--- a/client/server_scripts/openvpn_cloak/configure_container.sh
+++ b/client/server_scripts/openvpn_cloak/configure_container.sh
@@ -18,6 +18,7 @@ user nobody
group nobody
persist-key
persist-tun
+crl-verify crl.pem
status openvpn-status.log
verb 1
tls-server
diff --git a/client/server_scripts/openvpn_cloak/template.ovpn b/client/server_scripts/openvpn_cloak/template.ovpn
index 7f9494b9..062cf8a2 100644
--- a/client/server_scripts/openvpn_cloak/template.ovpn
+++ b/client/server_scripts/openvpn_cloak/template.ovpn
@@ -5,6 +5,7 @@ resolv-retry infinite
nobind
persist-key
persist-tun
+crl-verify crl.pem
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
auth $OPENVPN_HASH
diff --git a/client/server_scripts/openvpn_shadowsocks/configure_container.sh b/client/server_scripts/openvpn_shadowsocks/configure_container.sh
index db8fe055..0d176214 100644
--- a/client/server_scripts/openvpn_shadowsocks/configure_container.sh
+++ b/client/server_scripts/openvpn_shadowsocks/configure_container.sh
@@ -18,6 +18,7 @@ user nobody
group nobody
persist-key
persist-tun
+crl-verify crl.pem
status openvpn-status.log
verb 1
tls-server
diff --git a/client/server_scripts/openvpn_shadowsocks/template.ovpn b/client/server_scripts/openvpn_shadowsocks/template.ovpn
index 64cbd4be..3cdf2ef3 100644
--- a/client/server_scripts/openvpn_shadowsocks/template.ovpn
+++ b/client/server_scripts/openvpn_shadowsocks/template.ovpn
@@ -5,6 +5,7 @@ resolv-retry infinite
nobind
persist-key
persist-tun
+crl-verify crl.pem
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
auth $OPENVPN_HASH
diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp
new file mode 100644
index 00000000..87652ff2
--- /dev/null
+++ b/client/ui/models/clientManagementModel.cpp
@@ -0,0 +1,104 @@
+#include "clientManagementModel.h"
+
+#include
+
+ClientManagementModel::ClientManagementModel(QObject *parent) : QAbstractListModel(parent)
+{
+
+}
+
+void ClientManagementModel::clearData()
+{
+ beginResetModel();
+ m_content.clear();
+ endResetModel();
+}
+
+void ClientManagementModel::setContent(const QVector &data)
+{
+ beginResetModel();
+ m_content = data;
+ endResetModel();
+}
+
+QJsonObject ClientManagementModel::getContent(amnezia::Proto protocol)
+{
+ QJsonObject clientsTable;
+ for (const auto &item : m_content) {
+ if (protocol == amnezia::Proto::OpenVpn) {
+ clientsTable[item.toJsonObject()["openvpnCertId"].toString()] = item.toJsonObject();
+ } else if (protocol == amnezia::Proto::WireGuard) {
+ clientsTable[item.toJsonObject()["wireguardPublicKey"].toString()] = item.toJsonObject();
+ }
+ }
+ return clientsTable;
+}
+
+int ClientManagementModel::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+ return static_cast(m_content.size());
+}
+
+QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() < 0
+ || index.row() >= static_cast(m_content.size())) {
+ return QVariant();
+ }
+
+ if (role == NameRole) {
+ return m_content[index.row()].toJsonObject()["clientName"].toString();
+ } else if (role == OpenVpnCertIdRole) {
+ return m_content[index.row()].toJsonObject()["openvpnCertId"].toString();
+ } else if (role == OpenVpnCertDataRole) {
+ return m_content[index.row()].toJsonObject()["openvpnCertData"].toString();
+ } else if (role == WireGuardPublicKey) {
+ return m_content[index.row()].toJsonObject()["wireguardPublicKey"].toString();
+ }
+
+ return QVariant();
+}
+
+void ClientManagementModel::setData(const QModelIndex &index, QVariant data, int role)
+{
+ if (!index.isValid() || index.row() < 0
+ || index.row() >= static_cast(m_content.size())) {
+ return;
+ }
+
+ auto client = m_content[index.row()].toJsonObject();
+ if (role == NameRole) {
+ client["clientName"] = data.toString();
+ } else if (role == OpenVpnCertIdRole) {
+ client["openvpnCertId"] = data.toString();
+ } else if (role == OpenVpnCertDataRole) {
+ client["openvpnCertData"] = data.toString();
+ } else if (role == WireGuardPublicKey) {
+ client["wireguardPublicKey"] = data.toString();
+ } else {
+ return;
+ }
+ if (m_content[index.row()] != client) {
+ m_content[index.row()] = client;
+ emit dataChanged(index, index);
+ }
+}
+
+bool ClientManagementModel::removeRows(int row)
+{
+ beginRemoveRows(QModelIndex(), row, row);
+ m_content.removeAt(row);
+ endRemoveRows();
+ return true;
+}
+
+QHash ClientManagementModel::roleNames() const
+{
+ QHash roles;
+ roles[NameRole] = "clientName";
+ roles[OpenVpnCertIdRole] = "openvpnCertId";
+ roles[OpenVpnCertDataRole] = "openvpnCertData";
+ roles[WireGuardPublicKey] = "wireguardPublicKey";
+ return roles;
+}
diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h
new file mode 100644
index 00000000..5230c337
--- /dev/null
+++ b/client/ui/models/clientManagementModel.h
@@ -0,0 +1,37 @@
+#ifndef CLIENTMANAGEMENTMODEL_H
+#define CLIENTMANAGEMENTMODEL_H
+
+#include
+
+#include "protocols/protocols_defs.h"
+
+class ClientManagementModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ enum ClientRoles {
+ NameRole = Qt::UserRole + 1,
+ OpenVpnCertIdRole,
+ OpenVpnCertDataRole,
+ WireGuardPublicKey,
+ };
+
+ ClientManagementModel(QObject *parent = nullptr);
+
+ void clearData();
+ void setContent(const QVector &data);
+ QJsonObject getContent(amnezia::Proto protocol);
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ void setData(const QModelIndex &index, QVariant data, int role = Qt::DisplayRole);
+ bool removeRows(int row);
+
+protected:
+ QHash roleNames() const override;
+
+private:
+ QVector m_content;
+};
+
+#endif // CLIENTMANAGEMENTMODEL_H
diff --git a/client/ui/pages.h b/client/ui/pages.h
index dfc9a509..40054f61 100644
--- a/client/ui/pages.h
+++ b/client/ui/pages.h
@@ -12,7 +12,8 @@ public:
enum Type {
Basic,
Proto,
- ShareProto
+ ShareProto,
+ ClientInfo
};
Q_ENUM(Type)
};
@@ -24,7 +25,8 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn,
Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress,
GeneralSettings, AppSettings, NetworkSettings, ServerSettings,
ServerContainers, ServersList, ShareConnection, Sites,
- ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings};
+ ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig,
+ AdvancedServerSettings, ClientManagement, ClientInfo};
Q_ENUM_NS(Page)
static void declareQmlPageEnum() {
diff --git a/client/ui/pages_logic/ClientInfoLogic.cpp b/client/ui/pages_logic/ClientInfoLogic.cpp
new file mode 100644
index 00000000..728b113a
--- /dev/null
+++ b/client/ui/pages_logic/ClientInfoLogic.cpp
@@ -0,0 +1,213 @@
+#include "ClientInfoLogic.h"
+
+#include
+
+#include "defines.h"
+#include "core/errorstrings.h"
+#include "core/servercontroller.h"
+#include "ui/models/clientManagementModel.h"
+#include "ui/uilogic.h"
+
+namespace {
+ bool isErrorOccured(ErrorCode error) {
+ if (error != ErrorCode::NoError) {
+ QMessageBox::warning(nullptr, APPLICATION_NAME,
+ QObject::tr("An error occurred while saving the list of clients.") + "\n" + errorString(error));
+ return true;
+ }
+ return false;
+ }
+}
+
+ClientInfoLogic::ClientInfoLogic(UiLogic *logic, QObject *parent):
+ PageLogicBase(logic, parent)
+{
+
+}
+
+void ClientInfoLogic::setCurrentClientId(int index)
+{
+ m_currentClientIndex = index;
+}
+
+void ClientInfoLogic::onUpdatePage()
+{
+ set_pageContentVisible(false);
+ set_busyIndicatorIsRunning(true);
+
+ const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
+ const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
+ const QString containerNameString = ContainerProps::containerHumanNames().value(container);
+ set_labelCurrentVpnProtocolText(tr("Service: ") + containerNameString);
+
+ const QVector protocols = ContainerProps::protocolsForContainer(container);
+ if (!protocols.empty()) {
+ const Proto currentMainProtocol = protocols.front();
+
+ auto model = qobject_cast(uiLogic()->clientManagementModel());
+ const QModelIndex modelIndex = model->index(m_currentClientIndex);
+
+ set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString());
+ if (currentMainProtocol == Proto::OpenVpn) {
+ const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString();
+ QString certData = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertDataRole).toString();
+
+ if (certData.isEmpty() && !certId.isEmpty()) {
+ QString stdOut;
+ auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
+ stdOut += data + "\n";
+ return ErrorCode::NoError;
+ };
+
+ const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'")
+ .arg(certId);
+ ServerController serverController(m_settings);
+ const QString script = serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container));
+ ErrorCode error = serverController.runScript(credentials, script, cbReadStdOut);
+ certData = stdOut;
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ emit uiLogic()->closePage();
+ return;
+ }
+ }
+ set_labelOpenVpnCertId(certId);
+ set_textAreaOpenVpnCertData(certData);
+ } else if (currentMainProtocol == Proto::WireGuard) {
+ set_textAreaWireGuardKeyData(model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString());
+ }
+ }
+ set_pageContentVisible(true);
+ set_busyIndicatorIsRunning(false);
+}
+
+void ClientInfoLogic::onLineEditNameAliasEditingFinished()
+{
+ set_busyIndicatorIsRunning(true);
+
+ auto model = qobject_cast(uiLogic()->clientManagementModel());
+ const QModelIndex modelIndex = model->index(m_currentClientIndex);
+ model->setData(modelIndex, m_lineEditNameAliasText, ClientManagementModel::ClientRoles::NameRole);
+
+ const DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
+ const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
+ const QVector protocols = ContainerProps::protocolsForContainer(selectedContainer);
+ if (!protocols.empty()) {
+ const Proto currentMainProtocol = protocols.front();
+ const QJsonObject clientsTable = model->getContent(currentMainProtocol);
+ ErrorCode error = setClientsList(credentials,
+ selectedContainer,
+ currentMainProtocol,
+ clientsTable);
+ isErrorOccured(error);
+ }
+
+ set_busyIndicatorIsRunning(false);
+}
+
+void ClientInfoLogic::onRevokeOpenVpnCertificateClicked()
+{
+ set_busyIndicatorIsRunning(true);
+ const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
+ const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
+
+ auto model = qobject_cast(uiLogic()->clientManagementModel());
+ const QModelIndex modelIndex = model->index(m_currentClientIndex);
+ const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString();
+
+ const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '"
+ "cd /opt/amnezia/openvpn ;\\"
+ "easyrsa revoke %1 ;\\"
+ "easyrsa gen-crl ;\\"
+ "cp pki/crl.pem .'").arg(certId);
+ ServerController serverController(m_settings);
+ const QString script = serverController.replaceVars(getOpenVpnCertData,
+ serverController.genVarsForScript(credentials, container));
+ auto error = serverController.runScript(credentials, script);
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ emit uiLogic()->goToPage(Page::ServerSettings);
+ return;
+ }
+
+ model->removeRows(m_currentClientIndex);
+ const QJsonObject clientsTable = model->getContent(Proto::OpenVpn);
+ error = setClientsList(credentials, container, Proto::OpenVpn, clientsTable);
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ return;
+ }
+
+ const QJsonObject &containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, container);
+ error = serverController.startupContainerWorker(credentials, container, containerConfig);
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ return;
+ }
+
+ set_busyIndicatorIsRunning(false);
+}
+
+void ClientInfoLogic::onRevokeWireGuardKeyClicked()
+{
+ set_busyIndicatorIsRunning(true);
+ ErrorCode error;
+ const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
+ const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
+
+ ServerController serverController(m_settings);
+
+ const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf";
+ const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ return;
+ }
+
+ auto model = qobject_cast(uiLogic()->clientManagementModel());
+ const QModelIndex modelIndex = model->index(m_currentClientIndex);
+ const QString key = model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString();
+
+ auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts);
+ for (auto §ion : configSections) {
+ if (section.contains(key)) {
+ configSections.removeOne(section);
+ }
+ }
+ QString newWireGuardConfig = configSections.join("[");
+ newWireGuardConfig.insert(0, "[");
+ error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig,
+ protocols::wireguard::serverConfigPath,
+ libssh::SftpOverwriteMode::SftpOverwriteExisting);
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ return;
+ }
+
+ model->removeRows(m_currentClientIndex);
+ const QJsonObject clientsTable = model->getContent(Proto::WireGuard);
+ error = setClientsList(credentials, container, Proto::WireGuard, clientsTable);
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ return;
+ }
+
+ const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'";
+ error = serverController.runScript(credentials,
+ serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
+ if (isErrorOccured(error)) {
+ set_busyIndicatorIsRunning(false);
+ return;
+ }
+
+ set_busyIndicatorIsRunning(false);
+}
+
+ErrorCode ClientInfoLogic::setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns)
+{
+ const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol);
+ const QString clientsTableFile = QString("opt/amnezia/%1/clientsTable").arg(mainProtocolString);
+ ServerController serverController(m_settings);
+ ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), clientsTableFile);
+ return error;
+}
diff --git a/client/ui/pages_logic/ClientInfoLogic.h b/client/ui/pages_logic/ClientInfoLogic.h
new file mode 100644
index 00000000..5ba19887
--- /dev/null
+++ b/client/ui/pages_logic/ClientInfoLogic.h
@@ -0,0 +1,42 @@
+#ifndef CLIENTINFOLOGIC_H
+#define CLIENTINFOLOGIC_H
+
+#include "PageLogicBase.h"
+
+#include "core/defs.h"
+#include "containers/containers_defs.h"
+#include "protocols/protocols_defs.h"
+
+class UiLogic;
+
+class ClientInfoLogic : public PageLogicBase
+{
+ Q_OBJECT
+
+ AUTO_PROPERTY(QString, lineEditNameAliasText)
+ AUTO_PROPERTY(QString, labelOpenVpnCertId)
+ AUTO_PROPERTY(QString, textAreaOpenVpnCertData)
+ AUTO_PROPERTY(QString, labelCurrentVpnProtocolText)
+ AUTO_PROPERTY(QString, textAreaWireGuardKeyData)
+ AUTO_PROPERTY(bool, busyIndicatorIsRunning);
+ AUTO_PROPERTY(bool, pageContentVisible);
+
+public:
+ ClientInfoLogic(UiLogic *uiLogic, QObject *parent = nullptr);
+ ~ClientInfoLogic() = default;
+
+ void setCurrentClientId(int index);
+
+public slots:
+ void onUpdatePage() override;
+ void onLineEditNameAliasEditingFinished();
+ void onRevokeOpenVpnCertificateClicked();
+ void onRevokeWireGuardKeyClicked();
+
+private:
+ ErrorCode setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns);
+
+ int m_currentClientIndex;
+};
+
+#endif // CLIENTINFOLOGIC_H
diff --git a/client/ui/pages_logic/ClientManagementLogic.cpp b/client/ui/pages_logic/ClientManagementLogic.cpp
new file mode 100644
index 00000000..32c7d182
--- /dev/null
+++ b/client/ui/pages_logic/ClientManagementLogic.cpp
@@ -0,0 +1,143 @@
+#include "ClientManagementLogic.h"
+
+#include
+
+#include "defines.h"
+#include "core/errorstrings.h"
+#include "core/servercontroller.h"
+#include "ui/pages_logic/ClientInfoLogic.h"
+#include "ui/models/clientManagementModel.h"
+#include "ui/uilogic.h"
+
+ClientManagementLogic::ClientManagementLogic(UiLogic *logic, QObject *parent):
+ PageLogicBase(logic, parent)
+{
+
+}
+
+void ClientManagementLogic::onUpdatePage()
+{
+ set_busyIndicatorIsRunning(true);
+
+ qobject_cast(uiLogic()->clientManagementModel())->clearData();
+ DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
+ QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer);
+ set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName);
+
+ QJsonObject clients;
+
+ auto protocols = ContainerProps::protocolsForContainer(selectedContainer);
+ if (!protocols.empty()) {
+ m_currentMainProtocol = protocols.front();
+
+ const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
+
+ ErrorCode error = getClientsList(credentials, selectedContainer, m_currentMainProtocol, clients);
+ if (error != ErrorCode::NoError) {
+ QMessageBox::warning(nullptr, APPLICATION_NAME,
+ tr("An error occurred while getting the list of clients.") + "\n" + errorString(error));
+ set_busyIndicatorIsRunning(false);
+ return;
+ }
+ }
+ QVector clientsArray;
+ for (auto &clientId : clients.keys()) {
+ clientsArray.push_back(clients[clientId].toObject());
+ }
+ qobject_cast(uiLogic()->clientManagementModel())->setContent(clientsArray);
+
+ set_busyIndicatorIsRunning(false);
+}
+
+void ClientManagementLogic::onClientItemClicked(int index)
+{
+ uiLogic()->pageLogic()->setCurrentClientId(index);
+ emit uiLogic()->goToClientInfoPage(m_currentMainProtocol);
+}
+
+ErrorCode ClientManagementLogic::getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns)
+{
+ ErrorCode error = ErrorCode::NoError;
+ QString stdOut;
+ auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
+ stdOut += data + "\n";
+ return ErrorCode::NoError;
+ };
+
+ const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol);
+
+ ServerController serverController(m_settings);
+
+ const QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable").arg(mainProtocolString);
+ const QByteArray clientsTableString = serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error);
+ if (error != ErrorCode::NoError) {
+ return error;
+ }
+ QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object();
+ int count = 0;
+
+ if (mainProtocol == Proto::OpenVpn) {
+ const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'";
+ QString script = serverController.replaceVars(getOpenVpnClientsList, serverController.genVarsForScript(credentials, container));
+ error = serverController.runScript(credentials, script, cbReadStdOut);
+ if (error != ErrorCode::NoError) {
+ return error;
+ }
+
+ if (!stdOut.isEmpty()) {
+ QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts);
+ certsIds.removeAll("AmneziaReq.crt");
+
+ for (auto &openvpnCertId : certsIds) {
+ openvpnCertId.replace(".crt", "");
+ if (!clientsTable.contains(openvpnCertId)) {
+
+ QJsonObject client;
+ client["openvpnCertId"] = openvpnCertId;
+ client["clientName"] = QString("Client %1").arg(count);
+ client["openvpnCertData"] = "";
+ clientsTable[openvpnCertId] = client;
+ count++;
+ }
+ }
+ }
+ } else if (mainProtocol == Proto::WireGuard) {
+ const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf";
+ const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
+ if (error != ErrorCode::NoError) {
+ return error;
+ }
+
+ auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts);
+ QStringList wireguardKeys;
+ for (const auto &line : configLines) {
+ auto configPair = line.split(" = ", Qt::SkipEmptyParts);
+ if (configPair.front() == "PublicKey") {
+ wireguardKeys.push_back(configPair.back());
+ }
+ }
+
+ for (auto &wireguardKey : wireguardKeys) {
+ if (!clientsTable.contains(wireguardKey)) {
+ QJsonObject client;
+ client["clientName"] = QString("Client %1").arg(count);
+ client["wireguardPublicKey"] = wireguardKey;
+ clientsTable[wireguardKey] = client;
+ count++;
+ }
+ }
+ }
+
+ const QByteArray newClientsTableString = QJsonDocument(clientsTable).toJson();
+ if (clientsTableString != newClientsTableString) {
+ error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile);
+ }
+
+ if (error != ErrorCode::NoError) {
+ return error;
+ }
+
+ clietns = clientsTable;
+
+ return error;
+}
diff --git a/client/ui/pages_logic/ClientManagementLogic.h b/client/ui/pages_logic/ClientManagementLogic.h
new file mode 100644
index 00000000..9c181716
--- /dev/null
+++ b/client/ui/pages_logic/ClientManagementLogic.h
@@ -0,0 +1,33 @@
+#ifndef CLIENTMANAGMENTLOGIC_H
+#define CLIENTMANAGMENTLOGIC_H
+
+#include "PageLogicBase.h"
+
+#include "core/defs.h"
+#include "containers/containers_defs.h"
+#include "protocols/protocols_defs.h"
+
+class UiLogic;
+
+class ClientManagementLogic : public PageLogicBase
+{
+ Q_OBJECT
+
+ AUTO_PROPERTY(QString, labelCurrentVpnProtocolText)
+ AUTO_PROPERTY(bool, busyIndicatorIsRunning);
+
+public:
+ ClientManagementLogic(UiLogic *uiLogic, QObject *parent = nullptr);
+ ~ClientManagementLogic() = default;
+
+public slots:
+ void onUpdatePage() override;
+ void onClientItemClicked(int index);
+
+private:
+ ErrorCode getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns);
+
+ amnezia::Proto m_currentMainProtocol;
+};
+
+#endif // CLIENTMANAGMENTLOGIC_H
diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml
new file mode 100644
index 00000000..7fde5bab
--- /dev/null
+++ b/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml
@@ -0,0 +1,15 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import PageEnum 1.0
+import ProtocolEnum 1.0
+import "../"
+import "../../Controls"
+import "../../Config"
+
+PageBase {
+ id: root
+ property var protocol: ProtocolEnum.Any
+ page: PageEnum.ClientInfo
+ logic: ClientInfoLogic
+}
diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml
new file mode 100644
index 00000000..da5ba53c
--- /dev/null
+++ b/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml
@@ -0,0 +1,115 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import ProtocolEnum 1.0
+import "../"
+import "../../Controls"
+import "../../Config"
+
+PageClientInfoBase {
+ id: root
+ protocol: ProtocolEnum.OpenVpn
+
+ BackButton {
+ id: back
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ }
+
+ Caption {
+ id: caption
+ text: qsTr("Client Info")
+ }
+
+ BusyIndicator {
+ z: 99
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ visible: ClientInfoLogic.busyIndicatorIsRunning
+ running: ClientInfoLogic.busyIndicatorIsRunning
+ }
+
+ FlickableType {
+ id: fl
+ anchors.top: caption.bottom
+ contentHeight: content.height
+ visible: ClientInfoLogic.pageContentVisible
+
+ ColumnLayout {
+ id: content
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: GC.defaultMargin
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.fillWidth: true
+ font.pixelSize: 20
+ horizontalAlignment: Text.AlignHCenter
+ text: ClientInfoLogic.labelCurrentVpnProtocolText
+ }
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ height: 21
+ text: qsTr("Client name")
+ }
+
+ TextFieldType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.fillWidth: true
+ Layout.preferredHeight: 31
+ text: ClientInfoLogic.lineEditNameAliasText
+ onEditingFinished: {
+ if (text !== ClientInfoLogic.lineEditNameAliasText) {
+ ClientInfoLogic.lineEditNameAliasText = text
+ ClientInfoLogic.onLineEditNameAliasEditingFinished()
+ }
+ }
+ }
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.topMargin: 20
+ height: 21
+ text: qsTr("Certificate id")
+ }
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ text: ClientInfoLogic.labelOpenVpnCertId
+ }
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.topMargin: 20
+ height: 21
+ text: qsTr("Certificate")
+ }
+
+ TextAreaType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.preferredHeight: 200
+ Layout.fillWidth: true
+
+ textArea.readOnly: true
+ textArea.wrapMode: TextEdit.WrapAnywhere
+ textArea.verticalAlignment: Text.AlignTop
+ textArea.text: ClientInfoLogic.textAreaOpenVpnCertData
+ }
+
+ BlueButtonType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.fillWidth: true
+ Layout.preferredHeight: 41
+ text: qsTr("Revoke Certificate")
+ onClicked: {
+ ClientInfoLogic.onRevokeOpenVpnCertificateClicked()
+ UiLogic.closePage()
+ }
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml
new file mode 100644
index 00000000..befaf7f8
--- /dev/null
+++ b/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml
@@ -0,0 +1,100 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import ProtocolEnum 1.0
+import "../"
+import "../../Controls"
+import "../../Config"
+
+PageClientInfoBase {
+ id: root
+ protocol: ProtocolEnum.WireGuard
+
+ BackButton {
+ id: back
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ }
+
+ Caption {
+ id: caption
+ text: qsTr("Client Info")
+ }
+
+ BusyIndicator {
+ z: 99
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ visible: ClientInfoLogic.busyIndicatorIsRunning
+ running: ClientInfoLogic.busyIndicatorIsRunning
+ }
+
+ FlickableType {
+ id: fl
+ anchors.top: caption.bottom
+ contentHeight: content.height
+
+ ColumnLayout {
+ id: content
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: GC.defaultMargin
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.fillWidth: true
+ font.pixelSize: 20
+ horizontalAlignment: Text.AlignHCenter
+ text: ClientInfoLogic.labelCurrentVpnProtocolText
+ }
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ height: 21
+ text: qsTr("Client name")
+ }
+
+ TextFieldType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.fillWidth: true
+ Layout.preferredHeight: 31
+ text: ClientInfoLogic.lineEditNameAliasText
+ onEditingFinished: {
+ if (text !== ClientInfoLogic.lineEditNameAliasText) {
+ ClientInfoLogic.lineEditNameAliasText = text
+ ClientInfoLogic.onLineEditNameAliasEditingFinished()
+ }
+ }
+ }
+
+ LabelType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.topMargin: 20
+ height: 21
+ text: qsTr("Public Key")
+ }
+
+ TextAreaType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.preferredHeight: 200
+ Layout.fillWidth: true
+
+ textArea.readOnly: true
+ textArea.wrapMode: TextEdit.WrapAnywhere
+ textArea.verticalAlignment: Text.AlignTop
+ textArea.text: ClientInfoLogic.textAreaWireGuardKeyData
+ }
+
+ BlueButtonType {
+ enabled: !ClientInfoLogic.busyIndicatorIsRunning
+ Layout.fillWidth: true
+ Layout.preferredHeight: 41
+ text: qsTr("Revoke Key")
+ onClicked: {
+ ClientInfoLogic.onRevokeWireGuardKeyClicked()
+ UiLogic.closePage()
+ }
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/Pages/PageClientManagement.qml b/client/ui/qml/Pages/PageClientManagement.qml
new file mode 100644
index 00000000..2b656def
--- /dev/null
+++ b/client/ui/qml/Pages/PageClientManagement.qml
@@ -0,0 +1,120 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Shapes 1.4
+import SortFilterProxyModel 0.2
+import PageEnum 1.0
+import "./"
+import "../Controls"
+import "../Config"
+
+PageBase {
+ id: root
+ page: PageEnum.ClientManagement
+ logic: ClientManagementLogic
+ enabled: !ClientManagementLogic.busyIndicatorIsRunning
+
+ BackButton {
+ id: back
+ }
+
+ Caption {
+ id: caption
+ text: qsTr("Clients Management")
+ }
+
+ BusyIndicator {
+ z: 99
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ visible: ClientManagementLogic.busyIndicatorIsRunning
+ running: ClientManagementLogic.busyIndicatorIsRunning
+ }
+
+ FlickableType {
+ id: fl
+ anchors.top: caption.bottom
+ contentHeight: content.height
+
+ Column {
+ id: content
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ LabelType {
+ font.pixelSize: 20
+ leftPadding: -20
+ anchors.horizontalCenter: parent.horizontalCenter
+ horizontalAlignment: Text.AlignHCenter
+ text: ClientManagementLogic.labelCurrentVpnProtocolText
+ }
+
+ SortFilterProxyModel {
+ id: proxyClientManagementModel
+ sourceModel: UiLogic.clientManagementModel
+ sorters: RoleSorter { roleName: "clientName" }
+ }
+
+ ListView {
+ id: lv_clients
+ width: parent.width
+ implicitHeight: contentHeight + 20
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+ topMargin: 10
+ spacing: 10
+ clip: true
+ model: proxyClientManagementModel
+ highlightRangeMode: ListView.ApplyRange
+ highlightMoveVelocity: -1
+ delegate: Item {
+ implicitWidth: lv_clients.width
+ implicitHeight: 60
+
+ MouseArea {
+ id: ms
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ ClientManagementLogic.onClientItemClicked(proxyClientManagementModel.mapToSource(index))
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse
+ LinearGradient {
+ id: gradient_notContainsMouse
+ x1: 0 ; y1:0
+ x2: 0 ; y2: height
+ stops: [
+ GradientStop { position: 0.0; color: "#FAFBFE" },
+ GradientStop { position: 1.0; color: "#ECEEFF" }
+ ]
+ }
+ LinearGradient {
+ id: gradient_containsMouse
+ x1: 0 ; y1:0
+ x2: 0 ; y2: height
+ stops: [
+ GradientStop { position: 0.0; color: "#FAFBFE" },
+ GradientStop { position: 1.0; color: "#DCDEDF" }
+ ]
+ }
+ }
+
+ LabelType {
+ x: 20
+ y: 20
+ font.pixelSize: 20
+ text: clientName
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/Pages/PageServerSettings.qml b/client/ui/qml/Pages/PageServerSettings.qml
index 60459955..a3f40ae6 100644
--- a/client/ui/qml/Pages/PageServerSettings.qml
+++ b/client/ui/qml/Pages/PageServerSettings.qml
@@ -16,6 +16,7 @@ PageBase {
BackButton {
id: back
}
+
Caption {
id: caption
text: qsTr("Server settings")
@@ -96,8 +97,16 @@ PageBase {
}
BlueButtonType {
- Layout.fillWidth: true
Layout.topMargin: 60
+ Layout.fillWidth: true
+ text: qsTr("Clients Management")
+ onClicked: {
+ UiLogic.goToPage(PageEnum.ClientManagement)
+ }
+ }
+ BlueButtonType {
+ Layout.fillWidth: true
+ Layout.topMargin: 10
text: ServerSettingsLogic.pushButtonClearClientCacheText
visible: ServerSettingsLogic.pushButtonClearClientCacheVisible
onClicked: {
diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml
index d91c013f..894ff3dd 100644
--- a/client/ui/qml/main.qml
+++ b/client/ui/qml/main.qml
@@ -12,12 +12,14 @@ import "Controls"
import "Pages"
import "Pages/Protocols"
import "Pages/Share"
+import "Pages/ClientInfo"
import "Config"
Window {
property var pages: ({})
property var protocolPages: ({})
property var sharePages: ({})
+ property var clientInfoPages: ({})
id: root
visible: true
@@ -38,6 +40,7 @@ Window {
if (type === PageType.Basic) p_obj = pages[page]
else if (type === PageType.Proto) p_obj = protocolPages[page]
else if (type === PageType.ShareProto) p_obj = sharePages[page]
+ else if (type === PageType.ClientInfo) p_obj = clientInfoPages[page]
else return
//console.debug("QML gotoPage " + type + " " + page + " " + p_obj)
@@ -154,6 +157,19 @@ Window {
}
}
+ FolderListModel {
+ id: folderModelClientInfo
+ folder: "qrc:/ui/qml/Pages/ClientInfo/"
+ nameFilters: ["*.qml"]
+ showDirs: false
+
+ onStatusChanged: if (status == FolderListModel.Ready) {
+ for (var i=0; i settings, std::shared_ptrmoveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -533,6 +536,8 @@ void UiLogic::registerPagesLogic()
registerPageLogic();
registerPageLogic();
registerPageLogic();
+ registerPageLogic();
+ registerPageLogic();
registerPageLogic();
}
diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h
index f668a925..904c7f6e 100644
--- a/client/ui/uilogic.h
+++ b/client/ui/uilogic.h
@@ -19,6 +19,7 @@
#include "models/containers_model.h"
#include "models/protocols_model.h"
+#include "models/clientManagementModel.h"
#include "notificationhandler.h"
@@ -43,6 +44,8 @@ class StartPageLogic;
class ViewConfigLogic;
class VpnLogic;
class WizardLogic;
+class ClientManagementLogic;
+class ClientInfoLogic;
class AdvancedServerSettingsLogic;
class PageProtocolLogicBase;
@@ -66,6 +69,7 @@ class UiLogic : public QObject
READONLY_PROPERTY(QObject *, containersModel)
READONLY_PROPERTY(QObject *, protocolsModel)
+ READONLY_PROPERTY(QObject *, clientManagementModel)
public:
explicit UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent = nullptr);
@@ -88,6 +92,8 @@ public:
friend class ViewConfigLogic;
friend class VpnLogic;
friend class WizardLogic;
+ friend class ClientManagementLogic;
+ friend class ClientInfoLogic;
friend class AdvancedServerSettingsLogic;
friend class PageProtocolLogicBase;
@@ -129,6 +135,7 @@ signals:
void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true);
void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true);
void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true);
+ void goToClientInfoPage(Proto protocol, bool reset = true, bool slide = true);
void closePage();
void setStartPage(PageEnumNS::Page page, bool slide = true);
diff --git a/docs/openVpnClientInfoWorkFlow.plantuml b/docs/openVpnClientInfoWorkFlow.plantuml
new file mode 100644
index 00000000..ff4ec9ef
--- /dev/null
+++ b/docs/openVpnClientInfoWorkFlow.plantuml
@@ -0,0 +1,52 @@
+@startuml openVpnRevokeClientCertificate
+
+|client management page|
+start
+
+:on update client management page;
+:get clientsTable file from container;
+:get a list of issued certificates;
+
+repeat
+
+if ( for each issued certificate:
+clientsTable contains the issued certificate) then (yes)
+else (no)
+ :add certificate id to clientsTable;
+endif
+
+repeat while (is this the last issued certificate?)
+
+if (if clientsTable has been changed) then (yes)
+ :save the clientsTable file on the server;
+else (no)
+endif
+
+:add clientsTable to the clientManagementModel;
+
+|client info page|
+:on update client info page;
+floating note
+ clicked on one of the clients
+ on the client management page
+end note
+
+:get the certificate data for the selected client;
+if (if client name has been changed) then (yes)
+ :update clientManagementModel;
+ :get clientsTable from clientManagementModel;
+ :save the clientsTable file on the server;
+else (no)
+ if (if revoke certificate was clicked) then (yes)
+ :described in file openVpnRevokeClientCertificate.plantuml;
+ |client management page|
+ :return to page client management;
+ stop
+ else (no)
+ |client info page|
+ :just look at the beautiful page;
+ stop
+ endif
+endif
+stop
+@enduml
\ No newline at end of file
diff --git a/docs/openVpnRevokeClientCertificate.plantuml b/docs/openVpnRevokeClientCertificate.plantuml
new file mode 100644
index 00000000..2d8d4abd
--- /dev/null
+++ b/docs/openVpnRevokeClientCertificate.plantuml
@@ -0,0 +1,34 @@
+@startuml openVpnRevokeClientCertificate
+actor Admin as adm
+participant "Amnezia Client" as cli
+participant "Amnezia Container" as cont
+participant "OpenVpn Service" as ovpn
+
+adm -> cli: revoke the selected client certificate
+cli -> cli: start busy indicator
+cli -> cont: execute script "revoke openvpn client"
+
+cont -> cont: cd /opt/amnezia/openvpn
+cont -> cont: easyrsa revoke openvpnCertId
+cont -> cont: easyrsa gen-crl
+cont -> cont: cp pki/crl.pem crl.pem
+cont -> ovpn: restart openvpn service
+note right
+ In the OpenVpn config
+ there should be a line "crl-verify crl.pem".
+ After that, the service will ignore
+ the certificates contained in the crl.pem file
+end note
+
+
+group#lightgreen #lightgreen if [successful case]
+ ovpn --> cont: restart result
+ cont --> cli: back to the client management page
+else #pink some kind of failure
+ cont --> cli: display an error depending on when it occurred
+end
+
+cli -> cli: stop busy indicator
+cli --> adm: return control to the user
+
+@enduml
\ No newline at end of file
diff --git a/docs/wireGuardClientInfoWorkFlow.plantuml b/docs/wireGuardClientInfoWorkFlow.plantuml
new file mode 100644
index 00000000..96876c39
--- /dev/null
+++ b/docs/wireGuardClientInfoWorkFlow.plantuml
@@ -0,0 +1,59 @@
+@startuml wireGuardRevokeClientCertificate
+
+|client management page|
+start
+
+:on update client management page;
+:get clientsTable file from server;
+:get wireguard config from server;
+
+repeat
+
+if ( for each public key in wireguard config:
+clientsTable contains the public key) then (yes)
+else (no)
+ :add public key to clientsTable;
+endif
+
+repeat while (is this the last public key?)
+
+if (if clientsTable has been changed) then (yes)
+ :save the clientsTable file on the server;
+else (no)
+endif
+
+:add clientsTable to the clientManagementModel;
+
+|client info page|
+:on update client info page;
+floating note
+ clicked on one of the clients
+ on the client management page
+end note
+
+:get the certificate data for the selected client;
+if (if client name has been changed) then (yes)
+ :update clientManagementModel;
+ :get clientsTable from clientManagementModel;
+ :save the clientsTable file on the server;
+else (no)
+ if (if revoke key was clicked) then (yes)
+ :update clientManagementModel;
+ :get clientsTable from clientManagementModel;
+
+ :delete section with public key from wireguard config;
+ :save wireguard config on the server;
+ :restart wireguard service;
+
+ :save the clientsTable file on the server;
+ |client management page|
+ :return to page client management;
+ stop
+ else (no)
+ |client info page|
+ :just look at the beautiful page;
+ stop
+ endif
+endif
+stop
+@enduml
\ No newline at end of file