diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..ded31c0 --- /dev/null +++ b/.babelrc @@ -0,0 +1,18 @@ +{ + "presets": [ + ["env", { + "modules": false, + "targets": { + "browsers": "> 1%", + "uglify": true + }, + "useBuiltIns": true + }] + ], + + "plugins": [ + "syntax-dynamic-import", + "transform-object-rest-spread", + ["transform-class-properties", { "spec": true }] + ] +} diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index c5f3e68..0000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "vendor/assets/javascripts/bower" -} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4a58e13 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 120 + diff --git a/.gitignore b/.gitignore index e953594..66c245e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,9 @@ /db/*-user.txt /db/*-fluentd.json +/public/packs +/public/packs-test +/public/assets +/node_modules +yarn-debug.log* +.yarn-integrity diff --git a/.postcssrc.yml b/.postcssrc.yml new file mode 100644 index 0000000..150dac3 --- /dev/null +++ b/.postcssrc.yml @@ -0,0 +1,3 @@ +plugins: + postcss-import: {} + postcss-cssnext: {} diff --git a/.travis.yml b/.travis.yml index a3f699b..a568be0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,4 +22,4 @@ install: - bundle -v - gem i bundler - sudo sh -c 'curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh | sh' - - bundle install --jobs=4 --retry=4 + - bin/setup diff --git a/Gemfile.lock b/Gemfile.lock index 2678f24..eb88a2f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,6 +22,7 @@ PATH settingslogic sucker_punch (~> 2.0.4) thor + webpacker GEM remote: https://rubygems.org/ @@ -194,6 +195,8 @@ GEM public_suffix (3.0.2) puma (3.11.4) rack (2.0.4) + rack-proxy (0.6.4) + rack rack-test (1.0.0) rack (>= 1.0, < 3) rails (5.2.0) @@ -299,6 +302,10 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff + webpacker (3.5.3) + activesupport (>= 4.2) + rack-proxy (>= 0.6.1) + railties (>= 4.2) websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c895acd..a314b04 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -10,15 +10,4 @@ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details // about supported directives. // -//= require jquery -//= require jquery_ujs -//= require sb-admin-v2/bootstrap -//= require sb-admin-v2/plugins/dataTables/jquery.dataTables -//= require sb-admin-v2/plugins/dataTables/dataTables.bootstrap -//= require bower/vue/dist/vue -//= require bower/es6-promise/promise -//= require bower/lodash/dist/lodash.compat -//= require bower/codemirror/lib/codemirror -//= require codemirror_fluentd -//= require vue_common -//= require_tree . +//= require rails-ujs diff --git a/app/assets/javascripts/codemirror.js b/app/assets/javascripts/codemirror.js deleted file mode 100644 index 9c8cec6..0000000 --- a/app/assets/javascripts/codemirror.js +++ /dev/null @@ -1,29 +0,0 @@ -function codemirrorify(el) { - return CodeMirror.fromTextArea(el, { - theme: "neo", - lineNumbers: true, - viewportMargin: Infinity, - mode: "fluentd" - }); -} - -$(function(){ - $('.js-fluentd-config-editor').each(function(_, el){ - codemirrorify(el); - }); -}); - -Vue.directive('config-editor', { - bind: function(){ - var $parent = this.vm; - // NOTE: needed delay for waiting CodeMirror setup - _.delay(function(textarea){ - var cm = codemirrorify(textarea); - // textarea.codemirror = cm; // for test, but doesn't work for now (working on Chrome, but Poltergeist not) - cm.on('change', function(code_mirror){ - // bridge Vue - CodeMirror world - $parent.editContent = code_mirror.getValue(); - }); - }, 0, this.el); - } -}); diff --git a/app/assets/javascripts/enable_bootstrap_tooltip.js b/app/assets/javascripts/enable_bootstrap_tooltip.js deleted file mode 100644 index 2cd07ff..0000000 --- a/app/assets/javascripts/enable_bootstrap_tooltip.js +++ /dev/null @@ -1,3 +0,0 @@ -$(function(){ - $('[data-toggle="tooltip"]').tooltip(); -}); diff --git a/app/assets/javascripts/nested_setting.js b/app/assets/javascripts/nested_setting.js deleted file mode 100644 index 1d0a801..0000000 --- a/app/assets/javascripts/nested_setting.js +++ /dev/null @@ -1,41 +0,0 @@ -(function(){ - "use strict"; - - $(function(){ - var $firstSetting = $('.js-nested-column.js-multiple:first'); - - if($firstSetting.length === 0) return; - - var counter = 0; - $('.js-append', $firstSetting).on('click', function(ev){ - ev.preventDefault(); - var $new = $firstSetting.clone(true); - counter++; - - var fields = $('input,select,textarea', $new); - _.each(fields, function(elm){ - elm.name = elm.name.replace("0", counter); - }); - $('label', $new).each(function(_, label){ - var $label = $(label); - $label.attr('for', $label.attr('for').replace("0", counter)); - }); - - $('.js-remove', $new).show(); - $('.js-append', $new).hide(); - $new.appendTo($firstSetting.parent()); - }); - - $('.js-remove').on('click', function(ev){ - ev.preventDefault(); - $(this).closest('.js-nested-column').remove(); - }); - - var $allSettings = $('.js-nested-column.js-multiple'); - $('.js-append', $allSettings).hide(); - $('.js-remove', $allSettings).show(); - $('.js-append', $firstSetting).show(); - $('.js-remove', $firstSetting).hide(); - }); -})(); - diff --git a/app/assets/javascripts/vue/fluent_log.js b/app/assets/javascripts/vue/fluent_log.js deleted file mode 100644 index d3e96eb..0000000 --- a/app/assets/javascripts/vue/fluent_log.js +++ /dev/null @@ -1,64 +0,0 @@ -(function(){ - "use strict"; - - $(function(){ - if($('#fluent-log').length === 0) return; - - new Vue({ - el: "#fluent-log", - paramAttributes: ["logUrl", "initialAutoReload"], - data: { - "autoFetch": false, - "logs": [], - "limit": 30, - "processing": false - }, - - compiled: function(){ - this.fetchLogs(); - - var self = this; - var timer; - this.$watch("autoFetch", function(newValue){ - if(newValue === true) { - timer = setInterval(function(){ - self.fetchLogs(); - var $log = $(".log", self.$el); - $log.scrollTop($log.innerHeight()); - }, 1000); - } else { - clearInterval(timer); - } - }); - if(this.initialAutoReload) { - this.autoFetch = true; - } - }, - - computed: { - isPresentedLogs: function(){ - return this.logs.length > 0; - } - }, - - methods: { - fetchLogs: function() { - if(this.processing) return; - this.processing = true; - var self = this; - new Promise(function(resolve, reject) { - $.getJSON(self.logUrl + "?limit=" + self.limit, resolve).fail(reject); - }).then(function(logs){ - self.logs = logs; - setTimeout(function(){ - self.processing = false; - }, 256); // delay to reduce flicking loading icon - })["catch"](function(error){ - self.processing = false; - }); - } - } - }); - }); -})(); - diff --git a/app/assets/javascripts/vue/in_tail_format.js b/app/assets/javascripts/vue/in_tail_format.js deleted file mode 100644 index a5944e6..0000000 --- a/app/assets/javascripts/vue/in_tail_format.js +++ /dev/null @@ -1,177 +0,0 @@ -(function(){ - "use strict"; - var maxFormatCount = 20; - - $(function(){ - if($('#in_tail_format').length === 0) return; - - var FormatBundle = Vue.component('format-bundle', { - inherit: true, - template: "#format-bundle", - computed: { - options: { - get: function(){ - return this.formatOptions[this.format]; - }, - }, - selectableFormats: { - get: function() { - return Object.keys(this.formatOptions); - } - } - } - }); - - new Vue({ - el: "#in_tail_format", - paramAttributes: ["formatOptionsJson", "initialSelected", "targetFile", "paramsJson"], - data: { - previewProcessing: false, - format: "", - highlightedLines: null, - }, - - computed: { - useTextArea: function() { - return this.format === "multiline"; - } - }, - - compiled: function(){ - this.$watch('params.setting.formats', function(formats){ - _.range(1, maxFormatCount + 1).forEach(function(i) {params.setting["format" + String(i)] = "";}); - - _.compact(formats.split("\n")).forEach(function(formatLine, index) { - params.setting["format" + String(index + 1)] = formatLine; - }); - }), - this.$watch('params.setting.regexp', function(){ - this.preview(); - }); - this.$watch('format', function(){ - this.preview(); - }); - this.$set("formatOptions", JSON.parse(this.formatOptionsJson)); - this.format = this.initialSelected; - - // initialize params - // NOTE: if `params.setting.foo` is undefined, Vue can't binding with v-model="params.setting.foo" - var params = JSON.parse(this.paramsJson); - if(!params.setting) { - params.setting = {}; - } - - var formats = _.chain(_.range(1, maxFormatCount + 1)).map(function(i) {return params.setting["format" + String(i)];}).compact().value(); - params.setting.formats = formats.join("\n"); - - _.each(this.formatOptions, function(options){ - _.each(options, function(key){ - if(!params.setting.hasOwnProperty(key)){ - params.setting[key] = ""; - } - }); - }); - this.$set('params', params); - this.$emit("data-loaded"); - }, - - methods: { - onKeyup: function(ev){ - var el = ev.target; - if(el.name.match(/\[format/)){ - this.preview(); - } - }, - updateHighlightedLines: function() { - if(!this.regexpMatches) { - this.highlightedLines = null; - return; - } - - var $container = jQuery('
'); - _.each(this.regexpMatches, function(match){ - var colors = [ - "#ff9", "#cff", "#fcf", "#dfd" - ]; - var whole = match.whole; - var html = ""; - var matches = []; - - var lastPos = 0; - _.each(match.matches, function(match) { - var matched = match.matched; - if(!matched) return; - if(matched.length === 0) return; // Ignore empty matched with "foobar".match(/foo(.*?)bar/)[1] #=> "" - - // rotated highlight color - var currentColor = colors.shift(); - colors.push(currentColor); - - // create highlighted range HTML - var $highlighted = jQuery('').text(matched); - $highlighted.attr({ - "class": "regexp-preview", - "data-toggle": "tooltip", - "data-placement": "top", - "title": match.key, - 'style': 'background-color:' + currentColor - }); - var highlightedHtml = $highlighted.wrap('
').parent().html(); - - var pos = { - "start": match.pos[0], - "end": match.pos[1] - }; - if(pos.start > 0) { - html += _.escape(whole.substring(lastPos, pos.start)); - } - html += highlightedHtml; - lastPos = pos.end; - }); - html += whole.substring(lastPos); - - $container.append(html); - $container.append("
"); - }); - - this.highlightedLines = $container.html(); - setTimeout(function(){ - $('#in_tail_format').tooltip({ - selector: "[data-toggle=tooltip]", - container: "body" - }) - },0); - }, - - preview: function(){ - if(this.previewAjax) { - this.previewAjax.abort(); - } - var self = this; - new Promise(function(resolve, reject) { - self.previewAjax = $.ajax({ - method: "POST", - url: "/api/regexp_preview", - data: { - regexp: self.params.setting.regexp, - time_format: self.params.setting.time_format, - format: _.isEmpty(self.format) ? "regexp" : self.format, - params: self.params.setting, - file: self.targetFile - } - }).done(resolve).fail(reject); - }).then(function(result){ - self.params = _.merge(self.params, result.params); - self.regexpMatches = result.matches; - self.updateHighlightedLines(); - })["catch"](function(error){ - if(error.stack) { - console.error(error.stack); - } - }); - }, - } - }); - }); -})(); - diff --git a/app/assets/javascripts/vue/notification.js b/app/assets/javascripts/vue/notification.js deleted file mode 100644 index 04aa6ae..0000000 --- a/app/assets/javascripts/vue/notification.js +++ /dev/null @@ -1,65 +0,0 @@ -(function(){ - "use strict"; - var POLLING_INTERVAL = 3 * 1000; - var POLLING_URL = "/polling/alerts"; - - $(function(){ - if($('#vue-notification').length === 0) return; - - var alert = new Vue({ - el: "#vue-notification", - data: { - "alerts": [] - }, - - created: function(){ - var timer; - var self = this; - var currentInterval = POLLING_INTERVAL; - var fetch = function(){ - self.fetchAlertsData().then(function(alerts){ - if(self.alerts.toString() == alerts.toString()) { - currentInterval *= 1.1; - } else { - currentInterval = POLLING_INTERVAL; - } - self.alerts = alerts; - timer = setTimeout(fetch, currentInterval); - })["catch"](function(xhr){ - if(xhr.status === 401) { - // signed out - } - if(xhr.status === 0) { - // server unreachable (maybe down) - } - }); - }; - window.addEventListener('focus', function(ev){ - currentInterval = POLLING_INTERVAL; - timer = setTimeout(fetch, currentInterval); - }, false); - window.addEventListener('blur', function(ev){ - clearTimeout(timer); - }, false); - fetch(); - }, - - computed: { - alertsCount: { - get: function(){ return this.alerts.length; } - }, - hasAlerts: { - get: function(){ return this.alertsCount > 0; } - } - }, - - methods: { - fetchAlertsData: function() { - return new Promise(function(resolve, reject) { - $.getJSON(POLLING_URL, resolve).fail(reject); - }); - } - } - }); - }); -})(); diff --git a/app/assets/javascripts/vue/settings.js b/app/assets/javascripts/vue/settings.js deleted file mode 100644 index 64f685a..0000000 --- a/app/assets/javascripts/vue/settings.js +++ /dev/null @@ -1,121 +0,0 @@ -;(function(){ - "use strict"; - - $(function(){ - var el = document.querySelector("#vue-setting"); - if(!el) return; - - new Vue({ - el: el, - data: function(){ - return { - loaded: false, - loading: false, - sections: { - sources: [], - matches: [] - } - }; - }, - ready: function() { - this.update(); - }, - components: { - section: { - inherit: true, - template: "#vue-setting-section", - data: function(){ - return { - mode: "default", - processing: false, - editContent: null - }; - }, - created: function(){ - this.initialState(); - }, - computed: { - endpoint: function(){ - return "/api/settings/" + this.id; - } - }, - methods: { - onCancel: function(ev) { - this.initialState(); - }, - onEdit: function(ev) { - this.mode = "edit"; - }, - onDelete: function(ev) { - if(!confirm("really?")) return; - this.destroy(); - }, - onSubmit: function(ev) { - this.processing = true; - var self = this; - $.ajax({ - url: this.endpoint, - method: "POST", - data: { - _method: "PATCH", - id: this.id, - content: this.editContent - } - }).then(function(data){ - // NOTE: self.$data = data doesn't work as well, so using _.each - // whole $data swapping breaks mode switching.. - _.each(data, function(v,k){ - self[k] = v; - }); - self.initialState(); - }).always(function(){ - self.processing = false; - }); - }, - initialState: function(){ - this.$set('processing', false); - this.$set('mode', 'default'); - this.$set('editContent', this.content); - }, - destroy: function(){ - var self = this; - $.ajax({ - url: this.endpoint, - method: "POST", - data: { - _method: "DELETE", - id: this.id - } - }).then(function(){ - self.$parent.update(); - }); - } - } - } - }, - methods: { - update: function() { - this.loading = true; - var self = this; - $.getJSON("/api/settings", function(data){ - var sources = []; - var matches = []; - data.forEach(function(v){ - if(v.name === "source"){ - sources.push(v); - }else{ - matches.push(v); - } - }); - self.sections.sources = sources; - self.sections.matches = matches; - self.loaded = true; - setTimeout(function(){ - self.loading = false; - }, 500); - }); - } - } - }); - }); -})(); diff --git a/app/assets/javascripts/vue/treeview.js b/app/assets/javascripts/vue/treeview.js deleted file mode 100644 index 2786964..0000000 --- a/app/assets/javascripts/vue/treeview.js +++ /dev/null @@ -1,97 +0,0 @@ -(function(){ - "use strict"; - - $(function(){ - if($('#treeview').length === 0) return; - - new Vue({ - el: "#treeview", - paramAttributes: ["initialPath"], - data: { - preview: "", - path: "", - paths: [] - }, - - compiled: function(){ - this.path = this.initialPath; - this.fetchTree(); - this.$watch("path", this.fetchTree); - this.$watch("path", this.fetchPreview); - }, - - computed: { - selected: function(){ - var self = this; - return _.find(this.paths, function(path){ - return self.path == path.path; - }); - }, - selectedIsDir: function() { - if(!this.selected) return true; - return this.selected.is_dir; - }, - currentDirs: function() { - if(this.path === "/") { - return ["/"]; - } - - var path = this.path; - if(this.selected && !this.selected.is_dir) { - path = path.replace(/\/[^/]+$/, ""); - } - var root = "/"; - var dirs = []; - path.split("/").forEach(function(dir) { - dirs.push(root + dir); - if(dir) { - root = root + dir + "/"; - } - }); - return dirs; - } - }, - - methods: { - isAncestor: function(target) { - return this.path.indexOf(target) === 0; - }, - basename: function(path) { - if (path === "/") return "/"; - return path.match(/[^/]+$/)[0]; - }, - fetchTree: function() { - var self = this; - return new Promise(function(resolve, reject) { - $.getJSON("/api/tree?path=" + self.path, resolve).fail(reject); - }).then(function(paths){ - self.paths = paths; - }); - }, - fetchPreview: function(){ - if(!this.selected) return ; - var self = this; - this.preview = ""; - new Promise(function(resolve, reject) { - $.getJSON("/api/file_preview?file=" + self.selected.path, resolve).fail(reject); - }).catch(function(e){ - console.error(e); - }).then(function(lines){ - self.preview = lines.join("\n"); - }); - }, - - selectPath: function(path){ - this.path = path; - }, - isSuffixRequired: function(data){ - return data.is_dir && data.path != "/"; - }, - isSelected: function(path){ - return this.path == path; - } - } - }); - }); -})(); - diff --git a/app/assets/javascripts/vue_common.js b/app/assets/javascripts/vue_common.js deleted file mode 100644 index ffecc97..0000000 --- a/app/assets/javascripts/vue_common.js +++ /dev/null @@ -1,4 +0,0 @@ -Vue.filter('to_json', function (value) { - return JSON.stringify(value); -}) - diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 71cc6c1..ab49e87 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -10,11 +10,12 @@ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * - *= require sb-admin-v2/bootstrap.css - *= require sb-admin-v2/sb-admin - *= require "sb-admin-v2/font-awesome/scss/font-awesome.scss" - *= require bower/codemirror/lib/codemirror - *= require bower/codemirror/theme/neo + *= require font-awesome + *= require bootstrap/dist/css/bootstrap + *= require startbootstrap-sb-admin/css/sb-admin.css + *= require datatables.net-bs4/css/dataTables.bootstrap4.css + *= require codemirror/lib/codemirror + *= require codemirror/theme/neo *= require_tree . *= require_self */ diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e2d2e43..d81d332 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -37,12 +37,12 @@ module ApplicationHelper raw html end - def link_to_other(text, path) + def link_to_other(text, path, **options) if current_page?(path) # NOTE: sb-admin set style for element name instead of class name, such as ".nav a". So use "a" element even if it isn't a link. content_tag(:a, text, class: "current") else - link_to text, path + link_to text, path, class: "nav-link" end end @@ -62,4 +62,10 @@ module ApplicationHelper head.html_safe + block.try(:call).to_s end end + + def add_javascript_pack_tag(name, **options) + content_for(:additional_javascript_pack_tag) do + javascript_pack_tag(name, **options) + end + end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 99516dd..dfee00b 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -54,8 +54,8 @@ module SettingsHelper end def append_and_remove_links - %Q!#{icon('fa-plus')} ! + - %Q! ! + %Q!#{icon('fa-plus')} ! + + %Q! ! end def child_data(form, key) diff --git a/app/javascript/app.vue b/app/javascript/app.vue new file mode 100644 index 0000000..e304dc1 --- /dev/null +++ b/app/javascript/app.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js new file mode 100644 index 0000000..7202a88 --- /dev/null +++ b/app/javascript/packs/application.js @@ -0,0 +1,31 @@ +/* eslint no-console:0 */ +// This file is automatically compiled by Webpack, along with any other files +// present in this directory. You're encouraged to place your actual application logic in +// a relevant structure within app/javascript and only use these pack files to reference +// that code so it'll be compiled. +// +// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate +// layout file, like app/views/layouts/application.html.erb + +console.log('Hello World from Webpacker') + +import jQuery from 'jquery/dist/jquery' + +window.$ = jQuery +window.jQuery = jQuery + +import 'popper.js/dist/popper' +import 'bootstrap/dist/js/bootstrap' +import 'datatables.net/js/jquery.dataTables' +import 'startbootstrap-sb-admin/js/sb-admin' +import 'startbootstrap-sb-admin/js/sb-admin-datatables' + +import Vue from 'vue/dist/vue.esm' + +Vue.filter('to_json', function (value) { + return JSON.stringify(value); +}) + +window.Vue = Vue + +import '../stylesheets/application.scss' diff --git a/app/assets/javascripts/codemirror_fluentd.js b/app/javascript/packs/codemirror.js similarity index 65% rename from app/assets/javascripts/codemirror_fluentd.js rename to app/javascript/packs/codemirror.js index ae2b2a8..1830c3c 100644 --- a/app/assets/javascripts/codemirror_fluentd.js +++ b/app/javascript/packs/codemirror.js @@ -1,7 +1,9 @@ +import CodeMirror from 'codemirror/lib/codemirror' +import 'lodash/lodash' + // See: http://codemirror.net/doc/manual.html#modeapi // and sample mode files: https://github.com/codemirror/CodeMirror/tree/master/mode - CodeMirror.defineMode('fluentd', function(){ return { startState: function(aa){ @@ -57,3 +59,32 @@ CodeMirror.defineMode('fluentd', function(){ } }; }); + +function codemirrorify(el) { + return CodeMirror.fromTextArea(el, { + theme: "neo", + lineNumbers: true, + viewportMargin: Infinity, + mode: "fluentd" + }); +} + +$(function(){ + $('.js-fluentd-config-editor').each(function(_, el){ + codemirrorify(el); + }); +}); + +Vue.directive('config-editor', { + bind: function(el, binding, vnode, oldVnode){ + // NOTE: needed delay for waiting CodeMirror setup + _.delay(function(textarea){ + let cm = codemirrorify(textarea); + // textarea.codemirror = cm; // for test, but doesn't work for now (working on Chrome, but Poltergeist not) + cm.on('change', function(code_mirror){ + // bridge Vue - CodeMirror world + el.dataset.content = code_mirror.getValue(); + }); + }, 0, el); + } +}); diff --git a/app/javascript/packs/fluent_log.js b/app/javascript/packs/fluent_log.js new file mode 100644 index 0000000..4ac8aa6 --- /dev/null +++ b/app/javascript/packs/fluent_log.js @@ -0,0 +1,63 @@ +'use strict'; +$(document).ready(()=> { + new Vue({ + el: "#fluent-log", + props: ["logUrl", "initialAutoReload"], + data: { + "autoFetch": false, + "logs": [], + "limit": 30, + "processing": false + }, + + beforeMount: function() { + this.logUrl = this.$el.attributes.logUrl.nodeValue; + this.initialAutoReload = this.$el.attributes.initialAutoReload.nodeValue; + }, + + mounted: function(){ + this.fetchLogs(); + + var self = this; + var timer; + this.$watch("autoFetch", function(newValue){ + if(newValue === true) { + timer = setInterval(function(){ + self.fetchLogs(); + var $log = $(".log", self.$el); + $log.scrollTop($log.innerHeight()); + }, 1000); + } else { + clearInterval(timer); + } + }); + if(this.initialAutoReload) { + this.autoFetch = true; + } + }, + + computed: { + isPresentedLogs: function(){ + return this.logs.length > 0; + } + }, + + methods: { + fetchLogs: function() { + if(this.processing) return; + this.processing = true; + var self = this; + new Promise(function(resolve, reject) { + $.getJSON(self.logUrl + "?limit=" + self.limit, resolve).fail(reject); + }).then(function(logs){ + self.logs = logs; + setTimeout(function(){ + self.processing = false; + }, 256); // delay to reduce flicking loading icon + })["catch"](function(error){ + self.processing = false; + }); + } + } + }); +}); diff --git a/app/javascript/packs/hello_vue.js b/app/javascript/packs/hello_vue.js new file mode 100644 index 0000000..be8707a --- /dev/null +++ b/app/javascript/packs/hello_vue.js @@ -0,0 +1,71 @@ +/* eslint no-console: 0 */ +// Run this example by adding <%= javascript_pack_tag 'hello_vue' %> (and +// <%= stylesheet_pack_tag 'hello_vue' %> if you have styles in your component) +// to the head of your layout file, +// like app/views/layouts/application.html.erb. +// All it does is render
Hello Vue
at the bottom of the page. + +import Vue from 'vue' +import App from '../app.vue' + +document.addEventListener('DOMContentLoaded', () => { + const el = document.body.appendChild(document.createElement('hello')) + const app = new Vue({ + el, + render: h => h(App) + }) + + console.log(app) +}) + + +// The above code uses Vue without the compiler, which means you cannot +// use Vue to target elements in your existing html templates. You would +// need to always use single file components. +// To be able to target elements in your existing html/erb templates, +// comment out the above code and uncomment the below +// Add <%= javascript_pack_tag 'hello_vue' %> to your layout +// Then add this markup to your html template: +// +//
+// {{message}} +// +//
+ + +// import Vue from 'vue/dist/vue.esm' +// import App from '../app.vue' +// +// document.addEventListener('DOMContentLoaded', () => { +// const app = new Vue({ +// el: '#hello', +// data: { +// message: "Can you say hello?" +// }, +// components: { App } +// }) +// }) +// +// +// +// If the using turbolinks, install 'vue-turbolinks': +// +// yarn add 'vue-turbolinks' +// +// Then uncomment the code block below: +// +// import TurbolinksAdapter from 'vue-turbolinks'; +// import Vue from 'vue/dist/vue.esm' +// import App from '../app.vue' +// +// Vue.use(TurbolinksAdapter) +// +// document.addEventListener('turbolinks:load', () => { +// const app = new Vue({ +// el: '#hello', +// data: { +// message: "Can you say hello?" +// }, +// components: { App } +// }) +// }) diff --git a/app/javascript/packs/in_tail_format.js b/app/javascript/packs/in_tail_format.js new file mode 100644 index 0000000..2ba758d --- /dev/null +++ b/app/javascript/packs/in_tail_format.js @@ -0,0 +1,174 @@ +'use strict' +import 'lodash/lodash' + +window.addEventListener('load', () => { + const maxFormatCount = 20; + + new Vue({ + el: "#in_tail_format", + props: ["formatOptionsJson", "initialSelected", "targetFile", "paramsJson"], + data: { + previewProcessing: false, + format: "", + highlightedLines: null, + }, + + computed: { + options: { + get: function(){ + return this.formatOptions[this.format]; + }, + }, + selectableFormats: { + get: function() { + return Object.keys(this.formatOptions); + } + }, + useTextArea: function() { + return this.format === "multiline"; + } + }, + + beforeMount: function() { + this.formatOptions = JSON.parse(this.$el.attributes.formatOptionsJson.nodeValue); + this.format = this.$el.attributes.initialSelected.nodeValue; + + // initialize params + // NOTE: if `params.setting.foo` is undefined, Vue can't binding with v-model="params.setting.foo" + var params = JSON.parse(this.$el.attributes.paramsJson.nodeValue); + if(!params.setting) { + params.setting = {}; + } + + var formats = _.chain(_.range(1, maxFormatCount + 1)).map(function(i) {return params.setting["format" + String(i)];}).compact().value(); + params.setting.formats = formats.join("\n"); + + _.each(this.formatOptions, function(options){ + _.each(options, function(key){ + if(!params.setting.hasOwnProperty(key)){ + params.setting[key] = ""; + } + }); + }); + this.params = params; + + this.targetFile = this.$el.attributes.targetFile.nodeValue; + }, + mounted: function(){ + this.$emit("data-loaded"); + }, + + watch: { + 'params.setting.formats': function(formats) { + _.range(1, maxFormatCount + 1).forEach(()=> {params.setting["format" + String(i)] = "";}); + + _.compact(formats.split("\n")).forEach((formatLine, index)=> { + params.setting["format" + String(index + 1)] = formatLine; + }) + }, + 'params.setting.regexp': function() { + this.preview(); + }, + 'format': function() { + this.preview(); + }, + }, + + methods: { + onKeyup: function(ev){ + var el = ev.target; + if(el.name.match(/\[format/)){ + this.preview(); + } + }, + updateHighlightedLines: function() { + if(!this.regexpMatches) { + this.highlightedLines = null; + return; + } + + var $container = jQuery('
'); + _.each(this.regexpMatches, function(match){ + var colors = [ + "#ff9", "#cff", "#fcf", "#dfd" + ]; + var whole = match.whole; + var html = ""; + var matches = []; + + var lastPos = 0; + _.each(match.matches, function(match) { + var matched = match.matched; + if(!matched) return; + if(matched.length === 0) return; // Ignore empty matched with "foobar".match(/foo(.*?)bar/)[1] #=> "" + + // rotated highlight color + var currentColor = colors.shift(); + colors.push(currentColor); + + // create highlighted range HTML + var $highlighted = jQuery('').text(matched); + $highlighted.attr({ + "class": "regexp-preview", + "data-toggle": "tooltip", + "data-placement": "top", + "title": match.key, + 'style': 'background-color:' + currentColor + }); + var highlightedHtml = $highlighted.wrap('
').parent().html(); + + var pos = { + "start": match.pos[0], + "end": match.pos[1] + }; + if(pos.start > 0) { + html += _.escape(whole.substring(lastPos, pos.start)); + } + html += highlightedHtml; + lastPos = pos.end; + }); + html += whole.substring(lastPos); + + $container.append(html); + $container.append("
"); + }); + + this.highlightedLines = $container.html(); + }, + + preview: function(){ + if(this.previewAjax) { + this.previewAjax.abort(); + } + + const token = document.getElementsByName("csrf-token")[0].getAttribute('content') + + var self = this; + new Promise(function(resolve, reject) { + self.previewAjax = $.ajax({ + method: "POST", + url: "/api/regexp_preview", + headers: { + 'X-CSRF-Token': token + }, + data: { + regexp: self.params.setting.regexp, + time_format: self.params.setting.time_format, + format: _.isEmpty(self.format) ? "regexp" : self.format, + params: self.params.setting, + file: self.targetFile + } + }).done(resolve).fail(reject); + }).then(function(result){ + self.params = _.merge(self.params, result.params); + self.regexpMatches = result.matches; + self.updateHighlightedLines(); + })["catch"](function(error){ + if(error.stack) { + console.error(error.stack); + } + }); + }, + } + }); +}); diff --git a/app/javascript/packs/nested_settings.js b/app/javascript/packs/nested_settings.js new file mode 100644 index 0000000..650a535 --- /dev/null +++ b/app/javascript/packs/nested_settings.js @@ -0,0 +1,40 @@ +'use strict'; +import 'lodash/lodash'; +$(document).ready(()=> { + var $firstSetting = $('.js-nested-column.js-multiple:first'); + + if ($firstSetting.length === 0) { + return; + } + + var counter = 0; + $('.js-append', $firstSetting).on('click', function(ev){ + ev.preventDefault(); + var $new = $firstSetting.clone(true); + counter++; + + var fields = $('input,select,textarea', $new); + _.each(fields, function(elm){ + elm.name = elm.name.replace("0", counter); + }); + $('label', $new).each(function(_, label){ + var $label = $(label); + $label.attr('for', $label.attr('for').replace("0", counter)); + }); + + $('.js-remove', $new).show(); + $('.js-append', $new).hide(); + $new.appendTo($firstSetting.parent()); + }); + + $('.js-remove').on('click', function(ev){ + ev.preventDefault(); + $(this).closest('.js-nested-column').remove(); + }); + + var $allSettings = $('.js-nested-column.js-multiple'); + $('.js-append', $allSettings).hide(); + $('.js-remove', $allSettings).show(); + $('.js-append', $firstSetting).show(); + $('.js-remove', $firstSetting).hide(); +}); diff --git a/app/javascript/packs/notification.js b/app/javascript/packs/notification.js new file mode 100644 index 0000000..08229a6 --- /dev/null +++ b/app/javascript/packs/notification.js @@ -0,0 +1,60 @@ +const POLLING_INTERVAL = 3 * 1000 +const POLLING_URL = "/polling/alerts" + +$(document).ready(()=> { + let alert = new Vue({ + el: "#vue-notification", + data: { + "alerts": [] + }, + + created: function(){ + let timer; + let self = this; + let currentInterval = POLLING_INTERVAL; + let fetch = function(){ + self.fetchAlertsData().then(function(alerts){ + if(self.alerts.toString() == alerts.toString()) { + currentInterval *= 1.1; + } else { + currentInterval = POLLING_INTERVAL; + } + self.alerts = alerts; + timer = setTimeout(fetch, currentInterval); + })["catch"](function(xhr){ + if(xhr.status === 401) { + // signed out + } + if(xhr.status === 0) { + // server unreachable (maybe down) + } + }); + }; + window.addEventListener('focus', function(ev){ + currentInterval = POLLING_INTERVAL; + timer = setTimeout(fetch, currentInterval); + }, false); + window.addEventListener('blur', function(ev){ + clearTimeout(timer); + }, false); + fetch(); + }, + + computed: { + alertsCount: { + get: function(){ return this.alerts.length; } + }, + hasAlerts: { + get: function(){ return this.alertsCount > 0; } + } + }, + + methods: { + fetchAlertsData: function() { + return new Promise(function(resolve, reject) { + $.getJSON(POLLING_URL, resolve).fail(reject); + }); + } + } + }); +}); diff --git a/app/javascript/packs/settings.js b/app/javascript/packs/settings.js new file mode 100644 index 0000000..e2b9400 --- /dev/null +++ b/app/javascript/packs/settings.js @@ -0,0 +1,122 @@ +$(document).ready(() => { + const SettingSection = { + template: '#vue-setting-section', + props: ['id', 'content', 'type', 'name', 'arg'], + data: function() { + return { + mode: 'default', + processing: false + }; + }, + created: function() { + this.initialState(); + }, + computed: { + endpoint: function() { + return '/api/settings/' + this.id; + } + }, + methods: { + onCancel: function(event) { + this.initialState(); + }, + onEdit: function(ev) { + this.mode = "edit"; + }, + onDelete: function(ev) { + if (!confirm("really?")) { + return; + } + this.destroy(); + }, + onSubmit: function(ev) { + const token = document.getElementsByName("csrf-token")[0].getAttribute('content'); + this.processing = true; + this.content = $(`#${this.id} textarea.form-control`)[0].dataset.content; + $.ajax({ + url: this.endpoint, + method: "POST", + data: { + _method: "PATCH", + id: this.id, + content: this.content + }, + headers: { + 'X-CSRF-Token': token + } + }).then((data)=> { + _.each(data, function(v,k){ + this[k] = v; + }); + this.initialState(); + }).always(()=> { + this.processing = false; + }); + }, + initialState: function(){ + this.processing = false; + this.mode = 'default'; + }, + destroy: function(){ + const token = document.getElementsByName("csrf-token")[0].getAttribute('content'); + $.ajax({ + url: this.endpoint, + method: "POST", + data: { + _method: "DELETE", + id: this.id + }, + headers: { + 'X-CSRF-Token': token + } + }).then(()=> { + this.$parent.update(); + }); + } + } + }; + + new Vue({ + el: "#vue-setting", + data: function(){ + return { + loaded: false, + loading: false, + sections: { + sources: [], + matches: [] + } + }; + }, + mounted: function() { + this.$nextTick(() => { + this.update(); + }) + }, + components: { + 'setting-section': SettingSection + }, + methods: { + update: function() { + this.loading = true; + $.getJSON("/api/settings", (data)=> { + var sources = []; + var matches = []; + data.forEach((v)=> { + if(v.name === "source"){ + sources.push(v); + }else{ + matches.push(v); + } + }); + this.sections.sources = sources; + this.sections.matches = matches; + this.loaded = true; + setTimeout(()=> { + this.loading = false; + }, 500); + }); + } + } + }); +}) diff --git a/app/javascript/packs/treeview.js b/app/javascript/packs/treeview.js new file mode 100644 index 0000000..bae4aed --- /dev/null +++ b/app/javascript/packs/treeview.js @@ -0,0 +1,99 @@ +'use strict'; +import 'lodash/lodash'; +$(document).ready(() => { + new Vue({ + el: "#treeview", + props: { + initialPath: { + default: "/var/log", + type: String + } + }, + data: { + preview: "", + path: "", + paths: [] + }, + + mounted: function(){ + console.log(this.initialPath); + this.path = this.initialPath; + this.fetchTree(); + this.$watch("path", this.fetchTree); + this.$watch("path", this.fetchPreview); + }, + + computed: { + selected: function(){ + var self = this; + return _.find(this.paths, function(path){ + return self.path == path.path; + }); + }, + selectedIsDir: function() { + if(!this.selected) return true; + return this.selected.is_dir; + }, + currentDirs: function() { + if(this.path === "/") { + return ["/"]; + } + + var path = this.path; + if(this.selected && !this.selected.is_dir) { + path = path.replace(/\/[^/]+$/, ""); + } + var root = "/"; + var dirs = []; + path.split("/").forEach(function(dir) { + dirs.push(root + dir); + if(dir) { + root = root + dir + "/"; + } + }); + return dirs; + } + }, + + methods: { + isAncestor: function(target) { + return this.path.indexOf(target) === 0; + }, + basename: function(path) { + if (path === "/") return "/"; + return path.match(/[^/]+$/)[0]; + }, + fetchTree: function() { + var self = this; + return new Promise(function(resolve, reject) { + $.getJSON("/api/tree?path=" + self.path, resolve).fail(reject); + }).then(function(paths){ + console.log(paths); + self.paths = paths; + }); + }, + fetchPreview: function(){ + if(!this.selected) return ; + var self = this; + this.preview = ""; + new Promise(function(resolve, reject) { + $.getJSON("/api/file_preview?file=" + self.selected.path, resolve).fail(reject); + }).catch(function(e){ + console.error(e); + }).then(function(lines){ + self.preview = lines.join("\n"); + }); + }, + + selectPath: function(path){ + this.path = path; + }, + isSuffixRequired: function(data){ + return data.is_dir && data.path != "/"; + }, + isSelected: function(path){ + return this.path == path; + } + } + }); +}); diff --git a/vendor/assets/javascripts/.keep b/app/javascript/stylesheets/application.scss similarity index 100% rename from vendor/assets/javascripts/.keep rename to app/javascript/stylesheets/application.scss diff --git a/app/views/fluentd/_form.html.haml b/app/views/fluentd/_form.html.haml index 51854f9..d431735 100644 --- a/app/views/fluentd/_form.html.haml +++ b/app/views/fluentd/_form.html.haml @@ -1,4 +1,4 @@ -.col-xs-6 +.col-xl-6 - @fluentd.errors.full_messages.each do |e| .alert.alert-danger= e diff --git a/app/views/fluentd/errors.html.haml b/app/views/fluentd/errors.html.haml index 425d30a..40055b2 100644 --- a/app/views/fluentd/errors.html.haml +++ b/app/views/fluentd/errors.html.haml @@ -1,19 +1,18 @@ - page_title t('fluentd.common.recent_errors', days: @error_duration_days) do - - link_to raw_log_daemon_path(@fluentd), class: "btn btn-primary pull-right" do + - link_to raw_log_daemon_path(@fluentd), class: "btn btn-primary float-right mt-3" do = icon('fa-download') = t('fluentd.common.raw_log_link') .row - .col-xs-12 + .col-xl-12.col-sm-12 - @errors.each do |error| - .panel.panel-default - .panel-heading + .card.card-primary + .card-heading %h4= error[:subject] - if error[:notes].present? - .panel-body + .card-body %ul - error[:notes].each do |stack| %li= stack - if @errors.empty? %p= t('.error_is_empty') - diff --git a/app/views/fluentd/log.html.haml b/app/views/fluentd/log.html.haml index ba655d4..cbee04c 100644 --- a/app/views/fluentd/log.html.haml +++ b/app/views/fluentd/log.html.haml @@ -1,9 +1,8 @@ - page_title t('.page_title', label: @fluentd.label) do - - link_to raw_log_daemon_path(@fluentd), class: "btn btn-primary pull-right" do + - link_to raw_log_daemon_path(@fluentd), class: "btn btn-primary float-right mt-3" do = icon('fa-download') = t('fluentd.common.raw_log_link') -.row - .col-xs-12 - = preserve do # partial containing
, so shouldn't break indent
-      = render partial: "shared/vue/fluent_log", locals: { fluentd: @fluentd }
+.row.ml-3
+  = preserve do # partial containing 
, so shouldn't break indent
+    = render partial: "shared/vue/fluent_log", locals: { fluentd: @fluentd }
diff --git a/app/views/fluentd/settings/edit.html.haml b/app/views/fluentd/settings/edit.html.haml
index 2bde745..e24ff7a 100644
--- a/app/views/fluentd/settings/edit.html.haml
+++ b/app/views/fluentd/settings/edit.html.haml
@@ -1,8 +1,9 @@
 - page_title t('.page_title', label: @fluentd.label)
