Skip to content

Commit

Permalink
Added System Report feature v1.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
6 people committed Apr 22, 2024
1 parent 78ebf28 commit c4fedde
Show file tree
Hide file tree
Showing 44 changed files with 1,055 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Gemfile
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -771,6 +774,7 @@ DEPENDENCIES
koala
listen
localhost
macaddr
mail
messagebird-rest
mime-types
Expand Down
41 changes: 41 additions & 0 deletions 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 changes: 22 additions & 0 deletions 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 changes: 2 additions & 0 deletions app/assets/stylesheets/svg-dimensions.css
Expand Up @@ -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; }
Expand All @@ -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; }
Expand Down
32 changes: 32 additions & 0 deletions 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 changes: 58 additions & 0 deletions 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 changes: 25 additions & 0 deletions 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 changes: 19 additions & 0 deletions 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 changes: 9 additions & 0 deletions 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
15 changes: 15 additions & 0 deletions app/models/system_report/plugin/entities/counts.rb
@@ -0,0 +1,15 @@
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class SystemReport::Plugin::Entities::Counts < SystemReport::Plugin
DESCRIPTION = __('Entity counts of database objects (e.g. ticket count, user count, etc.)').freeze

def fetch
counts = {}

Models.all.each_key do |model|
counts[model.to_s] = model.count
end

counts
end
end
17 changes: 17 additions & 0 deletions app/models/system_report/plugin/entities/last_created_at.rb
@@ -0,0 +1,17 @@
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class SystemReport::Plugin::Entities::LastCreatedAt < SystemReport::Plugin
DESCRIPTION = __('Last created at of database objects (e.g. when was the last trigger created)').freeze

def fetch
counts = {}

Models.all.each_key do |model|
next if model.column_names.exclude?('created_at')

counts[model.to_s] = model.maximum(:created_at)
end

counts
end
end
12 changes: 12 additions & 0 deletions app/models/system_report/plugin/entities/ticket.rb
@@ -0,0 +1,12 @@
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class SystemReport::Plugin::Entities::Ticket < SystemReport::Plugin
DESCRIPTION = __('Open and closed tickets ratio (ticket counts based on state)').freeze

def fetch
{
'Open' => Ticket.where(state_id: Ticket::State.by_category(:open)).count,
'Closed' => Ticket.where(state_id: Ticket::State.by_category(:closed)).count,
}
end
end
13 changes: 13 additions & 0 deletions app/models/system_report/plugin/entities/user.rb
@@ -0,0 +1,13 @@
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class SystemReport::Plugin::Entities::User < SystemReport::Plugin
DESCRIPTION = __('Customer and agent ratio (role based user counts)').freeze

def fetch
{
'Agents' => User.with_permissions('ticket.agent').count,
'Customer' => User.with_permissions('ticket.customer').count,
'LastLogin' => User.maximum(:last_login),
}
end
end
18 changes: 18 additions & 0 deletions app/models/system_report/plugin/environment.rb
@@ -0,0 +1,18 @@
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class SystemReport::Plugin::Environment < SystemReport::Plugin
DESCRIPTION = __('Configuration of performance settings via environment variables.').freeze

def fetch
{
'RAILS_LOG_TO_STDOUT' => ENV['RAILS_LOG_TO_STDOUT'].present?,
'ZAMMAD_SAFE_MODE' => ENV['ZAMMAD_SAFE_MODE'].present?,
'ZAMMAD_RAILS_PORT' => ENV['ZAMMAD_RAILS_PORT'].present?,
'ZAMMAD_WEBSOCKET_PORT' => ENV['ZAMMAD_WEBSOCKET_PORT'].present?,
'WEB_CONCURRENCY' => ENV['WEB_CONCURRENCY'],
'ZAMMAD_SESSION_JOBS_CONCURRENT' => ENV['ZAMMAD_SESSION_JOBS_CONCURRENT'],
'ZAMMAD_PROCESS_SCHEDULED_JOBS_WORKERS' => ENV['ZAMMAD_PROCESS_SCHEDULED_JOBS_WORKERS'],
'ZAMMAD_PROCESS_DELAYED_JOBS_WORKERS' => ENV['ZAMMAD_PROCESS_DELAYED_JOBS_WORKERS'],
}
end
end
9 changes: 9 additions & 0 deletions app/models/system_report/plugin/failed_emails.rb
@@ -0,0 +1,9 @@
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class SystemReport::Plugin::FailedEmails < SystemReport::Plugin
DESCRIPTION = __('Count of failed emails').freeze

def fetch
FailedEmail.count
end
end

0 comments on commit c4fedde

Please sign in to comment.