kimurai-dashboard 0.1.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,74 @@
1
+ module Kimurai::Dashboard
2
+ class Run < Sequel::Model(DB)
3
+ many_to_one :session
4
+ many_to_one :spider
5
+
6
+ plugin :json_serializer
7
+ plugin :serialization
8
+ plugin :serialization_modification_detection
9
+ serialize_attributes :json, :visits, :items, :server, :events
10
+
11
+ # scopes
12
+ dataset_module do
13
+ def spider_id(id)
14
+ filter(spider_id: id)
15
+ end
16
+
17
+ def session_id(id)
18
+ filter(session_id: id)
19
+ end
20
+ end
21
+
22
+ def latest?
23
+ Run.where(spider_name: spider_name).last&.id == id
24
+ end
25
+
26
+ def log_file
27
+ if latest?
28
+ session ? { available: true } : { available: false, reason: "run is not in session" }
29
+ else
30
+ { available: false, reason: "run is not latest" }
31
+ end
32
+ end
33
+
34
+ def difference_between_previous_run
35
+ previous_run = Run.where(spider_name: spider_name).reverse_order(:id).first(Sequel[:id] < id)
36
+ return unless previous_run
37
+
38
+ {
39
+ visits: {
40
+ requests: {
41
+ current: visits["requests"],
42
+ previous: previous_run.visits["requests"],
43
+ difference: calculate_difference(visits["requests"], previous_run.visits["requests"])
44
+ },
45
+ responses: {
46
+ current: visits["responses"],
47
+ previous: previous_run.visits["responses"],
48
+ difference: calculate_difference(visits["responses"], previous_run.visits["responses"])
49
+ }
50
+ },
51
+ items: {
52
+ sent: {
53
+ current: items["sent"],
54
+ previous: previous_run.items["sent"],
55
+ difference: calculate_difference(items["sent"], previous_run.items["sent"])
56
+ },
57
+ processed: {
58
+ current: items["processed"],
59
+ previous: previous_run.items["processed"],
60
+ difference: calculate_difference(items["processed"], previous_run.items["processed"])
61
+ }
62
+ },
63
+ previous_run_id: previous_run.id
64
+ }
65
+ end
66
+
67
+ private
68
+
69
+ def calculate_difference(current, previous)
70
+ return if current == 0 || previous == 0
71
+ (((current - previous).to_r / previous) * 100).to_f.round(1)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,37 @@
1
+ module Kimurai::Dashboard
2
+ class Session < Sequel::Model(DB)
3
+ one_to_many :runs
4
+
5
+ plugin :json_serializer
6
+ plugin :serialization
7
+ plugin :serialization_modification_detection
8
+ serialize_attributes :json, :spiders
9
+
10
+ unrestrict_primary_key
11
+
12
+ def total_time
13
+ (stop_time ? stop_time - start_time : Time.now - start_time).round(3)
14
+ end
15
+
16
+ def processing?
17
+ status == "processing"
18
+ end
19
+
20
+ def spiders_in_queue
21
+ return [] unless processing?
22
+ spiders - runs_dataset.select_map(:spider_name)
23
+ end
24
+
25
+ def running_runs
26
+ runs_dataset.where(status: "running").all
27
+ end
28
+
29
+ def failed_runs
30
+ runs_dataset.where(status: "failed").all
31
+ end
32
+
33
+ def completed_runs
34
+ runs_dataset.where(status: "completed").all
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ module Kimurai::Dashboard
2
+ class Spider < Sequel::Model(DB)
3
+ one_to_many :runs
4
+ plugin :json_serializer
5
+
6
+ def current_session
7
+ session = Session.find(status: "processing")
8
+ return unless session
9
+ session if session.spiders.include?(name)
10
+ end
11
+
12
+ def running?
13
+ runs_dataset.first(status: "running") ? true : false
14
+ end
15
+
16
+ def current_state
17
+ running? ? "running" : "stopped"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ .alert-row {
2
+ padding-top: 1rem;
3
+ }
4
+
5
+ .footer {
6
+ position: absolute;
7
+ bottom: 0;
8
+ width: 100%;
9
+ height: 4rem; /* Set the fixed height of the footer here */
10
+ line-height: 4rem; /* Vertically center the text there */
11
+ background-color: #f5f5f5;
12
+ text-align: center;
13
+ }
14
+ html {
15
+ position: relative;
16
+ min-height: 100%;
17
+ }
18
+ body {
19
+ margin-bottom: 6rem;
20
+ }
21
+
22
+ main {
23
+ margin-top: 1.7rem;
24
+ }
25
+
26
+ .dotted {
27
+ text-decoration:underline;
28
+ text-decoration-style: dotted;
29
+ border: none;
30
+ }
31
+
32
+ .gray {
33
+ color: #6c757d;
34
+ }
35
+
36
+ .fancy-hr {
37
+ overflow: hidden;
38
+ text-align: center;
39
+ color: #6c757d;
40
+ }
41
+
42
+ .fancy-hr:before,
43
+ .fancy-hr:after {
44
+ background-color: #dee2e6;
45
+ content: "";
46
+ display: inline-block;
47
+ height: 1px;
48
+ position: relative;
49
+ vertical-align: middle;
50
+ width: 50%;
51
+ }
52
+
53
+ .fancy-hr:before {
54
+ right: 0.5em;
55
+ margin-left: -50%;
56
+ }
57
+
58
+ .fancy-hr:after {
59
+ left: 0.5em;
60
+ margin-right: -50%;
61
+ }
@@ -0,0 +1,4 @@
1
+ $(document).ready(function() {
2
+ let rootResourse = location.pathname.replace(/(^\/.*?)\/(.*)/, '$1')
3
+ $(`a[href="${rootResourse}"]`).closest('li').addClass('active');
4
+ });
@@ -0,0 +1,31 @@
1
+ require 'kimurai/runner'
2
+
3
+ module Kimurai
4
+ class Runner
5
+ alias_method :original_run!, :run!
6
+ def run!(exception_on_fail: true)
7
+ register_session(session_info)
8
+ _, error = original_run!(exception_on_fail: false)
9
+ update_session(session_info)
10
+
11
+ if error
12
+ exception_on_fail ? raise(error) : [session_info, error]
13
+ else
14
+ session_info
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def register_session(session_info)
21
+ Dashboard::Session.create(session_info)
22
+ end
23
+
24
+ def update_session(session_info)
25
+ session = Dashboard::Session.find(session_info[:id]).first
26
+ session.set(session_info)
27
+ session.save
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,5 @@
1
+ module Kimurai
2
+ module Dashboard
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ <nav class="navbar navbar-expand-lg navbar-light bg-light rounded" role="navigation">
2
+ <ul class="navbar-nav mr-auto">
3
+ <li class="nav-item">
4
+ <a class="nav-link" href="/spiders">Spiders</a>
5
+ </li>
6
+ <li class="nav-item">
7
+ <a class="nav-link" href="/runs">Runs</a>
8
+ </li>
9
+ <li class="nav-item">
10
+ <a class="nav-link" href="/sessions">Sessions</a>
11
+ </li>
12
+ </ul>
13
+ <ul class="navbar-nav ml-auto">
14
+ <li class="nav-item">
15
+ Environment: <%= Kimurai.env %>
16
+ <!-- <a class="nav-link" href="#" data-target="#login" data-toggle="modal"></a> -->
17
+ </li>
18
+ </ul>
19
+ </nav>
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Kimurai Dashboard</title>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
+
8
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
9
+ <link rel="stylesheet" href="/application.css?<%= Time.now.to_i %>">
10
+ </head>
11
+ <body>
12
+ <header class="container">
13
+ <%= erb :'_header' %>
14
+ </header>
15
+
16
+ <main class="container" role="main">
17
+ <%= yield %>
18
+ </main>
19
+
20
+ <footer class="footer" class="container" role="contentinfo">
21
+ <span class="text-muted">Kimurai Framework Dashboard 2018</span>
22
+ </footer>
23
+
24
+ <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
25
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
26
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
27
+ <script src="/application.js"></script>
28
+ </body>
29
+ </html>
@@ -0,0 +1,18 @@
1
+ <!-- <div class="col-lg-12"> -->
2
+ <table class="table table-hover table-sm">
3
+ <thead>
4
+ <tr>
5
+ <th scope="col">Message</th>
6
+ <th scope="col">Count</th>
7
+ </tr>
8
+ </thead>
9
+ <tbody>
10
+ <% errors.each do |error, count| %>
11
+ <tr>
12
+ <td><%= h(error) %></td>
13
+ <td><%= count %></td>
14
+ </tr>
15
+ <% end %>
16
+ </tbody>
17
+ </table>
18
+ <!-- </div> -->
@@ -0,0 +1,38 @@
1
+ <div class="table-responsive">
2
+ <table class="table table-hover table-sm">
3
+ <thead>
4
+ <tr>
5
+ <th scope="col"># id</th>
6
+ <th scope="col">Spider</th>
7
+ <th scope="col">Status</th>
8
+ <th scope="col">Start time</th>
9
+ <th scope="col">Stop time</th>
10
+ <th scope="col">Running</th>
11
+ <th scope="col">Session</th>
12
+ <th scope="col" data-toggle="tooltip" title="Requests / Responses">Visits*</th>
13
+ <th scope="col" data-toggle="tooltip" title="Sent / Processed">Items*</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <% runs.each do |run| %>
18
+ <tr>
19
+ <th scope="row">
20
+ <a href="/runs/<%= run.id %>"><%= run.id %></a>
21
+ </th>
22
+ <td>
23
+ <a href="/spiders/<%= run.spider_id %>"><%= run.spider_name %></a>
24
+ </td>
25
+ <td><%= get_badge(run.status) %></td>
26
+ <td><%= run.start_time %></td>
27
+ <td><%= run.stop_time %></td>
28
+ <td><%= run.running_time.duration %></td>
29
+ <td>
30
+ <a href="/sessions/<%= run.session_id %>"><%= run.session_id %></a>
31
+ </td>
32
+ <td><%= minimize_stats(run.visits).join("/") %></td>
33
+ <td><%= minimize_stats(run.items).join("/") %></td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ </div>
@@ -0,0 +1,11 @@
1
+ <%= breadcrumbs("/" => "Home", "" => "Runs") %>
2
+
3
+ <div class="row">
4
+ <div class="col-lg-12">
5
+ <h1>Runs</h1>
6
+ <%= render_filters(filters) if filters.any? %>
7
+ <%= erb :'runs/_table', locals: { runs: @runs } %>
8
+
9
+ <%= pagy_nav_bootstrap(@pagy) if @pagy && @pagy.pages > 1 %>
10
+ </div>
11
+ </div>
@@ -0,0 +1,142 @@
1
+ <%= breadcrumbs("/" => "Home", "/runs" => "Runs", "" => @run.id) %>
2
+
3
+ <div class="row">
4
+ <div class="col-lg-12">
5
+ <h1>Run #<%= @run.id %></h1>
6
+
7
+ <table class="table table-bordered">
8
+ <tbody>
9
+ <tr>
10
+ <th>ID</th>
11
+ <td><%= @run.id %></td>
12
+ </tr>
13
+ <tr>
14
+ <th>Spider</th>
15
+ <td>
16
+ <strong><a href="/spiders/<%= @run.spider_id %>"><%= @run.spider_name %></a></strong>
17
+ </td>
18
+ </tr>
19
+ <tr>
20
+ <th>Session</th>
21
+ <td><a href="/sessions/<%= @run.session_id %>"><%= @run.session_id %></a></td>
22
+ </tr>
23
+ <tr>
24
+ <th>Status</th>
25
+ <td>
26
+ <%= get_badge(@run.status) %>
27
+ <% if error = @run.error %>
28
+ <i>(<%= h(error) %>)</i>
29
+ <% end %>
30
+ </td>
31
+ </tr>
32
+ <tr>
33
+ <th>Start time</th>
34
+ <td><%= @run.start_time %></td>
35
+ </tr>
36
+ <tr>
37
+ <th>Stop time</th>
38
+ <td><%= @run.stop_time %></td>
39
+ </tr>
40
+ <tr>
41
+ <th>Running time</th>
42
+ <td><%= @run.running_time.duration %></td>
43
+ </tr>
44
+ <tr>
45
+ <th>Environment</th>
46
+ <td><%= @run.environment %></td>
47
+ </tr>
48
+ <tr>
49
+ <th>Log file</th>
50
+ <td>
51
+ <% if @run.log_file[:available] == false %>
52
+ Not available (<%= @run.log_file[:reason] %>)
53
+ <% else %>
54
+ <a target="_blank" href="/runs/<%= @run.id %>/log"><%= @run.spider_name %>.log</a>
55
+ <% end %>
56
+ </td>
57
+ </tr>
58
+ <tr>
59
+ <th>Server hostname</th>
60
+ <td><%= @run.server["hostname"] %></td>
61
+ </tr>
62
+ <tr>
63
+ <th>Server IPV4</th>
64
+ <td><%= @run.server["ipv4"] %></td>
65
+ </tr>
66
+ <tr>
67
+ <th>Process pid</th>
68
+ <td><%= @run.server["process_pid"] %></td>
69
+ </tr>
70
+ </tbody>
71
+ </table>
72
+ </div>
73
+ <div class="col-lg-12">
74
+ <h2>Visits</h2>
75
+ <table class="table table-bordered table-sm">
76
+ <tbody>
77
+ <tr>
78
+ <th>Requests</th>
79
+ <td>
80
+ <strong><%= @run.visits["requests"] %></strong>
81
+ <% if difference %>
82
+ <i><%= format_difference(difference[:visits][:requests][:previous], difference[:visits][:requests][:difference], difference[:previous_run_id]) %></i>
83
+ <% end %>
84
+ </td>
85
+ </tr>
86
+ <tr>
87
+ <th>Responses</th>
88
+ <td>
89
+ <strong><%= @run.visits["responses"] %></strong>
90
+ <% if difference %>
91
+ <i><%= format_difference(difference[:visits][:responses][:previous], difference[:visits][:responses][:difference], difference[:previous_run_id]) %></i>
92
+ <% end %>
93
+ </td>
94
+ </tr>
95
+ </tbody>
96
+ </table>
97
+ </div>
98
+
99
+ <div class="col-lg-12">
100
+ <h2>Items</h2>
101
+ <table class="table table-bordered table-sm">
102
+ <tbody>
103
+ <tr>
104
+ <th>Sent</th>
105
+ <td>
106
+ <strong><%= @run.items["sent"] %></strong>
107
+ <% if difference %>
108
+ <i><%= format_difference(difference[:items][:sent][:previous], difference[:items][:sent][:difference], difference[:previous_run_id]) %></i>
109
+ <% end %>
110
+ </td>
111
+ </tr>
112
+ <tr>
113
+ <th>Processed</th>
114
+ <td>
115
+ <strong><%= @run.items["processed"] %></strong>
116
+ <% if difference %>
117
+ <i><%= format_difference(difference[:items][:processed][:previous], difference[:items][:processed][:difference], difference[:previous_run_id]) %></i>
118
+ <% end %>
119
+ </td>
120
+ </tr>
121
+ </tbody>
122
+ </table>
123
+ </div>
124
+
125
+ <div class="col-lg-12">
126
+ <h2>Events</h2>
127
+ <% if @run.events["requests_errors"]&.any? %>
128
+ <p class="fancy-hr" id="running">Requests Errors (<%= errors_count(@run.events["requests_errors"]) %>)</p>
129
+ <%= erb :'runs/_errors', locals: { errors: @run.events["requests_errors"] } %>
130
+ <% end %>
131
+
132
+ <% if @run.events["drop_items_errors"]&.any? %>
133
+ <p class="fancy-hr" id="running">Drop Items Errors (<%= errors_count(@run.events["drop_items_errors"]) %>)</p>
134
+ <%= erb :'runs/_errors', locals: { errors: @run.events["drop_items_errors"] } %>
135
+ <% end %>
136
+
137
+ <% if @run.events["custom"]&.any? %>
138
+ <p class="fancy-hr" id="running">Custom (<%= errors_count(@run.events["custom"]) %>)</p>
139
+ <%= erb :'runs/_errors', locals: { errors: @run.events["custom"] } %>
140
+ <% end %>
141
+ </div>
142
+ </div>