Browse Source

Added System Report feature v1.

Co-authored-by: Tobias Schäfer <ts@zammad.com>
Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
Co-authored-by: Dominik Klein <dk@zammad.com>
Co-authored-by: Florian Liebe <fl@zammad.com>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Dominik Klein 10 months ago
parent
commit
c4fedde979

+ 3 - 0
Gemfile

@@ -197,6 +197,9 @@ gem 'pry-rescue'
 gem 'pry-stack_explorer'
 gem 'pry-theme'
 
+# monitoring / system report
+gem 'macaddr'
+
 # Gems used only for develop/test and not required
 # in production environments by default.
 group :development, :test do

+ 4 - 0
Gemfile.lock

@@ -318,6 +318,8 @@ GEM
     loofah (2.22.0)
       crass (~> 1.0.2)
       nokogiri (>= 1.12.0)
+    macaddr (1.7.2)
+      systemu (~> 2.6.5)
     mail (2.8.1)
       mini_mime (>= 0.1.1)
       net-imap
@@ -626,6 +628,7 @@ GEM
       actionpack (>= 5.2)
       activesupport (>= 5.2)
       sprockets (>= 3.0.0)
+    systemu (2.6.5)
     tcr (0.4.0)
     telegram-bot-ruby (2.0.0)
       dry-struct (~> 1.6)
@@ -771,6 +774,7 @@ DEPENDENCIES
   koala
   listen
   localhost
+  macaddr
   mail
   messagebird-rest
   mime-types

+ 41 - 0
app/assets/javascripts/app/controllers/system_report.coffee

@@ -0,0 +1,41 @@
+class SystemReport extends App.ControllerSubContent
+  @requiredPermission: 'admin.system_report'
+  header: __('System Report')
+
+  constructor: ->
+    super
+    @load()
+
+  # fetch data, render view
+  load: ->
+    @startLoading()
+    @ajax(
+      id:    'system_report'
+      type:  'GET'
+      url:   "#{@apiPath}/system_report"
+      success: (data) =>
+        @stopLoading()
+        @system_report = data.fetch
+        @descriptions  = data.descriptions
+        @render()
+    )
+
+  render: ->
+    content = $(App.view('system_report')(
+      system_report: @system_report
+      descriptions: @descriptions
+    ))
+
+    configureAttributes = [
+      { id: 'previewSystemReport', name: 'preview_system_report', tag: 'code_editor', null: true, disabled: true, lineNumbers: false, height: 620, value: JSON.stringify(@system_report, null, 2) },
+    ]
+    searchResultResponse = new App.ControllerForm(
+      el: content.find('.js-previewSystemReport')
+      model:
+        configure_attributes: configureAttributes
+      noFieldset: true
+    )
+
+    @html content
+
+App.Config.set('SystemReport', { prio: 3800, name: __('System Report'), parent: '#system', target: '#system/system_report', controller: SystemReport, permission: ['admin'] }, 'NavBarAdmin' )

+ 22 - 0
app/assets/javascripts/app/views/system_report.jst.eco

@@ -0,0 +1,22 @@
+<div class="page-header-title">
+  <h1><%- @T('System Report') %></h1>
+</div>
+<div class="page-content">
+  <p>
+    <%- @T('The system report provides a summarized version of the system state and configuration for support and analyzing purposes. You can download the report by clicking the Download-button. Zammad then provides a JSON file as you can see in the preview below. If you get in touch with the Zammad support, this JSON file is helpful identifying your issue.') %>
+  </p>
+  <p>
+    <%- @T('Note: Zammad never sends this data automatically and this data doesn\'t include personal account names or passwords. However, please have a look after downloading the file to avoid leaking personal data which could be included in comments or notes.') %>
+  </p>
+  <h2><%- @T('Contents') %></h2>
+  <p>
+    The following contents are provided in the system report:
+  </p>
+  <ul>
+  <% for description in @descriptions.map((desc) => @T(desc)).sort(): %>
+    <li><%= description %></li>
+  <% end %>
+  </ul>
+  <h2><%- @T('Preview') %> <a href="<%= @C('http_type') %>://<%= @C('fqdn') %>/api/v1/system_report/download" class="btn btn--action" data-type="attachment" download><%- @Icon('download') %><span><%- @T('Download') %></span></a></h2>
+  <div class="js-previewSystemReport"></div>
+</div>

+ 2 - 0
app/assets/stylesheets/svg-dimensions.css

@@ -115,6 +115,7 @@
 .icon-package { width: 16px; height: 16px; }
 .icon-paperclip { width: 28px; height: 28px; }
 .icon-pen { width: 16px; height: 16px; }
