panda-motd 0.0.7 → 0.0.8
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.
- checksums.yaml +4 -4
- data/lib/panda_motd/components/ascii_text_art.rb +1 -1
- data/lib/panda_motd/components/filesystems.rb +21 -33
- data/lib/panda_motd/components/last_login.rb +43 -43
- data/lib/panda_motd/components/service_status.rb +23 -37
- data/lib/panda_motd/components/ssl_certificates.rb +18 -21
- data/lib/panda_motd/components/uptime.rb +11 -7
- data/lib/panda_motd/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e96416cb34d16f565eb89916c6156cd2e293a04f0ba8ffeede5df5e92d73acc3
|
4
|
+
data.tar.gz: abc7ad56a5b1a0d90dcbf4cd4a7f948bed78232357f2ac7fc352f7388d136d0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 452c47ad204b5dfb044abf370c0f0643c24bcfea8565ca5329ad487717cd116fd1d4c88378985be2d14a4759269cbe363d7b86bfdba31425e673d9f5be4aecef
|
7
|
+
data.tar.gz: a2209d1666149ac3222c005b79af5d3edb129a79dec9bc12c8c07c906ad26aa384dfc0222378ce3aa4c8da097cb425c33e4de76b67d92a0c2677ead2ba493366
|
@@ -17,7 +17,7 @@ class ASCIITextArt
|
|
17
17
|
begin
|
18
18
|
@art = Artii::Base.new font: @config['font']
|
19
19
|
@results = @art.asciify(@text)
|
20
|
-
@results = @results.
|
20
|
+
@results = @results.colorize(@config['color'].to_sym) if @config['color']
|
21
21
|
rescue Errno::EISDIR # Artii doesn't handle invalid font names very well
|
22
22
|
@errors << ComponentError.new(self, 'Invalid font name')
|
23
23
|
end
|
@@ -80,42 +80,30 @@ class Filesystems
|
|
80
80
|
private
|
81
81
|
|
82
82
|
def percentage_color(percentage)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
def find_header_id_by_text(header_array, text)
|
90
|
-
return header_array.each_index.select { |i| header_array[i].downcase.include? text }.first
|
83
|
+
case percentage
|
84
|
+
when 0..75 then :green
|
85
|
+
when 76..95 then :yellow
|
86
|
+
when 96..100 then :red
|
87
|
+
else :white
|
88
|
+
end
|
91
89
|
end
|
92
90
|
|
93
91
|
def parse_filesystem_usage(filesystems)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
filesystem_name: matching_entry.split[name_index],
|
110
|
-
size: matching_entry.split[size_index].to_i * 1024,
|
111
|
-
used: matching_entry.split[used_index].to_i * 1024,
|
112
|
-
avail: matching_entry.split[avail_index].to_i * 1024
|
113
|
-
}
|
114
|
-
else
|
115
|
-
"#{filesystem} was not found"
|
116
|
-
end
|
92
|
+
entries = `BLOCKSIZE=1024 df --output=source,size,used,avail`.lines
|
93
|
+
.drop(1)
|
94
|
+
|
95
|
+
filesystems.map do |filesystem, pretty_name|
|
96
|
+
matching_entry = entries.map(&:split).find { |e| e.first == filesystem }
|
97
|
+
next "#{filesystem} was not found" unless matching_entry
|
98
|
+
|
99
|
+
filesystem_name, size, used, avail = matching_entry
|
100
|
+
{
|
101
|
+
pretty_name: pretty_name,
|
102
|
+
filesystem_name: filesystem_name,
|
103
|
+
size: size.to_i * 1024,
|
104
|
+
used: used.to_i * 1024,
|
105
|
+
avail: avail.to_i * 1024
|
106
|
+
}
|
117
107
|
end
|
118
|
-
|
119
|
-
return results
|
120
108
|
end
|
121
109
|
end
|
@@ -16,55 +16,55 @@ class LastLogin
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def to_s
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
19
|
+
<<~LAST
|
20
|
+
Last Login:
|
21
|
+
#{@results.map do |user, logins|
|
22
|
+
logins_part =
|
23
|
+
if logins.empty?
|
24
|
+
' no logins found for user.'
|
25
|
+
else
|
26
|
+
longest_location_size = logins.map { |l| l[:location].length }.max
|
27
|
+
logins.map do |login|
|
28
|
+
location_part = login[:location].ljust(longest_location_size, ' ')
|
29
|
+
start_part = login[:time_start].strftime('%m/%d/%Y %I:%M%p')
|
30
|
+
end_part = if login[:time_end].is_a? String # still logged in text
|
31
|
+
login[:time_end].green
|
32
|
+
else
|
33
|
+
"#{((login[:time_end] - login[:time_start]) * 24 * 60).to_i} minutes"
|
34
|
+
end
|
35
|
+
" from #{location_part} at #{start_part} (#{end_part})"
|
36
|
+
end.join("\n")
|
37
|
+
end
|
38
|
+
<<~USER
|
39
|
+
#{user}:
|
40
|
+
#{logins_part}
|
41
|
+
USER
|
42
|
+
end.join("\n")}
|
43
|
+
LAST
|
40
44
|
end
|
41
45
|
|
42
46
|
private
|
43
47
|
|
44
48
|
def parse_last_logins(users)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
user_logins << {
|
56
|
-
username: username,
|
57
|
-
location: data[2],
|
58
|
-
time_start: DateTime.parse(data[3]),
|
59
|
-
time_end: time_end
|
60
|
-
}
|
61
|
-
|
62
|
-
break if user_logins.count >= num_logins
|
63
|
-
end
|
49
|
+
users.map do |(username, num_logins)|
|
50
|
+
user_logins =
|
51
|
+
`last --time-format=iso #{username}`
|
52
|
+
.lines
|
53
|
+
.select { |entry| entry.start_with?(username) }
|
54
|
+
.take(num_logins)
|
55
|
+
.map do |entry|
|
56
|
+
data = entry.chomp.split(/(?:\s{2,})|(?:\s-\s)/)
|
57
|
+
time_end = data[4] == 'still logged in' ? data[4] : DateTime.parse(data[4])
|
64
58
|
|
65
|
-
|
66
|
-
|
59
|
+
{
|
60
|
+
username: username,
|
61
|
+
location: data[2],
|
62
|
+
time_start: DateTime.parse(data[3]),
|
63
|
+
time_end: time_end
|
64
|
+
}
|
65
|
+
end
|
67
66
|
|
68
|
-
|
67
|
+
[username.to_sym, user_logins]
|
68
|
+
end.to_h
|
69
69
|
end
|
70
70
|
end
|
@@ -17,52 +17,38 @@ class ServiceStatus
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_s
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
return result
|
31
|
-
else
|
32
|
-
return "Services:\n No matching services found."
|
33
|
-
end
|
20
|
+
return "Services:\n No matching services found." unless @results.any?
|
21
|
+
longest_name_size = @results.keys.map { |k| k.to_s.length }.max
|
22
|
+
<<~HEREDOC
|
23
|
+
Services:
|
24
|
+
#{@results.map do |(name, status)|
|
25
|
+
name_part = name.to_s.ljust(longest_name_size, ' ') + ':'
|
26
|
+
status_part = status.to_s.colorize(service_colors[status.to_sym])
|
27
|
+
" #{name_part} #{status_part}"
|
28
|
+
end.join("\n")}
|
29
|
+
HEREDOC
|
34
30
|
end
|
35
31
|
|
36
32
|
private
|
37
33
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
cmd_result
|
42
|
-
|
43
|
-
if cmd_result.empty?
|
44
|
-
@errors << ComponentError.new(self, 'Unable to parse systemctl output')
|
45
|
-
end
|
46
|
-
|
47
|
-
cmd_result.split("\n").each do |line|
|
48
|
-
parsed_name = line.split[0].gsub('.service', '')
|
49
|
-
parsed_status = line.split[3]
|
50
|
-
|
51
|
-
matching_service = services.find { |service, _name| service == parsed_name }
|
52
|
-
|
53
|
-
if matching_service
|
54
|
-
results[parsed_name.to_sym] = parsed_status.to_sym
|
55
|
-
end
|
56
|
-
end
|
34
|
+
def parse_service(service)
|
35
|
+
cmd_result = `systemctl is-active #{service[0]}`.strip
|
36
|
+
@errors << ComponentError.new(self, 'Unable to parse systemctl output') unless valid_responses.include? cmd_result
|
37
|
+
return cmd_result
|
38
|
+
end
|
57
39
|
|
58
|
-
|
40
|
+
def parse_services(services)
|
41
|
+
services.map { |service| [service[1].to_sym, parse_service(service).to_sym] }.to_h
|
59
42
|
end
|
60
43
|
|
61
44
|
def service_colors
|
62
45
|
return {
|
63
|
-
|
64
|
-
|
65
|
-
failed: :red
|
46
|
+
active: :green,
|
47
|
+
inactive: :red
|
66
48
|
}
|
67
49
|
end
|
50
|
+
|
51
|
+
def valid_responses
|
52
|
+
return ['active', 'inactive']
|
53
|
+
end
|
68
54
|
end
|
@@ -16,25 +16,19 @@ class SSLCertificates
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def to_s
|
19
|
-
result = "SSL Certificates:\n"
|
20
19
|
longest_name_size = @results.map { |r| r[0].length }.max
|
20
|
+
<<~HEREDOC
|
21
|
+
SSL Certificates:
|
22
|
+
#{@results.map do |cert|
|
23
|
+
return " #{cert}" if cert.is_a? String # print the not found message
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
date_portion = "#{cert_status_strings[status]} ".send(cert_status_colors[status]) + cert[1].strftime('%e %b %Y %H:%M:%S%p').to_s
|
31
|
-
result += name_portion + date_portion
|
32
|
-
end
|
33
|
-
|
34
|
-
result += "\n" unless i == @results.count - 1 # don't print newline for last entry
|
35
|
-
end
|
36
|
-
|
37
|
-
return result
|
25
|
+
name_portion = cert[0].ljust(longest_name_size + 6, ' ')
|
26
|
+
status = cert_status(cert[1])
|
27
|
+
status = cert_status_strings[status].to_s.colorize(cert_status_colors[status])
|
28
|
+
date_portion = cert[1].strftime('%e %b %Y %H:%M:%S%p')
|
29
|
+
" #{name_portion} #{status} #{date_portion}"
|
30
|
+
end.join("\n")}
|
31
|
+
HEREDOC
|
38
32
|
end
|
39
33
|
|
40
34
|
private
|
@@ -61,10 +55,13 @@ class SSLCertificates
|
|
61
55
|
end
|
62
56
|
|
63
57
|
def cert_status(expiry_date)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
58
|
+
if (DateTime.now...DateTime.now + 30).cover? expiry_date # ... range excludes end
|
59
|
+
:expiring
|
60
|
+
elsif DateTime.now >= expiry_date
|
61
|
+
:expired
|
62
|
+
else
|
63
|
+
:valid
|
64
|
+
end
|
68
65
|
end
|
69
66
|
|
70
67
|
def cert_status_colors
|
@@ -12,8 +12,7 @@ class Uptime
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def process
|
15
|
-
|
16
|
-
uptime = sysinfo.uptime
|
15
|
+
uptime = SysInfo.new.uptime
|
17
16
|
|
18
17
|
@days = (uptime / 24).floor
|
19
18
|
@hours = (uptime - @days * 24).floor
|
@@ -21,10 +20,15 @@ class Uptime
|
|
21
20
|
end
|
22
21
|
|
23
22
|
def to_s
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
return "#{@config['prefix'] || 'up'} #{format_uptime}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def format_uptime
|
29
|
+
[@days, @hours, @minutes].zip(%w[day hour minute])
|
30
|
+
.reject { |n, _word| n.zero? }
|
31
|
+
.map { |n, word| "#{n} #{word}#{'s' if n != 1}" }
|
32
|
+
.join(', ')
|
29
33
|
end
|
30
34
|
end
|
data/lib/panda_motd/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: panda-motd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Taylor Thurlow
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: artii
|
@@ -210,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
210
210
|
requirements:
|
211
211
|
- - ">="
|
212
212
|
- !ruby/object:Gem::Version
|
213
|
-
version: '2.
|
213
|
+
version: '2.3'
|
214
214
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
215
|
requirements:
|
216
216
|
- - ">="
|