+- add_javascript_pack_tag("codemirror")
 
 = form_tag(daemon_setting_path(@fluentd), method: :patch) do
   .form-group
     = text_area_tag "config", @config, class: "form-control js-fluentd-config-editor", rows: 40
   %p.text.text-danger= t('terms.notice_restart_for_config_edit', brand: fluentd_ui_brand)
   = submit_tag t("terms.update"), class: "btn btn-primary"
-  = submit_tag t("terms.configtest"), class: "btn btn-default", name: "dryrun"
+  = submit_tag t("terms.configtest"), class: "btn btn-secondary", name: "dryrun"
diff --git a/app/views/fluentd/settings/histories/_list.html.haml b/app/views/fluentd/settings/histories/_list.html.haml
index 0b23e6e..77e87a4 100644
--- a/app/views/fluentd/settings/histories/_list.html.haml
+++ b/app/views/fluentd/settings/histories/_list.html.haml
@@ -14,4 +14,4 @@
             .form-group.input-group
               = f.text_field :content, value: file.note.content, class: "note-content form-control", id: nil
               %span.input-group-btn
-                = submit_tag t('terms.save'), class: 'btn btn-default'
+                = submit_tag t('terms.save'), class: 'btn btn-info'
diff --git a/app/views/fluentd/settings/histories/show.html.haml b/app/views/fluentd/settings/histories/show.html.haml
index 1d4e924..d087068 100644
--- a/app/views/fluentd/settings/histories/show.html.haml
+++ b/app/views/fluentd/settings/histories/show.html.haml
@@ -1,7 +1,7 @@
 - page_title t('.page_title', label: @fluentd.label)
 
