serverest 3.2.2 → 3.3.0-beta.2
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.
- package/README.md +44 -0
- package/docs/swagger.json +154 -0
- package/package.json +2 -1
- package/src/app.js +18 -5
- package/src/controllers/carrinhos-controller.js +16 -0
- package/src/middlewares/authentication-middleware.js +22 -18
- package/src/middlewares/rate-limiter.js +1 -0
- package/src/routes/carrinhos-route.js +6 -5
- package/src/routes/login-route.js +2 -1
- package/src/routes/produtos-route.js +6 -5
- package/src/routes/usuarios-route.js +6 -5
- package/src/services/validateSchema-service.js +3 -1
- package/src/swagger/index.js +11 -2
- package/src/swagger/scripts/08-flow-panel.js +374 -0
- package/src/swagger/styles/flow-panel.css +231 -0
- package/src/swagger/translations/en.js +73 -0
- package/src/swagger/translations/es.js +73 -0
- package/src/swagger/translations/pt-BR.js +73 -0
- package/src/utils/etapas.js +98 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const { validate } = require('express-validation')
|
|
4
4
|
|
|
5
|
+
const { etapa } = require('../utils/etapas')
|
|
6
|
+
|
|
5
7
|
module.exports = schema => {
|
|
6
|
-
return validate(schema, { keyByField: false }, { abortEarly: false })
|
|
8
|
+
return etapa('schema', validate(schema, { keyByField: false }, { abortEarly: false }))
|
|
7
9
|
}
|
package/src/swagger/index.js
CHANGED
|
@@ -7,7 +7,7 @@ const STYLES_DIR = join(__dirname, 'styles')
|
|
|
7
7
|
const SCRIPTS_DIR = join(__dirname, 'scripts')
|
|
8
8
|
const TRANSLATIONS_DIR = join(__dirname, 'translations')
|
|
9
9
|
|
|
10
|
-
const STYLE_FILES = ['topbar.css', 'language-switcher.css', 'release-toast.css']
|
|
10
|
+
const STYLE_FILES = ['topbar.css', 'language-switcher.css', 'release-toast.css', 'flow-panel.css']
|
|
11
11
|
|
|
12
12
|
function loadCustomCss () {
|
|
13
13
|
return STYLE_FILES
|
|
@@ -40,6 +40,14 @@ function buildUiLabelTranslations () {
|
|
|
40
40
|
return entries.join(',\n')
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// O painel sempre renderiza pt-BR e a tradução da página troca o texto, como no resto da UI.
|
|
44
|
+
// ui.response entra porque o painel reaproveita o rótulo "Resposta" já existente.
|
|
45
|
+
function buildFlowPanelTexts () {
|
|
46
|
+
const ptBr = require(join(TRANSLATIONS_DIR, 'pt-BR.js'))
|
|
47
|
+
const keys = Object.keys(ptBr).filter(k => k.startsWith('ui.flow.') || k === 'ui.response')
|
|
48
|
+
return JSON.stringify(keys.reduce((texts, key) => ({ ...texts, [key]: ptBr[key] }), {}))
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
const customCss = loadCustomCss()
|
|
44
52
|
const customJsTemplate = loadCustomJsTemplate()
|
|
45
53
|
|
|
@@ -48,7 +56,8 @@ function buildCustomJsStr (releaseInfo, currentVersion, forceBanner) {
|
|
|
48
56
|
__RELEASE_INFO__: JSON.stringify(releaseInfo),
|
|
49
57
|
__CURRENT_VERSION__: JSON.stringify(currentVersion),
|
|
50
58
|
__FORCE_BANNER__: JSON.stringify(forceBanner),
|
|
51
|
-
__UI_LABEL_TRANSLATIONS__: buildUiLabelTranslations()
|
|
59
|
+
__UI_LABEL_TRANSLATIONS__: buildUiLabelTranslations(),
|
|
60
|
+
__FLOW_PANEL_TEXTS__: buildFlowPanelTexts()
|
|
52
61
|
}
|
|
53
62
|
return Object.keys(replacements).reduce(
|
|
54
63
|
(output, key) => output.split(key).join(replacements[key]),
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/* Painel "Fluxo da requisição": desenha o header X-ServeRest-Stages abaixo da resposta do "Try it out" */
|
|
2
|
+
// O ponto e vírgula inicial é obrigatório: os scripts desta pasta são concatenados e, sem ele, o
|
|
3
|
+
// navegador interpreta este bloco como uma chamada do resultado do script anterior.
|
|
4
|
+
;(function () {
|
|
5
|
+
const texts = __FLOW_PANEL_TEXTS__
|
|
6
|
+
const stagesHeader = 'x-serverest-stages'
|
|
7
|
+
const slowMotionKey = 'serverestFlowSlowMotion'
|
|
8
|
+
|
|
9
|
+
function knownKey (key) {
|
|
10
|
+
return Object.prototype.hasOwnProperty.call(texts, key) ? key : null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function text (key) {
|
|
14
|
+
return knownKey(key) ? texts[key] : ''
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseEntry (piece) {
|
|
18
|
+
const [name, ...params] = piece.split(';').map(part => part.trim())
|
|
19
|
+
const entry = {}
|
|
20
|
+
params.filter(Boolean).forEach(param => {
|
|
21
|
+
const separator = param.indexOf('=')
|
|
22
|
+
const key = separator === -1 ? param : param.slice(0, separator)
|
|
23
|
+
entry[key] = separator === -1 ? '' : param.slice(separator + 1)
|
|
24
|
+
})
|
|
25
|
+
entry.name = name
|
|
26
|
+
if (entry.dur !== undefined) {
|
|
27
|
+
const dur = Number(entry.dur)
|
|
28
|
+
entry.dur = entry.dur === '' || Number.isNaN(dur) ? undefined : dur
|
|
29
|
+
}
|
|
30
|
+
return entry
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseStages (headerValue) {
|
|
34
|
+
if (typeof headerValue !== 'string') return []
|
|
35
|
+
return headerValue.split(',')
|
|
36
|
+
.map(piece => piece.trim())
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.map(parseEntry)
|
|
39
|
+
.filter(entry => entry.name)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extraParams (entry) {
|
|
43
|
+
return Object.keys(entry)
|
|
44
|
+
.filter(key => !['name', 'st', 'dur'].includes(key))
|
|
45
|
+
.reduce((params, key) => Object.assign(params, { [key]: entry[key] }), {})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildStage (entry) {
|
|
49
|
+
return {
|
|
50
|
+
name: entry.name,
|
|
51
|
+
st: entry.st,
|
|
52
|
+
dur: entry.dur,
|
|
53
|
+
params: extraParams(entry),
|
|
54
|
+
labelKey: knownKey('ui.flow.stage_label.' + entry.name),
|
|
55
|
+
descriptionKey: knownKey('ui.flow.stage_description.' + entry.name),
|
|
56
|
+
statusKey: knownKey('ui.flow.status.' + entry.st),
|
|
57
|
+
width: 0,
|
|
58
|
+
rules: []
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildRule (entry, code) {
|
|
63
|
+
return {
|
|
64
|
+
name: entry.name,
|
|
65
|
+
code,
|
|
66
|
+
st: entry.st,
|
|
67
|
+
item: entry.item,
|
|
68
|
+
params: extraParams(entry),
|
|
69
|
+
labelKey: knownKey('ui.flow.rule_label.' + code),
|
|
70
|
+
statusKey: knownKey('ui.flow.status.' + entry.st)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function failureKeys (stage, rule, status) {
|
|
75
|
+
// 408 marca como failed a etapa em execução no timeout, e 500 ou mais é erro inesperado: a etapa não explica a falha.
|
|
76
|
+
if (status === 408) return { whyKey: 'ui.flow.why.status.timeout', howKey: 'ui.flow.how.status.timeout' }
|
|
77
|
+
if (status >= 500) return { whyKey: 'ui.flow.why.status.unexpected_error', howKey: 'ui.flow.how.status.unexpected_error' }
|
|
78
|
+
if (rule) {
|
|
79
|
+
const why = knownKey('ui.flow.why.rule.' + rule.code)
|
|
80
|
+
const how = knownKey('ui.flow.how.rule.' + rule.code)
|
|
81
|
+
if (why && how) return { whyKey: why, howKey: how }
|
|
82
|
+
}
|
|
83
|
+
if (stage.name === 'auth' && stage.params.reason) {
|
|
84
|
+
const why = knownKey('ui.flow.why.auth.' + stage.params.reason)
|
|
85
|
+
const how = knownKey('ui.flow.how.auth.' + stage.params.reason)
|
|
86
|
+
if (why && how) return { whyKey: why, howKey: how }
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
whyKey: knownKey('ui.flow.why.stage.' + stage.name),
|
|
90
|
+
howKey: knownKey('ui.flow.how.stage.' + stage.name)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildFlowModel (entries, statusCode) {
|
|
95
|
+
if (!Array.isArray(entries) || entries.length === 0) return null
|
|
96
|
+
const stages = []
|
|
97
|
+
entries.forEach(entry => {
|
|
98
|
+
const separator = entry.name.indexOf('.')
|
|
99
|
+
const owner = separator > 0 ? entry.name.slice(0, separator) : null
|
|
100
|
+
const parent = owner ? stages.filter(stage => stage.name === owner).pop() : null
|
|
101
|
+
if (parent) {
|
|
102
|
+
parent.rules.push(buildRule(entry, entry.name.slice(separator + 1)))
|
|
103
|
+
} else {
|
|
104
|
+
stages.push(buildStage(entry))
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
const totalDur = stages.reduce((total, stage) => total + (stage.dur || 0), 0)
|
|
108
|
+
stages.forEach(stage => {
|
|
109
|
+
stage.width = totalDur > 0 ? (stage.dur || 0) / totalDur : 0
|
|
110
|
+
})
|
|
111
|
+
const status = Number(statusCode)
|
|
112
|
+
const failedStage = stages.find(stage => stage.st === 'failed') || null
|
|
113
|
+
const failedRule = failedStage ? failedStage.rules.find(rule => rule.st === 'failed') || null : null
|
|
114
|
+
let summary = 'completed'
|
|
115
|
+
let keys = { whyKey: null, howKey: null }
|
|
116
|
+
if (failedStage) {
|
|
117
|
+
summary = 'stopped'
|
|
118
|
+
keys = failureKeys(failedStage, failedRule, status)
|
|
119
|
+
} else if (status >= 400) {
|
|
120
|
+
summary = 'outside'
|
|
121
|
+
keys = { whyKey: 'ui.flow.why.outside', howKey: 'ui.flow.how.outside' }
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
statusCode: Number.isNaN(status) ? null : status,
|
|
125
|
+
summary,
|
|
126
|
+
failedStage: failedStage ? failedStage.name : null,
|
|
127
|
+
failedRule: failedRule ? failedRule.code : null,
|
|
128
|
+
whyKey: keys.whyKey,
|
|
129
|
+
howKey: keys.howKey,
|
|
130
|
+
stages
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function stepDelay (stage) {
|
|
135
|
+
if (stage.dur === undefined) return 420
|
|
136
|
+
return Math.min(1700, Math.max(420, stage.dur * 480))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function prefersReducedMotion () {
|
|
140
|
+
try {
|
|
141
|
+
return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
142
|
+
} catch (e) {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function readSlowMotion () {
|
|
148
|
+
try {
|
|
149
|
+
return window.localStorage.getItem(slowMotionKey) !== 'false'
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function saveSlowMotion (enabled) {
|
|
156
|
+
try {
|
|
157
|
+
window.localStorage.setItem(slowMotionKey, enabled ? 'true' : 'false')
|
|
158
|
+
} catch (e) {}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function readResponse (response) {
|
|
162
|
+
try {
|
|
163
|
+
const headers = response && response.get('headers')
|
|
164
|
+
const map = headers && headers.toJS ? headers.toJS() : {}
|
|
165
|
+
const name = Object.keys(map).find(key => key.toLowerCase() === stagesHeader)
|
|
166
|
+
if (!name || map[name] === undefined || map[name] === null) return null
|
|
167
|
+
return { header: String(map[name]), status: response.get('status') }
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createComponents (React) {
|
|
174
|
+
const h = React.createElement
|
|
175
|
+
|
|
176
|
+
class FlowBoundary extends React.Component {
|
|
177
|
+
constructor (props) {
|
|
178
|
+
super(props)
|
|
179
|
+
this.state = { failed: false }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static getDerivedStateFromError () {
|
|
183
|
+
return { failed: true }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
componentDidCatch () {}
|
|
187
|
+
|
|
188
|
+
render () {
|
|
189
|
+
return this.state.failed ? null : this.props.children
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Todo texto traduzível fica sozinho no seu nó: a tradução da página compara o nó inteiro.
|
|
194
|
+
function label (key, className) {
|
|
195
|
+
return h('span', { key, className }, text(key))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Código desconhecido vai em <code>, que a tradução da página nunca altera.
|
|
199
|
+
function codeOrLabel (key, raw, className) {
|
|
200
|
+
return key ? label(key, className) : h('code', { key: 'raw-' + raw, className }, String(raw === undefined ? '' : raw))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function statusClass (item) {
|
|
204
|
+
return 'serverest-flow-st-' + (item.statusKey ? item.st : 'unknown')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function textBlock (titleKey, bodyKey, className) {
|
|
208
|
+
return h('div', { key: titleKey, className: 'serverest-flow-block ' + className },
|
|
209
|
+
h('strong', { className: 'serverest-flow-block-title' }, text(titleKey)),
|
|
210
|
+
h('p', { className: 'serverest-flow-block-text' }, text(bodyKey))
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function RuleRow (props) {
|
|
215
|
+
const rule = props.rule
|
|
216
|
+
return h('li', { className: 'serverest-flow-rule ' + statusClass(rule) },
|
|
217
|
+
codeOrLabel(rule.statusKey, rule.st, 'serverest-flow-status'),
|
|
218
|
+
codeOrLabel(rule.labelKey, rule.code, 'serverest-flow-name'),
|
|
219
|
+
rule.item !== undefined
|
|
220
|
+
? h('span', { className: 'serverest-flow-item' },
|
|
221
|
+
label('ui.flow.item_position', 'serverest-flow-item-label'),
|
|
222
|
+
h('span', { className: 'serverest-flow-item-value' }, String(rule.item)))
|
|
223
|
+
: null
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function StageRow (props) {
|
|
228
|
+
const { stage, model, expanded, onToggle } = props
|
|
229
|
+
const isFailed = model.failedStage === stage.name && stage.st === 'failed'
|
|
230
|
+
const expandable = !!stage.descriptionKey || (isFailed && !!model.whyKey)
|
|
231
|
+
const content = [
|
|
232
|
+
codeOrLabel(stage.statusKey, stage.st, 'serverest-flow-status'),
|
|
233
|
+
codeOrLabel(stage.labelKey, stage.name, 'serverest-flow-name'),
|
|
234
|
+
stage.dur !== undefined ? h('span', { className: 'serverest-flow-dur' }, stage.dur.toFixed(2) + ' ms') : null
|
|
235
|
+
]
|
|
236
|
+
const head = expandable
|
|
237
|
+
? h('button', { type: 'button', className: 'serverest-flow-row', 'aria-expanded': expanded ? 'true' : 'false', onClick: onToggle }, ...content)
|
|
238
|
+
: h('div', { className: 'serverest-flow-row' }, ...content)
|
|
239
|
+
const details = expandable && expanded
|
|
240
|
+
? h('div', { className: 'serverest-flow-details' },
|
|
241
|
+
stage.descriptionKey ? textBlock('ui.flow.stage_description_title', stage.descriptionKey, 'serverest-flow-block-description') : null,
|
|
242
|
+
isFailed && model.whyKey ? textBlock('ui.flow.why_title', model.whyKey, 'serverest-flow-block-why') : null,
|
|
243
|
+
isFailed && model.howKey ? textBlock('ui.flow.how_title', model.howKey, 'serverest-flow-block-how') : null)
|
|
244
|
+
: null
|
|
245
|
+
const rules = stage.rules.length
|
|
246
|
+
? h('ol', { className: 'serverest-flow-rules' }, stage.rules.map(rule => h(RuleRow, { key: rule.name, rule })))
|
|
247
|
+
: null
|
|
248
|
+
return h('li', { className: 'serverest-flow-stage ' + statusClass(stage) + (isFailed ? ' serverest-flow-stage-failed' : '') }, head, details, rules)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function Summary (props) {
|
|
252
|
+
const model = props.model
|
|
253
|
+
if (model.summary === 'stopped') {
|
|
254
|
+
const stage = model.stages.find(item => item.name === model.failedStage)
|
|
255
|
+
return h('p', { key: 'stopped-' + stage.name, className: 'serverest-flow-summary' },
|
|
256
|
+
label('ui.flow.stopped_at', 'serverest-flow-summary-label'),
|
|
257
|
+
codeOrLabel(stage.labelKey, stage.name, 'serverest-flow-summary-stage'))
|
|
258
|
+
}
|
|
259
|
+
const key = model.summary === 'outside' ? 'ui.flow.outside' : 'ui.flow.completed'
|
|
260
|
+
return h('p', { key, className: 'serverest-flow-summary' }, label(key, 'serverest-flow-summary-label'))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function FlowPanel (props) {
|
|
264
|
+
const { model, animate, showControls, slowMotion, onToggleSlowMotion, onReplay } = props
|
|
265
|
+
const executed = model.stages.filter(stage => stage.st !== 'not_run')
|
|
266
|
+
const [revealed, setRevealed] = React.useState(animate ? 0 : executed.length)
|
|
267
|
+
const [expanded, setExpanded] = React.useState(model.failedStage ? { [model.failedStage]: true } : {})
|
|
268
|
+
const done = revealed >= executed.length
|
|
269
|
+
|
|
270
|
+
React.useEffect(() => {
|
|
271
|
+
if (done) return undefined
|
|
272
|
+
if (!animate) {
|
|
273
|
+
setRevealed(executed.length)
|
|
274
|
+
return undefined
|
|
275
|
+
}
|
|
276
|
+
const timer = setTimeout(() => setRevealed(count => count + 1), stepDelay(executed[revealed]))
|
|
277
|
+
return () => clearTimeout(timer)
|
|
278
|
+
}, [revealed, animate, done])
|
|
279
|
+
|
|
280
|
+
const visible = model.stages.filter(stage => {
|
|
281
|
+
if (stage.st === 'not_run') return done
|
|
282
|
+
return executed.indexOf(stage) <= revealed
|
|
283
|
+
})
|
|
284
|
+
const active = done ? null : executed[revealed]
|
|
285
|
+
const listed = visible.filter(stage => stage !== active)
|
|
286
|
+
|
|
287
|
+
const segments = visible.map(stage => h('span', {
|
|
288
|
+
key: stage.name,
|
|
289
|
+
className: 'serverest-flow-segment ' + statusClass(stage) + (stage === active ? ' serverest-flow-active' : ''),
|
|
290
|
+
style: { flexGrow: stage.width }
|
|
291
|
+
}, codeOrLabel(stage.labelKey, stage.name, 'serverest-flow-segment-label')))
|
|
292
|
+
if (done) {
|
|
293
|
+
segments.push(h('span', { key: 'response', className: 'serverest-flow-segment serverest-flow-segment-response' },
|
|
294
|
+
label('ui.response', 'serverest-flow-segment-label'),
|
|
295
|
+
model.statusCode !== null ? h('span', { className: 'serverest-flow-segment-code' }, String(model.statusCode)) : null))
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const controls = showControls
|
|
299
|
+
? h('div', { className: 'serverest-flow-controls' },
|
|
300
|
+
h('label', { className: 'serverest-flow-slow-motion' },
|
|
301
|
+
h('input', { type: 'checkbox', checked: slowMotion, onChange: onToggleSlowMotion }),
|
|
302
|
+
label('ui.flow.slow_motion', 'serverest-flow-slow-motion-label')),
|
|
303
|
+
h('button', { type: 'button', className: 'serverest-flow-replay', onClick: onReplay }, text('ui.flow.replay')))
|
|
304
|
+
: null
|
|
305
|
+
|
|
306
|
+
return h('section', { className: 'serverest-flow' },
|
|
307
|
+
h('div', { className: 'serverest-flow-header' },
|
|
308
|
+
h('h4', { className: 'serverest-flow-title' }, text('ui.flow.title')),
|
|
309
|
+
done ? h(Summary, { key: 'summary-' + model.summary, model }) : null,
|
|
310
|
+
controls),
|
|
311
|
+
h('div', { className: 'serverest-flow-bar' }, segments),
|
|
312
|
+
done && model.summary === 'outside'
|
|
313
|
+
? h('p', { className: 'serverest-flow-outside' },
|
|
314
|
+
label(model.whyKey, 'serverest-flow-outside-why'),
|
|
315
|
+
label(model.howKey, 'serverest-flow-outside-how'))
|
|
316
|
+
: null,
|
|
317
|
+
h('ol', { className: 'serverest-flow-stages' }, listed.map(stage => h(StageRow, {
|
|
318
|
+
key: stage.name,
|
|
319
|
+
stage,
|
|
320
|
+
model,
|
|
321
|
+
expanded: !!expanded[stage.name],
|
|
322
|
+
onToggle: () => setExpanded(current => Object.assign({}, current, { [stage.name]: !current[stage.name] }))
|
|
323
|
+
})))
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function FlowRoot (props) {
|
|
328
|
+
const [run, setRun] = React.useState(0)
|
|
329
|
+
const [slowMotion, setSlowMotion] = React.useState(readSlowMotion)
|
|
330
|
+
const { header, status } = props
|
|
331
|
+
const model = buildFlowModel(parseStages(header), status)
|
|
332
|
+
if (!model) return null
|
|
333
|
+
const reduced = prefersReducedMotion()
|
|
334
|
+
return h(FlowPanel, {
|
|
335
|
+
// Remontar a cada resposta ou repetição evita que o React edite um nó já traduzido.
|
|
336
|
+
key: header + '|' + status + '|' + run,
|
|
337
|
+
model,
|
|
338
|
+
animate: slowMotion && !reduced,
|
|
339
|
+
showControls: !reduced,
|
|
340
|
+
slowMotion,
|
|
341
|
+
onToggleSlowMotion: event => {
|
|
342
|
+
const enabled = !!event.target.checked
|
|
343
|
+
saveSlowMotion(enabled)
|
|
344
|
+
setSlowMotion(enabled)
|
|
345
|
+
},
|
|
346
|
+
onReplay: () => setRun(count => count + 1)
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return { FlowBoundary, FlowRoot }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function serverestFlowPanelPlugin (system) {
|
|
354
|
+
const React = system && system.React
|
|
355
|
+
if (!React || !React.Component || !React.useState) return {}
|
|
356
|
+
const { FlowBoundary, FlowRoot } = createComponents(React)
|
|
357
|
+
const h = React.createElement
|
|
358
|
+
return {
|
|
359
|
+
wrapComponents: {
|
|
360
|
+
liveResponse: Original => function ServerestLiveResponse (props) {
|
|
361
|
+
const data = readResponse(props.response)
|
|
362
|
+
const panel = data
|
|
363
|
+
? h(FlowBoundary, { key: data.header + '|' + data.status }, h(FlowRoot, { header: data.header, status: data.status }))
|
|
364
|
+
: null
|
|
365
|
+
return h(React.Fragment, null, h(Original, props), panel)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
serverestFlowPanelPlugin.parseStages = parseStages
|
|
372
|
+
serverestFlowPanelPlugin.buildFlowModel = buildFlowModel
|
|
373
|
+
window.serverestFlowPanelPlugin = serverestFlowPanelPlugin
|
|
374
|
+
})()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/* Painel "Fluxo da requisição" abaixo da resposta do "Try it out" */
|
|
2
|
+
.swagger-ui .serverest-flow {
|
|
3
|
+
margin: 16px 0 8px;
|
|
4
|
+
padding: 14px 16px;
|
|
5
|
+
border: 1px solid #d8dde7;
|
|
6
|
+
border-radius: 8px;
|
|
7
|
+
background: #ffffff;
|
|
8
|
+
color: #1f2937;
|
|
9
|
+
font-family: sans-serif;
|
|
10
|
+
font-size: 13px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.swagger-ui .serverest-flow-header {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 8px 16px;
|
|
18
|
+
margin-bottom: 12px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.swagger-ui .serverest-flow-title {
|
|
22
|
+
margin: 0;
|
|
23
|
+
font-size: 14px;
|
|
24
|
+
font-weight: 700;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.swagger-ui .serverest-flow-summary {
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
gap: 6px;
|
|
30
|
+
margin: 0;
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.swagger-ui .serverest-flow-controls {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 12px;
|
|
38
|
+
margin-left: auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.swagger-ui .serverest-flow-slow-motion {
|
|
42
|
+
display: inline-flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
gap: 6px;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.swagger-ui .serverest-flow-replay {
|
|
49
|
+
padding: 4px 12px;
|
|
50
|
+
border: 1px solid #9ca3af;
|
|
51
|
+
border-radius: 4px;
|
|
52
|
+
background: #ffffff;
|
|
53
|
+
font-size: 12px;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.swagger-ui .serverest-flow-bar {
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-wrap: wrap;
|
|
60
|
+
gap: 3px;
|
|
61
|
+
min-height: 26px;
|
|
62
|
+
margin-bottom: 12px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.swagger-ui .serverest-flow-segment {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-basis: 0;
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
gap: 4px;
|
|
72
|
+
min-width: max-content;
|
|
73
|
+
min-height: 26px;
|
|
74
|
+
padding: 0 8px;
|
|
75
|
+
border-radius: 4px;
|
|
76
|
+
font-size: 11px;
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
animation: serverestFlowIn 220ms ease-out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.swagger-ui .serverest-flow-segment-response {
|
|
83
|
+
flex-grow: 0;
|
|
84
|
+
background: #1f2937;
|
|
85
|
+
color: #ffffff;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.swagger-ui .serverest-flow-segment.serverest-flow-active {
|
|
89
|
+
animation: serverestFlowPulse 900ms ease-in-out infinite;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.swagger-ui .serverest-flow-st-ok.serverest-flow-segment { background: #2f9e6b; color: #ffffff; }
|
|
93
|
+
.swagger-ui .serverest-flow-st-failed.serverest-flow-segment { background: #c53030; color: #ffffff; }
|
|
94
|
+
.swagger-ui .serverest-flow-st-skipped.serverest-flow-segment { background: #6b7280; color: #ffffff; }
|
|
95
|
+
.swagger-ui .serverest-flow-st-not_run.serverest-flow-segment { background: #e5e7eb; color: #4b5563; }
|
|
96
|
+
.swagger-ui .serverest-flow-st-unknown.serverest-flow-segment { background: #d1d5db; color: #1f2937; }
|
|
97
|
+
|
|
98
|
+
.swagger-ui .serverest-flow-outside {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-wrap: wrap;
|
|
101
|
+
gap: 4px;
|
|
102
|
+
margin: 0 0 12px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.swagger-ui .serverest-flow-stages,
|
|
106
|
+
.swagger-ui .serverest-flow-rules {
|
|
107
|
+
list-style: none;
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.swagger-ui .serverest-flow-stage {
|
|
113
|
+
border-top: 1px solid #eef1f5;
|
|
114
|
+
animation: serverestFlowIn 220ms ease-out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.swagger-ui .serverest-flow-row {
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: 10px;
|
|
121
|
+
width: 100%;
|
|
122
|
+
padding: 6px 0;
|
|
123
|
+
border: 0;
|
|
124
|
+
background: none;
|
|
125
|
+
color: inherit;
|
|
126
|
+
font: inherit;
|
|
127
|
+
text-align: left;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.swagger-ui button.serverest-flow-row {
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.swagger-ui .serverest-flow-status {
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
min-width: 11em;
|
|
137
|
+
padding: 1px 8px;
|
|
138
|
+
border-radius: 10px;
|
|
139
|
+
font-size: 11px;
|
|
140
|
+
text-align: center;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.swagger-ui .serverest-flow-st-ok > .serverest-flow-row .serverest-flow-status,
|
|
144
|
+
.swagger-ui .serverest-flow-rule.serverest-flow-st-ok .serverest-flow-status { background: #d1fae5; color: #065f46; }
|
|
145
|
+
.swagger-ui .serverest-flow-st-failed > .serverest-flow-row .serverest-flow-status,
|
|
146
|
+
.swagger-ui .serverest-flow-rule.serverest-flow-st-failed .serverest-flow-status { background: #fee2e2; color: #991b1b; }
|
|
147
|
+
.swagger-ui .serverest-flow-st-skipped > .serverest-flow-row .serverest-flow-status,
|
|
148
|
+
.swagger-ui .serverest-flow-rule.serverest-flow-st-skipped .serverest-flow-status { background: #e5e7eb; color: #374151; }
|
|
149
|
+
.swagger-ui .serverest-flow-st-not_run > .serverest-flow-row .serverest-flow-status,
|
|
150
|
+
.swagger-ui .serverest-flow-rule.serverest-flow-st-not_run .serverest-flow-status { background: #f3f4f6; color: #6b7280; }
|
|
151
|
+
|
|
152
|
+
.swagger-ui .serverest-flow-name {
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.swagger-ui .serverest-flow-dur {
|
|
157
|
+
margin-left: auto;
|
|
158
|
+
color: #6b7280;
|
|
159
|
+
font-variant-numeric: tabular-nums;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.swagger-ui .serverest-flow-stage-failed > .serverest-flow-row .serverest-flow-name {
|
|
163
|
+
color: #991b1b;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.swagger-ui .serverest-flow-details {
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
gap: 8px;
|
|
170
|
+
padding: 0 0 10px 22px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.swagger-ui .serverest-flow-block-title {
|
|
174
|
+
display: block;
|
|
175
|
+
margin-bottom: 2px;
|
|
176
|
+
font-size: 12px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.swagger-ui .serverest-flow-block-text {
|
|
180
|
+
margin: 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.swagger-ui .serverest-flow-block-why,
|
|
184
|
+
.swagger-ui .serverest-flow-block-how {
|
|
185
|
+
padding: 6px 10px;
|
|
186
|
+
border-left: 3px solid #c53030;
|
|
187
|
+
background: #fef2f2;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.swagger-ui .serverest-flow-block-how {
|
|
191
|
+
border-left-color: #2f9e6b;
|
|
192
|
+
background: #f0fdf4;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.swagger-ui .serverest-flow-rule {
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-wrap: wrap;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 10px;
|
|
200
|
+
padding: 4px 0 4px 22px;
|
|
201
|
+
color: #4b5563;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.swagger-ui .serverest-flow-item {
|
|
205
|
+
display: inline-flex;
|
|
206
|
+
gap: 4px;
|
|
207
|
+
margin-left: auto;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@keyframes serverestFlowIn {
|
|
211
|
+
0% { opacity: 0; }
|
|
212
|
+
100% { opacity: 1; }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@keyframes serverestFlowPulse {
|
|
216
|
+
0% { opacity: 0.45; }
|
|
217
|
+
50% { opacity: 1; }
|
|
218
|
+
100% { opacity: 0.45; }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@media (prefers-reduced-motion: reduce) {
|
|
222
|
+
.swagger-ui .serverest-flow-segment,
|
|
223
|
+
.swagger-ui .serverest-flow-stage,
|
|
224
|
+
.swagger-ui .serverest-flow-segment.serverest-flow-active {
|
|
225
|
+
animation: none;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.swagger-ui .serverest-flow-controls {
|
|
229
|
+
display: none;
|
|
230
|
+
}
|
|
231
|
+
}
|