blufin-lib 1.7.6 → 1.8.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 +4 -4
- data/lib/blufin-lib.rb +5 -0
- data/lib/core/arrays.rb +21 -0
- data/lib/core/aws.rb +12 -3
- data/lib/core/config.rb +1 -1
- data/lib/core/git.rb +274 -0
- data/lib/core/projects.rb +285 -32
- data/lib/core/routes.rb +20 -1
- data/lib/core/strings.rb +16 -0
- data/lib/core/terminal.rb +25 -15
- data/lib/core/update.rb +15 -0
- data/lib/core/yml.rb +43 -0
- data/lib/generate/generate_base.rb +25 -0
- data/lib/generate/generate_ui_routes.rb +165 -0
- data/lib/version.rb +1 -1
- data/opt/schema/projects.yml +15 -2
- data/opt/schema/routes.yml +44 -0
- data/opt/shell/ec2-check +24 -0
- metadata +9 -2
data/lib/core/routes.rb
CHANGED
@@ -67,11 +67,30 @@ module Blufin
|
|
67
67
|
flags_set = 0
|
68
68
|
possible_flags.each { |possible_flag|
|
69
69
|
raise RuntimeError, "@opts does not contain flag: #{possible_flag}" unless opts.has_key?(possible_flag)
|
70
|
-
flags_set += 1 if opts[possible_flag]
|
70
|
+
flags_set += 1 if opts[possible_flag] && !opts[possible_flag].nil?
|
71
71
|
}
|
72
72
|
Blufin::Terminal::error("Cannot set #{Blufin::Terminal::format_highlight('more than one')} of the following flags: \x1B[38;5;172m#{possible_flags.join(', ')}\x1B[0m") if flags_set > 1
|
73
73
|
end
|
74
74
|
|
75
|
+
# Prevents code from running twice.
|
76
|
+
# @return boolean
|
77
|
+
def self.code_already_ran(tmp_key, timeout = 1000)
|
78
|
+
t = Time.new
|
79
|
+
epoch = t.strftime('%s%3N')
|
80
|
+
epoch_previous = nil
|
81
|
+
tmp_file = "/tmp/#{tmp_key}-last-run-epoch.txt"
|
82
|
+
if Blufin::Files::file_exists(tmp_file)
|
83
|
+
Blufin::Files::read_file(tmp_file).each do |line|
|
84
|
+
epoch_previous = line
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
Blufin::Files::write_file(tmp_file, [epoch])
|
89
|
+
return if epoch_previous.nil?
|
90
|
+
diff = (epoch.to_i - epoch_previous.to_i).abs
|
91
|
+
diff < timeout
|
92
|
+
end
|
93
|
+
|
75
94
|
private
|
76
95
|
|
77
96
|
# Does exactly what it says on the Tin.
|
data/lib/core/strings.rb
CHANGED
@@ -97,6 +97,22 @@ module Blufin
|
|
97
97
|
rs
|
98
98
|
end
|
99
99
|
|
100
|
+
# Strips all newline character(s) -- IE: "abc\n" -> "abc"
|
101
|
+
# Be careful, if multiple newlines are in string, they will all get stripped and you might end up with weird output.
|
102
|
+
# @return string
|
103
|
+
def self.strip_newline(string)
|
104
|
+
raise RuntimeError, "Expected String, instead got: #{string.class}" unless string.is_a?(String)
|
105
|
+
string.gsub(/[\r\n]+/m, '').strip
|
106
|
+
end
|
107
|
+
|
108
|
+
# Removes all colors from a string.
|
109
|
+
# https://stackoverflow.com/questions/16032726/removing-color-decorations-from-strings-before-writing-them-to-logfile
|
110
|
+
# @return string
|
111
|
+
def self.strip_ansi_colors(string)
|
112
|
+
raise RuntimeError, "Expected String, instead got: #{string.class}" unless string.is_a?(String)
|
113
|
+
string.gsub(/\e\[([;\d]+)?m/, '').strip
|
114
|
+
end
|
115
|
+
|
100
116
|
private
|
101
117
|
|
102
118
|
# Internal string validation.
|
data/lib/core/terminal.rb
CHANGED
@@ -22,6 +22,7 @@ module Blufin
|
|
22
22
|
MSG_PROGRESS = 'progress'
|
23
23
|
MSG_CUSTOM = 'custom'
|
24
24
|
MSG_CUSTOM_AUTO_PAD = 'custom_auto_pad'
|
25
|
+
ERR_OUTPUT = '/tmp/execute-output'
|
25
26
|
|
26
27
|
# Runs a series of commands in the terminal.
|
27
28
|
# @return void
|
@@ -77,30 +78,39 @@ module Blufin
|
|
77
78
|
# If capture: is false, returns TRUE:FALSE whether command was successful or not.
|
78
79
|
# If capture: is true, returns raw output of command.
|
79
80
|
# See: https://github.com/piotrmurach/tty-spinner/blob/master/lib/tty/spinner/formats.rb (for spinner options).
|
80
|
-
# @return
|
81
|
-
|
81
|
+
# @return idx-1 => Command was successful (exit code 0) will output TRUE if capture: FALSE or otherwise STDOUT will be returned, IE: 'On branch master'
|
82
|
+
# @return idx-2 => STDERR (if any), IE: anything written to /tmp/execute-output
|
83
|
+
def self.execute(command, path = nil, capture: false, verbose: true, text: nil, error_output: ERR_OUTPUT, display_error: true, ignore_error: false)
|
82
84
|
text = text.is_a?(String) ? text : command
|
83
85
|
t1 = Time.now
|
84
86
|
spinner = TTY::Spinner.new("[:spinner] \x1B[38;5;208m#{text}#{!path.nil? ? " \x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;240m#{path}" : nil}\x1B[0m", format: :dots) if verbose
|
85
87
|
spinner.auto_spin if verbose
|
86
88
|
path = File.expand_path('~/') if path.nil?
|
89
|
+
path = File.expand_path(path) unless path.nil?
|
90
|
+
`echo '' > #{ERR_OUTPUT}`
|
87
91
|
if capture
|
88
|
-
res = `cd #{path} && #{command}`
|
92
|
+
res = `cd #{path} && #{command} 2>#{error_output}`
|
89
93
|
else
|
90
|
-
res = system("cd #{path} && #{command}
|
94
|
+
res = system("cd #{path} && #{command} 1>/dev/null 2>#{error_output}")
|
91
95
|
end
|
92
96
|
t2 = Time.now
|
93
97
|
delta = "#{'%.3f' % (t2 - t1).abs}s"
|
94
|
-
|
98
|
+
error = `cat #{ERR_OUTPUT}`.to_s.strip
|
99
|
+
if (res && error.length == 0) || ignore_error
|
95
100
|
spinner.success("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;46mComplete \x1B[38;5;240m(#{delta})\x1B[0m\x1B[0m") if verbose
|
101
|
+
res = true unless capture
|
96
102
|
else
|
97
103
|
spinner.error("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;196mFailed (#{delta})\x1B[0m") if verbose
|
98
104
|
# If there is an error, output it (even if verbose == false).
|
99
|
-
|
100
|
-
|
101
|
-
|
105
|
+
if error.length > 0 && display_error
|
106
|
+
puts "\x1B[38;5;240m"
|
107
|
+
system("cat #{ERR_OUTPUT}")
|
108
|
+
puts "\x1B[0m"
|
109
|
+
end
|
110
|
+
res = false unless capture
|
102
111
|
end
|
103
|
-
|
112
|
+
error = nil if error.nil? || error.strip == ''
|
113
|
+
return res, error
|
104
114
|
end
|
105
115
|
|
106
116
|
# Same as above but with a proc/lambda instead of a terminal command.
|
@@ -124,11 +134,9 @@ module Blufin
|
|
124
134
|
# If CUSTOM, title must be 11 characters to match existing scheme.
|
125
135
|
# @return void
|
126
136
|
def self.output(message = 'No message', type = MSG_INFO, title = nil, bg_color = nil)
|
127
|
-
|
128
137
|
raise RuntimeError, "'title' cannot be nil." if title.nil? && [MSG_CUSTOM, MSG_CUSTOM_AUTO_PAD].include?(type)
|
129
138
|
raise RuntimeError, "'bg_color' cannot be nil." if bg_color.nil? && [MSG_CUSTOM, MSG_CUSTOM_AUTO_PAD].include?(type)
|
130
139
|
raise RuntimeError, "'title' cannot be more that 9 characerts when MSG_CUSTOM_AUTO_PAD is the message type." if type == MSG_CUSTOM_AUTO_PAD && title.length > 9
|
131
|
-
|
132
140
|
case type
|
133
141
|
when MSG_INFO
|
134
142
|
puts "\x1B[38;5;231m\x1B[48;5;22m Executing \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
@@ -151,8 +159,7 @@ module Blufin
|
|
151
159
|
when MSG_CUSTOM_AUTO_PAD
|
152
160
|
puts "\x1B[38;5;231m#{''.rjust((9 - title.length), ' ')}\x1B[48;5;#{bg_color}m #{title} \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
153
161
|
else
|
154
|
-
|
155
|
-
exit
|
162
|
+
raise RuntimeError, "Invalid Terminal::output type: #{type}"
|
156
163
|
end
|
157
164
|
end
|
158
165
|
|
@@ -170,7 +177,7 @@ module Blufin
|
|
170
177
|
puts if preceding_blank_line
|
171
178
|
puts " \x1B[38;5;231m\x1B[48;5;124m #{error_text} \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
172
179
|
parse_messages(message)
|
173
|
-
exit if exit_script
|
180
|
+
exit 1 if exit_script
|
174
181
|
end
|
175
182
|
|
176
183
|
# Displays automatic message.
|
@@ -607,7 +614,10 @@ module Blufin
|
|
607
614
|
type = type.downcase
|
608
615
|
types = {
|
609
616
|
'yml' => Rouge::Lexers::YAML.new,
|
610
|
-
'json' => Rouge::Lexers::JSON.new
|
617
|
+
'json' => Rouge::Lexers::JSON.new,
|
618
|
+
'js' => Rouge::Lexers::Javascript.new,
|
619
|
+
'java' => Rouge::Lexers::Javascript.new,
|
620
|
+
'sass' => Rouge::Lexers::Sass.new
|
611
621
|
}
|
612
622
|
raise RuntimeError, "Lexer not defined for type: #{type}" unless types.has_key?(type)
|
613
623
|
repeat = ' ' * indent
|
data/lib/core/update.rb
ADDED
data/lib/core/yml.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class Yml
|
4
|
+
|
5
|
+
# Validate and initialize a YML file.
|
6
|
+
# @return Hash
|
7
|
+
def self.read_file(content_file, schema_file)
|
8
|
+
content_file = File.expand_path(content_file)
|
9
|
+
schema_file = File.expand_path(schema_file)
|
10
|
+
# Check file(s) exist.
|
11
|
+
[content_file, schema_file].each { |file| Blufin::Terminal::error("File not found: #{schema_file}") unless Blufin::Files::file_exists(file) }
|
12
|
+
# Parse the schema file.
|
13
|
+
begin
|
14
|
+
schema_file_parsed = YAML.load_file(schema_file)
|
15
|
+
rescue => e
|
16
|
+
Blufin::Terminal::error("Failed to parse schema file: #{Blufin::Terminal::format_directory(schema_file)}", e.message)
|
17
|
+
end
|
18
|
+
# Initialize the validator.
|
19
|
+
validator = Kwalify::Validator.new(schema_file_parsed)
|
20
|
+
# Validate the content file.
|
21
|
+
begin
|
22
|
+
document = YAML.load_file(content_file)
|
23
|
+
# noinspection RubyArgCount
|
24
|
+
errors = validator.validate(document)
|
25
|
+
rescue => e
|
26
|
+
Blufin::Terminal::error("Failed to parse content file: #{Blufin::Terminal::format_directory(content_file)}", e.message)
|
27
|
+
end
|
28
|
+
# If errors occurred, display and bomb-out.
|
29
|
+
if errors && !errors.empty?
|
30
|
+
errors_output = []
|
31
|
+
errors.each { |e|
|
32
|
+
errors_output << "[#{e.path}] #{e.message}"
|
33
|
+
}
|
34
|
+
Blufin::Terminal::error("File had errors: #{Blufin::Terminal::format_directory(content_file)}", errors_output)
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
# Otherwise, return valid YML.
|
38
|
+
document
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class GenerateBase
|
4
|
+
|
5
|
+
OUTPUT_GENERATE = 'generated'
|
6
|
+
OUTPUT_SKIP = 'exists'
|
7
|
+
|
8
|
+
# Standardized output for code-generation.
|
9
|
+
# @return void
|
10
|
+
def self.output(message, type)
|
11
|
+
|
12
|
+
case type
|
13
|
+
when OUTPUT_GENERATE
|
14
|
+
puts "\x1B[38;5;231m\x1B[48;5;55m Generated \x1B[0m \xe2\x86\x92 \x1B[38;5;182m#{message}\x1B[0m"
|
15
|
+
when OUTPUT_SKIP
|
16
|
+
puts " \x1B[38;5;246m\x1B[48;5;234m Skipping \x1B[0m \xe2\x86\x92 \x1B[38;5;240m#{message}\x1B[0m"
|
17
|
+
else
|
18
|
+
raise "Unrecognized output type: #{type}"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class GenerateUiRoutes
|
4
|
+
|
5
|
+
SCHEMA_FILE = "#{Blufin::Base::get_base_path}#{Blufin::Base::OPT_PATH}/schema/routes.yml"
|
6
|
+
HOME_TERM = 'dashboard'
|
7
|
+
|
8
|
+
# Generates routes in the UI.
|
9
|
+
# @return void
|
10
|
+
def self.run(project)
|
11
|
+
raise RuntimeError, "Expected project type to be: #{Blufin::Projects::TYPE_UI}, instead got: #{project[Blufin::Projects::TYPE]}" unless project[Blufin::Projects::TYPE] == Blufin::Projects::TYPE_UI
|
12
|
+
@errors = []
|
13
|
+
@warnings = []
|
14
|
+
@files = []
|
15
|
+
@files_to_create = []
|
16
|
+
begin
|
17
|
+
@output = <<TEMPLATE
|
18
|
+
const routes = [
|
19
|
+
{
|
20
|
+
TEMPLATE
|
21
|
+
project_path = Blufin::Projects::get_project_path(project[Blufin::Projects::PROJECT_ID], true)
|
22
|
+
routes_file = "#{project_path}/#{project[Blufin::Projects::UI][Blufin::Projects::ROUTES_FILE]}"
|
23
|
+
routes_yml = Blufin::Yml::read_file(routes_file, SCHEMA_FILE)
|
24
|
+
target_file = "#{project_path}/src/#{Blufin::Strings::remove_surrounding_slashes(routes_yml['RoutesFile'])}"
|
25
|
+
layout = routes_yml['Layout']
|
26
|
+
pages_root = routes_yml['PagesRoot']
|
27
|
+
pages_not_found = routes_yml['Pages404']
|
28
|
+
path_pages_ignore = []
|
29
|
+
path_to_pages = "#{project_path}/src/#{pages_root}"
|
30
|
+
# Add ignore path(s) -- if exists.
|
31
|
+
routes_yml['Ignore'].each { |ignore_path| path_pages_ignore << "#{Blufin::Strings::remove_surrounding_slashes(ignore_path)}" } if routes_yml.has_key?('Ignore')
|
32
|
+
routes = routes_yml['Routes']
|
33
|
+
routes.each_with_index do |route, idx|
|
34
|
+
comma = route.has_key?('Children') ? ',' : nil
|
35
|
+
parent_path = route['Parent']['Path']
|
36
|
+
@output += <<TEMPLATE
|
37
|
+
path: '/#{route['Parent'].has_key?('RootPath') && route['Parent']['RootPath'] ? nil : parent_path}',
|
38
|
+
sectionName: '#{route['Parent']['Name']}',
|
39
|
+
sectionIcon: '#{route['Parent']['Icon']}',
|
40
|
+
component: () => import('./../#{layout}')#{comma}
|
41
|
+
TEMPLATE
|
42
|
+
file = parent_path == HOME_TERM ? "#{pages_root}/#{parent_path}/#{HOME_TERM}.vue" : "#{pages_root}/#{parent_path}/#{parent_path}-#{HOME_TERM}.vue"
|
43
|
+
prefix = parent_path == HOME_TERM ? 'dashboard/' : nil
|
44
|
+
process_file(file, project_path)
|
45
|
+
if route.has_key?('Children')
|
46
|
+
@output += <<TEMPLATE
|
47
|
+
children: [
|
48
|
+
{
|
49
|
+
path: '',
|
50
|
+
component: () => import('./../#{file}')
|
51
|
+
},
|
52
|
+
TEMPLATE
|
53
|
+
children = route['Children']
|
54
|
+
children.each_with_index do |child, idx|
|
55
|
+
comma = idx == children.length - 1 ? nil : ','
|
56
|
+
file = "#{pages_root}/#{parent_path}/#{child['Path']}.vue"
|
57
|
+
disabled = child.has_key?('Disabled') && child['Disabled'] ? "\n disabled: true," : nil
|
58
|
+
process_file(file, project_path)
|
59
|
+
@output += <<TEMPLATE
|
60
|
+
{
|
61
|
+
path: '#{prefix}#{child['Path']}',
|
62
|
+
name: '#{child['Name']}',#{disabled}
|
63
|
+
component: () => import('./../#{file}')
|
64
|
+
}#{comma}
|
65
|
+
TEMPLATE
|
66
|
+
end
|
67
|
+
@output += " ]\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
if idx == routes.length - 1
|
71
|
+
@output += <<TEMPLATE
|
72
|
+
}
|
73
|
+
TEMPLATE
|
74
|
+
else
|
75
|
+
@output += <<TEMPLATE
|
76
|
+
},
|
77
|
+
{
|
78
|
+
TEMPLATE
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
@output += <<TEMPLATE
|
83
|
+
];
|
84
|
+
|
85
|
+
if (process.env.MODE !== 'ssr') {
|
86
|
+
routes.push({
|
87
|
+
path: '*',
|
88
|
+
component: () => import('./../#{pages_not_found}')
|
89
|
+
});
|
90
|
+
}
|
91
|
+
|
92
|
+
export default routes;
|
93
|
+
TEMPLATE
|
94
|
+
rescue => e
|
95
|
+
Blufin::Terminal::print_exception(e)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Write the output to the file.
|
99
|
+
Blufin::Files::write_file(target_file, Blufin::Arrays::convert_string_to_line_array(@output))
|
100
|
+
|
101
|
+
# Output message.
|
102
|
+
Blufin::Terminal::info('Generating route file(s):') if @files_to_create.any?
|
103
|
+
|
104
|
+
# Write all the blank route files.
|
105
|
+
@files_to_create.each do |ftc|
|
106
|
+
if Blufin::Files::file_exists(ftc)
|
107
|
+
Blufin::GenerateBase::output(ftc, Blufin::GenerateBase::OUTPUT_SKIP)
|
108
|
+
else
|
109
|
+
Blufin::Files::write_file(ftc, Blufin::Arrays::convert_string_to_line_array(get_blank_file_content))
|
110
|
+
Blufin::GenerateBase::output(ftc, Blufin::GenerateBase::OUTPUT_GENERATE)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
puts if @files_to_create.any?
|
115
|
+
|
116
|
+
# Check for rogue files.
|
117
|
+
Blufin::Files::get_files_in_dir(path_to_pages).each do |file|
|
118
|
+
next if file =~ /#{path_to_pages}\/(#{path_pages_ignore.join('|')})/
|
119
|
+
unless @files_to_create.include?(file)
|
120
|
+
@warnings << "Found rogue file: #{Blufin::Terminal::format_invalid(file)}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
return @errors, @warnings
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Ensures that we're not defining a path twice.
|
131
|
+
# Creates file if not exists
|
132
|
+
# @return void
|
133
|
+
def self.process_file(file, project_path)
|
134
|
+
Blufin::Terminal::error("Duplicate file: #{Blufin::Terminal::format_invalid(file)}", 'Your configuration would produce a duplicate file. Please fix.') if @files.include?(file)
|
135
|
+
@files << file
|
136
|
+
@files_to_create << "#{project_path}/src/#{file}"
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the contents of a blank file.
|
140
|
+
# @return string
|
141
|
+
def self.get_blank_file_content
|
142
|
+
<<TEMPLATE
|
143
|
+
<template>
|
144
|
+
<div>
|
145
|
+
<base-title h="5" :nudge-right="30" :nudge-up="10">{{ $route.name }}</base-title>
|
146
|
+
</div>
|
147
|
+
</template>
|
148
|
+
|
149
|
+
<script type="text/ecmascript-6">
|
150
|
+
export default {
|
151
|
+
data() {
|
152
|
+
return {
|
153
|
+
test: this.$route.path
|
154
|
+
};
|
155
|
+
}
|
156
|
+
};
|
157
|
+
</script>
|
158
|
+
|
159
|
+
<style scoped type="text/scss" lang="scss"></style>
|
160
|
+
TEMPLATE
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
data/lib/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
BLUFIN_LIB_VERSION = '1.
|
1
|
+
BLUFIN_LIB_VERSION = '1.8.0'
|
data/opt/schema/projects.yml
CHANGED
@@ -56,7 +56,15 @@ mapping:
|
|
56
56
|
Remote:
|
57
57
|
required: yes
|
58
58
|
ProjectRoot:
|
59
|
-
required:
|
59
|
+
required: no
|
60
|
+
Upstream:
|
61
|
+
type: seq
|
62
|
+
sequence:
|
63
|
+
- type: str
|
64
|
+
Downstream:
|
65
|
+
type: seq
|
66
|
+
sequence:
|
67
|
+
- type: str
|
60
68
|
Run:
|
61
69
|
type: map
|
62
70
|
mapping:
|
@@ -137,4 +145,9 @@ mapping:
|
|
137
145
|
type: seq
|
138
146
|
required: yes
|
139
147
|
sequence:
|
140
|
-
- type: str
|
148
|
+
- type: str
|
149
|
+
UI:
|
150
|
+
type: map
|
151
|
+
mapping:
|
152
|
+
RoutesFile:
|
153
|
+
required: yes
|
@@ -0,0 +1,44 @@
|
|
1
|
+
type: map
|
2
|
+
mapping:
|
3
|
+
Layout:
|
4
|
+
required: true
|
5
|
+
PagesRoot:
|
6
|
+
required: true
|
7
|
+
Pages404:
|
8
|
+
required: true
|
9
|
+
RoutesFile:
|
10
|
+
required: true
|
11
|
+
Ignore:
|
12
|
+
type: seq
|
13
|
+
sequence:
|
14
|
+
- type: str
|
15
|
+
Routes:
|
16
|
+
type: seq
|
17
|
+
required: true
|
18
|
+
sequence:
|
19
|
+
- type: map
|
20
|
+
mapping:
|
21
|
+
Parent:
|
22
|
+
type: map
|
23
|
+
required: true
|
24
|
+
mapping:
|
25
|
+
Path:
|
26
|
+
required: true
|
27
|
+
Name:
|
28
|
+
required: true
|
29
|
+
Icon:
|
30
|
+
required: true
|
31
|
+
RootPath:
|
32
|
+
type: bool
|
33
|
+
Children:
|
34
|
+
type: seq
|
35
|
+
sequence:
|
36
|
+
- type: map
|
37
|
+
required: true
|
38
|
+
mapping:
|
39
|
+
Path:
|
40
|
+
required: true
|
41
|
+
Name:
|
42
|
+
required: true
|
43
|
+
Disabled:
|
44
|
+
type: bool
|