+.icon-person-magnifier { width: 24px; height: 24px; }
 .icon-person { width: 24px; height: 24px; }
 .icon-phone { width: 17px; height: 17px; }
 .icon-plus-small { width: 16px; height: 16px; }
@@ -141,6 +142,7 @@
 .icon-split { width: 16px; height: 17px; }
 .icon-sso-button { width: 29px; height: 24px; }
 .icon-status-modified-outer-circle { width: 16px; height: 16px; }
+.icon-status-report { width: 22px; height: 22px; }
 .icon-status { width: 16px; height: 16px; }
 .icon-stopwatch { width: 77px; height: 83px; }
 .icon-strikethrough { width: 12px; height: 12px; }

+ 32 - 0
app/controllers/system_report_controller.rb

@@ -0,0 +1,32 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+class SystemReportController < ApplicationController
+
+  prepend_before_action :authenticate_and_authorize!
+
+  # GET /api/v1/system_report
+  def index
+    render json: {
+      descriptions: SystemReport.descriptions,
+      fetch:        SystemReport.fetch
+    }
+  end
+
+  # GET /api/v1/system_report/download
+  def download
+    instance = SystemReport.fetch_with_create
+
+    send_data(
+      instance.data.to_json,
+      filename:    instance.filename,
+      type:        'application/json',
+      disposition: 'attachment'
+    )
+  end
+
+  # GET /api/v1/system_report/plugins
+  def plugins
+    render json: { plugins: SystemReport.plugins }
+  end
+
+end

+ 58 - 0
app/models/system_report.rb

@@ -0,0 +1,58 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+class SystemReport < ApplicationModel
+  store :data
+
+  before_create :prepare_uuid
+
+  def self.fetch
+    {
+      system_report: fetch_system_report,
+    }
+  end
+
+  def self.fetch_with_create
+    SystemReport.create(data: fetch, created_by_id: UserInfo.current_user_id || 1)
+  end
+
+  def self.plugins
+    SystemReport::Plugin.list.map { |plugin| plugin.to_s.split('::').last }
+  end
+
+  def self.descriptions
+    SystemReport::Plugin.list.map { |plugin| "#{plugin}::DESCRIPTION".constantize }
+  end
+
+  def self.fetch_system_report
+    system_report = {}
+
+    SystemReport::Plugin.list.each do |plugin|
+      plugin_instance = plugin.new
+
+      path = plugin_instance.class.path
+
+      last_path = path.pop # Remove and store the last key
+
+      nested_hash = path.inject(system_report) do |current_hash, key|
+        current_hash[key] ||= {}
+        current_hash[key]
+      end
+
+      # Set the value to the last key
+      nested_hash[last_path] = plugin_instance.fetch
+    end
+
+    system_report
+  end
+  private_class_method :fetch_system_report
+
+  def filename
+    File.basename("zammad_system_report_#{Setting.get('fqdn')}_#{uuid}.json")
+  end
+
+  private
+
+  def prepare_uuid
+    self.uuid = SecureRandom.uuid
+  end
+end

+ 25 - 0
app/models/system_report/plugin.rb

@@ -0,0 +1,25 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+class SystemReport::Plugin
+  include Mixin::RequiredSubPaths
+
+  def self.list
+    @list ||= descendants.sort_by(&:name)
+  end
+
+  def self.name_plugin
+    name.sub('SystemReport::Plugin::', '')
+  end
+
+  def self.path
+    name_plugin.split('::')
+  end
+
+  def initialize
+    # TODO
+  end
+
+  def fetch
+    raise NotImplementedError
+  end
+end

+ 19 - 0
app/models/system_report/plugin/addons.rb

@@ -0,0 +1,19 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+class SystemReport::Plugin::Addons < SystemReport::Plugin
+  DESCRIPTION = __('List of installed addons').freeze
+
+  def fetch
+    ::Package.all.map do |package|
+      package.attributes.delete_if do |k|
+        k.in? %w[
+          id
+          created_at
+          updated_at
+          created_by_id
+          updated_by_id
+        ]
+      end
+    end
+  end
+end

+ 9 - 0
app/models/system_report/plugin/channel.rb

@@ -0,0 +1,9 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+class SystemReport::Plugin::Channel < SystemReport::Plugin
+  DESCRIPTION = __('Lists active channels (e.g. 1 Telegram channel, 2 Microsoft channels and 1 Google channel)').freeze
+
+  def fetch
+    ::Channel.where(active: true).map(&:area).tally
+  end
+end

Some files were not shown because too many files changed in this diff