changeset 3622:8afc14fab01f

New sample plugin: ConnectivityChecks
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 24 Jan 2020 17:06:23 +0100
parents 649489b9bfdb
children a6bfa09df8b3
files CMakeLists.txt NEWS Plugins/Samples/ConnectivityChecks/CMakeLists.txt Plugins/Samples/ConnectivityChecks/JavaScriptLibraries.cmake Plugins/Samples/ConnectivityChecks/Plugin.cpp Plugins/Samples/ConnectivityChecks/WebResources/app.js Plugins/Samples/ConnectivityChecks/WebResources/index.html Plugins/Samples/ConnectivityChecks/WebResources/style.css
diffstat 8 files changed, 549 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Jan 23 18:00:08 2020 +0100
+++ b/CMakeLists.txt	Fri Jan 24 17:06:23 2020 +0100
@@ -37,6 +37,7 @@
 SET(BUILD_MODALITY_WORKLISTS ON CACHE BOOL "Whether to build the sample plugin to serve modality worklists")
 SET(BUILD_RECOVER_COMPRESSED_FILE ON CACHE BOOL "Whether to build the companion tool to recover files compressed using Orthanc")
 SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Whether to build the ServeFolders plugin")
+SET(BUILD_CONNECTIVITY_CHECKS ON CACHE BOOL "Whether to build the ConnectivityChecks plugin")
 SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins")
 SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests")
 
@@ -461,6 +462,65 @@
 
 
 #####################################################################
+## Build the "ConnectivityChecks" plugin
+#####################################################################
+
+if (ENABLE_PLUGINS AND BUILD_CONNECTIVITY_CHECKS)
+  include(ExternalProject)
+
+  unset(Flags)
+  
+  if (CMAKE_TOOLCHAIN_FILE)
+    # Take absolute path to the toolchain
+    get_filename_component(TMP ${CMAKE_TOOLCHAIN_FILE} REALPATH BASE ${CMAKE_SOURCE_DIR})
+    list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${TMP})
+  endif()
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    list(APPEND Flags
+      -DLSB_CC=$ENV{LSB_CC}
+      -DLSB_CXX=$ENV{LSB_CXX}
+      )
+  endif()
+
+  externalproject_add(ConnectivityChecksProject
+    SOURCE_DIR "${ORTHANC_ROOT}/Plugins/Samples/ConnectivityChecks"
+
+    CMAKE_ARGS
+    -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+    -DPLUGIN_VERSION=${ORTHANC_VERSION}
+    -DSTATIC_BUILD=${STATIC_BUILD}
+    -DUSE_LEGACY_JSONCPP=${USE_LEGACY_JSONCPP}
+    ${Flags}
+
+    INSTALL_COMMAND ""  # Skip the install step
+    )
+
+  ExternalProject_Get_Property(ConnectivityChecksProject binary_dir)
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    if (MSVC)
+      set(Prefix "")
+    else()
+      set(Prefix "lib")  # MinGW
+    endif()
+
+    install(FILES
+      ${binary_dir}/${Prefix}OrthancChecks.dll
+      DESTINATION "lib")
+  else()
+    list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+    list(GET CMAKE_FIND_LIBRARY_SUFFIXES 0 Suffix)
+    install(FILES
+      ${binary_dir}/${Prefix}OrthancChecks${Suffix}
+      ${binary_dir}/${Prefix}OrthancChecks${Suffix}.${ORTHANC_VERSION}
+      DESTINATION "share/orthanc/plugins")
+  endif()
+endif()
+
+
+
+#####################################################################
 ## Build the companion tool to recover files compressed using Orthanc
 #####################################################################
 
--- a/NEWS	Thu Jan 23 18:00:08 2020 +0100
+++ b/NEWS	Fri Jan 24 17:06:23 2020 +0100
@@ -1,6 +1,11 @@
 Pending changes in the mainline
 ===============================
 