-%p.pull-right
-  = link_to configtest_daemon_setting_history_path(id: @backup_file.file_id), method: "post", class: "btn btn-default" do
+%p.float-right
+  = link_to configtest_daemon_setting_history_path(id: @backup_file.file_id), method: "post", class: "btn btn-secondary" do
     = icon('fa-legal')
     = t("terms.configtest")
   = link_to reuse_daemon_setting_history_path(id: @backup_file.file_id), method: 'post', class: "btn btn-primary" do
@@ -10,22 +10,22 @@
 
 - if @backup_file.note.content.present?
   .row
-    .col-xs-12
+    .col-xl-12
       %p= @backup_file.note.content
 
 .row
-  .col-xs-12
+  .col-xl-12
     %h2
       ="#{t('.target_config')}(#{params[:id]})"
     %pre
       = preserve do
         = @backup_file.content
 
-  .col-xs-12.diff
+  .col-xl-12.diff
     %h2
       Diff:
     %div
       = t(".diff_description", file_name: params[:id] )
-      .pull-right
+      .float-right
         = link_to t(".show_current"), daemon_setting_path(@fluentd)
     = preserve render "/shared/settings/diff"
diff --git a/app/views/fluentd/settings/in_tail/after_file_choose.html.haml b/app/views/fluentd/settings/in_tail/after_file_choose.html.haml
index 8c494c9..74834d9 100644
--- a/app/views/fluentd/settings/in_tail/after_file_choose.html.haml
+++ b/app/views/fluentd/settings/in_tail/after_file_choose.html.haml
@@ -12,8 +12,9 @@
     = f.text_field :path, class: "form-control", disabled: true
   = render partial: "shared/vue/in_tail_format", locals: { file: f.object.path, formats: @setting.known_formats, initialSelected: f.object.format || @setting.guess_format }
 
