editus 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3cec41e8e70a12bb3dd860f31729437bad4870ff201eb3a4892be778fdcf8558
4
- data.tar.gz: 76b985c2519cb34198be4ce940128f1dd6265347e3ea48ec1cb1ceb5335b6028
3
+ metadata.gz: 60b7c7adcfdb0b6d489e79e0bb946c2a19df3ec8642d6ad701a11400db7e581f
4
+ data.tar.gz: 10ca7886874f225e7b3ff73d51a3befeee3bc10f09a5f5d6324c535a841a5372
5
5
  SHA512:
6
- metadata.gz: e705129944f1362d82e5dc9d749547ffd4ec0d59a4d315a0b90149f843f073f4dde0522ff3ec997b7d45c29b23780df1e4a56e94ac7a239b16e087c776e1a5c4
7
- data.tar.gz: 4b7d829b9c4df393a913f9b05eaebfa149c6953189ad413204e47e358409a8d1e35dfbbb0398d566686e31461729b64185e0d12a5566fe023d78733ea2fa14a8
6
+ metadata.gz: 38dbd733e0865a021849d89bcdd8c896082956903bee8ac717d38c09fd9cf62b8f38c857a9fa3a53b3ee39d08d94eee2633e7d216617d7caa66807cc4ddaf417
7
+ data.tar.gz: 69e48942bbeb9289aff7e256c105dd794fa7ff5aea852d1ddb759122be18e1af41710e778bc4c174447632b6eb959426d1705779bd597dc1468f02dd5447dfe0
data/README.md CHANGED
@@ -1,11 +1,30 @@
1
1
  # Editus
2
- Simplify code execution and database editing. Intuitive web interface. Run code snippets, modify databases in real-time.
3
2
 
