xolo-server 1.0.0
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 +7 -0
- data/LICENSE.txt +177 -0
- data/README.md +7 -0
- data/bin/xoloserver +106 -0
- data/data/com.pixar.xoloserver.plist +29 -0
- data/data/uninstall-pkgs-by-id.zsh +103 -0
- data/lib/xolo/server/app.rb +133 -0
- data/lib/xolo/server/command_line.rb +216 -0
- data/lib/xolo/server/configuration.rb +739 -0
- data/lib/xolo/server/constants.rb +70 -0
- data/lib/xolo/server/helpers/auth.rb +257 -0
- data/lib/xolo/server/helpers/client_data.rb +415 -0
- data/lib/xolo/server/helpers/file_transfers.rb +265 -0
- data/lib/xolo/server/helpers/jamf_pro.rb +156 -0
- data/lib/xolo/server/helpers/log.rb +97 -0
- data/lib/xolo/server/helpers/maintenance.rb +401 -0
- data/lib/xolo/server/helpers/notification.rb +145 -0
- data/lib/xolo/server/helpers/pkg_signing.rb +141 -0
- data/lib/xolo/server/helpers/progress_streaming.rb +252 -0
- data/lib/xolo/server/helpers/title_editor.rb +92 -0
- data/lib/xolo/server/helpers/titles.rb +145 -0
- data/lib/xolo/server/helpers/versions.rb +160 -0
- data/lib/xolo/server/log.rb +286 -0
- data/lib/xolo/server/mixins/changelog.rb +315 -0
- data/lib/xolo/server/mixins/title_jamf_access.rb +1668 -0
- data/lib/xolo/server/mixins/title_ted_access.rb +519 -0
- data/lib/xolo/server/mixins/version_jamf_access.rb +1541 -0
- data/lib/xolo/server/mixins/version_ted_access.rb +373 -0
- data/lib/xolo/server/object_locks.rb +102 -0
- data/lib/xolo/server/routes/auth.rb +89 -0
- data/lib/xolo/server/routes/jamf_pro.rb +89 -0
- data/lib/xolo/server/routes/maint.rb +174 -0
- data/lib/xolo/server/routes/title_editor.rb +71 -0
- data/lib/xolo/server/routes/titles.rb +285 -0
- data/lib/xolo/server/routes/uploads.rb +93 -0
- data/lib/xolo/server/routes/versions.rb +261 -0
- data/lib/xolo/server/routes.rb +168 -0
- data/lib/xolo/server/title.rb +1143 -0
- data/lib/xolo/server/version.rb +902 -0
- data/lib/xolo/server.rb +205 -0
- data/lib/xolo-server.rb +8 -0
- metadata +243 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Copyright 2025 Pixar
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms set forth in the LICENSE.txt file available at
|
|
4
|
+
# at the root of this project.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# frozen_string_literal: true
|
|
8
|
+
|
|
9
|
+
# main module
|
|
10
|
+
module Xolo
|
|
11
|
+
|
|
12
|
+
# Server Module
|
|
13
|
+
module Server
|
|
14
|
+
|
|
15
|
+
module Routes
|
|
16
|
+
|
|
17
|
+
module Versions
|
|
18
|
+
|
|
19
|
+
# This is how we 'mix in' modules to Sinatra servers
|
|
20
|
+
# for route definitions and similar things
|
|
21
|
+
#
|
|
22
|
+
# (things to be 'included' for use in route and view processing
|
|
23
|
+
# are mixed in by delcaring them to be helpers)
|
|
24
|
+
#
|
|
25
|
+
# We make them extentions here with
|
|
26
|
+
# extend Sinatra::Extension (from sinatra-contrib)
|
|
27
|
+
# and then 'register' them in the server with
|
|
28
|
+
# register Xolo::Server::<Module>
|
|
29
|
+
# Doing it this way allows us to split the code into a logical
|
|
30
|
+
# file structure, without re-opening the Sinatra::Base server app,
|
|
31
|
+
# and let xeitwork do the requiring of those files
|
|
32
|
+
extend Sinatra::Extension
|
|
33
|
+
|
|
34
|
+
# when this module is included
|
|
35
|
+
def self.included(includer)
|
|
36
|
+
Xolo.verbose_include includer, self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [String] The default min_os for versions
|
|
40
|
+
#################################
|
|
41
|
+
get '/default_min_os' do
|
|
42
|
+
resp = { min_os: default_min_os.to_s }
|
|
43
|
+
body resp
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Create a new version from the body content of the request
|
|
47
|
+
#
|
|
48
|
+
# @return [Hash] A response hash
|
|
49
|
+
#################################
|
|
50
|
+
post '/titles/:title/versions' do
|
|
51
|
+
request.body.rewind
|
|
52
|
+
data = parse_json request.body.read
|
|
53
|
+
|
|
54
|
+
unless data[:title] == params[:title]
|
|
55
|
+
halt 400,
|
|
56
|
+
"Path/Data Mismatch! params[:title] => '#{params[:title]}' / data[:title] => '#{data[:title]}'"
|
|
57
|
+
end
|
|
58
|
+
data[:min_os] = default_min_os if data[:min_os].pix_empty?
|
|
59
|
+
|
|
60
|
+
log_debug "Incoming new version data: #{data}"
|
|
61
|
+
log_debug "Incoming new version data: #{data.class}"
|
|
62
|
+
|
|
63
|
+
vers = instantiate_version(data)
|
|
64
|
+
halt_on_existing_version vers.title, vers.version
|
|
65
|
+
|
|
66
|
+
if vers.title_object.jamf_patch_ea_awaiting_acceptance? && !Xolo::Server.config.jamf_auto_accept_xolo_eas
|
|
67
|
+
|
|
68
|
+
log_info "Jamf: Patch Title '#{params[:title]}' version_script must be manually accepted as a Patch EA before version can be activated. Admin has been notified."
|
|
69
|
+
|
|
70
|
+
raise Xolo::ActionRequiredError,
|
|
71
|
+
"This title has a version-script, which must be accepted manually in Jamf Pro at #{vers.title_object.jamf_patch_ea_url} under the 'Extension Attribute' tab (click 'Edit'). Please do that and try again"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
log_info "Admin #{session[:admin]} is creating version #{data[:version]} of title '#{params[:title]}'"
|
|
75
|
+
|
|
76
|
+
Xolo::Server.rw_lock(data[:title], data[:version]).with_write_lock do
|
|
77
|
+
with_streaming do
|
|
78
|
+
vers.create
|
|
79
|
+
update_client_data
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# get a list of versions for a title
|
|
85
|
+
# @return [Array<Hash>] the names of existing versions for the title
|
|
86
|
+
#################################
|
|
87
|
+
get '/titles/:title/versions' do
|
|
88
|
+
halt_on_missing_title params[:title]
|
|
89
|
+
|
|
90
|
+
log_debug "Admin #{session[:admin]} is listing all versions for title '#{params[:title]}'"
|
|
91
|
+
# body all_versions(params[:title])
|
|
92
|
+
vers_ins = all_version_instances(params[:title])
|
|
93
|
+
# log_debug "vers_ins: #{vers_ins}"
|
|
94
|
+
body vers_ins.map(&:to_h)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# get all the data for a single version
|
|
98
|
+
# @return [Hash] The data for this version
|
|
99
|
+
#################################
|
|
100
|
+
get '/titles/:title/versions/:version' do
|
|
101
|
+
Xolo::Server.rw_lock(params[:title], params[:version]).with_read_lock do
|
|
102
|
+
log_debug "Admin #{session[:admin]} is fetching version '#{params[:version]}' of title '#{params[:title]}'"
|
|
103
|
+
halt_on_missing_version params[:title], params[:version]
|
|
104
|
+
|
|
105
|
+
vers = instantiate_version title: params[:title], version: params[:version]
|
|
106
|
+
body vers.to_h
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Update a version,
|
|
111
|
+
# Replace the data for an existing version with the content of the request
|
|
112
|
+
# @return [Hash] A response hash
|
|
113
|
+
#################################
|
|
114
|
+
put '/titles/:title/versions/:version' do
|
|
115
|
+
request.body.rewind
|
|
116
|
+
new_data = parse_json(request.body.read)
|
|
117
|
+
new_data[:min_os] = default_min_os if new_data[:min_os].pix_empty?
|
|
118
|
+
log_debug "Incoming update version data: #{new_data}"
|
|
119
|
+
|
|
120
|
+
halt_on_missing_title params[:title]
|
|
121
|
+
halt_on_missing_version params[:title], params[:version]
|
|
122
|
+
halt_on_locked_version params[:title], params[:version]
|
|
123
|
+
|
|
124
|
+
vers = instantiate_version title: params[:title], version: params[:version]
|
|
125
|
+
|
|
126
|
+
with_streaming do
|
|
127
|
+
vers.update new_data
|
|
128
|
+
update_client_data
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Delete an existing version
|
|
133
|
+
#
|
|
134
|
+
# This route sends a streamed response indicating progress
|
|
135
|
+
# in realtime, not a JSON object.
|
|
136
|
+
#
|
|
137
|
+
# @return [Hash] A response hash
|
|
138
|
+
#################################
|
|
139
|
+
delete '/titles/:title/versions/:version' do
|
|
140
|
+
halt_on_missing_version params[:title], params[:version]
|
|
141
|
+
halt_on_locked_version params[:title], params[:version]
|
|
142
|
+
|
|
143
|
+
log_info "Admin #{session[:admin]} is deleting version '#{params[:version]}' of title '#{params[:title]}'"
|
|
144
|
+
|
|
145
|
+
# for some reason, instantiating in the with_streaming block
|
|
146
|
+
# causes a throw error
|
|
147
|
+
vers = instantiate_version title: params[:title], version: params[:version]
|
|
148
|
+
|
|
149
|
+
with_streaming do
|
|
150
|
+
vers.delete
|
|
151
|
+
update_client_data
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# upload a pkg for a version
|
|
156
|
+
# param with the uploaded file must be :file
|
|
157
|
+
######################
|
|
158
|
+
post '/titles/:title/versions/:version/pkg' do
|
|
159
|
+
process_incoming_pkg
|
|
160
|
+
body({ result: :uploaded })
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Install a version on computers and/or a group, via
|
|
164
|
+
# the Jamf API's deploy_package endpoint and the
|
|
165
|
+
# InstallEnterpriseApplication MDM command.
|
|
166
|
+
#
|
|
167
|
+
# Request body is a JSON object with the following keys
|
|
168
|
+
# computers: [Array<String, Integer>] The computer identifiers to install on.
|
|
169
|
+
# Identifiers are either serial numbers, names, or Jamf IDs.
|
|
170
|
+
# groups: [Array<String, Integer>] Identifiers of the groups to install on.
|
|
171
|
+
#
|
|
172
|
+
# Response body is a JSON object with the following keys
|
|
173
|
+
# removals: [Array<Hash>] { device: <Integer>, group: <Integer>, reason: <String> }
|
|
174
|
+
# queuedCommands: [Array<Hash>] { device: <Integer>, commandUuid: <String> }
|
|
175
|
+
# errors: [Array<Hash>] { device: <Integer>, group: <Integer>, reason: <String> }
|
|
176
|
+
######################
|
|
177
|
+
post '/titles/:title/versions/:version/deploy' do
|
|
178
|
+
request.body.rewind
|
|
179
|
+
targets = parse_json(request.body.read)
|
|
180
|
+
|
|
181
|
+
log_info "Incoming MDM deployment from admin #{session[:admin]} for title '#{params[:title]}', version '#{params[:version]}'."
|
|
182
|
+
log_info "MDM deployment targets: #{targets}"
|
|
183
|
+
|
|
184
|
+
halt_on_missing_version params[:title], params[:version]
|
|
185
|
+
|
|
186
|
+
vers = instantiate_version title: params[:title], version: params[:version]
|
|
187
|
+
|
|
188
|
+
result = vers.deploy_via_mdm targets
|
|
189
|
+
|
|
190
|
+
body result
|
|
191
|
+
rescue => e
|
|
192
|
+
msg = "#{e.class}: #{e}"
|
|
193
|
+
log_error msg
|
|
194
|
+
halt 400, { status: 400, error: msg }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# run 'repair' on a version
|
|
198
|
+
######################
|
|
199
|
+
post '/titles/:title/versions/:version/repair' do
|
|
200
|
+
log_debug "Admin #{session[:admin]} is repairing version '#{params[:version]}' of title '#{params[:title]}'"
|
|
201
|
+
halt_on_missing_version params[:title], params[:version]
|
|
202
|
+
halt_on_locked_version params[:title], params[:version]
|
|
203
|
+
|
|
204
|
+
vers = instantiate_version title: params[:title], version: params[:version]
|
|
205
|
+
|
|
206
|
+
with_streaming do
|
|
207
|
+
vers.repair
|
|
208
|
+
update_client_data
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Return info about all the computers with a given version of a title installed
|
|
213
|
+
#
|
|
214
|
+
# @return [Array<Hash>] The data for all computers with the given version of the title
|
|
215
|
+
#################################
|
|
216
|
+
get '/titles/:title/versions/:version/patch_report' do
|
|
217
|
+
log_debug "Admin #{session[:admin]} is fetching patch report for version #{params[:version]} title '#{params[:title]}'"
|
|
218
|
+
|
|
219
|
+
if params[:version] == Xolo::UNKNOWN
|
|
220
|
+
halt_on_missing_title params[:title]
|
|
221
|
+
title = instantiate_title params[:title]
|
|
222
|
+
data = title.patch_report vers: Xolo::UNKNOWN
|
|
223
|
+
|
|
224
|
+
else
|
|
225
|
+
halt_on_missing_version params[:title], params[:version]
|
|
226
|
+
version = instantiate_version title: params[:title], version: params[:version]
|
|
227
|
+
data = version.patch_report
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
body data
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Return URLs for all the UI pages for a version
|
|
234
|
+
#
|
|
235
|
+
# @return [Hash] The URLs for all the UI pages for a version
|
|
236
|
+
#################################
|
|
237
|
+
get '/titles/:title/versions/:version/urls' do
|
|
238
|
+
log_debug "Admin #{session[:admin]} is fetching GUI URLS for version #{params[:version]} of title '#{params[:title]}'"
|
|
239
|
+
|
|
240
|
+
halt_on_missing_version params[:title], params[:version]
|
|
241
|
+
vers = instantiate_version title: params[:title], version: params[:version]
|
|
242
|
+
data = {
|
|
243
|
+
ted_patch_url: vers.ted_patch_url,
|
|
244
|
+
jamf_auto_install_policy_url: vers.jamf_auto_install_policy_url,
|
|
245
|
+
jamf_manual_install_policy_url: vers.jamf_manual_install_policy_url,
|
|
246
|
+
jamf_patch_policy_url: vers.jamf_patch_policy_url,
|
|
247
|
+
jamf_package_url: vers.jamf_package_url
|
|
248
|
+
}
|
|
249
|
+
data[:jamf_version_installed_group] = vers.jamf_installed_group_url if vers.jamf_installed_group_exist?
|
|
250
|
+
data[:jamf_auto_reinstall_policy_url] = vers.jamf_auto_reinstall_policy_url if vers.jamf_auto_reinstall_policy_exist?
|
|
251
|
+
|
|
252
|
+
body data
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
end # Versions
|
|
256
|
+
|
|
257
|
+
end # Routes
|
|
258
|
+
|
|
259
|
+
end # Server
|
|
260
|
+
|
|
261
|
+
end # module Xolo
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Copyright 2025 Pixar
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms set forth in the LICENSE.txt file available at
|
|
4
|
+
# at the root of this project.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# frozen_string_literal: true
|
|
8
|
+
|
|
9
|
+
# main module
|
|
10
|
+
module Xolo
|
|
11
|
+
|
|
12
|
+
# Server Module
|
|
13
|
+
module Server
|
|
14
|
+
|
|
15
|
+
# Some "global" routes are defined here.
|
|
16
|
+
# most are defined in other modules.
|
|
17
|
+
##################################
|
|
18
|
+
module Routes
|
|
19
|
+
|
|
20
|
+
# This is how we extend modules to Sinatra servers:
|
|
21
|
+
# We make them extentions here with
|
|
22
|
+
# extend Sinatra::Extension (from sinatra-contrib)
|
|
23
|
+
# and then 'register' them in the server with
|
|
24
|
+
# register Xolo::Server::<Module>
|
|
25
|
+
# Doing it this way allows us to split the code into a logical
|
|
26
|
+
# file structure, without re-opening the Sinatra::Base server app,
|
|
27
|
+
# and let xeitwork do the requiring of those files.
|
|
28
|
+
#
|
|
29
|
+
# To 'include' modules in Sinatra servers, you declare them as helpers.
|
|
30
|
+
#
|
|
31
|
+
extend Sinatra::Extension
|
|
32
|
+
|
|
33
|
+
# pre-process
|
|
34
|
+
##############
|
|
35
|
+
before do
|
|
36
|
+
halt 503, { status: 503, error: 'Server is shutting down' } if Xolo::Server.shutting_down? && !request.path.start_with?('/streamed_progress/')
|
|
37
|
+
|
|
38
|
+
adm = session[:admin] ? ", admin '#{session[:admin]}'" : Xolo::BLANK
|
|
39
|
+
log_info "Processing #{request.request_method} #{request.path} from #{request.ip}#{adm}"
|
|
40
|
+
|
|
41
|
+
log_debug "Session in before-filter: #{session.inspect}"
|
|
42
|
+
|
|
43
|
+
# these routes don't need an auth'd session
|
|
44
|
+
log_debug "Checking if request path '#{request.path}' is in NO_AUTH_ROUTES or NO_AUTH_PREFIXES"
|
|
45
|
+
break if Xolo::Server::Helpers::Auth::NO_AUTH_ROUTES.include? request.path
|
|
46
|
+
break if Xolo::Server::Helpers::Auth::NO_AUTH_PREFIXES.any? { |pfx| request.path.start_with? pfx }
|
|
47
|
+
|
|
48
|
+
# these routes are expected to be called by the xolo server itself
|
|
49
|
+
log_debug "Checking if request path '#{request.path}' is in INTERNAL_ROUTES"
|
|
50
|
+
break if Xolo::Server::Helpers::Auth::INTERNAL_ROUTES.include?(request.path) && valid_internal_auth_token?
|
|
51
|
+
|
|
52
|
+
# these routes are for server admins only, and require an authenticated session
|
|
53
|
+
log_debug "Checking if request path '#{request.path}' is in SERVER_ADMIN_ROUTES"
|
|
54
|
+
break if Xolo::Server::Helpers::Auth::SERVER_ADMIN_ROUTES.include?(request.path) && valid_server_admin?
|
|
55
|
+
|
|
56
|
+
# If here, we must have a session cookie marked as 'authenticated'
|
|
57
|
+
halt 401, { status: 401, error: 'You must log in to the Xolo server' } unless session[:authenticated]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# error process
|
|
61
|
+
##############
|
|
62
|
+
error do
|
|
63
|
+
log_debug 'Running error filter'
|
|
64
|
+
|
|
65
|
+
resp_body = { status: response.status, error: env['sinatra.error'].message }
|
|
66
|
+
body resp_body
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# post-process
|
|
70
|
+
# Convert the body to JSON unless @no_json is set
|
|
71
|
+
##############
|
|
72
|
+
after do
|
|
73
|
+
# log a stack trace if the response status is 4xx or 5xx, or if there's a sinatra.error
|
|
74
|
+
if response.status >= 400
|
|
75
|
+
log_error "Response status #{response.status} for #{request.request_method} #{request.path}"
|
|
76
|
+
if env['sinatra.error']
|
|
77
|
+
log_error "Sinatra Error message: #{env['sinatra.error'].message}"
|
|
78
|
+
env['sinatra.error'].backtrace.each { |line| log_error "..#{line}" }
|
|
79
|
+
else
|
|
80
|
+
log_error 'Xolo Stack trace:'
|
|
81
|
+
caller.each { |line| log_error "..#{line}" }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if @no_json
|
|
86
|
+
log_debug 'NOT converting body to JSON in after filter'
|
|
87
|
+
else
|
|
88
|
+
log_debug 'Converting body to JSON in after filter'
|
|
89
|
+
content_type :json
|
|
90
|
+
# IMPORTANT, this only works if you remember to explicitly use
|
|
91
|
+
# `body body_content` in every route.
|
|
92
|
+
# You can't just define the body
|
|
93
|
+
# by the last evaluated statement of the route.
|
|
94
|
+
#
|
|
95
|
+
response.body = JSON.dump(response.body)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# TODO: See if there's any appropate place to disconnect
|
|
99
|
+
# from ruby-jss and windoo api connections?
|
|
100
|
+
# perhaps a callback to when a Sinatra server instance
|
|
101
|
+
# 'finishes'?
|
|
102
|
+
# can't
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Ping
|
|
106
|
+
##########
|
|
107
|
+
get '/ping' do
|
|
108
|
+
@no_json = true
|
|
109
|
+
body 'pong'
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The streamed progress updates
|
|
113
|
+
# The stream_file param should be in the URL query, i.e.
|
|
114
|
+
# "/streamed_progress/?stream_file=<url-escaped path to file>"
|
|
115
|
+
#
|
|
116
|
+
################
|
|
117
|
+
get '/streamed_progress/' do
|
|
118
|
+
log_debug "Starting progress stream from file: #{params[:stream_file]}"
|
|
119
|
+
# make note that this Server instance is just streaming from a file
|
|
120
|
+
# not acuatlly processing anything.
|
|
121
|
+
@streaming_from_file = true
|
|
122
|
+
|
|
123
|
+
@no_json = true
|
|
124
|
+
stream_file = Pathname.new params[:stream_file]
|
|
125
|
+
|
|
126
|
+
stream do |stream_out|
|
|
127
|
+
stream_progress(stream_file: stream_file, stream: stream_out)
|
|
128
|
+
rescue => e
|
|
129
|
+
stream_out << "ERROR DURING PROGRESS STREAM: #{e.class}: #{e}"
|
|
130
|
+
ensure
|
|
131
|
+
stream_out.close
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# test
|
|
136
|
+
##########
|
|
137
|
+
get '/test' do
|
|
138
|
+
# Xolo::Server::Helpers::Maintenance.post_to_start_cleanup force: true
|
|
139
|
+
# result = { result: 'posted to start cleanup' }
|
|
140
|
+
|
|
141
|
+
# send_email(
|
|
142
|
+
# to: 'xolo@pixar.com',
|
|
143
|
+
# subject: 'Test Email from Xolo Server',
|
|
144
|
+
# msg: 'This is a test email from the Xolo Server'
|
|
145
|
+
# )
|
|
146
|
+
# result = { result: 'message sent' }
|
|
147
|
+
|
|
148
|
+
# client_data_testing
|
|
149
|
+
# update_client_data
|
|
150
|
+
|
|
151
|
+
result = { result: 'test' }
|
|
152
|
+
|
|
153
|
+
body result
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end # Routes
|
|
157
|
+
|
|
158
|
+
end # Server
|
|
159
|
+
|
|
160
|
+
end # module Xolo
|
|
161
|
+
|
|
162
|
+
require 'xolo/server/routes/auth'
|
|
163
|
+
require 'xolo/server/routes/jamf_pro'
|
|
164
|
+
require 'xolo/server/routes/maint'
|
|
165
|
+
require 'xolo/server/routes/title_editor'
|
|
166
|
+
require 'xolo/server/routes/titles'
|
|
167
|
+
require 'xolo/server/routes/uploads'
|
|
168
|
+
require 'xolo/server/routes/versions'
|