-  %pre= file_tail(@setting.path, Settings.in_tail_preview_line_count).join("\n")
+  .card
+    %pre.card-body= file_tail(@setting.path, Settings.in_tail_preview_line_count).join("\n")
 
   %p
-    = f.submit t('terms.next'), class: "btn btn-lg btn-primary pull-right"
-    = link_to t('terms.prev'), daemon_setting_in_tail_path(@fluentd), class: "btn btn-lg btn-default"
+    = f.submit t('terms.next'), class: "btn btn-lg btn-primary float-right"
+    = link_to t('terms.prev'), daemon_setting_in_tail_path(@fluentd), class: "btn btn-lg btn-secondary"
diff --git a/app/views/fluentd/settings/in_tail/after_format.html.haml b/app/views/fluentd/settings/in_tail/after_format.html.haml
index 6d518ad..4c521ff 100644
--- a/app/views/fluentd/settings/in_tail/after_format.html.haml
+++ b/app/views/fluentd/settings/in_tail/after_format.html.haml
@@ -6,5 +6,5 @@
   = render partial: "form", locals: { f: f }
 
   %p
-    = f.submit t('terms.next'), class: "btn btn-lg btn-primary pull-right"
-    = f.submit t('terms.prev'), class: "btn btn-lg btn-default", name: "back"
+    = f.submit t('terms.next'), class: "btn btn-lg btn-primary float-right"
+    = f.submit t('terms.prev'), class: "btn btn-lg btn-secondary", name: "back"
diff --git a/app/views/fluentd/settings/in_tail/confirm.html.haml b/app/views/fluentd/settings/in_tail/confirm.html.haml
index 785958c..76748a1 100644
--- a/app/views/fluentd/settings/in_tail/confirm.html.haml
+++ b/app/views/fluentd/settings/in_tail/confirm.html.haml
@@ -8,6 +8,6 @@
   %pre= @setting.to_conf
 
   %p