4
- ## Installation
3
+ [![Gem Version](https://badge.fury.io/rb/editus.svg)](https://badge.fury.io/rb/editus)
4
+
5
+ Streamline your coding and database editing processes with Editus. Its intuitive web interface allows you to execute code snippets and perform real-time database modifications with ease.
6
+
7
+ # Table of contents
8
+
9
+ - [Editus](#editus)
10
+ - [Table of contents](#table-of-contents)
11
+ - [Installation](#installation)
12
+ - [Usage](#usage)
13
+ * [Authentication](#authentication)
14
+ * [Models](#models)
15
+ * [Add Script](#add-script)
16
+ + [Query](#query)
17
+ - [Contributing](#contributing)
18
+ - [License](#license)
19
+
20
+ <small><i><a href='http://ecotrust-canada.github.io/markdown-toc/'>Table of contents generated with markdown-toc</a></i></small>
21
+
22
+
23
+ # Installation
5
24
  Add this line to your application's Gemfile:
6
25
 
7
26
  ```ruby
8
- gem "editus", git: "https://github.com/muoihai-com/editus.git"
27
+ gem "editus"
9
28
  ```
10
29
 
11
30
  And then execute:
@@ -23,14 +42,14 @@ Add the following to your `config/routes.rb`:
23
42
 
24
43
  ```ruby
25
44
  Rails.application.routes.draw do
26
- mount Editus::Engine => "/updater" unless Rails.env.production?
45
+ mount Editus::Engine => "/editus" unless Rails.env.production?
27
46
  ...
28
47
  end
29
48
  ```
30
49
 
31
- ## Usage
50
+ # Usage
32
51
 
33
- ### Authentication
52
+ ## Authentication
34
53
 
35
54
  Editus supports two forms of authentication:
36
55
 
@@ -52,11 +71,19 @@ config.auth = [%w[user@example.com Pass@123456], %w[manager@example.com Pass@123
52
71
 
53
72
  Use one of the above authentication methods to secure access to Editus. Note that the `editus_account` method will take precedence if both methods are provided.
54
73
 
55
- ### Models
74
+ ## Models
56
75
 
57
76
  Display a simple form interface that helps you update the fields of the selected model. The update will use `update_columns` so will ignore callback and validate
58
77
 
59
- ### Add Script
78
+ You can configure to display only the models and fields that you allow:
79
+
80
+ ```
81
+ config.models = ["User", {name: "Admin", fields: %w[name], exclude_fields: %w[id]}]
82
+ ```
83
+
84
+ For the `Admin` model specified using the fields key. Additionally, the exclude_fields key is used to exclude the `id` field from being displayed.
85
+
86
+ ## Add Script
60
87
 
61
88
  To execute existing code create a directory `config/editus` in your code
62
89
 
@@ -66,12 +93,12 @@ Example:
66
93
  ```rb
67
94
  Editus::Script.define :update_nick_name_user do
68
95
  title "Update nick_name of user"
69
- task :up do
70
- user = User.find(1)
71
- nick_name = user.nick_name
72
- user.update_columns nick_name: "xatara"
96
+ task :up do |id, new_nick_name|
97
+ user = User.find(id)
98
+ old_nick_name = user.nick_name
99
+ user.update_columns nick_name: new_nick_name
73
100
 
74
- [1, nick_name]
101
+ [id, old_nick_name]
75
102
  end
76
103
 
77
104
  task :down do |id, nick_name|
@@ -85,10 +112,23 @@ Make sure the filename and the defined name are the same. In the above code `tit
85
112
  `task :up` is the code that will be executed when you run it.
86
113
  `task :down` is the code that will be executed when you undo, if you don't need to undo you can skip it.
87
114
 
88
- It can use the result returned from the `up` function to use as an input parameter
115
+ It can use the result returned from the `up` method to use as an input parameter. The `up` method can also accept parameters directly.
116
+
117
+ ### Query
118
+
119
+ In addition to running the two tasks `up` and `down`, you can perform other small code snippets by using `query`.
120
+
121
+ ```rb
122
+ Editus::Script.define :users do
123
+ desc "Number of users"
124
+ query :count_user do
125
+ User.count
126
+ end
127
+ end
128
+ ```
89
129
 
90
- ## Contributing
130
+ # Contributing
91
131
  Contribution directions go here.
92
132
 
93
- ## License
133
+ # License
94
134
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -53,12 +53,26 @@ module Editus
53
53
  action.user = request_account
54
54
  action.model_id = script.name
55
55
  action.model_name = script.title
56
- action.changes = script.proxy.up if script.proxy.respond_to?(:up)
56
+ action.changes = script.proxy.up(*parameters) if script.proxy.respond_to?(:up)
57
57
  action.save
58
58
 
59
59
  redirect_to root_path
60
60
  end
61
61
 
62
+ def query
63
+ script = Editus::Script.all[params[:id].to_sym]
64
+ method = params[:method].to_sym
65
+ return render json: {status: false, error: :script_not_found}, status: 200 if script.blank?
66
+
67
+ unless script.proxy.respond_to?(method)
68
+ return render json: {status: false, error: :undefined_method},
69
+ status: 200
70
+ end
71
+
72
+ result = script.proxy.try(method, *parameters)
73
+ render json: {status: true, data: result}, status: 200
74
+ end
75
+
62
76
  def undo
63
77
  action = Editus::Actions.all.find{|act| act.id == params[:id]}
64
78
  return redirect_to(root_path) if action.blank?
@@ -117,5 +131,9 @@ module Editus
117
131
  params[:user]&.each{|key, _| params[:user].delete(key) if params[:user][key].blank?}
118
132
  (params[:user] || {}).as_json
119
133
  end
134
+
135
+ def parameters
136
+ params[:parameters]&.values || []
137
+ end
120
138
  end
121
139
  end
@@ -5,14 +5,31 @@ module Editus
5
5
  end
6
6
 
7
7
  def field_tag record, col, *args
8
- if record.try(col).is_a?(Time)
8
+ case record.type_of_col(col)
9
+ when :datetime
9
10
  text_field_tag(*args, placeholder: "YYYY-MM-DD HH:mm:ss UTC")
10
- elsif [true, false].include? record.try(col)
11
+ when :boolean
11
12
  safe_join [hidden_field_tag(args.first, "false", id: nil),
12
13
  check_box_tag(args.first, "true", record.try(col))]
13
14
  else
14
15
  text_field_tag(*args)
15
16
  end
16
17
  end
18
+
19
+ def parameters proxy, method
20
+ return [] unless proxy.respond_to?(method)
21
+
22
+ proxy.method(method).parameters.filter_map do |(type, value)|
23
+ value if type.in?(%i[req opt])
24
+ end
25
+ end
26
+
27
+ def field_enums? record, col
28
+ record.defined_enums.key?(col.to_s) && record.defined_enums[col].present?
29
+ end
30
+
31
+ def enums record, col
32
+ record.defined_enums[col].keys.join(", ")
33
+ end
17
34
  end
18
35
  end
@@ -2,7 +2,7 @@
2
2
  <h1 class="ui header">Models</h1>
3
3
  </div>
4
4
 
5
- <%= form_tag validate_path, class: 'ui form warning' do %>
5
+ <%= form_tag validate_path, class: 'ui form warning manual' do %>
6
6
  <div class="ui mt-4 text container">
7
7
  <p>
8
8
  <%= select_tag "klass", options_for_select(@model_names.sort, params[:klass]), prompt: "Select a model", id: 'select-klass' %>
@@ -40,6 +40,10 @@
40
40
  <%= label_tag "user[#{col}]", class: "flex items-center" do %>
41
41
  <%= field_tag @record, col, "user[#{col}]", nil %><span class="ml-2">True</span>
42
42
  <% end %>
43
+ <% elsif field_enums?(@record, col) %>
44
+ <div class="ui" data-tooltip="<%= enums(@record, col) %>" data-inverted="" data-variation="mini">
45
+ <%= field_tag @record, col, "user[#{col}]", nil %>
46
+ </div>
43
47
  <% else %>
44
48
  <%= field_tag @record, col, "user[#{col}]", nil %>
45
49
  <% end %>
@@ -18,33 +18,157 @@
18
18
  <% @scripts.each do |script| %>
19
19
  <section id="<%= script.name %>">
20
20
  <h1 class="ui header"><%= script.title %></h1>
21
- <div>
22
- <button class="ui labeled mini icon red button confirmation" data-method="run">
21
+ <% if parameters(script.proxy, :up).present? %>
22
+ <button class="ui labeled mini icon red button parameters" data-name="<%= script.name %>">
23
23
  <i class="plus icon"></i>
24
24
  Run
25
25
  </button>
26
+ <% end %>
26
27
 
27
- <%= form_tag run_path(id: script.name), method: :post, id: "run" do %>
28
- <% end %>
28
+ <div class="ui parameters-<%= script.name %> small modal">
29
+ <div class="header">
30
+ <%= parameters(script.proxy, :up).present? ? "Parameters" : "Confirmation" %>
31
+ </div>
32
+ <div class="scrolling content">
33
+ <%= form_tag run_path(id: script.name), method: :post, id: "run-#{script.name}", class: 'ui form' do %>
34
+ <% parameters(script.proxy, :up).each do |key| %>
35
+ <div class="inline fields">
36
+ <div class="two wide field">
37
+ </div>
38
+ <div class="six wide field">
39
+ <label><%= key.to_s.capitalize %></label>
40
+ </div>
41
+ <div class="six wide field">
42
+ <%= text_field_tag "parameters[#{key}]", nil %>
43
+ </div>
44
+ </div>
45
+ <% end %>
46
+ <% end %>
47
+
48
+ <p class="<%= parameters(script.proxy, :up).present? ? "mt-8" : "" %>">
49
+ These changes may cause data errors, do you still want to continue?
50
+ </p>
51
+ </div>
52
+ <div class="actions">
53
+ <div class="ui cancel button">Cancel</div>
54
+ <div class="ui red ok button">OK</div>
55
+ </div>
29
56
  </div>
30
57
  <div class="ui accordion">
31
58
  <div class="title">
32
59
  <i class="dropdown icon"></i>
33
- Up
60
+ Content
34
61
  </div>
35
62
  <div class="content">
36
63
  <div class="transition hidden">
37
- <div class="editor"><%= script.up %></div>
64
+ <div class="editor"><%= script.content %></div>
38
65
  </div>
39
66
  </div>
40
- <div class="title">
41
- <i class="dropdown icon"></i>
42
- Down
43
- </div>
44
- <div class="content">
45
- <div class="editor"><%= script.down %></div>
46
- </div>
67
+
68
+ <% if script.queries.present? %>
69
+ <div class="title">
70
+ <i class="dropdown icon"></i>
71
+ Queries
72
+ </div>
73
+ <div class="content">
74
+ <div class="transition hidden">
75
+ <div class="ui middle aligned selection list">
76
+ <% script.queries.each_with_index do |query, index| %>
77
+ <% query_key = "#{script.name}-#{index}" %>
78
+
79
+ <div class="item flex items-center justify-space-between query" data-index="<%= query_key %>">
80
+ <div class="content">
81
+ <%= query[:description] %>
82
+ </div>
83
+ <div class="content">
84
+ <div class="ui mini icon primary button">
85
+ <i class="terminal icon"></i>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <div class="ui query_<%= query_key %> small modal">
91
+ <div class="header">
92
+ Query
93
+ </div>
94
+ <div class="scrolling content">
95
+ <%= form_tag query_path(id: script.name, method: query[:query_name]), method: :post, id: "query_form_#{query_key}", class: 'ui form' do %>
96
+ <% parameters(script.proxy, query[:query_name]).each do |key| %>
97
+ <div class="inline fields">
98
+ <div class="two wide field">
99
+ </div>
100
+ <div class="six wide field">
101
+ <label><%= key.to_s.capitalize %></label>
102
+ </div>
103
+ <div class="six wide field">
104
+ <%= text_field_tag "parameters[#{key}]", nil %>
105
+ </div>
106
+ </div>
107
+ <% end %>
108
+ <% end %>
109
+
110
+ <p class="<%= parameters(script.proxy, query[:query_name]).present? ? "mt-8" : "" %>">
111
+ These changes may cause data errors, do you still want to continue?
112
+ </p>
113
+
114
+ <button class="ui button primary" id="run_query_<%= query_key %>">
115
+ Query
116
+ </button>
117
+
118
+ <pre class="bg-neutral-300 rounded p-4" id="pre_query_<%= query_key %>" style="display: none;">
119
+ </pre>
120
+ </div>
121
+ <div class="actions">
122
+ <div class="ui cancel button">Close</div>
123
+ </div>
124
+ </div>
125
+ <% end %>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ <% end %>
130
+ </div>
47
131
  </section>
48
132
  <div class="ui divider"></div>
49
133
  <% end %>
50
134
  </div>
135
+
136
+ <script>
137
+ $("button.parameters").on("click", function (e) {
138
+ const name = $(e.currentTarget).data('name')
139
+
140
+ $(`.ui.parameters-${name}.modal`)
141
+ .modal({
142
+ onApprove: function () {
143
+ $(`#run-${name}`).submit()
144
+ }
145
+ })
146
+ .modal("show");
147
+ });
148
+
149
+ $(".query").on("click", function (e) {
150
+ const index = $(e.currentTarget).data('index')
151
+
152
+ $(`.ui.query_${index}.modal`).modal("show");
153
+ $(`#run_query_${index}`).on("click", function() {
154
+ const formId = `#query_form_${index}`
155
+ const preId = `#pre_query_${index}`
156
+ const serializeArray = $(formId).serializeArray()
157
+ const action = $(formId).attr("action")
158
+
159
+ $.post(action, $(formId).serialize()).done(function(res) {
160
+ if (res.status) {
161
+ $(preId).text(JSON.stringify(res.data))
162
+ } else {
163
+ $(preId).text(`Query Error: ${res.error}`)
164
+ }
165
+
166
+ $(preId).show()
167
+ });
168
+ })
169
+ });
170
+
171
+ $('form input').on('keypress', function(e) {
172
+ return e.which !== 13;
173
+ });
174
+ </script>
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1" />
5
5
 
6
- <title>Model updater</title>
6
+ <title>Editus</title>
7
7
  <%= csrf_meta_tags %>
8
8
  <%= csp_meta_tag %>
9
9
 
@@ -48,11 +48,14 @@
48
48
  margin-top: 7em;
49
49
  }
50
50
  .flex{
51
- display: flex;
51
+ display: flex !important;
52
52
  }
53
- items-center{
53
+ .items-center{
54
54
  align-items: center;
55
55
  }
56
+ .justify-space-between {
57
+ justify-content: space-between;
58
+ }
56
59
  .flex-column {
57
60
  flex-direction: column;
58
61
  }
@@ -86,6 +89,9 @@
86
89
  .mt-4 {
87
90
  margin-top: 1rem;
88
91
  }
92
+ .mt-8 {
93
+ margin-top: 2rem;
94
+ }
89
95
  .mx-4 {
90
96
  margin-bottom: 1rem;
91
97
  margin-top: 1rem;
@@ -95,6 +101,7 @@
95
101
  }
96
102
  pre {
97
103
  white-space: pre-line;
104
+ word-break: break-word;
98
105
  }
99
106
  .ui.cards>.orange.card, .ui.orange.card, .ui.orange.cards>.card {
100
107
  box-shadow: 0 0 0 1px #d4d4d5, 0 0px 3px 1px #f2711c, 0 1px 3px 0 #d4d4d5;
@@ -108,12 +115,18 @@
108
115
  .ui.celled.table tr td, .ui.celled.table tr th {
109
116
  word-break: break-all;
110
117
  }
118
+ .ui.list>.item.flex:after {
119
+ display: none;
120
+ }
121
+ [data-tooltip][data-variation='mini']:after {
122
+ font-size: 12px;
123
+ }
111
124
  </style>
112
125
  </head>
113
126
  <body>
114
127
  <div class="ui fixed inverted menu">
115
128
  <div class="ui container">
116
- <%= link_to 'Model Updater', root_path, class: "header item" %>
129
+ <%= link_to 'Editus', root_path, class: "header item" %>
117
130
  <%= link_to 'Home', root_path, class: "item" %>
118
131
  <div class="ui simple dropdown item">
119
132
  Menu <i class="dropdown icon"></i>
@@ -146,7 +159,6 @@
146
159
  $(".ui.confirmation.modal")
147
160
  .modal({
148
161
  onApprove: function () {
149
- console.log($(e.currentTarget).data('method'))
150
162
  switch ($(e.currentTarget).data('method')) {
151
163
  case 'run':
152
164
  $("#" + $(e.currentTarget).data('method')).submit()
data/config/routes.rb CHANGED
@@ -5,7 +5,7 @@ Editus::Engine.routes.draw do
5
5
  get "scripts", to: "home#scripts"
6
6
  post "/update", to: "home#update", as: :update
7
7
  post "validate", to: "home#validate", as: :validate
8
-
9
8
  post "scripts/:id/run", to: "home#run_script", as: :run
10
9
  post "undo/:id", to: "home#undo", as: :undo
10
+ post "query/:id/:method", to: "home#query", as: :query
11
11
  end
data/lib/editus/cop.rb CHANGED
@@ -33,9 +33,9 @@ module Editus
33
33
  return nil if model.blank?
34
34
  return nil unless model.is_a?(String) || model.is_a?(Hash)
35
35
 
36
- name = model.is_a?(String) ? model : model["name"]
37
- fields = model.is_a?(Hash) ? model["fields"] : []
38
- exclude_fields = model.is_a?(Hash) ? model["exclude_fields"] : []
36
+ name = model.is_a?(String) ? model : model[:name]
37
+ fields = model.is_a?(Hash) ? model[:fields] : []
38
+ exclude_fields = model.is_a?(Hash) ? model[:exclude_fields] : []
39
39
 
40
40
  {name: name, fields: fields, exclude_fields: exclude_fields}
41
41
  end
data/lib/editus/proxy.rb CHANGED
@@ -29,7 +29,7 @@ module Editus
29
29
  info = Editus::Cop.instance.info(klass)
30
30
  all = cols
31
31
  exclude_fields = info[:exclude_fields] || []
32
- fields = info[:fields] ? (all & info[:fields]) : all
32
+ fields = info[:fields].present? ? (all & info[:fields]) : all
33
33
 
34
34
  fields - %w[id] - exclude_fields
35
35
  end
@@ -55,6 +55,14 @@ module Editus
55
55
  klass.update_columns attributes
56
56
  end
57
57
 
58
+ def type_of_col column
59
+ klass.class.columns_hash[column.to_s]&.type
60
+ end
61
+
62
+ def defined_enums
63
+ klass.class.defined_enums
64
+ end
65
+
58
66
  private
59
67
 
60
68
  def cols
data/lib/editus/script.rb CHANGED
@@ -21,10 +21,16 @@ module Editus
21
21
  @scripts[name.to_sym] || @scripts[name.to_sym] = new(name.to_sym)
22
22
  end
23
23
 
24
- attr_accessor :name, :title, :path, :proxy, :content
24
+ attr_accessor :name, :path, :proxy, :content, :queries
25
+ attr_writer :title
25
26
 
26
27
  def initialize name = nil
27
28
  @name = name
29
+ @queries = []
30
+ end
31
+
32
+ def add_query desc, name
33
+ @queries.push({description: desc, query_name: name})
28
34
  end
29
35
 
30
36
  def up
@@ -34,6 +40,10 @@ module Editus
34
40
  def down
35
41
  content[/task\s.*down.*do.*\n[\s\S]*?\n\s*end/]
36
42
  end
43
+
44
+ def title
45
+ @title || @name.to_s.humanize
46
+ end
37
47
  end
38
48
 
39
49
  class DSL
@@ -45,6 +55,7 @@ module Editus
45
55
 
46
56
  def initialize name
47
57
  @name = name
58
+ @query_index = 0
48
59
  end
49
60
 
50
61
  def task method, &block
@@ -59,6 +70,31 @@ module Editus
59
70
  internal = Internal.find_or_create @name
60
71
  internal.title = txt
61
72
  end
73
+
74
+ def desc description
75
+ @description = if description.blank? || !description.is_a?(String)
76
+ @query_index += 1
77
+ "Query##{@query_index}"
78
+ else
79
+ description
80
+ end
81
+ end
82
+
83
+ def query method, &block
84
+ internal = Internal.find_or_create @name
85
+ internal.proxy ||= Editus::DefinitionProxy.new(@name)
86
+ query_name = "query_#{method}"
87
+ internal.proxy.define_singleton_method(query_name, &block)
88
+ add_query_to_internal internal, query_name
89
+ end
90
+
91
+ private
92
+
93
+ def add_query_to_internal internal, query_name
94
+ description = desc(@description)
95
+ internal.add_query description, query_name
96
+ @description = nil
97
+ end
62
98
  end
63
99
 
64
100
  class Reader
@@ -1,5 +1,5 @@
1
1
  module Editus
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  SUMMARY = "This application is a simple web interface integrated with an existing Ruby on Rails application. It allows users to directly edit models, run predefined scripts, and easily undo changes."
4
4
  DESCRIPTION = <<~DOC
5
5
  This application is a user-friendly web interface designed to work with a Ruby on Rails application. With a simple and intuitive web interface, users can conveniently access the features of the application.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: editus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hungkieu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-02 00:00:00.000000000 Z
11
+ date: 2023-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -89,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
89
  requirements:
90
90
  - - ">="
91
91
  - !ruby/object:Gem::Version
92
- version: '0'
92
+ version: 2.6.0
93
93
  required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - ">="