1
0
Fork 0
mirror of https://github.com/mperham/sidekiq.git synced 2022-11-09 13:52:34 -05:00

Make Sidekiq Web UI RTL-friendly (#3381)

* Unminify rickshaw graph CSS so we can maintain it

* Initial BiDi support for the Web UI

BiDi means the web app can serve both LTR and RTL languages at the same time.

1. Bootstrap-RTL overrides Bootstrap 3.3 styles for RTL clients
2. Application CSS was preprocessed thru rtlcss and then hand-trimmed to contain only RTL-specific directives.
3. Dashboard was tweaked to hardcode LTR for footer and polling gadget

* Vendor bootstrap-rtl.css

* Various RTL style fixes, need to flip all pull-* elements

* Upgrade Rickshaw to latest, remove dupe CSS

* Add Arabic language (credit to Milena Novakova)
Add Hebrew placeholder
Added dir attribute to html tag

* changes

* Clean up HTTP header generation, add Content-Language response header

* Use correct locale for hebrew
This commit is contained in:
Mike Perham 2017-03-16 13:51:29 -07:00 committed by GitHub
parent 5806792716
commit ed485e47e4
23 changed files with 811 additions and 54 deletions

View file

@ -30,6 +30,9 @@ versions of Ruby and Rails.
**ST**o**P**) instead of USR1 as USR1 is not available on JRuby.
USR1 will continue to be supported in Sidekiq 5.x for backwards
compatibility and will be removed in Sidekiq 6.x. [#3302]
* The Web UI is now bi-directional - it can render either LTR
(left-to-right) or RTL languages. With this change, Farsi, Arabic
and Hebrew are all now officially supported! [#3381]
* Rails 3.2 is no longer supported.
* Ruby 2.0 and Ruby 2.1 are no longer supported. Ruby 2.2.2+ is required.
* Jobs which can't be parsed due to invalid JSON are now pushed

View file

@ -14,6 +14,7 @@ Sidekiq::Middleware::Server::Logging -> Sidekiq::JobLogging
- Quieting Sidekiq is now done via the TSTP signal, the USR1 signal is deprecated.
- The `delay` extension APIs are no longer available by default, you
must opt into them.
- The Web UI is now BiDi and can render in Arabic, Farsi and Hebrew.
- Rails 3.2 and Ruby 2.0 and 2.1 are no longer supported.
- Please see the [5.0 Upgrade notes](5.0-Upgrade.md) for more detail.

View file

@ -39,10 +39,6 @@ module Sidekiq
env[RACK_SESSION]
end
def content_type(type)
@type = type
end
def erb(content, options = {})
if content.kind_of? Symbol
unless respond_to?(:"_erb_#{content}")

View file

@ -274,19 +274,14 @@ module Sidekiq
resp = case resp
when Array
resp
when Integer
[resp, {}, []]
else
type_header = case action.type
when :json
{ "Content-Type" => "application/json", "Cache-Control" => "no-cache" }
when String
{ "Content-Type" => (action.type || "text/html"), "Cache-Control" => "no-cache" }
else
{ "Content-Type" => "text/html", "Cache-Control" => "no-cache" }
end
headers = {
"Content-Type" => "text/html",
"Cache-Control" => "no-cache",
"Content-Language" => action.locale,
}
[200, type_header, [resp]]
[200, headers, [resp]]
end
resp[1] = resp[1].dup

View file

@ -65,6 +65,14 @@ module Sidekiq
end
end
def text_direction
get_locale['TextDirection'] || 'ltr'
end
def rtl?
text_direction == 'rtl'
end
# Given a browser request Accept-Language header like
# "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function
# will return "fr" since that's the first code with a matching
@ -144,7 +152,7 @@ module Sidekiq
def relative_time(time)
stamp = time.getutc.iso8601
%{<time title="#{stamp}" datetime="#{stamp}">#{time}</time>}
%{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
end
def job_params(job, score)

View file

@ -3,7 +3,7 @@ source 'https://rubygems.org'
gem 'appraisal'
gem 'pry'
gem 'sidekiq', :path => '..'
gem 'rails', '5.0.1'
gem 'rails', '5.0.2'
platforms :ruby do
gem 'sqlite3'

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,246 @@
.navbar-right {
float: left !important;
}
footer .edits {
margin-right: unset;
margin-left: 40px;
}
.summary_bar .status {
margin-left: unset;
margin-right: 10px;
}
@media (max-width: 767px) and (min-width: 400px) {
.summary_bar ul .desc {
text-align: right;
}
.summary_bar ul .count {
text-align: left;
}
}
@media (max-width: 979px) and (min-width: 768px) {
}
.summary_bar ul .count {
float: left;
}
form .btn {
margin-right: unset;
margin-left: 5px;
}
form .btn-group .btn {
margin-right: unset;
margin-left: 1px;
}
@media (max-width: 768px) {
.navbar.navbar-fixed-top ul {
margin-right: unset;
margin-left: -15px!important;
}
}
@media (width: 768px) {
.navbar .poll-wrapper {
margin: 4px 0 0 4px;
}
.navbar .dropdown-menu a {
text-align: right;
}
}
.navbar-footer .navbar ul.nav a.navbar-brand {
padding: 0 0 0 15px;
}
.navbar-fixed-bottom li {
margin-right: unset;
margin-left: 20px;
}
.status-active {
background-position: 100% 0;
}
.status-idle {
background-position: 100% -54px;
}
.stat {
float: right;
margin-right: unset;
margin-left: 20px;
}
.stat:last-child {
margin-left: 0;
}
@media (max-width: 767px) {
.stat {
float: right;
margin-right: unset;
margin-left: 10px;
text-align: right;
}
.stat h3{
float: left;
margin: 5px 5px 5px 10px;
}
.stat p{
font-size: 1em;
margin: 5px 10px 5px 5px;
}
}
/* Dashboard
********************************** */
div.dashboard h3 {
float: right;
}
div.interval-slider {
float: left;
}
#realtime-legend,
#history-legend {
text-align: right;
float: left;
}
#realtime-legend .timestamp,
#history-legend .timestamp {
text-align: left;
}
#realtime-legend .line,
#history-legend .line {
margin: 0 20px 0 0;
}
#realtime-legend .swatch,
#history-legend .swatch {
margin: 0 0 0 8px;
}
/* Beacon
********************************** */
.beacon .dot,
.beacon .ring {
right: 50%;
}
.beacon .dot {
margin: -5px -5px 0 0;
}
.beacon .ring {
margin: -14px -14px 0 0;
}
.history-heading {
padding-right: unset;
padding-left: 15px;
}
@media (max-width: 767px) {
.navbar.navbar-fixed-top ul {
margin-left: 0;
}
.navbar.navbar-fixed-top li {
margin-left: 0;
}
.navbar.navbar-fixed-bottom ul {
margin-left: 0;
}
.navbar.navbar-fixed-bottom li {
margin-left: 0;
}
}
@media (max-width: 500px) {
.navbar-footer .navbar ul.nav a.navbar-brand {
padding-right: unset;
padding-left: 5px;
}
}
/* Rickshaw */
.rickshaw_graph .detail .x_label.left {
right: 0
}
.rickshaw_graph .detail .x_label.right {
left: 0
}
.rickshaw_graph .detail .item.left {
right: 0
}
.rickshaw_graph .detail .item.right {
left: 0
}
.rickshaw_graph .detail .item.left:after {
left: 0;
right: -5px;
border-right-color: unset;
border-left-color: rgba(0, 0, 0, .8);
border-right-width: 0;
border-left-width: unset;
}
.rickshaw_graph .detail .item.right:after {
right: 0;
left: -5px;
border-left-color: unset;
border-right-color: rgba(0, 0, 0, .8);
border-left-width: 0;
border-right-width: unset;
}
.rickshaw_graph .detail .dot {
margin-right: -3px;
margin-left: unset;
}
.rickshaw_graph .x_tick {
border-left: unset;
border-right: 1px dotted rgba(0, 0, 0, .2);
}
.rickshaw_graph .x_tick .title {
margin-right: 3px;
margin-left: unset;
}
.rickshaw_annotation_timeline .annotation {
margin-right: -2px;
margin-left: unset;
}
.rickshaw_graph .annotation_line {
border-right: 2px solid rgba(0, 0, 0, .3);
border-left: unset;
}
.rickshaw_annotation_timeline .annotation .content {
left: unset;
right: -11px;
}
.rickshaw_graph .x_tick.glow .title,
.rickshaw_graph .y_ticks.glow text {
text-shadow: 1px 1px 0 rgba(255, 255, 255, .1), -1px -1px 0 rgba(255, 255, 255, .1), -1px 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1), 0 -1px 0 rgba(255, 255, 255, .1), -1px 0 0 rgba(255, 255, 255, .1), 1px 0 0 rgba(255, 255, 255, .1), 1px -1px 0 rgba(255, 255, 255, .1)
}
.rickshaw_graph .x_tick.inverse .title,
.rickshaw_graph .y_ticks.inverse text {
text-shadow: 1px 1px 0 rgba(0, 0, 0, .8), -1px -1px 0 rgba(0, 0, 0, .8), -1px 1px 0 rgba(0, 0, 0, .8), 0 1px 0 rgba(0, 0, 0, .8), 0 -1px 0 rgba(0, 0, 0, .8), -1px 0 0 rgba(0, 0, 0, .8), 1px 0 0 rgba(0, 0, 0, .8), 1px -1px 0 rgba(0, 0, 0, .8)
}
.rickshaw_legend .line {
padding-left: 15px;
padding-right: unset;
}
.rickshaw_legend .line .swatch {
margin-left: 3px;
margin-right: unset;
}
.rickshaw_legend .action {
margin-left: .2em;
margin-right: unset;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

80
web/locales/ar.yml Normal file
View file

@ -0,0 +1,80 @@
# elements like %{queue} are variables and should not be translated
ar:
TextDirection: 'rtl'
Dashboard: لوحة القيادة
Status: حالة
Time: وقت
Namespace: مساحة الأسماء
Realtime: راهن
History: تاريخ
Busy: مصروف
Processed: مصنوع
Failed: فشل
Scheduled: مبرمج
Retries: محاولة
Enqueued: بالدور
Worker: عامل
LivePoll: استجواب
StopPolling: استجوب على الملكان
Queue: دورة
Class: فىة
Job: وظيفة
Arguments: براهين
Extras: اضافة
Started: ابتداء
ShowAll: عرض كل الشي
CurrentMessagesInQueue: الأعمال الجارية في <span class='title'>%{queue}</span>
AddToQueue: الإضافة إلى الدور
AreYouSureDeleteJob: ا تتأكد من العمل؟
AreYouSureDeleteQueue: ا تتأكد من إزالة %{queue} الدور؟
Delete: حذف
Queues: دورات
Size: حجم
Actions: اجراءات
NextRetry: المحاولة القادمة
RetryCount: عدد إعادة المحاولة
RetryNow: إعادة المحاولة الآن
Kill: ابطل
LastRetry: المحاولة الأخيرة
OriginallyFailed: فشل أصلا
AreYouSure: هل انت متأكد؟
DeleteAll: حذف كل شي
RetryAll: عاد كل شي
NoRetriesFound: لم وجد المحاولات
Error: خطأ
ErrorClass: طبقة الخطأ
ErrorMessage: رسالة الخطأ
ErrorBacktrace: الخطأ العودة التتبع
GoBack: تقهقر
NoScheduledFound: لم وجد المشاكل المبرمجة
When: متى
ScheduledJobs: المشاكل المبرمجة
idle: اشل
active: عامل
Version: تفريعة
Connections: اشتراك
MemoryUsage: استخدام الذاكرة
PeakMemoryUsage: ذروة استخدام الذاكرة
Uptime: أجال عامل
OneWeek: أسبوع
OneMonth: شهر
ThreeMonths: ثلاثة أشهر
SixMonths: ستة أشهر
Failures: فشل
DeadJobs: وظائف الميتة
NoDeadJobsFound: لم وجد وظائف الميتة
Dead: ميتة
Processes: عملية
Thread: خيط
Threads: خيوط
Jobs: وظائف
Paused: ركزة
Stop: خلاص
Quiet: هدوء
StopAll: خلاص كل الشي
QuietAll: الهدوء فقط
PollingInterval: استقصاء
Plugins: الإضافات
NotYetEnqueued: ليس في دورة حتى
CreatedAt: أنشئت في
BackToApp: العودة إلى التطبيق

View file

@ -1,5 +1,6 @@
# elements like %{queue} are variables and should not be translated
fa: # <---- change this to your locale code
TextDirection: 'rtl'
Dashboard: داشبورد
Status: اعلان
Time: رمان

79
web/locales/he.yml Normal file
View file

@ -0,0 +1,79 @@
he:
TextDirection: 'rtl'
Dashboard: Dashboard
Status: Status
Time: Time
Namespace: Namespace
Realtime: Real-time
History: History
Busy: Busy
Processed: Processed
Failed: Failed
Scheduled: Scheduled
Retries: Retries
Enqueued: Enqueued
Worker: Worker
LivePoll: Live Poll
StopPolling: Stop Polling
Queue: Queue
Class: Class
Job: Job
Arguments: Arguments
Extras: Extras
Started: Started
ShowAll: Show All
CurrentMessagesInQueue: Current jobs in <span class='title'>%{queue}</span>
Delete: Delete
AddToQueue: Add to queue
AreYouSureDeleteJob: Are you sure you want to delete this job?
AreYouSureDeleteQueue: Are you sure you want to delete the %{queue} queue?
Queues: Queues
Size: Size
Actions: Actions
NextRetry: Next Retry
RetryCount: Retry Count
RetryNow: Retry Now
Kill: Kill
LastRetry: Last Retry
OriginallyFailed: Originally Failed
AreYouSure: Are you sure?
DeleteAll: Delete All
RetryAll: Retry All
NoRetriesFound: No retries were found
Error: Error
ErrorClass: Error Class
ErrorMessage: Error Message
ErrorBacktrace: Error Backtrace
GoBack: ← Back
NoScheduledFound: No scheduled jobs were found
When: When
ScheduledJobs: Scheduled Jobs
idle: idle
active: active
Version: Version
Connections: Connections
MemoryUsage: Memory Usage
PeakMemoryUsage: Peak Memory Usage
Uptime: Uptime (days)
OneWeek: 1 week
OneMonth: 1 month
ThreeMonths: 3 months
SixMonths: 6 months
Failures: Failures
DeadJobs: Dead Jobs
NoDeadJobsFound: No dead jobs were found
Dead: Dead
Processes: Processes
Thread: Thread
Threads: Threads
Jobs: Jobs
Paused: Paused
Stop: Stop
Quiet: Quiet
StopAll: Stop All
QuietAll: Quiet All
PollingInterval: Polling interval
Plugins: Plugins
NotYetEnqueued: Not yet enqueued
CreatedAt: Created At
BackToApp: Back to App

View file

@ -1,4 +1,4 @@
<div class="navbar navbar-fixed-bottom navbar-inverse">
<div class="navbar navbar-fixed-bottom navbar-inverse ltr">
<div class="navbar-inner">
<div class="container text-center">
<ul class="nav">

View file

@ -53,7 +53,7 @@
</ul>
<ul class="nav navbar-nav navbar-right navbar-livereload" data-navbar="static">
<li>
<div class="poll-wrapper pull-right">
<div class="poll-wrapper">
<%= erb :_poll_link %>
<% if Sidekiq::Web.app_url %>
<a class="btn btn-inverse" href="<%= Sidekiq::Web.app_url %>"><%= t('BackToApp') %></a>

View file

@ -1,5 +1,5 @@
<% if @total_size > @count %>
<ul class="pagination pull-right">
<ul class="pagination pull-right flip">
<li class="<%= 'disabled' if @current_page == 1 %>">
<a href="<%= url %>?page=1">&laquo;</a>
</li>

View file

@ -1,11 +1,11 @@
<div class="row header">
<div class="col-sm-8 pull-left">
<div class="col-sm-8 pull-left flip">
<h3><%= t('Processes') %></h3>
</div>
<div class="col-sm-4 pull-right">
<div class="col-sm-4 pull-right flip">
<form method="POST" class="warning-messages">
<%= csrf_tag %>
<div class="btn-group pull-right">
<div class="btn-group pull-right flip">
<button class="btn btn-warn" type="submit" name="quiet" value="1" data-confirm="<%= t('AreYouSure') %>"><%= t('QuietAll') %></button>
<button class="btn btn-danger" type="submit" name="stop" value="1" data-confirm="<%= t('AreYouSure') %>"><%= t('StopAll') %></button>
</div>
@ -41,7 +41,7 @@
<td><%= process['concurrency'] %></td>
<td><%= process['busy'] %></td>
<td>
<div class="btn-group pull-right">
<div class="btn-group pull-right flip">
<form method="POST">
<%= csrf_tag %>
<input type="hidden" name="identity" value="<%= process['identity'] %>"/>

View file

@ -7,7 +7,7 @@
<span class="dot"></span>
</span>
</h3>
<div class="interval-slider">
<div class="interval-slider ltr">
<span class="interval-slider-label"><%= t('PollingInterval') %>:</span>
<span class="current-interval">5 sec</span>
<br/>

View file

@ -1,11 +1,20 @@
<!doctype html>
<html>
<html dir="<%= text_direction %>">
<head>
<title><%= environment_title_prefix %><%= Sidekiq::NAME %></title>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link href="<%= root_path %>stylesheets/bootstrap.css" media="screen" rel="stylesheet" type="text/css" />
<% if rtl? %>
<link href="<%= root_path %>stylesheets/bootstrap-rtl.min.css" media="screen" rel="stylesheet" type="text/css"/>
<% end %>
<link href="<%= root_path %>stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
<% if rtl? %>
<link href="<%= root_path %>stylesheets/application-rtl.css" media="screen" rel="stylesheet" type="text/css" />
<% end %>
<link rel="shortcut icon" type="image/ico" href="<%= root_path %>images/favicon.ico" />
<script type="text/javascript" src="<%= root_path %>javascripts/application.js"></script>
<meta name="google" content="notranslate" />

View file

@ -55,18 +55,18 @@
<% end %>
</table>
</div>
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="<%= t('RetryNow') %>" />
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="<%= t('Delete') %>" />
<input class="btn btn-primary btn-xs pull-left flip" type="submit" name="retry" value="<%= t('RetryNow') %>" />
<input class="btn btn-danger btn-xs pull-left flip" type="submit" name="delete" value="<%= t('Delete') %>" />
</form>
<% unfiltered? do %>
<form action="<%= root_path %>morgue/all/delete" method="post">
<%= csrf_tag %>
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
<input class="btn btn-danger btn-xs pull-right flip" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
</form>
<form action="<%= root_path %>morgue/all/retry" method="post">
<%= csrf_tag %>
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="<%= t('RetryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
<input class="btn btn-danger btn-xs pull-right flip" type="submit" name="retry" value="<%= t('RetryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
</form>
<% end %>

View file

@ -7,7 +7,7 @@
<% end %>
</h3>
</div>
<div class="col-sm-4 pull-right">
<div class="col-sm-4 pull-right flip">
<%= erb :_paging, locals: { url: "#{root_path}queues/#{CGI.escape(@name)}" } %>
</div>
</header>

View file

@ -55,19 +55,19 @@
<% end %>
</table>
</div>
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="<%= t('RetryNow') %>" />
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="<%= t('Delete') %>" />
<input class="btn btn-danger btn-xs pull-left" type="submit" name="kill" value="<%= t('Kill') %>" />
<input class="btn btn-primary btn-xs pull-left flip" type="submit" name="retry" value="<%= t('RetryNow') %>" />
<input class="btn btn-danger btn-xs pull-left flip" type="submit" name="delete" value="<%= t('Delete') %>" />
<input class="btn btn-danger btn-xs pull-left flip" type="submit" name="kill" value="<%= t('Kill') %>" />
</form>
<% unfiltered? do %>
<form action="<%= root_path %>retries/all/delete" method="post">
<%= csrf_tag %>
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
<input class="btn btn-danger btn-xs pull-right flip" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
</form>
<form action="<%= root_path %>retries/all/retry" method="post">
<%= csrf_tag %>
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="<%= t('RetryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
<input class="btn btn-danger btn-xs pull-right flip" type="submit" name="retry" value="<%= t('RetryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
</form>
<% end %>

View file

@ -46,8 +46,8 @@
<% end %>
</table>
</div>
<input class="btn btn-danger pull-right" type="submit" name="delete" value="<%= t('Delete') %>" />
<input class="btn btn-danger pull-right" type="submit" name="add_to_queue" value="<%= t('AddToQueue') %>" />
<input class="btn btn-danger pull-right flip" type="submit" name="delete" value="<%= t('Delete') %>" />
<input class="btn btn-danger pull-right flip" type="submit" name="add_to_queue" value="<%= t('AddToQueue') %>" />
</form>
<% else %>
<div class="alert alert-success"><%= t('NoScheduledFound') %></div>