-    = f.submit t('fluentd.common.finish')   , class: "btn btn-lg btn-primary pull-right"
-    = f.submit t('terms.prev'),   class: "btn btn-lg btn-default", name: "back"
-    .clearfix.pull-right= t('terms.notice_restart_for_config_edit', brand: fluentd_ui_brand)
+    = f.submit t('fluentd.common.finish')   , class: "btn btn-lg btn-primary float-right"
+    = f.submit t('terms.prev'),   class: "btn btn-lg btn-secondary", name: "back"
+    .clearfix.float-right= t('terms.notice_restart_for_config_edit', brand: fluentd_ui_brand)
diff --git a/app/views/fluentd/settings/running_backup/show.html.haml b/app/views/fluentd/settings/running_backup/show.html.haml
index 5e28212..670f767 100644
--- a/app/views/fluentd/settings/running_backup/show.html.haml
+++ b/app/views/fluentd/settings/running_backup/show.html.haml
@@ -1,8 +1,8 @@
 - page_title t('.page_title', label: @fluentd.label)
 
 - if @backup_file.content
-  %p.pull-right
-    = link_to configtest_daemon_setting_running_backup_path, method: 'post', class: "btn btn-default" do
+  %p.float-right
+    = link_to configtest_daemon_setting_running_backup_path, method: 'post', class: "btn btn-secondary" do
       = icon('fa-legal')
       = t("terms.configtest")
     = link_to reuse_daemon_setting_running_backup_path, method: 'post', class: "btn btn-primary" do
