kimurai-dashboard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>