diff --git a/core/version b/core/version index 1b87bf5..0d91a54 100644 --- a/core/version +++ b/core/version @@ -1 +1 @@ -0.2.29 +0.3.0 diff --git a/generator/frontend.go b/generator/frontend.go index 971c2c9..5091d58 100644 --- a/generator/frontend.go +++ b/generator/frontend.go @@ -24,16 +24,6 @@ func generateEmptyFrontend(_ *openapi3.T, conf GeneratorConfig) { func generateFrontend(spec *openapi3.T, conf GeneratorConfig) { generateOpenAPIDoc(conf) - if spec.Paths.Find("/events") == nil || (spec.Paths.Find("/events").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/events").Operations()[http.MethodGet].Tags, "builtin")) { - log.Debug().Msg("Generating default /events endpoint.") - - op := openapi3.NewOperation() - op.AddResponse(http.StatusOK, createOAPIResponse("The service support sse")) - updateOAPIOperation(op, "HandleEvents", "support for sse", "200") - spec.AddOperation("/events", http.MethodGet, op) - spec.AddOperation("/events", http.MethodPost, op) - } - // create folders restPath := filepath.Join(conf.OutputPath, "rest") frontendPath := filepath.Join(conf.OutputPath, "web") @@ -79,7 +69,6 @@ func generateFrontend(spec *openapi3.T, conf GeneratorConfig) { // files in pages directory fs.CopyWebFile("web/pages", restPath, "render.go", true) - createFileFromTemplate(filepath.Join(restPath, "progress.go"), "templates/web/pages/progress.go.tmpl", conf) createFileFromTemplate(filepath.Join(pagesPath, "localize.go"), "templates/web/pages/localize.go.tmpl", conf) if _, err := os.Stat(filepath.Join(pagesPath, "languages.templ")); errors.Is(err, os.ErrNotExist) { createFileFromTemplate(filepath.Join(pagesPath, "languages.templ"), "templates/web/pages/languages.templ.tmpl", conf) @@ -116,6 +105,19 @@ func generateFrontend(spec *openapi3.T, conf GeneratorConfig) { // files in public directory fs.CopyWebFile("web", publicPath, "README-public.md", false) + // support for events + if spec.Paths.Find("/events") != nil && spec.Paths.Find("/events").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/events").Operations()[http.MethodGet].Tags, "builtin") { + log.Debug().Msg("Generating default /events endpoint.") + createFileFromTemplate(filepath.Join(restPath, "progress.go"), "templates/web/pages/progress.go.tmpl", conf) + createFileFromTemplate(filepath.Join(restPath, "notice.go"), "templates/web/pages/notice.go.tmpl", conf) + + op := openapi3.NewOperation() + op.AddResponse(http.StatusOK, createOAPIResponse("The service support sse")) + updateOAPIOperation(op, "HandleEvents", "support for sse", "200") + spec.AddOperation("/events", http.MethodGet, op) + spec.AddOperation("/events", http.MethodPost, op) + } + log.Info().Msg("Created Frontend successfully.") } diff --git a/generator/handler.go b/generator/handler.go index ec60837..8fd7644 100644 --- a/generator/handler.go +++ b/generator/handler.go @@ -39,7 +39,7 @@ func generateHandlerFuncStub(op *openapi3.Operation, method string, path string, log.Warn().Msg("No summary found for endpoint: " + methodPath) } - conf.OperationID = xstrings.ToCamelCase(op.OperationID) + conf.OperationID = xstrings.FirstRuneToUpper(xstrings.ToCamelCase(op.OperationID)) if op.OperationID == "" { log.Error().Msg("No operation ID found for endpoint: " + methodPath) return conf, errors.New("no operation id, can't create function") @@ -121,6 +121,7 @@ func generateHandlerFuncStub(op *openapi3.Operation, method string, path string, } } + canBeEdited := true fileName := xstrings.FirstRuneToLower(xstrings.ToCamelCase(conf.OperationID)) + ".go" filePath := filepath.Join(config.Path, RestPkg, fileName) templateFile := "templates/rest/handlerFunc.go.tmpl" @@ -128,12 +129,15 @@ func generateHandlerFuncStub(op *openapi3.Operation, method string, path string, templateFile = "templates/rest/pageHandlerFunc.go.tmpl" } if conf.OperationID == "GetLive" { + canBeEdited = false templateFile = "templates/rest/getLive.go.tmpl" } if conf.OperationID == "GetInfo" { + canBeEdited = false templateFile = "templates/rest/getInfo.go.tmpl" } if conf.OperationID == "GetRobots" { + canBeEdited = false templateFile = "templates/rest/getRobots.go.tmpl" } if conf.OperationID == "GetIndex" { @@ -146,13 +150,13 @@ func generateHandlerFuncStub(op *openapi3.Operation, method string, path string, templateFile = "templates/rest/getContent.go.tmpl" } if conf.OperationID == "HandleEvents" { + canBeEdited = false templateFile = "templates/rest/handleEvents.go.tmpl" } - if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) { + log.Debug().Str("operation", conf.OperationID).Str("template", templateFile).Msg("Generate handler") + if _, err := os.Stat(filePath); !canBeEdited || errors.Is(err, os.ErrNotExist) { createFileFromTemplate(filePath, templateFile, conf) - } else { - log.Debug().Err(err).Str("template", templateFile).Msg("Creating handler failed") } // remove unused imports extCmd.RunCommand("goimports -w "+fileName, filepath.Join(config.Path, RestPkg)) diff --git a/generator/info.go b/generator/info.go index 4f15b76..1219371 100644 --- a/generator/info.go +++ b/generator/info.go @@ -26,7 +26,7 @@ func generateInfoFiles(spec *openapi3.T, serverConf ServerConfig) { createFileFromTemplate(filePath, templateFile, serverConf) } - if spec.Paths.Find("/infoz") == nil || (spec.Paths.Find("/infoz").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/infoz").Operations()[http.MethodGet].Tags, "builtin")) { + if spec.Paths.Find("/infoz") != nil && (spec.Paths.Find("/infoz").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/infoz").Operations()[http.MethodGet].Tags, "builtin")) { log.Debug().Msg("Generating default /infoz endpoint.") op := openapi3.NewOperation() diff --git a/generator/lifecycle.go b/generator/lifecycle.go index 339d3ae..6343d7f 100644 --- a/generator/lifecycle.go +++ b/generator/lifecycle.go @@ -11,7 +11,7 @@ import ( ) func generateLifecycleFiles(spec *openapi3.T, conf GeneratorConfig) { - if spec.Paths.Find("/livez") == nil || (spec.Paths.Find("/livez").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/livez").Operations()[http.MethodGet].Tags, "builtin")) { + if spec.Paths.Find("/livez") != nil && (spec.Paths.Find("/livez").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/livez").Operations()[http.MethodGet].Tags, "builtin")) { log.Debug().Msg("Generating default /livez endpoint.") op := openapi3.NewOperation() @@ -20,7 +20,7 @@ func generateLifecycleFiles(spec *openapi3.T, conf GeneratorConfig) { spec.AddOperation("/livez", http.MethodGet, op) } - if spec.Paths.Find("/readyz") == nil || (spec.Paths.Find("/readyz").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/readyz").Operations()[http.MethodGet].Tags, "builtin")) { + if spec.Paths.Find("/readyz") != nil && (spec.Paths.Find("/readyz").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/readyz").Operations()[http.MethodGet].Tags, "builtin")) { log.Debug().Msg("Generating default /readyz endpoint.") op := openapi3.NewOperation() @@ -30,7 +30,7 @@ func generateLifecycleFiles(spec *openapi3.T, conf GeneratorConfig) { spec.AddOperation("/readyz", http.MethodGet, op) } - if spec.Paths.Find("/robots.txt") == nil || (spec.Paths.Find("/robots.txt").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/robots.txt").Operations()[http.MethodGet].Tags, "builtin")) { + if spec.Paths.Find("/robots.txt") != nil && (spec.Paths.Find("/robots.txt").Operations()[http.MethodGet] != nil && slices.Contains(spec.Paths.Find("/robots.txt").Operations()[http.MethodGet].Tags, "builtin")) { log.Debug().Msg("Generating default /robots.txt endpoint.") op := openapi3.NewOperation() diff --git a/generator/templates.go b/generator/templates.go index ac4e666..e7f00e0 100644 --- a/generator/templates.go +++ b/generator/templates.go @@ -48,7 +48,7 @@ func createFileFromTemplate(filePath string, tmplPath string, config interface{} panic(tmplErr) } - log.Info().Msg("CREATE " + filePath) + log.Info().Str("template", templateName).Msg("CREATE " + filePath) } func createFileFromTemplates(filePath string, tmplPaths []string, config interface{}) { diff --git a/main.go b/main.go index 9432983..fe2cd2f 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ func main() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix // Set pretty logging on log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) - zerolog.SetGlobalLevel(zerolog.InfoLevel) + zerolog.SetGlobalLevel(zerolog.DebugLevel) // Export embed template filesystem to generator package generator.TmplFS = tmplFS diff --git a/templates/rest/handleEvents.go.tmpl b/templates/rest/handleEvents.go.tmpl index e21d8b7..802cb6f 100644 --- a/templates/rest/handleEvents.go.tmpl +++ b/templates/rest/handleEvents.go.tmpl @@ -12,5 +12,6 @@ var HandleEvents echo.HandlerFunc func init() { SseServer = sse.New() SseServer.CreateStream("progress") + SseServer.CreateStream("notice") HandleEvents = echo.WrapHandler(SseServer) } diff --git a/templates/web/images/favicon.ico b/templates/web/images/favicon.ico new file mode 100644 index 0000000..46d61fa Binary files /dev/null and b/templates/web/images/favicon.ico differ diff --git a/templates/web/pages/content.templ.tmpl b/templates/web/pages/content.templ.tmpl index 3cfca91..a0e1d44 100644 --- a/templates/web/pages/content.templ.tmpl +++ b/templates/web/pages/content.templ.tmpl @@ -6,4 +6,7 @@ import ( // the main content of the index page templ Content(lzr *i18n.Localizer) { + // enable the lines, if you want to use events for notices and a progress bar + //
+ //
} diff --git a/templates/web/pages/index.templ.tmpl b/templates/web/pages/index.templ.tmpl index 55f3f9b..d524360 100644 --- a/templates/web/pages/index.templ.tmpl +++ b/templates/web/pages/index.templ.tmpl @@ -12,6 +12,7 @@ templ Index(lzr *i18n.Localizer) { + index diff --git a/templates/web/pages/notice.go.tmpl b/templates/web/pages/notice.go.tmpl new file mode 100644 index 0000000..7c3419f --- /dev/null +++ b/templates/web/pages/notice.go.tmpl @@ -0,0 +1,37 @@ +// Don't edit this file, as it is generated by dredger +package rest + +import ( + "fmt" + + "github.com/r3labs/sse/v2" +) + +const bsSuccessNotice = `` + +func NoticeSuccessBootstrap(msg string) { + SseServer.Publish("notice", &sse.Event{ + Event: []byte("Notice"), + Data: []byte(fmt.Sprintf(bsSuccessNotice, msg)), + }) +} + +const bsWarningNotice = `` + +func NoticeWarningBootstrap(msg string) { + SseServer.Publish("notice", &sse.Event{ + Event: []byte("Notice"), + Data: []byte(fmt.Sprintf(bsWarningNotice, msg)), + }) + +} + +const bsErrorNotice = `` + +func NoticeErrorBootstrap(msg string) { + SseServer.Publish("notice", &sse.Event{ + Event: []byte("Notice"), + Data: []byte(fmt.Sprintf(bsErrorNotice, msg)), + }) + +} diff --git a/templates/web/pages/progress.go.tmpl b/templates/web/pages/progress.go.tmpl index c19dc4b..9afb427 100644 --- a/templates/web/pages/progress.go.tmpl +++ b/templates/web/pages/progress.go.tmpl @@ -1,4 +1,4 @@ -// Edit this file, as it is a specific handler function for your service +// Don't edit this file, as it is generated by dredger package rest import ( @@ -11,17 +11,22 @@ import ( ) // learn medium duration -// ToDo: use a map for more than one progress case -var durationSum int = core.AppConfig.ProgressDuration // initial duration in deciseconds -var durationNb int = 1 +var durationSum map[string]int = map[string]int{"": core.AppConfig.ProgressDuration} // initial duration in deciseconds +var durationNb map[string]int = map[string]int{"": 1} + +// InitialDuration sets the expected duration time in deciseconds +func InitialDuration(context string, duration int) { + durationSum[context] = duration + durationNb[context] = 1 +} const picoNull = `` const picoEndless = `` const picoProgress = `` const picoEmpty = `
` -func ProgressPico(f func()) { - duration := durationSum / durationNb +func ProgressPico(context string, f func()) { + duration := durationSum[context] / durationNb[context] c1 := make(chan bool) go func() { f() @@ -58,22 +63,35 @@ func ProgressPico(f func()) { }) // update medium duration - durationSum += v - durationNb += 1 + durationSum[context] += v + durationNb[context] += 1 // avoid overflow of durationNb - if durationNb > 10000 { - durationSum = durationSum / durationNb - durationNb = 1 + if durationNb[context] > 10000 { + durationSum[context] = durationSum[context] / durationNb[context] + durationNb[context] = 1 } } -const bsNull = `
` -const bsEndless = `
` -const bsProgress = `
` +const styleProgress = ` style="--bs-progress-border-radius:0px;--bs-progress-height:15px;"` +const styleProgressBar = `--bs-progress-bar-bg:#005b7f;` + +const bsNull = `
` +const bsEndless = `
%s
` +const bsProgress = `
%s
` const bsEmpty = `
` -func ProgressBootstrap(f func()) { - duration := durationSum / durationNb +func ProgressBootstrap(context string, f func(), labels ...string) { + duration := durationSum[context] / durationNb[context] + + if len(labels) == 0 { + labels = append(labels, "") + } + if len(labels) == 1 { + labels = append(labels, "") + } + msg := labels[0] + nbOfIntervals := len(labels) - 1 + c1 := make(chan bool) go func() { f() @@ -91,15 +109,16 @@ func ProgressBootstrap(f func()) { ready = true case <-time.After(500 * time.Millisecond): v = v + 5 - if v > duration { + if v >= duration { SseServer.Publish("progress", &sse.Event{ Event: []byte("Progress"), - Data: []byte(bsEndless), + Data: []byte(fmt.Sprintf(bsEndless, msg)), }) } else { + label := labels[(v/(duration/nbOfIntervals))+1] SseServer.Publish("progress", &sse.Event{ Event: []byte("Progress"), - Data: []byte(fmt.Sprintf(bsProgress, v, v)), + Data: []byte(fmt.Sprintf(bsProgress, v, v, label)), }) } } @@ -110,11 +129,11 @@ func ProgressBootstrap(f func()) { }) // update medium duration - durationSum += v - durationNb += 1 + durationSum[context] += v + durationNb[context] += 1 // avoid overflow of durationNb - if durationNb > 10000 { - durationSum = durationSum / durationNb - durationNb = 1 + if durationNb[context] > 10000 { + durationSum[context] = durationSum[context] / durationNb[context] + durationNb[context] = 1 } }