@@ -10,7 +10,7 @@
       = t("terms.reuse")
 
 .row
-  .col-xs-12
+  .col-xl-12
     - if @backup_file.content
       %h2
         = t('.page_title')
@@ -21,11 +21,11 @@
       %p
         =t('fluentd.common.never_started_yet', brand: fluentd_ui_brand)
   - if @backup_file.content
-    .col-xs-12.diff
+    .col-xl-12.diff
       %h2
         Diff:
       %div
         = t(".diff_description")
-        .pull-right
+        .float-right
           = link_to t(".show_current"), daemon_setting_path(@fluentd)
       = preserve render "/shared/settings/diff"
diff --git a/app/views/fluentd/settings/show.html.haml b/app/views/fluentd/settings/show.html.haml
index 545c84f..8f54afc 100644
--- a/app/views/fluentd/settings/show.html.haml
+++ b/app/views/fluentd/settings/show.html.haml
@@ -1,23 +1,23 @@
 - page_title t('.page_title') do
-  - link_to edit_daemon_setting_path(@fluentd), class: "btn btn-primary pull-right" do
+  - link_to edit_daemon_setting_path(@fluentd), class: "btn btn-primary float-right mt-3" do
     = icon('fa-pencil')
     = t("terms.edit")
 
-.row
-  .col-xs-12
-    %pre
+.row.mb-3
+  .card.col-xl-12.bg-light.ml-3
+    %pre.card-body
       = preserve do
         = @config
 
-.row
-  .col-xs-12
+.row.mb-3
+  .col-xl-12
     %h3= link_to(t('fluentd.settings.running_backup.title'), daemon_setting_running_backup_path)
     %p
       %label= t('terms.backup_time')
       = @running_backedup_file.ctime.try(:strftime, I18n.t('time.formats.default')) || t('fluentd.common.never_started_yet', brand: fluentd_ui_brand)
 
 .row
-  .col-xs-12
+  .col-xl-12
     %h3= t('fluentd.settings.history')
     = render '/fluentd/settings/histories/list'
     .link= link_to t('.link_to_histories'), daemon_setting_histories_path
diff --git a/app/views/fluentd/settings/source_and_output.html.haml b/app/views/fluentd/settings/source_and_output.html.haml
index 3da76ab..3096747 100644
--- a/app/views/fluentd/settings/source_and_output.html.haml
+++ b/app/views/fluentd/settings/source_and_output.html.haml
@@ -1,27 +1,28 @@
+- add_javascript_pack_tag("settings")
 - page_title t('.page_title')
 
 .row.fluentd-setting-inout
-  .col-xs-4
-    .panel.panel-default
-      .panel-heading
+  .col-xl-4
+    .card.card-primary
+      .card-header
         %h4= t('.in')
-      .panel-body
+      .card-body
         - %w|tail syslog monitor_agent http forward|.each do |type|
           %p
             = link_to(send("daemon_setting_in_#{type}_path", @fluentd)) do
               = icon('fa-file-text-o fa-lg')
               = t("fluentd.common.setup_in_#{type}")
-  .col-xs-1.arrow-right
+  .col-xl-1.arrow-right
     = icon "fa-arrow-circle-right"
-  .col-xs-2
+  .col-xl-2
     = image_tag "/fluentd-logo.png", style: "max-width: 100%"
-  .col-xs-1.arrow-right
+  .col-xl-1.arrow-right
     = icon "fa-arrow-circle-right"
-  .col-xs-4
-    .panel.panel-default
-      .panel-heading
+  .col-xl-4
+    .card.card-primary
+      .card-header
         %h4= t('.out')
-      .panel-body
+      .card-body
         - %w|stdout td s3 mongo elasticsearch forward|.each do |type|
           %p
             = link_to(send("daemon_setting_out_#{type}_path", @fluentd)) do
@@ -33,17 +34,29 @@
 #vue-setting.current-settings
   %h2
     = t('.current')
