spree_dashboard 5.6.1 → 6.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +28 -0
- data/app/controllers/spree/dashboard/app_controller.rb +8 -69
- data/app/controllers/spree/dashboard/spa_controller.rb +86 -0
- data/app/controllers/spree/seller_panel/app_controller.rb +18 -0
- data/config/routes.rb +6 -0
- data/lib/spree/dashboard.rb +11 -0
- metadata +14 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28547ad04b0d9b667f01c7b88bb38bcb764a0c59130a6c7e5c37400306ff173c
|
|
4
|
+
data.tar.gz: ffc9d7d8ac62ce7f67348e3aee068842733b3f9c072d6ec3f834f0627960bb66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4f98563d15ff2b1849f5b263e5b4606da7993d77498dd490f48a2ba3a14c22ca5d70ff6187ac0ae7698f2ae1189e197ed0896202009f6dbae3c15d37889567ce
|
|
7
|
+
data.tar.gz: 8a66bfb48de8cec16458e267b558327c143923c359f7fd6efa16ce305fb7ef8c790c93211ce554559afd3a61672498715639054c959485b532b4058660d66412
|
data/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present, Vendo Sp. z o.o., Vendo Connect Inc.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -1,77 +1,16 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Dashboard
|
|
3
|
-
# Serves
|
|
4
|
-
#
|
|
5
|
-
# CORS entries and Lax cookies. The dist directory comes from
|
|
6
|
-
# `Spree::Dashboard.dist_path` (the official Docker image sets it via
|
|
7
|
-
# SPREE_DASHBOARD_DIST_PATH); when unset, /dashboard 404s.
|
|
3
|
+
# Serves the built React Dashboard at /dashboard. Everything about how a
|
|
4
|
+
# bundle is served lives in SpaController; this names which bundle.
|
|
8
5
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
# the SPA authenticates its API calls itself.
|
|
14
|
-
class AppController < ActionController::Base
|
|
15
|
-
# GET-only file serving mutates nothing, but enable forgery protection
|
|
16
|
-
# anyway so the controller stays safe if an action is ever added.
|
|
17
|
-
protect_from_forgery with: :exception
|
|
18
|
-
# `verify_same_origin_request` blocks JavaScript responses to plain GET
|
|
19
|
-
# requests — protection against JSONP-style data leaks from dynamically
|
|
20
|
-
# generated JS. The Vite bundle is static public code that the SPA's own
|
|
21
|
-
# <script> tags must load, which is exactly that request shape.
|
|
22
|
-
skip_after_action :verify_same_origin_request
|
|
23
|
-
|
|
24
|
-
def show
|
|
25
|
-
root = dist_root
|
|
26
|
-
return head :not_found unless root
|
|
27
|
-
|
|
28
|
-
if (file = resolve_file(root, params[:path].to_s))
|
|
29
|
-
response.headers['Cache-Control'] = cache_control_for(params[:path].to_s)
|
|
30
|
-
send_file file, disposition: 'inline'
|
|
31
|
-
elsif (index = root.join('index.html')).file?
|
|
32
|
-
response.headers['Cache-Control'] = 'no-cache'
|
|
33
|
-
send_file index, type: 'text/html', disposition: 'inline'
|
|
34
|
-
else
|
|
35
|
-
head :not_found
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
6
|
+
# The dist directory comes from `Spree::Dashboard.dist_path` (the official
|
|
7
|
+
# Docker image sets it via SPREE_DASHBOARD_DIST_PATH); when unset,
|
|
8
|
+
# /dashboard 404s.
|
|
9
|
+
class AppController < SpaController
|
|
39
10
|
private
|
|
40
11
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
return if path.blank?
|
|
44
|
-
|
|
45
|
-
root = Pathname.new(path).expand_path
|
|
46
|
-
root if root.directory?
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Resolve a request path to a real file inside the dist directory.
|
|
50
|
-
# Defense in depth against traversal: reject null bytes, absolute
|
|
51
|
-
# paths, and any `.`/`..` segment before touching the filesystem, then
|
|
52
|
-
# verify the expanded path still lives under the dist root (which also
|
|
53
|
-
# covers symlinks pointing outside it).
|
|
54
|
-
def resolve_file(root, relative_path)
|
|
55
|
-
return if relative_path.blank? || relative_path.include?("\0")
|
|
56
|
-
|
|
57
|
-
relative = Pathname.new(relative_path)
|
|
58
|
-
return if relative.absolute?
|
|
59
|
-
|
|
60
|
-
segments = relative.each_filename.to_a
|
|
61
|
-
return if segments.empty? || segments.any? { |segment| segment == '.' || segment == '..' }
|
|
62
|
-
|
|
63
|
-
candidate = root.join(*segments).expand_path
|
|
64
|
-
return unless candidate.to_s.start_with?("#{root}#{File::SEPARATOR}")
|
|
65
|
-
|
|
66
|
-
candidate if candidate.file?
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def cache_control_for(relative_path)
|
|
70
|
-
if relative_path.start_with?('assets/')
|
|
71
|
-
'public, max-age=31536000, immutable'
|
|
72
|
-
else
|
|
73
|
-
'public, max-age=3600'
|
|
74
|
-
end
|
|
12
|
+
def dist_path
|
|
13
|
+
Spree::Dashboard.dist_path
|
|
75
14
|
end
|
|
76
15
|
end
|
|
77
16
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Dashboard
|
|
3
|
+
# Serves a built Vite SPA from a directory on disk — the single-node
|
|
4
|
+
# topology, where the SPA and the API it calls share one origin, so no
|
|
5
|
+
# CORS entries and Lax cookies.
|
|
6
|
+
#
|
|
7
|
+
# Abstract: a subclass names the directory by implementing `dist_path`.
|
|
8
|
+
# Two panels ride on this — the operator's dashboard at /dashboard and the
|
|
9
|
+
# marketplace seller panel at /sellers — and they differ in nothing but
|
|
10
|
+
# that directory and their mount point.
|
|
11
|
+
#
|
|
12
|
+
# SPA semantics: real files are served with long-lived caching (Vite's
|
|
13
|
+
# `assets/` are content-hashed, so they're immutable); every other path
|
|
14
|
+
# falls back to index.html with no-cache so deploys take effect on the
|
|
15
|
+
# next navigation. No authentication — the bundle is public client code;
|
|
16
|
+
# the SPA authenticates its API calls itself.
|
|
17
|
+
class SpaController < ActionController::Base
|
|
18
|
+
# GET-only file serving mutates nothing, but enable forgery protection
|
|
19
|
+
# anyway so the controller stays safe if an action is ever added.
|
|
20
|
+
protect_from_forgery with: :exception
|
|
21
|
+
# `verify_same_origin_request` blocks JavaScript responses to plain GET
|
|
22
|
+
# requests — protection against JSONP-style data leaks from dynamically
|
|
23
|
+
# generated JS. The Vite bundle is static public code that the SPA's own
|
|
24
|
+
# <script> tags must load, which is exactly that request shape.
|
|
25
|
+
skip_after_action :verify_same_origin_request
|
|
26
|
+
|
|
27
|
+
def show
|
|
28
|
+
root = dist_root
|
|
29
|
+
return head :not_found unless root
|
|
30
|
+
|
|
31
|
+
if (file = resolve_file(root, params[:path].to_s))
|
|
32
|
+
response.headers['Cache-Control'] = cache_control_for(params[:path].to_s)
|
|
33
|
+
send_file file, disposition: 'inline'
|
|
34
|
+
elsif (index = root.join('index.html')).file?
|
|
35
|
+
response.headers['Cache-Control'] = 'no-cache'
|
|
36
|
+
send_file index, type: 'text/html', disposition: 'inline'
|
|
37
|
+
else
|
|
38
|
+
head :not_found
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# @return [String, nil] directory holding the built bundle
|
|
45
|
+
def dist_path
|
|
46
|
+
raise NotImplementedError, "#{self.class} must implement #dist_path"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def dist_root
|
|
50
|
+
path = dist_path
|
|
51
|
+
return if path.blank?
|
|
52
|
+
|
|
53
|
+
root = Pathname.new(path).expand_path
|
|
54
|
+
root if root.directory?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Resolve a request path to a real file inside the dist directory.
|
|
58
|
+
# Defense in depth against traversal: reject null bytes, absolute
|
|
59
|
+
# paths, and any `.`/`..` segment before touching the filesystem, then
|
|
60
|
+
# verify the expanded path still lives under the dist root (which also
|
|
61
|
+
# covers symlinks pointing outside it).
|
|
62
|
+
def resolve_file(root, relative_path)
|
|
63
|
+
return if relative_path.blank? || relative_path.include?("\0")
|
|
64
|
+
|
|
65
|
+
relative = Pathname.new(relative_path)
|
|
66
|
+
return if relative.absolute?
|
|
67
|
+
|
|
68
|
+
segments = relative.each_filename.to_a
|
|
69
|
+
return if segments.empty? || segments.any? { |segment| segment == '.' || segment == '..' }
|
|
70
|
+
|
|
71
|
+
candidate = root.join(*segments).expand_path
|
|
72
|
+
return unless candidate.to_s.start_with?("#{root}#{File::SEPARATOR}")
|
|
73
|
+
|
|
74
|
+
candidate if candidate.file?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def cache_control_for(relative_path)
|
|
78
|
+
if relative_path.start_with?('assets/')
|
|
79
|
+
'public, max-age=31536000, immutable'
|
|
80
|
+
else
|
|
81
|
+
'public, max-age=3600'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module SellerPanel
|
|
3
|
+
# Serves the built marketplace seller panel at /sellers.
|
|
4
|
+
#
|
|
5
|
+
# Same origin as the dashboard and the API on purpose: the two panels'
|
|
6
|
+
# refresh cookies are already isolated by name and path, so sharing a host
|
|
7
|
+
# saves every deployment a second hostname and certificate. A marketplace
|
|
8
|
+
# that wants sellers on their own domain deploys the bundle there and
|
|
9
|
+
# points SPREE_SELLER_PANEL_URL at it.
|
|
10
|
+
class AppController < Spree::Dashboard::SpaController
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def dist_path
|
|
14
|
+
Spree::Dashboard.seller_panel_dist_path
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/config/routes.rb
CHANGED
|
@@ -5,4 +5,10 @@ Spree::Core::Engine.add_routes do
|
|
|
5
5
|
# being parsed as a response format.
|
|
6
6
|
get '/dashboard', to: 'dashboard/app#show', as: :dashboard_app
|
|
7
7
|
get '/dashboard/*path', to: 'dashboard/app#show', format: false
|
|
8
|
+
|
|
9
|
+
# Hosted seller panel, same topology as the dashboard above. Its own mount
|
|
10
|
+
# rather than a nested route: it is a separate app with its own bundle, and
|
|
11
|
+
# a marketplace that runs no seller panel leaves it unconfigured.
|
|
12
|
+
get '/sellers', to: 'seller_panel/app#show', as: :seller_panel_app
|
|
13
|
+
get '/sellers/*path', to: 'seller_panel/app#show', format: false
|
|
8
14
|
end
|
data/lib/spree/dashboard.rb
CHANGED
|
@@ -12,6 +12,17 @@ module Spree
|
|
|
12
12
|
def dist_path
|
|
13
13
|
@dist_path.presence || ENV.fetch('SPREE_DASHBOARD_DIST_PATH', nil)
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
# Directory holding a built marketplace seller panel, served at
|
|
17
|
+
# /sellers. A separate bundle rather than the dashboard's: it is a
|
|
18
|
+
# different app — its own entry point, router and API client — so
|
|
19
|
+
# pointing both mounts at one directory would serve sellers the
|
|
20
|
+
# operator's back office. Unset, /sellers responds 404.
|
|
21
|
+
attr_writer :seller_panel_dist_path
|
|
22
|
+
|
|
23
|
+
def seller_panel_dist_path
|
|
24
|
+
@seller_panel_dist_path.presence || ENV.fetch('SPREE_SELLER_PANEL_DIST_PATH', nil)
|
|
25
|
+
end
|
|
15
26
|
end
|
|
16
27
|
end
|
|
17
28
|
end
|
metadata
CHANGED
|
@@ -1,40 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_dashboard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc.
|
|
8
|
+
- Vendo Sp. z o.o.
|
|
8
9
|
autorequire:
|
|
9
10
|
bindir: bin
|
|
10
11
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
12
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
12
13
|
dependencies:
|
|
13
14
|
- !ruby/object:Gem::Dependency
|
|
14
15
|
name: spree
|
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
|
16
17
|
requirements:
|
|
17
|
-
- -
|
|
18
|
+
- - '='
|
|
18
19
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
20
|
+
version: 6.0.0.beta1
|
|
20
21
|
type: :runtime
|
|
21
22
|
prerelease: false
|
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
24
|
requirements:
|
|
24
|
-
- -
|
|
25
|
+
- - '='
|
|
25
26
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
27
|
-
description: Serves a built Spree React Dashboard
|
|
28
|
-
|
|
29
|
-
Developer Preview; becomes the default admin delivery in Spree 6.
|
|
27
|
+
version: 6.0.0.beta1
|
|
28
|
+
description: Serves a built Spree React Dashboard with your Rails server, simplest
|
|
29
|
+
way to deploy Spree API + Dashboard
|
|
30
30
|
email: hello@spreecommerce.org
|
|
31
31
|
executables: []
|
|
32
32
|
extensions: []
|
|
33
33
|
extra_rdoc_files: []
|
|
34
34
|
files:
|
|
35
|
+
- LICENSE
|
|
35
36
|
- README.md
|
|
36
37
|
- Rakefile
|
|
37
38
|
- app/controllers/spree/dashboard/app_controller.rb
|
|
39
|
+
- app/controllers/spree/dashboard/spa_controller.rb
|
|
40
|
+
- app/controllers/spree/seller_panel/app_controller.rb
|
|
38
41
|
- config/routes.rb
|
|
39
42
|
- lib/spree/dashboard.rb
|
|
40
43
|
- lib/spree/dashboard/engine.rb
|
|
@@ -44,9 +47,9 @@ licenses:
|
|
|
44
47
|
- BSD-3-Clause
|
|
45
48
|
metadata:
|
|
46
49
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
47
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/
|
|
50
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.0.beta1
|
|
48
51
|
documentation_uri: https://spreecommerce.org/docs/developer/dashboard/deployment
|
|
49
|
-
source_code_uri: https://github.com/spree/spree/tree/
|
|
52
|
+
source_code_uri: https://github.com/spree/spree/tree/v6.0.0.beta1
|
|
50
53
|
post_install_message:
|
|
51
54
|
rdoc_options: []
|
|
52
55
|
require_paths:
|