intranet-system 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3d4d7f6e26e171a12ece83efe7be3a993a52d726
4
+ data.tar.gz: 3e8a424dd4e72f670443dbeb707c7200845bf1e3
5
+ SHA512:
6
+ metadata.gz: 933eeeb020b40518af27883e09c79828b095b68b3e9f8c3f643bf9b462aeed3069c8b32b2a58c65baecd3a7cf627baed50131fd20d75c119726a0e988d2fcd49
7
+ data.tar.gz: 5dba5bd6d1a2e746de3c5f24a33c3b10805f3076e92dbdc366a83dca2b4d8617feddbe4632578602b25d291b9fa8836165d4a2819eede89eb36c3b9a33ae9435
@@ -0,0 +1,26 @@
1
+ %section
2
+ = to_markup 'title_and_breadcrumb', {title: title, nav: nav}
3
+
4
+ %h3#cpu_model= I18n.t('system.processor') + ' : '
5
+ %p#load1min.meter
6
+ %span.desc= '1 min'
7
+ %span.info= '0.00'
8
+ %meter{value: 0, min: 0, max: 100, low: 80, high: 90, optimum: 50, title: '0 %'}
9
+ %p#load5min.meter
10
+ %span.desc= '5 min'
11
+ %span.info= '0.00'
12
+ %meter{value: 0, min: 0, max: 100, low: 80, high: 90, optimum: 50, title: '0 %'}
13
+ %p#load15min.meter
14
+ %span.desc= '15 min'
15
+ %span.info= '0.00'
16
+ %meter{value: 0, min: 0, max: 100, low: 80, high: 90, optimum: 50, title: '0 %'}
17
+
18
+ %h3= I18n.t('system.memory')
19
+ %p#ram.meter
20
+ %span.desc= 'RAM'
21
+ %span.info= '0 M' + I18n.t('system.byte_abbrev') + ' ' + I18n.t('system.out_of') + ' 0 M' + I18n.t('system.byte_abbrev')
22
+ %meter{value: 0, min: 0, max: 100, low: 80, high: 90, optimum: 50, title: '0 %'}
23
+ %div#swaps.loading
24
+
25
+ %h3= I18n.t('system.storage')
26
+ %div#storage.loading
@@ -0,0 +1,12 @@
1
+ # system/locales/en.yml - Translation file for the System module in en-GB/en-US
2
+ ---
3
+
4
+ en:
5
+ system:
6
+ menu: 'System'
7
+ monitor: 'System monitor'
8
+ storage: 'Storage'
9
+ processor: 'Processor'
10
+ memory: 'Memory'
11
+ out_of: 'of'
12
+ byte_abbrev: 'b'
@@ -0,0 +1,12 @@
1
+ # system/locales/fr.yml - Translation file for the System module in fr-FR
2
+ ---
3
+
4
+ fr:
5
+ system:
6
+ menu: 'Système'
7
+ monitor: 'Moniteur système'
8
+ storage: 'Stockage'
9
+ processor: 'Processeur'
10
+ memory: 'Mémoire'
11
+ out_of: 'sur'
12
+ byte_abbrev: 'o'
@@ -0,0 +1,78 @@
1
+ /**
2
+ * jsystem.js
3
+ * JavaScript functions for the system monitor.
4
+ */
5
+ "use strict";
6
+
7
+ const refreshIntervalMs = 5000;
8
+
9
+ const orderToString = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
10
+
11
+ function humanizeSize(size, order) {
12
+ if (size < 1024)
13
+ return Math.ceil(size) + ' ' + orderToString[order] + byte_abbrev;
14
+ else if (size < 10 * 1024)
15
+ return Math.ceil(size / 102.4) / 10 + ' ' + orderToString[order + 1] + byte_abbrev;
16
+ else
17
+ return humanizeSize(size / 1024, order + 1)
18
+ }
19
+
20
+ function updateMarkdownContent(json) {
21
+ /* CPU */
22
+ const cpuNumber = json.cpu.nb_cores;
23
+
24
+ if (document.getElementById('cpu_model').textContent.endsWith(' : ')) {
25
+ document.getElementById('cpu_model').textContent += json.cpu.model;
26
+ }
27
+
28
+ const loadAvgTriplet = ['load1min', 'load5min', 'load15min'];
29
+ for (let i = 0; i < loadAvgTriplet.length; i++) {
30
+ const load = json.loadavg[i];
31
+ document.getElementById(loadAvgTriplet[i]).getElementsByClassName('info')[0].textContent = load;
32
+ document.getElementById(loadAvgTriplet[i]).getElementsByTagName('meter')[0].max = 100 * cpuNumber;
33
+ document.getElementById(loadAvgTriplet[i]).getElementsByTagName('meter')[0].optimum = 50 * cpuNumber;
34
+ document.getElementById(loadAvgTriplet[i]).getElementsByTagName('meter')[0].low = 80 * cpuNumber;
35
+ document.getElementById(loadAvgTriplet[i]).getElementsByTagName('meter')[0].high = 90 * cpuNumber;
36
+ document.getElementById(loadAvgTriplet[i]).getElementsByTagName('meter')[0].value = Math.ceil(100 * load);
37
+ document.getElementById(loadAvgTriplet[i]).getElementsByTagName('meter')[0].title = Math.ceil(100 * load) + ' %';
38
+ }
39
+
40
+ /* RAM */
41
+ const percent = Math.ceil(100 * json.ram.used / json.ram.total);
42
+ const string = humanizeSize(json.ram.used, 0) + ' ' + out_of + ' ' + humanizeSize(json.ram.total, 0);
43
+ document.getElementById('ram').getElementsByClassName('info')[0].textContent = string;
44
+ document.getElementById('ram').getElementsByTagName('meter')[0].value = percent;
45
+ document.getElementById('ram').getElementsByTagName('meter')[0].title = percent + ' %';
46
+
47
+ /* SWAPs */
48
+ var swapHtml = '';
49
+ for (const [mountpoint, swap] of Object.entries(json.swaps)) {
50
+ const percent = Math.ceil(100 * swap.used / swap.total);
51
+ const string = humanizeSize(swap.used, 0) + ' ' + out_of + ' ' + humanizeSize(swap.total, 0);
52
+ swapHtml += '<p class="meter"><span class="desc">Swap : ' + mountpoint + '</span><span class="info">' + string + '</span>';
53
+ swapHtml += '<meter value="' + percent + '" min="0" max="100" low="80" high="90" optimum="50" title="' + percent + ' %">' + percent + '%</meter></p>';
54
+ }
55
+ document.getElementById('swaps').classList.remove('loading');
56
+ document.getElementById('swaps').innerHTML = swapHtml;
57
+
58
+ /* Partitions */
59
+ var storageHtml = '';
60
+ for (const [mountpoint, partition] of Object.entries(json.partitions)) {
61
+ const percent = Math.ceil(100 * partition.used / partition.total);
62
+ const string = humanizeSize(partition.used, 0) + ' ' + out_of + ' ' + humanizeSize(partition.total, 0);
63
+ storageHtml += '<p class="meter"><span class="desc">' + mountpoint + '</span><span class="info">' + string + '</span>';
64
+ storageHtml += '<meter value="' + percent + '" min="0" max="100" low="80" high="90" optimum="50" title="' + percent + ' %">' + percent + '%</meter></p>';
65
+ }
66
+ document.getElementById('storage').classList.remove('loading');
67
+ document.getElementById('storage').innerHTML = storageHtml;
68
+ }
69
+
70
+ function updateSystemStats() {
71
+ fetch('api')
72
+ .then(response => response.json())
73
+ .then(data => updateMarkdownContent(data))
74
+ }
75
+
76
+ /* Code executed after page load */
77
+ updateSystemStats();
78
+ setInterval(updateSystemStats, refreshIntervalMs);
@@ -0,0 +1,56 @@
1
+ /**
2
+ * system/style.css
3
+ * Design for the System module of the IntraNet.
4
+ */
5
+
6
+ /******************************* System monitor *******************************/
7
+
8
+ p.meter {
9
+ margin: 0px 50px 2em; /* top sides bottom */
10
+ font-size: 110%;
11
+ }
12
+ p.meter span {
13
+ display: inline-block;
14
+ margin: 0px 8px;
15
+ }
16
+ p.meter span.desc {
17
+ float: left;
18
+ }
19
+ p.meter span.info {
20
+ float: right;
21
+ }
22
+
23
+ /* Mobile devices only */
24
+ @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
25
+ p.meter {
26
+ width: auto;
27
+ }
28
+ }
29
+
30
+ meter {
31
+ width: 100%;
32
+ height: 1.6em;
33
+ border-radius: 0.8em;
34
+ background: none; /* required to get rid of default style */
35
+ background-color: #eaeaea;
36
+ }
37
+ :-moz-meter-optimum::-moz-meter-bar {
38
+ background: #8bcf69;
39
+ border-radius: inherit;
40
+ transition: 1s width;
41
+ }
42
+ :-moz-meter-sub-optimum::-moz-meter-bar {
43
+ background: #e6d450;
44
+ border-radius: inherit;
45
+ transition: 1s width;
46
+ }
47
+ :-moz-meter-sub-sub-optimum::-moz-meter-bar {
48
+ background: #f28f68;
49
+ border-radius: inherit;
50
+ transition: 1s width;
51
+ }
52
+
53
+ canvas {
54
+ width: 100%;
55
+ }
56
+
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ module Intranet
6
+ module System
7
+ # Provides system statistics for a Linux system.
8
+ class LinuxStatsProvider
9
+ # @!visibility protected
10
+ CPUINFO_FILE = '/proc/cpuinfo'
11
+ # @!visibility protected
12
+ LOADAVG_FILE = '/proc/loadavg'
13
+ # @!visibility protected
14
+ MEMINFO_FILE = '/proc/meminfo'
15
+ # @!visibility protected
16
+ SWAPS_FILE = '/proc/swaps'
17
+
18
+ # The system statistics refresh delay, in seconds.
19
+ # Under Linux, it is not necessary to refresh statistics more frequently than 5s since this is
20
+ # the refresh rate of the load average.
21
+ STATS_REFRESH_DELAY_S = 5
22
+
23
+ # Initializes a new Linux statistics provider.
24
+ def initialize
25
+ @stats = Hash[
26
+ hostname: Socket.gethostname,
27
+ loadavg: nil,
28
+ cpu: { model: cpu_model, nb_cores: nb_cores },
29
+ ram: { total: ram_available },
30
+ swaps: {},
31
+ partitions: {}]
32
+ @stats_date = Time.now - STATS_REFRESH_DELAY_S
33
+ end
34
+
35
+ # Returns the latest system statistics. System statistics are updated if they are outdated.
36
+ # @return [Hash] The system statistics, see {Responder#generate_page} for the structure.
37
+ def stats
38
+ now = Time.now
39
+ if outdated?(now)
40
+ @stats[:loadavg] = load_average
41
+ @stats[:ram][:used] = ram_used
42
+ @stats[:swaps] = swaps_usage
43
+ @stats[:partitions] = partitions_usage
44
+ @stats_date = now
45
+ end
46
+ @stats
47
+ end
48
+
49
+ private
50
+
51
+ def outdated?(at)
52
+ (at - @stats_date) >= STATS_REFRESH_DELAY_S
53
+ end
54
+
55
+ def cpu_model
56
+ File.readlines(CPUINFO_FILE).grep(/model name/)[0].split(/:/)[1].strip
57
+ end
58
+
59
+ def nb_cores
60
+ File.readlines(CPUINFO_FILE).grep(/^processor/).size
61
+ end
62
+
63
+ def load_average
64
+ File.read(LOADAVG_FILE).split(' ').first(3).map(&:to_f)
65
+ end
66
+
67
+ def ram_available
68
+ # /proc/meminfo & /proc/swaps display Kib values despite indicating 'Kb'.
69
+ File.readlines(MEMINFO_FILE).grep(/^MemTotal/)[0].split(' ')[1].to_i * 1024
70
+ end
71
+
72
+ def ram_used
73
+ free = File.readlines(MEMINFO_FILE).grep(/^MemAvailable/)[0].split(' ')[1].to_i * 1024
74
+ @stats[:ram][:total] - free
75
+ end
76
+
77
+ def swaps_usage
78
+ File.readlines(SWAPS_FILE).grep(%r{^/}).map do |swap|
79
+ infos = swap.split
80
+ { infos[0] => { total: infos[2].to_i * 1024, used: infos[3].to_i * 1024 } }
81
+ end.reduce({}, :update)
82
+ end
83
+
84
+ def partitions_usage
85
+ `df -lB1`.lines.grep(%r{^/}).sort.map do |partition|
86
+ infos = partition.split
87
+ { infos[5] => { total: infos[1].to_i, used: infos[2].to_i } }
88
+ end.reduce({}, :update)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'intranet/abstract_responder'
4
+ require 'intranet/core/haml_wrapper'
5
+ require 'intranet/core/locales'
6
+ require_relative 'version'
7
+ require 'json'
8
+
9
+ module Intranet
10
+ module System
11
+ # The responder for the System monitor module of the Intranet.
12
+ class Responder < AbstractResponder
13
+ include Core::HamlWrapper # 'inherits' from methods of HamlWrapper
14
+
15
+ # Returns the name of the module.
16
+ # @return [String] The name of the module
17
+ def self.module_name
18
+ NAME
19
+ end
20
+
21
+ # The version of the module, according to semantic versionning.
22
+ # @return [String] The version of the module
23
+ def self.module_version
24
+ VERSION
25
+ end
26
+
27
+ # The homepage of the module.
28
+ # @return [String] The homepage URL of the module.
29
+ def self.module_homepage
30
+ HOMEPAGE_URL
31
+ end
32
+
33
+ # Initializes a new System responder instance.
34
+ # @param provider [Object] The system statistics provider, responding to +#stats+.
35
+ # @param in_menu [Boolean] Whether the module instance should be displayed in the main
36
+ # navigation menu or not.
37
+ def initialize(provider, in_menu = true)
38
+ @provider = provider
39
+ @in_menu = in_menu
40
+ end
41
+
42
+ # Specifies if the responder instance should be displayed in the main navigation menu or not.
43
+ # @return [Boolean] True if the responder instance should be added to the main navigation
44
+ # menu, False otherwise.
45
+ def in_menu?
46
+ @in_menu
47
+ end
48
+
49
+ # Specifies the absolute path to the resources directory for that module.
50
+ # @return [String] The absolute path to the resources directory for the module.
51
+ def resources_dir
52
+ File.absolute_path(File.join('..', 'resources'), __dir__)
53
+ end
54
+
55
+ # Generates the HTML content associated to the given +path+ and +query+.
56
+ # === REST API Description:
57
+ # Read-only access to system statistics under +/api+ using +GET+ method. Response is in JSON
58
+ # format with the following structure:
59
+ # {
60
+ # "hostname": "trantor",
61
+ # "loadavg": [0.42,0.48,0.56],
62
+ # "cpu": {
63
+ # "model": "Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz",
64
+ # "nb_cores": 4
65
+ # },
66
+ # "ram": { "total": 8243249152, "used": 3149385728 },
67
+ # "swaps": {
68
+ # "/dev/sdb1": { "total": 19999485952, "used": 0 }
69
+ # },
70
+ # "partitions": {
71
+ # "/boot/efi": { "total": 509640704, "used": 135168 },
72
+ # "/": { "total": 58306199552, "used": 11482054656 }
73
+ # }
74
+ # }
75
+ # @param path [String] The requested URI, relative to that module root URI.
76
+ # @param query [Hash] The URI variable/value pairs, if any.
77
+ # @return [Array] The HTTP return code, the MIME type and the answer body.
78
+ def generate_page(path, query)
79
+ case path
80
+ when %r{^/index\.html$}
81
+ return 206, 'text/html', generate_home_html
82
+ when %r{^/api$}
83
+ return 200, 'application/json', @provider.stats.to_json
84
+ when %r{^/i18n\.js$}
85
+ return 200, 'text/javascript', generate_i18n_js
86
+ end
87
+ super(path, query)
88
+ end
89
+
90
+ # The title of the System monitor module, as displayed on the web page.
91
+ # @return [String] The title of the System monitor web page.
92
+ def title
93
+ I18n.t('system.monitor')
94
+ end
95
+
96
+ # Provides the list of Cascade Style Sheets (CSS) dependencies for this module.
97
+ # @return [Array] The list of CSS dependencies.
98
+ def css_dependencies
99
+ super + ['design/style.css']
100
+ end
101
+
102
+ # Provides the list of Javascript files (JS) dependencies for this module.
103
+ # @return [Array] The list of JS dependencies.
104
+ def js_dependencies
105
+ super + ['i18n.js', 'design/jsystem.js']
106
+ end
107
+
108
+ private
109
+
110
+ def generate_home_html
111
+ content = to_markup('system_home',
112
+ nav: { I18n.t('nav.home') => '/index.html', title => nil })
113
+ { content: content, title: title }
114
+ end
115
+
116
+ def generate_i18n_js
117
+ 'var byte_abbrev = \'' + I18n.t('system.byte_abbrev') + '\';' + "\n" \
118
+ 'var out_of = \'' + I18n.t('system.out_of') + '\';' + "\n"
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The main Intranet namespace.
4
+ module Intranet
5
+ # The System monitor module for the Intranet.
6
+ module System
7
+ # The name of the gem.
8
+ NAME = 'intranet-system'
9
+
10
+ # The version of the gem, according to semantic versionning.
11
+ VERSION = '1.0.0'
12
+
13
+ # The URL of the gem homepage.
14
+ HOMEPAGE_URL = 'https://rubygems.org/gems/intranet-system'
15
+
16
+ # The URL of the gem source code.
17
+ SOURCES_URL = 'https://bitbucket.org/ebling-mis/intranet-system'
18
+ end
19
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'intranet/system/linux_stats_provider'
4
+
5
+ RSpec.describe Intranet::System::LinuxStatsProvider do
6
+ before do
7
+ expect(described_class::CPUINFO_FILE).to eql('/proc/cpuinfo')
8
+ stub_const('Intranet::System::LinuxStatsProvider::CPUINFO_FILE',
9
+ File.join(__dir__, 'sample_cpuinfo.txt'))
10
+
11
+ expect(described_class::LOADAVG_FILE).to eql('/proc/loadavg')
12
+ stub_const('Intranet::System::LinuxStatsProvider::LOADAVG_FILE',
13
+ File.join(__dir__, 'sample_loadavg.txt'))
14
+
15
+ expect(described_class::MEMINFO_FILE).to eql('/proc/meminfo')
16
+ stub_const('Intranet::System::LinuxStatsProvider::MEMINFO_FILE',
17
+ File.join(__dir__, 'sample_meminfo.txt'))
18
+
19
+ expect(described_class::SWAPS_FILE).to eql('/proc/swaps')
20
+ stub_const('Intranet::System::LinuxStatsProvider::SWAPS_FILE',
21
+ File.join(__dir__, 'sample_swaps.txt'))
22
+
23
+ allow(Time).to receive(:now).and_return(0)
24
+
25
+ @provider = Intranet::System::LinuxStatsProvider.new
26
+ allow(@provider).to receive(:`).with('df -lB1').and_return(
27
+ File.read(File.join(__dir__, 'sample_df-lB1_1.txt'))
28
+ )
29
+ end
30
+
31
+ describe '#stats' do
32
+ it 'should return the latest system statistics' do
33
+ expected1 = {
34
+ hostname: File.read('/etc/hostname').chomp,
35
+ loadavg: [0.76, 0.61, 0.42],
36
+ cpu: {
37
+ model: 'Intel(R) Pentium(R) CPU G4400 @ 3.30GHz',
38
+ nb_cores: 2
39
+ },
40
+ ram: { total: 4_014_243_840, used: 2_765_328_384 },
41
+ swaps: {
42
+ '/dev/sda3' => { total: 9_699_323_904, used: 464_449_536 },
43
+ '/swapfile' => { total: 134_209_536, used: 38_797_312 }
44
+ },
45
+ partitions: {
46
+ '/boot/efi' => { total: 509_640_704, used: 135_168 },
47
+ '/' => { total: 58_306_199_552, used: 11_480_780_800 },
48
+ '/home' => { total: 884_998_905_856, used: 248_169_803_776 },
49
+ '/media/user/disk' => { total: 1_000_202_039_296, used: 674_241_961_984 }
50
+ }
51
+ }
52
+ expected2 = {
53
+ hostname: File.read('/etc/hostname').chomp,
54
+ loadavg: [0.76, 0.61, 0.42],
55
+ cpu: {
56
+ model: 'Intel(R) Pentium(R) CPU G4400 @ 3.30GHz',
57
+ nb_cores: 2
58
+ },
59
+ ram: { total: 4_014_243_840, used: 2_765_328_384 },
60
+ swaps: {
61
+ '/dev/sda3' => { total: 9_699_323_904, used: 464_449_536 },
62
+ '/swapfile' => { total: 134_209_536, used: 38_797_312 }
63
+ },
64
+ partitions: {
65
+ '/boot/efi' => { total: 509_640_704, used: 135_168 },
66
+ '/' => { total: 58_306_199_552, used: 11_480_780_800 },
67
+ '/home' => { total: 884_998_905_856, used: 248_169_803_776 }
68
+ }
69
+ }
70
+
71
+ expect(@provider.stats).to eql(expected1)
72
+
73
+ # Modify df return to remove a disk
74
+ allow(@provider).to receive(:`).with('df -lB1').and_return(
75
+ File.read(File.join(__dir__, 'sample_df-lB1_2.txt'))
76
+ )
77
+
78
+ allow(Time).to receive(:now).and_return(4)
79
+
80
+ # Check that output is not modified: system statistics are still valid
81
+ expect(@provider.stats).to eql(expected1)
82
+
83
+ allow(Time).to receive(:now).and_return(5)
84
+
85
+ # Check that output is modified: system statistics have expired
86
+ expect(@provider.stats).to eql(expected2)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'intranet/core'
4
+ require 'intranet/logger'
5
+ require 'intranet/abstract_responder'
6
+ require 'intranet/system/responder'
7
+
8
+ RSpec.describe Intranet::System::Responder do
9
+ it 'should inherit from Intranet::AbstractResponder' do
10
+ expect(described_class.superclass).to eql(Intranet::AbstractResponder)
11
+ end
12
+
13
+ it 'should define its name, version and homepage' do
14
+ expect { described_class.module_name }.not_to raise_error
15
+ expect { described_class.module_version }.not_to raise_error
16
+ expect { described_class.module_homepage }.not_to raise_error
17
+ end
18
+
19
+ before do
20
+ logger = Intranet::Logger.new(Intranet::Logger::FATAL)
21
+ @core = Intranet::Core.new(logger)
22
+
23
+ provider = double('Intranet::System::Provider')
24
+ @stats = {
25
+ hostname: File.read('/etc/hostname').chomp,
26
+ loadavg: [0.76, 0.61, 0.42],
27
+ cpu: {
28
+ model: 'Intel(R) Pentium(R) CPU G4400 @ 3.30GHz',
29
+ nb_cores: 2
30
+ },
31
+ ram: { total: 4_014_243_840, used: 2_765_328_384 },
32
+ swaps: {
33
+ '/dev/sda3' => { total: 9_699_323_904, used: 464_449_536 },
34
+ '/swapfile' => { total: 134_209_536, used: 38_797_312 }
35
+ },
36
+ partitions: {
37
+ '/boot/efi' => { total: 509_640_704, used: 135_168 },
38
+ '/' => { total: 58_306_199_552, used: 11_480_780_800 },
39
+ '/home' => { total: 884_998_905_856, used: 248_169_803_776 },
40
+ '/media/user/disk' => { total: 1_000_202_039_296, used: 674_241_961_984 }
41
+ }
42
+ }
43
+ allow(provider).to receive(:stats).and_return(@stats)
44
+
45
+ @responder = described_class.new(provider)
46
+ @core.register_module(
47
+ @responder, ['system'], File.absolute_path('../../../lib/intranet/resources', __dir__)
48
+ )
49
+ end
50
+
51
+ describe '#in_menu?' do
52
+ it 'should return the value provided at initialization' do
53
+ expect(described_class.new(nil, false).in_menu?).to be false
54
+ expect(described_class.new(nil, true).in_menu?).to be true
55
+ end
56
+ end
57
+
58
+ describe '#resources_dir' do
59
+ it 'should return the absolute path of the resources directory' do
60
+ expect(described_class.new(nil, false).resources_dir).to eql(
61
+ File.absolute_path('../../../lib/intranet/resources', __dir__)
62
+ )
63
+ end
64
+ end
65
+
66
+ describe '#title' do
67
+ it 'should return the title of the webpage provided by the module' do
68
+ expect(@responder.title).to eql(I18n.t('system.monitor'))
69
+ end
70
+ end
71
+
72
+ describe '#css_dependencies' do
73
+ it 'should return the list of CSS dependencies' do
74
+ expect(@responder.css_dependencies).to include('design/style.css')
75
+ end
76
+ end
77
+
78
+ describe '#js_dependencies' do
79
+ it 'should return the list of JavaScript dependencies' do
80
+ expect(@responder.js_dependencies).to include('design/jsystem.js')
81
+ expect(@responder.js_dependencies).to include('i18n.js')
82
+ end
83
+ end
84
+
85
+ describe '#generate_page' do
86
+ context 'when asked for \'/index.html\'' do
87
+ it 'should return a partial HTML content with a loader' do
88
+ code, mime, content = @responder.generate_page('/index.html', {})
89
+ expect(code).to eql(206)
90
+ expect(mime).to eql('text/html')
91
+ expect(content).to eql(
92
+ Hash[content: "<section>\n<h2>#{I18n.t('system.monitor')}</h2>\n" \
93
+ "<ul class='breadcrumb'>\n" \
94
+ "<li>\n<a href='/index.html'>#{I18n.t('nav.home')}</a>\n</li>\n" \
95
+ "<li>#{I18n.t('system.monitor')}</li>\n" \
96
+ "</ul>\n\n" \
97
+ "<h3 id='cpu_model'>#{I18n.t('system.processor')} : </h3>\n" \
98
+ "<p class='meter' id='load1min'>\n<span class='desc'>1 min</span>\n<span " \
99
+ "class='info'>0.00</span>\n<meter high='90' low='80' max='100' min='0' op" \
100
+ "timum='50' title='0 %' value='0'></meter>\n</p>\n" \
101
+ "<p class='meter' id='load5min'>\n<span class='desc'>5 min</span>\n<span " \
102
+ "class='info'>0.00</span>\n<meter high='90' low='80' max='100' min='0' op" \
103
+ "timum='50' title='0 %' value='0'></meter>\n</p>\n" \
104
+ "<p class='meter' id='load15min'>\n<span class='desc'>15 min</span>\n<spa" \
105
+ "n class='info'>0.00</span>\n<meter high='90' low='80' max='100' min='0' " \
106
+ "optimum='50' title='0 %' value='0'></meter>\n</p>\n" \
107
+ "<h3>#{I18n.t('system.memory')}</h3>\n" \
108
+ "<p class='meter' id='ram'>\n<span class='desc'>RAM</span>\n<span class='" \
109
+ "info'>0 M#{I18n.t('system.byte_abbrev')} #{I18n.t('system.out_of')} 0 M" \
110
+ "#{I18n.t('system.byte_abbrev')}</span>\n<meter high='90' low='80' max='1" \
111
+ "00' min='0' optimum='50' title='0 %' value='0'></meter>\n</p>\n" \
112
+ "<div class='loading' id='swaps'></div>\n" \
113
+ "<h3>#{I18n.t('system.storage')}</h3>\n" \
114
+ "<div class='loading' id='storage'></div>\n" \
115
+ "</section>\n",
116
+ title: I18n.t('system.monitor')]
117
+ )
118
+ end
119
+ end
120
+
121
+ context 'when asked for \'/api\'' do
122
+ it 'should return a JSON representation of the system statistics' do
123
+ code, mime, content = @responder.generate_page('/api', {})
124
+ expect(code).to eql(200)
125
+ expect(mime).to eql('application/json')
126
+ expect(content).to eql(@stats.to_json)
127
+ end
128
+ end
129
+
130
+ context 'when asked for \'/i18n.js\'' do
131
+ it 'should return the internationalized version of required JavaScript variables' do
132
+ code, mime, content = @responder.generate_page('/i18n.js', {})
133
+ expect(code).to eql(200)
134
+ expect(mime).to eql('text/javascript')
135
+ expect(content).to eql(
136
+ "var byte_abbrev = '#{I18n.t('system.byte_abbrev')}';\n" \
137
+ "var out_of = '#{I18n.t('system.out_of')}';\n"
138
+ )
139
+ end
140
+ end
141
+
142
+ context 'otherwise' do
143
+ it 'should return an HTTP 404 error' do
144
+ expect(@responder.generate_page('index.html', {})).to eql([404, '', ''])
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,54 @@
1
+ processor : 0
2
+ vendor_id : GenuineIntel
3
+ cpu family : 6
4
+ model : 94
5
+ model name : Intel(R) Pentium(R) CPU G4400 @ 3.30GHz
6
+ stepping : 3
7
+ microcode : 0xba
8
+ cpu MHz : 799.822
9
+ cache size : 3072 KB
10
+ physical id : 0
11
+ siblings : 2
12
+ core id : 0
13
+ cpu cores : 2
14
+ apicid : 0
15
+ initial apicid : 0
16
+ fpu : yes
17
+ fpu_exception : yes
18
+ cpuid level : 22
19
+ wp : yes
20
+ flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave rdrand lahf_lm abm 3dnowprefetch intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust erms invpcid rdseed smap clflushopt xsaveopt xsavec xgetbv1 xsaves dtherm arat pln pts hwp hwp_notify hwp_act_window hwp_epp
21
+ bugs :
22
+ bogomips : 6624.00
23
+ clflush size : 64
24
+ cache_alignment : 64
25
+ address sizes : 39 bits physical, 48 bits virtual
26
+ power management:
27
+
28
+ processor : 1
29
+ vendor_id : GenuineIntel
30
+ cpu family : 6
31
+ model : 94
32
+ model name : Intel(R) Pentium(R) CPU G4400 @ 3.30GHz
33
+ stepping : 3
34
+ microcode : 0xba
35
+ cpu MHz : 799.822
36
+ cache size : 3072 KB
37
+ physical id : 0
38
+ siblings : 2
39
+ core id : 1
40
+ cpu cores : 2
41
+ apicid : 2
42
+ initial apicid : 2
43
+ fpu : yes
44
+ fpu_exception : yes
45
+ cpuid level : 22
46
+ wp : yes
47
+ flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave rdrand lahf_lm abm 3dnowprefetch intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust erms invpcid rdseed smap clflushopt xsaveopt xsavec xgetbv1 xsaves dtherm arat pln pts hwp hwp_notify hwp_act_window hwp_epp
48
+ bugs :
49
+ bogomips : 6625.05
50
+ clflush size : 64
51
+ cache_alignment : 64
52
+ address sizes : 39 bits physical, 48 bits virtual
53
+ power management:
54
+
@@ -0,0 +1,12 @@
1
+ Filesystem 1B-blocks Used Available Use% Mounted on
2
+ udev 4108283904 0 4108283904 0% /dev
3
+ tmpfs 824324096 18341888 805982208 3% /run
4
+ /dev/sda2 58306199552 11480780800 43833151488 21% /
5
+ tmpfs 4121620480 49844224 4071776256 2% /dev/shm
6
+ tmpfs 5242880 4096 5238784 1% /run/lock
7
+ tmpfs 4121620480 0 4121620480 0% /sys/fs/cgroup
8
+ /dev/sda1 509640704 135168 509505536 1% /boot/efi
9
+ /dev/sdb4 884998905856 248169803776 591802150912 30% /home
10
+ /dev/sdc1 1000202039296 674241961984 325960077312 68% /media/user/disk
11
+ tmpfs 824324096 16384 824307712 1% /run/user/115
12
+ tmpfs 824324096 53248 824270848 1% /run/user/1000
@@ -0,0 +1,11 @@
1
+ Filesystem 1B-blocks Used Available Use% Mounted on
2
+ udev 4108283904 0 4108283904 0% /dev
3
+ tmpfs 824324096 18341888 805982208 3% /run
4
+ /dev/sda2 58306199552 11480780800 43833151488 21% /
5
+ tmpfs 4121620480 49844224 4071776256 2% /dev/shm
6
+ tmpfs 5242880 4096 5238784 1% /run/lock
7
+ tmpfs 4121620480 0 4121620480 0% /sys/fs/cgroup
8
+ /dev/sda1 509640704 135168 509505536 1% /boot/efi
9
+ /dev/sdb4 884998905856 248169803776 591802150912 30% /home
10
+ tmpfs 824324096 16384 824307712 1% /run/user/115
11
+ tmpfs 824324096 53248 824270848 1% /run/user/1000
@@ -0,0 +1 @@
1
+ 0.76 0.61 0.42 1/559 9547
@@ -0,0 +1,46 @@
1
+ MemTotal: 3920160 kB
2
+ MemFree: 395348 kB
3
+ MemAvailable: 1219644 kB
4
+ Buffers: 80692 kB
5
+ Cached: 1187364 kB
6
+ SwapCached: 61280 kB
7
+ Active: 1804312 kB
8
+ Inactive: 1542132 kB
9
+ Active(anon): 1316516 kB
10
+ Inactive(anon): 1024448 kB
11
+ Active(file): 487796 kB
12
+ Inactive(file): 517684 kB
13
+ Unevictable: 0 kB
14
+ Mlocked: 0 kB
15
+ SwapTotal: 9471996 kB
16
+ SwapFree: 9018424 kB
17
+ Dirty: 16 kB
18
+ Writeback: 0 kB
19
+ AnonPages: 2067636 kB
20
+ Mapped: 336356 kB
21
+ Shmem: 262580 kB
22
+ Slab: 89196 kB
23
+ SReclaimable: 46096 kB
24
+ SUnreclaim: 43100 kB
25
+ KernelStack: 8000 kB
26
+ PageTables: 38636 kB
27
+ NFS_Unstable: 0 kB
28
+ Bounce: 0 kB
29
+ WritebackTmp: 0 kB
30
+ CommitLimit: 11432076 kB
31
+ Committed_AS: 8624020 kB
32
+ VmallocTotal: 34359738367 kB
33
+ VmallocUsed: 0 kB
34
+ VmallocChunk: 0 kB
35
+ HardwareCorrupted: 0 kB
36
+ AnonHugePages: 0 kB
37
+ ShmemHugePages: 0 kB
38
+ ShmemPmdMapped: 0 kB
39
+ HugePages_Total: 0
40
+ HugePages_Free: 0
41
+ HugePages_Rsvd: 0
42
+ HugePages_Surp: 0
43
+ Hugepagesize: 2048 kB
44
+ DirectMap4k: 160356 kB
45
+ DirectMap2M: 3903488 kB
46
+ DirectMap1G: 0 kB
@@ -0,0 +1,3 @@
1
+ Filename Type Size Used Priority
2
+ /dev/sda3 partition 9471996 453564 -1
3
+ /swapfile file 131064 37888 60
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is automatically loaded in each *_spec.rb file, so keep it light!
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with :rspec do |expectations|
7
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
8
+ end
9
+
10
+ config.mock_with :rspec do |mocks|
11
+ mocks.verify_partial_doubles = true
12
+ end
13
+
14
+ config.shared_context_metadata_behavior = :apply_to_host_groups
15
+
16
+ config.disable_monkey_patching!
17
+
18
+ # config.warnings = true
19
+
20
+ # Use the documentation formatter when RSpec is launched with one file only
21
+ config.default_formatter = 'doc' if config.files_to_run.one?
22
+
23
+ # Print the 10 slowest examples and example groups at the
24
+ # end of the spec run, to help surface which specs are running
25
+ # particularly slow.
26
+ # config.profile_examples = 10
27
+
28
+ # Run specs in random order to surface order dependencies. If you find an
29
+ # order dependency and want to debug it, you can fix the order by providing
30
+ # the seed, which is printed after each run.
31
+ # --seed 1234
32
+ config.order = :random
33
+
34
+ # Seed global randomization in this process using the `--seed` CLI option.
35
+ # Setting this allows you to use `--seed` to deterministically reproduce
36
+ # test failures related to randomization by passing the same `--seed` value
37
+ # as the one that triggered the failure.
38
+ Kernel.srand config.seed
39
+
40
+ # Add lib/ directory to load path
41
+ $LOAD_PATH << File.absolute_path(File.join('..', 'lib'), __dir__)
42
+
43
+ # Load and start SimpleCov to gather code coverage information
44
+ unless config.files_to_run.one?
45
+ require 'simplecov'
46
+ SimpleCov.add_filter 'spec' # exclude 'spec' folder from coverage
47
+ SimpleCov.start
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: intranet-system
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ebling Mis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-02-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: intranet-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rubocop
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.63.0
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.63.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: simplecov
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: yard
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ description:
104
+ email: ebling.mis@protonmail.com
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - lib/intranet/resources/haml/system_home.haml
110
+ - lib/intranet/resources/locales/en.yml
111
+ - lib/intranet/resources/locales/fr.yml
112
+ - lib/intranet/resources/www/jsystem.js
113
+ - lib/intranet/resources/www/style.css
114
+ - lib/intranet/system/linux_stats_provider.rb
115
+ - lib/intranet/system/responder.rb
116
+ - lib/intranet/system/version.rb
117
+ - spec/intranet/system/linux_stats_provider_spec.rb
118
+ - spec/intranet/system/responder_spec.rb
119
+ - spec/intranet/system/sample_cpuinfo.txt
120
+ - spec/intranet/system/sample_df-lB1_1.txt
121
+ - spec/intranet/system/sample_df-lB1_2.txt
122
+ - spec/intranet/system/sample_loadavg.txt
123
+ - spec/intranet/system/sample_meminfo.txt
124
+ - spec/intranet/system/sample_swaps.txt
125
+ - spec/spec_helper.rb
126
+ homepage: https://rubygems.org/gems/intranet-system
127
+ licenses:
128
+ - MIT
129
+ metadata:
130
+ source_code_uri: https://bitbucket.org/ebling-mis/intranet-system
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '2.3'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.5.2.1
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: System module for the intranet.
151
+ test_files: []