-    .pull-right
-      %button.btn.btn-default.btn-sm{"v-on" => "click: update", "v-if" => "!loading"}= icon('fa-refresh')
-      %button.btn.btn-default.btn-sm{"v-if" => "loading"}= icon('fa-spin fa-refresh')
+    .float-right
+      %button.btn.btn-outline-dark.btn-sm{"v-on:click" => "update", "v-if" => "!loading"}= icon('fa-refresh')
+      %button.btn.btn-outline-dark.btn-sm{"v-if" => "loading"}= icon('fa-spin fa-refresh')
   .row
-    .col-xs-6.input
+    .col-xl-6.input
       %h3= t('.in')
       %div{"v-if" => "loaded && sections.sources.length == 0"}
         %p.empty= t('.setting_empty')
-      %div{"v-repeat" => "sections.sources", "v-component" => "section"}
-    .col-xs-6.output
+      %setting-section{"v-if" => "loaded && sections.sources.length > 0",
+                       "v-for" => "source in sections.sources",
+                       "v-bind:id" => "source.id",
+                       "v-bind:content" => "source.content",
+                       "v-bind:type" => "source.type",
+                       "v-bind:name" => "source.name",
+                       "v-bind:arg" => "source.arg"}
+    .col-xl-6.output
       %h3= t('.out')
       %div{"v-if" => "loaded && sections.matches.length == 0"}
         %p.empty= t('.setting_empty')
-      %div{"v-repeat" => "sections.matches", "v-component" => "section"}
+      %setting-section{"v-if" => "loaded && sections.matches.length > 0",
+                       "v-for" => "match in sections.matches",
+                       "v-bind:id" => "match.id",
+                       "v-bind:content" => "match.content",
+                       "v-bind:type" => "match.type",
+                       "v-bind:name" => "match.name",
+                       "v-bind:arg" => "match.arg"}
diff --git a/app/views/fluentd/show.html.haml b/app/views/fluentd/show.html.haml
index 10b8fb7..f9cd02a 100644
--- a/app/views/fluentd/show.html.haml
+++ b/app/views/fluentd/show.html.haml
@@ -1,9 +1,9 @@
 - if @fluentd.present?
   - page_title t('.page_title')
   .row
-    .col-xs-6
-      %div{class: "panel panel-#{@fluentd.agent.running? ? "success":"default"}"}
-        .panel-heading
+    .col-xl-6.col-sm-6
+      %div{class: "card card-#{@fluentd.agent.running? ? "primary":"secondary"}"}
+        .card-header
           %h4
             - if @fluentd.agent.running?
               = icon("fa-play")
@@ -11,17 +11,17 @@
             - else
               = icon("fa-pause")
               = t("fluentd.common.stopped")
-        .panel-body
+        .card-body
           - if flash[:error]
             .alert.alert-danger= flash[:error]
-          = link_to icon("fa-play") << t("fluentd.common.start"), start_daemon_agent_path(@fluentd),     method: :put, class: "btn #{@fluentd.agent.running? ? "disabled btn-default" : "btn-primary"}"
-          = link_to icon("fa-pause") << t("fluentd.common.stop"), stop_daemon_agent_path(@fluentd),       method: :put, class: "btn #{@fluentd.agent.running? ? "btn-danger" : "disabled btn-default"}"
-          = link_to icon("fa-refresh") << t("fluentd.common.restart"), restart_daemon_agent_path(@fluentd), method: :put, class: "btn #{@fluentd.agent.running? ? "btn-warning" : "disabled btn-default"}"
-    .col-xs-6
-      .panel.panel-default
-        .panel-heading
-          .pull-right
-            = link_to t('terms.edit'), edit_daemon_path, class: "btn btn-default"
+          = link_to icon("fa-play") << t("fluentd.common.start"), start_daemon_agent_path(@fluentd),     method: :put, class: "btn #{@fluentd.agent.running? ? "disabled btn-outline-dark" : "btn-primary"}"
+          = link_to icon("fa-pause") << t("fluentd.common.stop"), stop_daemon_agent_path(@fluentd),       method: :put, class: "btn #{@fluentd.agent.running? ? "btn-danger" : "disabled btn-outline-dark"}"
+          = link_to icon("fa-refresh") << t("fluentd.common.restart"), restart_daemon_agent_path(@fluentd), method: :put, class: "btn #{@fluentd.agent.running? ? "btn-warning" : "disabled btn-outline-dark"}"
+    .col-xl-6.col-sm-6
+      .card.card-default
+        .card-header
+          .float-right
+            = link_to t('terms.edit'), edit_daemon_path, class: "btn btn-info"
             = link_to t('terms.destroy'), "#", class: "btn btn-danger", data: { toggle: "modal", target: "#setting-destroy-modal" }
           %h4= t('fluentd.common.fluentd_info')
           .modal.fade{id: "setting-destroy-modal"}
@@ -36,11 +36,11 @@
                   = raw t('fluentd.common.destroy_fluentd_setting_warning', brand: fluentd_ui_brand)
                 .modal-footer
                   = form_tag(daemon_path(@fluentd), method: :delete) do
-                    %button.btn.btn-default{"data-dismiss" => "modal"}
+                    %button.btn.btn-secondary{"data-dismiss" => "modal"}
                       Close
                     = submit_tag t('terms.destroy'), class: "btn btn-danger"
 
-        %table.table.table-hover
+        %table.table.table-hover.mb-0
           %tbody
             %tr
               %th= @fluentd.class.human_attribute_name(:pid_file)
@@ -52,7 +52,7 @@
               %th= @fluentd.class.human_attribute_name(:config_file)
               %td= @fluentd.agent.config_file
   .row
-    .col-xs-12
+    .col-xl-12
       = preserve do # partial containing 
, so shouldn't break indent
         = render partial: "shared/vue/fluent_log", locals: { fluentd: @fluentd }
 
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 3e92172..ea44b3c 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -10,71 +10,56 @@
       <% end %>
       <%= fluentd_ui_title %>
     
-    <%= stylesheet_link_tag    'application', media: 'all'%>
-    <%= javascript_include_tag 'application' %>
+    <%= stylesheet_link_tag 'application', media: 'all'%>
+    <%= stylesheet_pack_tag 'application', media: 'all'%>
+    <%= javascript_pack_tag 'application' %>
+    <%= javascript_pack_tag 'notification' %>
+    <%= content_for(:additional_javascript_pack_tag) %>
     <%= csrf_meta_tags %>
   
-  
-    
- - -
+
+
<%= render partial: "shared/flash" %> <% if content_for?(:page_head) %>
-
-

<%= content_for(:page_head) %>

+
+

<%= content_for(:page_head) %>