+Plugins
+-------
+
+* New sample plugin: "ConnectivityChecks"
+
 REST API
 --------
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/CMakeLists.txt	Fri Jan 24 17:06:23 2020 +0100
@@ -0,0 +1,60 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(ConnectivityChecks)
+
+SET(PLUGIN_NAME "connectivity-checks" CACHE STRING "Name of the plugin")
+SET(PLUGIN_VERSION "mainline" CACHE STRING "Version of the plugin")
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../Resources/CMake/OrthancFrameworkParameters.cmake)
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../Resources/CMake/OrthancFrameworkConfiguration.cmake)
+
+include(JavaScriptLibraries.cmake)
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
+    ${PLUGIN_VERSION} ConnectivityChecks ConnectivityChecks.dll "Orthanc plugin to serve additional folders"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/ConnectivityChecks.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  list(APPEND ADDITIONAL_RESOURCES ${AUTOGENERATED_DIR}/ConnectivityChecks.rc)
+endif()  
+
+EmbedResources(
+  WEB_RESOURCES  ${CMAKE_CURRENT_SOURCE_DIR}/WebResources
+  LIBRARIES      ${JAVASCRIPT_LIBS_DIR}
+  )
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+  -DORTHANC_PLUGIN_NAME="${PLUGIN_NAME}"
+  -DORTHANC_PLUGIN_VERSION="${PLUGIN_VERSION}"
+  )
+
+include_directories(
+  ${ORTHANC_ROOT}/Plugins/Include/
+  )
+
+add_library(OrthancChecks SHARED
+  ${ADDITIONAL_RESOURCES}
+  ${AUTOGENERATED_SOURCES}
+  ${ORTHANC_CORE_SOURCES_DEPENDENCIES}
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/Logging.cpp
+  ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  Plugin.cpp
+  )
+
+set_target_properties(
+  OrthancChecks PROPERTIES 
+  VERSION ${PLUGIN_VERSION} 
+  SOVERSION ${PLUGIN_VERSION}
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/JavaScriptLibraries.cmake	Fri Jan 24 17:06:23 2020 +0100
@@ -0,0 +1,42 @@
+set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads")
+
+DownloadPackage(
+  "da0189f7c33bf9f652ea65401e0a3dc9"
+  "${BASE_URL}/dicom-web/bootstrap-4.3.1.zip"
+  "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1")
+
+DownloadPackage(
+  "8242afdc5bd44105d9dc9e6535315484"
+  "${BASE_URL}/dicom-web/vuejs-2.6.10.tar.gz"
+  "${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10")
+
+DownloadPackage(
+  "3e2b4e1522661f7fcf8ad49cb933296c"
+  "${BASE_URL}/dicom-web/axios-0.19.0.tar.gz"
+  "${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0")
+
+DownloadFile(
+  "220afd743d9e9643852e31a135a9f3ae"
+  "${BASE_URL}/jquery-3.4.1.min.js")
+
+
+set(JAVASCRIPT_LIBS_DIR  ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs)
+file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR})
+
+file(COPY
+  ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.map
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js.map
+  ${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10/dist/vue.min.js
+  ${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/jquery-3.4.1.min.js
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/js
+  )
+
+file(COPY
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css.map
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/css
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/Plugin.cpp	Fri Jan 24 17:06:23 2020 +0100
@@ -0,0 +1,124 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <EmbeddedResources.h>
+#include <orthanc/OrthancCPlugin.h>
+
+#include "../../../Core/OrthancException.h"
+#include "../../../Core/SystemToolbox.h"
+
+#define ROOT_URI "/connectivity-checks"
+
+
+static OrthancPluginContext* context_ = NULL;
+
+
+template <Orthanc::EmbeddedResources::DirectoryResourceId DIRECTORY>
+static OrthancPluginErrorCode ServeStaticResource(OrthancPluginRestOutput* output,
+                                                  const char* url,
+                                                  const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    return OrthancPluginErrorCode_Success;
+  }
+
+  std::string path = "/" + std::string(request->groups[0]);
+  std::string mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path));
+
+  try
+  {
+    std::string s;
+    Orthanc::EmbeddedResources::GetDirectoryResource(s, DIRECTORY, path.c_str());
+
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime.c_str());
+  }
+  catch (Orthanc::OrthancException&)
+  {
+    std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]);
+    OrthancPluginLogError(context_, s.c_str());
+    OrthancPluginSendHttpStatusCode(context_, output, 404);
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    context_ = c;
+    
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 0)
+    {
+      char info[256];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              c->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    /* Register the callbacks */
+    OrthancPluginSetDescription(context_, "Utilities to check connectivity to DICOM modalities and Orthanc peers.");
+    OrthancPluginSetRootUri(context_, ROOT_URI "/app/index.html");
+    OrthancPluginRegisterRestCallback(context_, ROOT_URI "/libs/(.*)", ServeStaticResource<Orthanc::EmbeddedResources::LIBRARIES>);
+    OrthancPluginRegisterRestCallback(context_, ROOT_URI "/app/(.*)", ServeStaticResource<Orthanc::EmbeddedResources::WEB_RESOURCES>);
+ 
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return ORTHANC_PLUGIN_NAME;
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/WebResources/app.js	Fri Jan 24 17:06:23 2020 +0100
@@ -0,0 +1,145 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+new Vue({
+  el: '#app',
+  data: {
+    dicomNodes: {},
+    peers: [],
+    canTestPeers: false,
+    dicomWebServers: []
+  },
+  methods: {
+    toggle: function (todo) {
+      todo.done = !todo.done
+    },
+
+    testDicomModalities: function () {
+      console.log('testing DICOM modalities');
+      axios
+        .get('../../modalities?expand')
+        .then(response => {
+          this.dicomNodes = response.data;
+          for (let alias of Object.keys(this.dicomNodes)) {
+            this.dicomNodes[alias]['alias'] = alias;
+            this.dicomNodes[alias]['status'] = 'testing';
+            axios
+              .post('../../modalities/' + alias + '/echo')
+              .then(response => {
+                this.dicomNodes[alias]['status'] = 'ok';
+                this.$forceUpdate();
+              })
+              .catch(response => {
+                this.dicomNodes[alias]['status'] = 'ko';
+                this.$forceUpdate();
+              })
+                }
+        })
+    },
+
+    testOrthancPeers: function () {
+      console.log('testing Orthanc peers');
+      axios
+        .get('../../peers?expand')
+        .then(response => {
+          this.peers = response.data;
+          for (let alias of Object.keys(this.peers)) {
+            this.peers[alias]['alias'] = alias;
+
+            if (this.canTestPeers) {
+              this.peers[alias]['status'] = 'testing';
+              axios
+                .get('../../peers/' + alias + '/system') // introduced in ApiVersion 5 only !
+                .then(response => {
+                  this.peers[alias]['status'] = 'ok';
+                  this.$forceUpdate();
+                })
+                .catch(response => {
+                  this.peers[alias]['status'] = 'ko';
+                  this.$forceUpdate();
+                })
+                  }
+            else {
+              this.peers[alias]['status'] = 'unknown';
+              this.$forceUpdate();
+            }
+          }
+        })
+    },
+
+    testDicomWebServers: function () {
+      console.log('testing Dicom-web servers');
+      axios
+        .get('../../dicom-web/servers?expand')
+        .then(response => {
+          this.dicomWebServers = response.data;
+          for (let alias of Object.keys(this.dicomWebServers)) {
+            this.dicomWebServers[alias]['alias'] = alias;
+            this.dicomWebServers[alias]['status'] = 'testing';
+
+            // perform a dummy qido-rs to test the connectivity
+            axios
+              .post('../../dicom-web/servers/' + alias + '/qido', {
+                'Uri' : '/studies',
+                'Arguments' : {
+                  '00100010' : 'CONNECTIVITY^CHECKS'
+                }
+              })
+              .then(response => {
+                this.dicomWebServers[alias]['status'] = 'ok';
+                this.$forceUpdate();
+              })
+              .catch(response => {
+                this.dicomWebServers[alias]['status'] = 'ko';
+                this.$forceUpdate();
+              })
+                }
+        })
+    },
+
+  },
+  computed: {
+  },
+  mounted() {
+    axios
+      .get('../../system')
+      .then(response => {
+        this.canTestPeers = response.data.ApiVersion >= 5;
+        this.testDicomModalities();
+        if (this.canTestPeers) {
+          this.testOrthancPeers();
+        }
+        this.testDicomWebServers();
+      })
+  }
+})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/WebResources/index.html	Fri Jan 24 17:06:23 2020 +0100
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8">
+
+    <link rel="stylesheet" href="../libs/css/bootstrap.min.css">
+
+    <title>Orthanc Connectivity checks</title>
+    <link rel="stylesheet" href="style.css" type="text/css">
+  </head>
+
+  <body>
+    <div id="app" class="container-fluid">
+      <h2>DICOM nodes</h2>
+      <table class="table">
+        <thead>
+          <tr>
+            <th scope="col">Alias</th>
+            <th scope="col">AET</th>
+            <th scope="col">Host</th>
+            <th scope="col">Port</th>
+            <th scope="col">Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="node in dicomNodes">
+            <th scope="row">{{node.alias}}</th>
+            <td>{{node.AET}}</td>
+            <td>{{node.Host}}</td>
+            <td>{{node.Port}}</td>
+            <td v-if="node.status=='ok'" class="connected">Connected</td>
+            <td v-if="node.status=='ko'" class="disconnected">Disconnected</td>
+            <td v-if="node.status=='testing'">
+              <div class="spinner-border" role="status">
+                <span class="sr-only">Testing...</span>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+
+      <h2>Orthanc peers</h2>
+      <table class="table" v-if="canTestPeers">
+        <thead>
+          <tr>
+            <th scope="col">Alias</th>
+            <th scope="col">Url</th>
+            <th scope="col">Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="node in peers">
+            <th scope="row">{{node.alias}}</th>
+            <td>{{node.Url}}</td>
+            <td v-if="node.status=='ok'" class="connected">Connected</td>
+            <td v-if="node.status=='ko'" class="disconnected">Disconnected</td>
+            <td v-if="node.status=='unknown'" class="unknown">
+              Can not test the peers connectivity with this version of Orthanc
+            </td>
+            <td v-if="node.status=='testing'">
+              <div class="spinner-border" role="status">
+                <span class="sr-only">Testing...</span>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+
+      <h2>DicomWeb servers</h2>
+      <table class="table">
+        <thead>
+          <tr>
+            <th scope="col">Alias</th>
+            <th scope="col">Url</th>
+            <th scope="col">Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="node in dicomWebServers">
+            <th scope="row">{{node.alias}}</th>
+            <td>{{node.Url}}</td>
+            <td v-if="node.status=='ok'" class="connected">Connected</td>
+            <td v-if="node.status=='ko'" class="disconnected">Disconnected</td>
+            <td v-if="node.status=='testing'">
+              <div class="spinner-border" role="status">
+                <span class="sr-only">Testing...</span>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <script src="../libs/js/jquery-3.4.1.min.js" type="text/javascript"></script>
+    <script src="../libs/js/bootstrap.min.js" type="text/javascript"></script>
+    <script src="../libs/js/axios.min.js" type="text/javascript"></script>
+    <script src="../libs/js/vue.min.js" type="text/javascript"></script>
+    <script src="app.js" type="text/javascript"></script>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/WebResources/style.css	Fri Jan 24 17:06:23 2020 +0100
@@ -0,0 +1,13 @@
+.connected {
+    background-color: darkgreen;
+    color: white;
+}
+
+.disconnected {
+  background-color: darkred;
+    color: white;
+}
+
+.unknown {
+  background-color: gold;
+}