-
- <% end %>
-
+
<%= yield %>
-
- -
- + diff --git a/app/views/layouts/sign_in.html.erb b/app/views/layouts/sign_in.html.erb index 23e537d..082d4a0 100644 --- a/app/views/layouts/sign_in.html.erb +++ b/app/views/layouts/sign_in.html.erb @@ -3,26 +3,19 @@ - - + + - Fluentd-UI - <%= stylesheet_link_tag 'application', media: 'all'%> - <%= javascript_include_tag 'application' %> + Fluentd-UI + <%= stylesheet_link_tag 'application', media: 'all'%> + <%= javascript_pack_tag 'application' %> <%= csrf_meta_tags %> - - - -
-
-
- <%= yield %> -
-
-
- + +
+ <%= yield %> +
diff --git a/app/views/misc/information.html.haml b/app/views/misc/information.html.haml index b7ae2bb..1da249e 100644 --- a/app/views/misc/information.html.haml +++ b/app/views/misc/information.html.haml @@ -1,23 +1,23 @@ - page_title t('.page_title') do - - link_to misc_download_info_path, class: "btn btn-primary pull-right" do + - link_to misc_download_info_path, class: "btn btn-primary float-right" do = icon('fa-download') = t('.download_system_information') - if FluentdUI.update_available? && !FluentdUI.td_agent_ui? - # NOTE: td-agent-ui shouldn't have auto update feature .row - .col-xs-12 + .col-xl-12 %p = link_to misc_update_fluentd_ui_path, class: "btn btn-primary btn-lg", method: :post do = t('.update_fluentd_ui', version: FluentdUI.latest_version, title: fluentd_ui_title) = t('.update_fluentd_ui_caution', brand: fluentd_ui_brand) .row - .col-xs-6 - .panel.panel-default - .panel-heading + .col-xl-6.col-sm-6.mb-3 + .card.card-default + .card-header %h4= t("terms.version") - .panel-body + .card-body %dl{class: "dl-horizontal"} %dt ruby %dd= RUBY_DESCRIPTION @@ -27,11 +27,11 @@ %dt fluentd-ui %dd= FluentdUI::VERSION - .col-xs-6 - .panel.panel-default - .panel-heading + .col-xl-6.col-sm-6.mb-3 + .card.card-default + .card-header %h4= t('terms.installed_plugins') - .panel-body + .card-body - if @plugins.present? %table{class: "table table-hover", id: "plugins-table"} %thead @@ -47,9 +47,9 @@ = t "plugins.common.no_installed" .row - .col-xs-12 - .panel.panel-default - .panel-heading + .col-xl-12.col-sm-12 + .card.card-default + .card-header %h4{"data-toggle" => "collapse", "href" => "#env-table"} = icon('fa-caret-down') = t('.env') diff --git a/app/views/misc/update_fluentd_ui.html.haml b/app/views/misc/update_fluentd_ui.html.haml index 6f924df..12e5453 100644 --- a/app/views/misc/update_fluentd_ui.html.haml +++ b/app/views/misc/update_fluentd_ui.html.haml @@ -1,7 +1,7 @@ -.panel.panel-default - .panel-heading +.card + .card-header = t('.update_title', title: fluentd_ui_title) - .panel-body + .card-body #processing = icon('fa-lg fa-gear fa-spin') = t('.updating') diff --git a/app/views/plugins/installed.html.haml b/app/views/plugins/installed.html.haml index cc836a5..9fb1fe0 100644 --- a/app/views/plugins/installed.html.haml +++ b/app/views/plugins/installed.html.haml @@ -3,13 +3,13 @@ %table{class: "table table-striped table-hover", id: "plugins-table"} %thead %tr - %th.col-xs-1 - %th.col-xs-1= t('plugins.common.name') - %th{width: 160}= t('plugins.common.authors') - %th{width: 320}= t('plugins.common.summary') - %th.col-xs-1= t('plugins.common.installed_version') - %th.col-xs-1= t('plugins.common.latest_version') - %th.col-xs-1 + %th{scope: "col"} + %th{scope: "col"}= t('plugins.common.name') + %th{scope: "col"}= t('plugins.common.authors') + %th{scope: "col"}= t('plugins.common.summary') + %th{scope: "col"}= t('plugins.common.installed_version') + %th{scope: "col"}= t('plugins.common.latest_version') + %th{scope: "col"} %tbody - @plugins.each do |plugin| %tr @@ -28,7 +28,7 @@ = t('terms.confirm_body', action: t('terms.uninstall')) .modal-footer = form_tag(uninstall_plugins_path, method: :patch) do - %button.btn.btn-default{"data-dismiss" => "modal"} + %button.btn.btn-secondary{"data-dismiss" => "modal"} Close = hidden_field_tag("plugins[]", plugin.gem_name) = submit_tag t('terms.uninstall'), class: "btn btn-danger" diff --git a/app/views/plugins/recommended.html.haml b/app/views/plugins/recommended.html.haml index 400ff9a..3998f91 100644 --- a/app/views/plugins/recommended.html.haml +++ b/app/views/plugins/recommended.html.haml @@ -3,11 +3,11 @@ %table{class: "table table-striped table-hover", id: "plugins-table"} %thead %tr - %th{width: "24"} - %th= t('plugins.common.name') - %th= t('plugins.common.category') - %th= t('plugins.common.status') - %th + %th{scope: "col", width: "24"} + %th{scope: "col"}= t('plugins.common.name') + %th{scope: "col"}= t('plugins.common.category') + %th{scope: "col"}= t('plugins.common.status') + %th{scope: "col"} %tbody - @plugins.each do |plugin| %tr diff --git a/app/views/plugins/updated.html.haml b/app/views/plugins/updated.html.haml index 392e9c2..46d671a 100644 --- a/app/views/plugins/updated.html.haml +++ b/app/views/plugins/updated.html.haml @@ -7,11 +7,11 @@ %table{class: "table table-striped table-hover"} %thead %tr - %th.col-xs-2= t('plugins.common.name') - %th.col-xs-2= t('plugins.common.authors') - %th.col-xs-6= t('plugins.common.summary') - %th.col-xs-1= t('plugins.common.installed_version') - %th.col-xs-1= t('plugins.common.latest_version') + %th.col-xl-2= t('plugins.common.name') + %th.col-xl-2= t('plugins.common.authors') + %th.col-xl-6= t('plugins.common.summary') + %th.col-xl-1= t('plugins.common.installed_version') + %th.col-xl-1= t('plugins.common.latest_version') %tbody - @plugins.each do |plugin| %tr diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml index d7d8b68..3cf57d1 100644 --- a/app/views/sessions/new.html.haml +++ b/app/views/sessions/new.html.haml @@ -1,8 +1,7 @@ -.login-panel.panel.panel-default - .panel-heading - %h3.panel-title - = t('messages.please_sign_in') - .panel-body +.card.card-login.mx-auto.mt-5 + .card-header + = t('messages.please_sign_in') + .card-body = render partial: "shared/error" = form_for(:session, url: sessions_path) do |f| .form-group @@ -10,4 +9,4 @@ .form-group = f.password_field :password, placeholder: t('terms.password'), class: "form-control" - = submit_tag t("terms.sign_in"), class: "btn btn-success" + = submit_tag t("terms.sign_in"), class: "btn btn-primary btn-block" diff --git a/app/views/shared/_global_nav.html.erb b/app/views/shared/_global_nav.html.erb index 625bdea..314a08d 100644 --- a/app/views/shared/_global_nav.html.erb +++ b/app/views/shared/_global_nav.html.erb @@ -1,9 +1,9 @@ -
diff --git a/app/views/shared/settings/_form.html.haml b/app/views/shared/settings/_form.html.haml index 5b97f25..859ad86 100644 --- a/app/views/shared/settings/_form.html.haml +++ b/app/views/shared/settings/_form.html.haml @@ -6,12 +6,13 @@ = field(f, key) - if @setting.advanced_options.present? - .well.well-sm - %h4{"data-toggle" => "collapse", "href" => "#advanced-setting"} - = icon('fa-caret-down') - = t('terms.advanced_setting') - #advanced-setting.collapse - - @setting.advanced_options.each do |key| - = field(f, key) - = f.submit t('fluentd.common.finish'), class: "btn btn-lg btn-primary pull-right" + .card.bg-light + .card-body + %h4{"data-toggle" => "collapse", "href" => "#advanced-setting"} + = icon('fa-caret-down') + = t('terms.advanced_setting') + #advanced-setting.collapse + - @setting.advanced_options.each do |key| + = field(f, key) + = f.submit t('fluentd.common.finish'), class: "btn btn-lg btn-primary float-right" diff --git a/app/views/shared/settings/show.html.haml b/app/views/shared/settings/show.html.haml index d591c9a..48462bc 100644 --- a/app/views/shared/settings/show.html.haml +++ b/app/views/shared/settings/show.html.haml @@ -1,6 +1,8 @@ - page_title t("fluentd.settings.#{target_plugin_name}.show.page_title") +- add_javascript_pack_tag("nested_settings") -.well - = raw t("fluentd.settings.#{target_plugin_name}.option_guide") +.card.mb-3 + .card-body.bg-light + = raw t("fluentd.settings.#{target_plugin_name}.option_guide") = render "shared/settings/form" diff --git a/app/views/shared/vue/_fluent_log.html.erb b/app/views/shared/vue/_fluent_log.html.erb index 963274b..dec7763 100644 --- a/app/views/shared/vue/_fluent_log.html.erb +++ b/app/views/shared/vue/_fluent_log.html.erb @@ -1,20 +1,21 @@ +<%- add_javascript_pack_tag("fluent_log") %> <% auto_reload ||= true %>
">
- - + <%= t('terms.lines') %> -
-

-
{{ $value }} +

+
{{ log }}
@@ -22,4 +23,3 @@
- diff --git a/app/views/shared/vue/_in_tail_format.html.erb b/app/views/shared/vue/_in_tail_format.html.erb index eb642c6..bf8b6fe 100644 --- a/app/views/shared/vue/_in_tail_format.html.erb +++ b/app/views/shared/vue/_in_tail_format.html.erb @@ -1,28 +1,4 @@ - +<%- add_javascript_pack_tag("in_tail_format") %>
-
+
+ + +
+
+ + +
-
{{{ highlightedLines }}}
+
+
+ + +
-
- <%= raw t('fluentd.settings.in_tail_option_guide') %> +
+

<%= t("fluentd.settings.in_tail.notice_for_multiline_limit") %>

+ + +
+
+ +
+

+  
+ +
+

+ <%= raw t('fluentd.settings.in_tail_option_guide') %> +

diff --git a/app/views/shared/vue/_notification.html.erb b/app/views/shared/vue/_notification.html.erb index dfe5081..d3a475d 100644 --- a/app/views/shared/vue/_notification.html.erb +++ b/app/views/shared/vue/_notification.html.erb @@ -1,14 +1,10 @@ -