feat(healthchecks): store history and nice code editor for scripts

This commit is contained in:
Tine 2024-02-21 23:15:21 +01:00
parent 33e8d2091d
commit 3a8badb61b
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
56 changed files with 39003 additions and 109 deletions

View file

@ -5,6 +5,7 @@
"watchexec@latest",
"tailwindcss@latest",
"flyctl@latest",
"just@latest"
"just@latest",
"nodePackages.npm@latest"
]
}

View file

@ -61,6 +61,20 @@
}
}
},
"nodePackages.npm@latest": {
"last_modified": "2024-02-15T12:53:33Z",
"resolved": "github:NixOS/nixpkgs/085589047343aad800c4d305cf7b98e8a3d51ae2#nodePackages.npm",
"source": "devbox-search",
"version": "10.4.0",
"systems": {
"aarch64-linux": {
"store_path": "/nix/store/06ywqfygg4xc4rwvp40ksrrvsspnpxyl-npm-10.4.0"
},
"x86_64-linux": {
"store_path": "/nix/store/p5hi9rp2qhhfw2ih4wgb1waplmwrkjqc-npm-10.4.0"
}
}
},
"tailwindcss@latest": {
"last_modified": "2024-01-27T14:55:31Z",
"resolved": "github:NixOS/nixpkgs/160b762eda6d139ac10ae081f8f78d640dd523eb#tailwindcss",

View file

@ -0,0 +1,11 @@
package activities
import "code.tjo.space/mentos1386/zdravko/internal/config"
type Activities struct {
config *config.WorkerConfig
}
func NewActivities(config *config.WorkerConfig) *Activities {
return &Activities{config: config}
}

View file

@ -1,10 +1,14 @@
package activities
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"code.tjo.space/mentos1386/zdravko/pkg/api"
"code.tjo.space/mentos1386/zdravko/pkg/k6"
)
@ -13,33 +17,57 @@ type HealtcheckParam struct {
}
type HealthcheckResult struct {
StatusCode int
Success bool
Note string
}
func Healthcheck(ctx context.Context, param HealtcheckParam) (*HealthcheckResult, error) {
statusCode := http.StatusOK // FIXME
func (a *Activities) Healthcheck(ctx context.Context, param HealtcheckParam) (*HealthcheckResult, error) {
execution := k6.NewExecution(slog.Default(), param.Script)
err := execution.Run(ctx)
result, err := execution.Run(ctx)
if err != nil {
return nil, err
}
return &HealthcheckResult{StatusCode: statusCode}, nil
return &HealthcheckResult{Success: result.Success, Note: result.Note}, nil
}
type HealtcheckAddToHistoryParam struct {
Id string
Success bool
StatusCode int
Slug string
Status string
Note string
}
type HealthcheckAddToHistoryResult struct {
}
func HealthcheckAddToHistory(ctx context.Context, param HealtcheckAddToHistoryParam) (*HealthcheckAddToHistoryResult, error) {
func (a *Activities) HealthcheckAddToHistory(ctx context.Context, param HealtcheckAddToHistoryParam) (*HealthcheckAddToHistoryResult, error) {
url := fmt.Sprintf("%s/api/v1/healthchecks/%s/history", a.config.ApiUrl, param.Slug)
body := api.ApiV1HealthchecksHistoryPOSTBody{
Status: param.Status,
Note: param.Note,
}
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := api.NewRequest(http.MethodPost, url, a.config.Token, bytes.NewReader(jsonBody))
if err != nil {
return nil, err
}
response, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode)
}
return &HealthcheckAddToHistoryResult{}, nil
}

View file

@ -6,6 +6,7 @@ import (
"code.tjo.space/mentos1386/zdravko/internal/models"
"code.tjo.space/mentos1386/zdravko/internal/services"
"code.tjo.space/mentos1386/zdravko/pkg/api"
"github.com/labstack/echo/v4"
)
@ -35,6 +36,12 @@ func (h *BaseHandler) ApiV1HealthchecksHistoryPOST(c echo.Context) error {
slug := c.Param("slug")
var body api.ApiV1HealthchecksHistoryPOSTBody
err := (&echo.DefaultBinder{}).BindBody(c, &body)
if err != nil {
return err
}
healthcheck, err := services.GetHealthcheck(ctx, h.query, slug)
if err != nil {
return err
@ -42,7 +49,8 @@ func (h *BaseHandler) ApiV1HealthchecksHistoryPOST(c echo.Context) error {
err = h.query.Healthcheck.History.Model(healthcheck).Append(
&models.HealthcheckHistory{
Status: "UP",
Status: body.Status,
Note: body.Note,
})
if err != nil {
return err

View file

@ -44,7 +44,8 @@ type Cronjob struct {
type HealthcheckHistory struct {
gorm.Model
Healthcheck Healthcheck `gorm:"foreignkey:ID"`
Status string
Status string // SUCCESS, FAIL, ERROR
Note string
}
type CronjobHistory struct {

View file

@ -27,10 +27,12 @@ func StartHealthcheck(ctx context.Context, t client.Client, healthcheck *models.
log.Println("Starting Healthcheck Workflow")
args := make([]interface{}, 0)
args = append(args, workflows.HealthcheckWorkflowParam{Script: healthcheck.Script})
args = append(args, workflows.HealthcheckWorkflowParam{Script: healthcheck.Script, Slug: healthcheck.Slug})
id := "healthcheck-" + healthcheck.Slug
fakeWorkflows := workflows.NewWorkflows(nil)
for _, group := range healthcheck.WorkerGroups {
_, err := t.ScheduleClient().Create(ctx, client.ScheduleOptions{
ID: id + "-" + group,
@ -39,7 +41,7 @@ func StartHealthcheck(ctx context.Context, t client.Client, healthcheck *models.
},
Action: &client.ScheduleWorkflowAction{
ID: id + "-" + group,
Workflow: workflows.HealthcheckWorkflowDefinition,
Workflow: fakeWorkflows.HealthcheckWorkflowDefinition,
Args: args,
TaskQueue: group,
RetryPolicy: &temporal.RetryPolicy{

View file

@ -31,7 +31,7 @@ func ConnectServerToTemporal(cfg *config.ServerConfig) (client.Client, error) {
provider := &AuthHeadersProvider{token}
// Try to connect to the Temporal Server
return retry.Retry(5, 6*time.Second, func() (client.Client, error) {
return retry.Retry(10, 3*time.Second, func() (client.Client, error) {
return client.Dial(client.Options{
HostPort: cfg.Temporal.ServerHost,
HeadersProvider: provider,

View file

@ -9,20 +9,38 @@ import (
type HealthcheckWorkflowParam struct {
Script string
Slug string
}
func HealthcheckWorkflowDefinition(ctx workflow.Context, param HealthcheckWorkflowParam) error {
func (w *Workflows) HealthcheckWorkflowDefinition(ctx workflow.Context, param HealthcheckWorkflowParam) error {
options := workflow.ActivityOptions{
StartToCloseTimeout: 10 * time.Second,
}
ctx = workflow.WithActivityOptions(ctx, options)
activityParam := activities.HealtcheckParam{
heatlcheckParam := activities.HealtcheckParam{
Script: param.Script,
}
var result *activities.HealthcheckResult
err := workflow.ExecuteActivity(ctx, activities.Healthcheck, activityParam).Get(ctx, &result)
var healthcheckResult *activities.HealthcheckResult
err := workflow.ExecuteActivity(ctx, w.activities.Healthcheck, heatlcheckParam).Get(ctx, &healthcheckResult)
if err != nil {
return err
}
status := "failure"
if healthcheckResult.Success {
status = "success"
}
historyParam := activities.HealtcheckAddToHistoryParam{
Slug: param.Slug,
Status: status,
Note: healthcheckResult.Note,
}
var historyResult *activities.HealthcheckAddToHistoryResult
err = workflow.ExecuteActivity(ctx, w.activities.HealthcheckAddToHistory, historyParam).Get(ctx, &historyResult)
if err != nil {
return err
}

View file

@ -0,0 +1,13 @@
package workflows
import (
"code.tjo.space/mentos1386/zdravko/internal/activities"
)
type Workflows struct {
activities *activities.Activities
}
func NewWorkflows(a *activities.Activities) *Workflows {
return &Workflows{activities: a}
}

View file

@ -35,7 +35,7 @@ run-temporal:
# Test
test:
go test -v -cover ./...
go test -v ./...
# Generates new jwt key pair
generate-jwt-key:
@ -95,6 +95,20 @@ _htmx-download:
mkdir -p {{STATIC_DIR}}/js
curl -sLo {{STATIC_DIR}}/js/htmx.min.js https://unpkg.com/htmx.org/dist/htmx.min.js
_monaco-download:
rm -rf {{STATIC_DIR}}/monaco
npm install monaco-editor@0.46.0
mv node_modules/monaco-editor/min {{STATIC_DIR}}/monaco
rm -rf node_modules
# We onlt care about javascript language
find {{STATIC_DIR}}/monaco/vs/basic-languages/ \
-type d \
-not -name 'javascript' \
-not -name 'typescript' \
-not -name 'basic-languages' \
-prune -exec rm -rf {} \;
_feather-icons-download:
mkdir -p {{STATIC_DIR}}/icons
curl -sLo {{STATIC_DIR}}/icons/feather-sprite.svg https://unpkg.com/feather-icons/dist/feather-sprite.svg

17
package-lock.json generated Normal file
View file

@ -0,0 +1,17 @@
{
"name": "zdravko",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"monaco-editor": "^0.46.0"
}
},
"node_modules/monaco-editor": {
"version": "0.46.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.46.0.tgz",
"integrity": "sha512-ADwtLIIww+9FKybWscd7OCfm9odsFYHImBRI1v9AviGce55QY8raT+9ihH8jX/E/e6QVSGM+pKj4jSUSRmALNQ=="
}
}
}

5
package.json Normal file
View file

@ -0,0 +1,5 @@
{
"dependencies": {
"monaco-editor": "^0.46.0"
}
}

23
pkg/api/api.go Normal file
View file

@ -0,0 +1,23 @@
package api
import (
"io"
"net/http"
)
func NewRequest(method string, urlString string, token string, body io.Reader) (*http.Request, error) {
headers := http.Header{}
headers.Add("Authorization", "Bearer "+token)
if body != nil {
headers.Add("Content-Type", "application/json")
}
req, err := http.NewRequest(method, urlString, body)
if err != nil {
return nil, err
}
req.Header = headers
return req, nil
}

6
pkg/api/healthchecks.go Normal file
View file

@ -0,0 +1,6 @@
package api
type ApiV1HealthchecksHistoryPOSTBody struct {
Status string `json:"status"`
Note string `json:"note"`
}

View file

@ -12,8 +12,6 @@ import (
"time"
"github.com/sirupsen/logrus"
"go.k6.io/k6/errext"
"go.k6.io/k6/errext/exitcodes"
"go.k6.io/k6/event"
"go.k6.io/k6/execution"
"go.k6.io/k6/execution/local"
@ -39,6 +37,11 @@ const (
waitForTracerProviderStopTimeout = 3 * time.Minute
)
type ExecutionResult struct {
Note string
Success bool
}
type Execution struct {
FS fsext.Fs
Env map[string]string
@ -53,6 +56,7 @@ type Execution struct {
func NewExecution(logger *slog.Logger, script string) *Execution {
loggerCompat := logrus.StandardLogger()
loggerCompat.SetLevel(logrus.DebugLevel)
return &Execution{
FS: fsext.NewOsFs(),
@ -136,10 +140,7 @@ func (e *Execution) setupTracerProvider(ctx context.Context, test *loadedAndConf
return nil
}
func (e *Execution) Run(ctx context.Context) error {
var err error
var logger logrus.FieldLogger = logrus.StandardLogger()
func (e *Execution) Run(ctx context.Context) (result *ExecutionResult, err error) {
globalCtx, globalCancel := context.WithCancel(ctx)
defer globalCancel()
@ -151,7 +152,7 @@ func (e *Execution) Run(ctx context.Context) error {
// runCtx is used for the test run execution and is created with the special
// execution.NewTestRunContext() function so that it can be aborted even
// from sub-contexts while also attaching a reason for the abort.
runCtx, runAbort := execution.NewTestRunContext(lingerCtx, logger)
runCtx, runAbort := execution.NewTestRunContext(lingerCtx, e.LoggerCompat)
emitEvent := func(evt *event.Event) func() {
waitDone := e.Events.Emit(evt)
@ -159,7 +160,7 @@ func (e *Execution) Run(ctx context.Context) error {
waitCtx, waitCancel := context.WithTimeout(globalCtx, waitEventDoneTimeout)
defer waitCancel()
if werr := waitDone(waitCtx); werr != nil {
logger.WithError(werr).Warn()
e.Logger.With(werr).Warn("")
}
}
}
@ -175,17 +176,17 @@ func (e *Execution) Run(ctx context.Context) error {
configuredTest, controller, err := e.loadLocalTest()
if err != nil {
return err
return nil, err
}
if err = e.setupTracerProvider(globalCtx, configuredTest); err != nil {
return err
return nil, err
}
waitTracesFlushed := func() {
ctx, cancel := context.WithTimeout(globalCtx, waitForTracerProviderStopTimeout)
defer cancel()
if tpErr := configuredTest.preInitState.TracerProvider.Shutdown(ctx); tpErr != nil {
logger.Errorf("The tracer provider didn't stop gracefully: %v", tpErr)
e.Logger.Error("The tracer provider didn't stop gracefully", tpErr)
}
}
@ -193,14 +194,14 @@ func (e *Execution) Run(ctx context.Context) error {
conf := configuredTest.config
testRunState, err := configuredTest.buildTestRunState(conf)
if err != nil {
return err
return nil, err
}
// Create a local execution scheduler wrapping the runner.
logger.Debug("Initializing the execution scheduler...")
e.Logger.Debug("Initializing the execution scheduler...")
execScheduler, err := execution.NewScheduler(testRunState, controller)
if err != nil {
return err
return nil, err
}
backgroundProcesses := &sync.WaitGroup{}
@ -210,9 +211,9 @@ func (e *Execution) Run(ctx context.Context) error {
// executionPlan := execScheduler.GetExecutionPlan()
outputs := []output.Output{}
metricsEngine, err := engine.NewMetricsEngine(testRunState.Registry, logger)
metricsEngine, err := engine.NewMetricsEngine(testRunState.Registry, e.LoggerCompat)
if err != nil {
return err
return nil, err
}
// We'll need to pipe metrics to the MetricsEngine and process them if any
@ -223,7 +224,7 @@ func (e *Execution) Run(ctx context.Context) error {
if shouldProcessMetrics {
err = metricsEngine.InitSubMetricsAndThresholds(conf, testRunState.RuntimeOptions.NoThresholds.Bool)
if err != nil {
return err
return nil, err
}
// We'll need to pipe metrics to the MetricsEngine if either the
// thresholds or the end-of-test summary are enabled.
@ -234,7 +235,7 @@ func (e *Execution) Run(ctx context.Context) error {
executionState := execScheduler.GetState()
if !testRunState.RuntimeOptions.NoSummary.Bool {
defer func() {
logger.Debug("Generating the end-of-test summary...")
e.Logger.Debug("Generating the end-of-test summary...")
summaryResult, hsErr := configuredTest.initRunner.HandleSummary(globalCtx, &lib.Summary{
Metrics: metricsEngine.ObservedMetrics,
RootGroup: testRunState.Runner.GetDefaultGroup(),
@ -249,21 +250,21 @@ func (e *Execution) Run(ctx context.Context) error {
for _, o := range summaryResult {
_, err := io.Copy(os.Stdout, o)
if err != nil {
logger.WithError(err).Error("failed to write summary output")
e.Logger.With(err).Error("failed to write summary output")
}
}
}
if hsErr != nil {
logger.WithError(hsErr).Error("failed to handle the end-of-test summary")
e.Logger.With(hsErr).Error("failed to handle the end-of-test summary")
}
}()
}
waitInitDone := emitEvent(&event.Event{Type: event.Init})
outputManager := output.NewManager(outputs, logger, func(err error) {
outputManager := output.NewManager(outputs, e.LoggerCompat, func(err error) {
if err != nil {
logger.WithError(err).Error("Received error to stop from output")
e.Logger.With(err).Error("Received error to stop from output")
}
// TODO: attach run status and exit code?
runAbort(err)
@ -271,10 +272,10 @@ func (e *Execution) Run(ctx context.Context) error {
samples := make(chan metrics.SampleContainer, configuredTest.config.MetricSamplesBufferSize.Int64)
waitOutputsFlushed, stopOutputs, err := outputManager.Start(samples)
if err != nil {
return err
return nil, err
}
defer func() {
logger.Debug("Stopping outputs...")
e.Logger.Debug("Stopping outputs...")
// We call waitOutputsFlushed() below because the threshold calculations
// need all of the metrics to be sent to the MetricsEngine before we can
// calculate them one last time. We need the threshold calculated here,
@ -286,35 +287,29 @@ func (e *Execution) Run(ctx context.Context) error {
finalizeThresholds := metricsEngine.StartThresholdCalculations(
metricsIngester, runAbort, executionState.GetCurrentTestRunDuration,
)
handleFinalThresholdCalculation := func() {
// This gets called after the Samples channel has been closed and
// the OutputManager has flushed all of the cached samples to
// outputs (including MetricsEngine's ingester). So we are sure
// there won't be any more metrics being sent.
logger.Debug("Finalizing thresholds...")
breachedThresholds := finalizeThresholds()
if len(breachedThresholds) == 0 {
return
}
tErr := errext.WithAbortReasonIfNone(
errext.WithExitCodeIfNone(
fmt.Errorf("thresholds on metrics '%s' have been crossed", strings.Join(breachedThresholds, ", ")),
exitcodes.ThresholdsHaveFailed,
), errext.AbortedByThresholdsAfterTestEnd)
if err == nil {
err = tErr
} else {
logger.WithError(tErr).Debug("Crossed thresholds, but test already exited with another error")
}
}
if finalizeThresholds != nil {
defer handleFinalThresholdCalculation()
defer func() {
// This gets called after the Samples channel has been closed and
// the OutputManager has flushed all of the cached samples to
// outputs (including MetricsEngine's ingester). So we are sure
// there won't be any more metrics being sent.
e.Logger.Debug("Finalizing thresholds...")
breachedThresholds := finalizeThresholds()
if len(breachedThresholds) == 0 {
return
}
if err == nil {
result = &ExecutionResult{
Success: false,
Note: fmt.Sprintf("thresholds on metrics '%s' have been crossed", strings.Join(breachedThresholds, ", ")),
}
}
}()
}
}
defer func() {
logger.Debug("Waiting for metrics and traces processing to finish...")
e.Logger.Debug("Waiting for metrics and traces processing to finish...")
close(samples)
ww := [...]func(){
@ -332,13 +327,13 @@ func (e *Execution) Run(ctx context.Context) error {
}
wg.Wait()
logger.Debug("Metrics and traces processing finished!")
e.Logger.Debug("Metrics and traces processing finished!")
}()
// Initialize the VUs and executors
stopVUEmission, err := execScheduler.Init(runCtx, samples)
if err != nil {
return err
return nil, err
}
defer stopVUEmission()
@ -357,16 +352,16 @@ func (e *Execution) Run(ctx context.Context) error {
// Check what the execScheduler.Run() error is.
if err != nil {
err = common.UnwrapGojaInterruptedError(err)
logger.WithError(err).Debug("Test finished with an error")
return err
e.Logger.With(err).Debug("Test finished with an error")
return nil, err
}
// Warn if no iterations could be completed.
if executionState.GetFullIterationCount() == 0 {
logger.Warn("No script iterations fully finished, consider making the test duration longer")
e.Logger.Warn("No script iterations fully finished, consider making the test duration longer")
}
logger.Debug("Test finished cleanly")
e.Logger.Debug("Test finished cleanly")
return nil
return &ExecutionResult{Success: true, Note: ""}, nil
}

View file

@ -3,12 +3,23 @@ package k6
import (
"context"
"log/slog"
"os"
"testing"
)
func getLogger() *slog.Logger {
opts := &slog.HandlerOptions{
Level: slog.LevelDebug,
}
handler := slog.NewTextHandler(os.Stdout, opts)
return slog.New(handler)
}
func TestK6Success(t *testing.T) {
ctx := context.Background()
logger := slog.Default()
logger := getLogger()
script := `
import http from 'k6/http';
@ -27,15 +38,18 @@ export default function () {
execution := NewExecution(logger, script)
err := execution.Run(ctx)
result, err := execution.Run(ctx)
if err != nil {
t.Errorf("Error starting execution: %v", err)
}
if result != nil {
t.Logf("Result: %v", result)
}
}
func TestK6Fail(t *testing.T) {
ctx := context.Background()
logger := slog.Default()
logger := getLogger()
script := `
import http from 'k6/http';
@ -55,8 +69,11 @@ export default function () {
execution := NewExecution(logger, script)
err := execution.Run(ctx)
result, err := execution.Run(ctx)
if err != nil {
t.Errorf("Error starting execution: %v", err)
}
if result != nil {
t.Logf("Result: %v", result)
}
}

View file

@ -98,7 +98,7 @@ func (s *Server) Start() error {
apiv1 := s.echo.Group("/api/v1")
apiv1.Use(h.Authenticated)
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
apiv1.POST("/healthcheck/:slug/history", h.ApiV1HealthchecksHistoryPOST)
apiv1.POST("/healthchecks/:slug/history", h.ApiV1HealthchecksHistoryPOST)
// Error handler
s.echo.HTTPErrorHandler = func(err error, c echo.Context) {

View file

@ -11,6 +11,7 @@ import (
"code.tjo.space/mentos1386/zdravko/internal/config"
"code.tjo.space/mentos1386/zdravko/internal/temporal"
"code.tjo.space/mentos1386/zdravko/internal/workflows"
"code.tjo.space/mentos1386/zdravko/pkg/api"
"code.tjo.space/mentos1386/zdravko/pkg/retry"
"github.com/pkg/errors"
"go.temporal.io/sdk/worker"
@ -23,13 +24,10 @@ type ConnectionConfig struct {
}
func getConnectionConfig(token string, apiUrl string) (*ConnectionConfig, error) {
url := apiUrl + "/api/v1/workers/connect"
req, err := http.NewRequest(http.MethodGet, url, nil)
req, err := api.NewRequest(http.MethodGet, apiUrl+"/api/v1/workers/connect", token, nil)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", "Bearer "+token)
return retry.Retry(10, 3*time.Second, func() (*ConnectionConfig, error) {
res, err := http.DefaultClient.Do(req)
@ -82,14 +80,17 @@ func (w *Worker) Start() error {
}
// Create a new Worker
// TODO: Maybe identify by region or something?
w.worker = worker.New(temporalClient, config.Group, worker.Options{})
workerActivities := activities.NewActivities(w.cfg)
workerWorkflows := workflows.NewWorkflows(workerActivities)
// Register Workflows
w.worker.RegisterWorkflow(workflows.HealthcheckWorkflowDefinition)
w.worker.RegisterWorkflow(workerWorkflows.HealthcheckWorkflowDefinition)
// Register Activities
w.worker.RegisterActivity(activities.Healthcheck)
w.worker.RegisterActivity(workerActivities.Healthcheck)
w.worker.RegisterActivity(workerActivities.HealthcheckAddToHistory)
return w.worker.Run(worker.InterruptCh())
}

View file

@ -668,6 +668,10 @@ video {
display: grid;
}
.hidden {
display: none;
}
.h-20 {
height: 5rem;
}
@ -688,6 +692,10 @@ video {
height: 2rem;
}
.h-96 {
height: 24rem;
}
.w-20 {
width: 5rem;
}
@ -1309,10 +1317,3 @@ video {
.rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) {
text-align: right;
}
@media (prefers-color-scheme: dark) {
.dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
}

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.de",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["Array","Boolescher Wert","Klasse","Konstante","Konstruktor","Enumeration","Enumerationsmember","Ereignis","Feld","Datei","Funktion","Schnittstelle","Schl\xFCssel","Methode","Modul","Namespace","NULL","Zahl","Objekt","Operator","Paket","Eigenschaft","Zeichenfolge","Struktur","Typparameter","Variable","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.de.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.es",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["matriz","booleano","clase","constante","constructor","enumeraci\xF3n","miembro de la enumeraci\xF3n","evento","campo","archivo","funci\xF3n","interfaz","clave","m\xE9todo","m\xF3dulo","espacio de nombres","NULL","n\xFAmero","objeto","operador","paquete","propiedad","cadena","estructura","par\xE1metro de tipo","variable","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.es.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.fr",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["tableau","bool\xE9en","classe","constante","constructeur","\xE9num\xE9ration","membre d'\xE9num\xE9ration","\xE9v\xE9nement","champ","fichier","fonction","interface","cl\xE9","m\xE9thode","module","espace de noms","NULL","nombre","objet","op\xE9rateur","package","propri\xE9t\xE9","cha\xEEne","struct","param\xE8tre de type","variable","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.fr.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.it",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["matrice","valore booleano","classe","costante","costruttore","enumerazione","membro di enumerazione","evento","campo","file","funzione","interfaccia","chiave","metodo","modulo","spazio dei nomi","Null","numero","oggetto","operatore","pacchetto","propriet\xE0","stringa","struct","parametro di tipo","variabile","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.it.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.ja",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\u914D\u5217","\u30D6\u30FC\u30EB\u5024","\u30AF\u30E9\u30B9","\u5B9A\u6570","\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u30FC","\u5217\u6319\u578B","\u5217\u6319\u578B\u30E1\u30F3\u30D0\u30FC","\u30A4\u30D9\u30F3\u30C8","\u30D5\u30A3\u30FC\u30EB\u30C9","\u30D5\u30A1\u30A4\u30EB","\u95A2\u6570","\u30A4\u30F3\u30BF\u30FC\u30D5\u30A7\u30A4\u30B9","\u30AD\u30FC","\u30E1\u30BD\u30C3\u30C9","\u30E2\u30B8\u30E5\u30FC\u30EB","\u540D\u524D\u7A7A\u9593","NULL","\u6570\u5024","\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8","\u6F14\u7B97\u5B50","\u30D1\u30C3\u30B1\u30FC\u30B8","\u30D7\u30ED\u30D1\u30C6\u30A3","\u6587\u5B57\u5217","\u69CB\u9020\u4F53","\u578B\u30D1\u30E9\u30E1\u30FC\u30BF\u30FC","\u5909\u6570","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.ja.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["array","boolean","class","constant","constructor","enumeration","enumeration member","event","field","file","function","interface","key","method","module","namespace","null","number","object","operator","package","property","string","struct","type parameter","variable","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.ko",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\uBC30\uC5F4","\uBD80\uC6B8","\uD074\uB798\uC2A4","\uC0C1\uC218","\uC0DD\uC131\uC790","\uC5F4\uAC70\uD615","\uC5F4\uAC70\uD615 \uBA64\uBC84","\uC774\uBCA4\uD2B8","\uD544\uB4DC","\uD30C\uC77C","\uD568\uC218","\uC778\uD130\uD398\uC774\uC2A4","\uD0A4","\uBA54\uC11C\uB4DC","\uBAA8\uB4C8","\uB124\uC784\uC2A4\uD398\uC774\uC2A4","Null","\uC22B\uC790","\uAC1C\uCCB4","\uC5F0\uC0B0\uC790","\uD328\uD0A4\uC9C0","\uC18D\uC131","\uBB38\uC790\uC5F4","\uAD6C\uC870\uCCB4","\uD615\uC2DD \uB9E4\uAC1C \uBCC0\uC218","\uBCC0\uC218","{0}({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.ko.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.ru",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\u043C\u0430\u0441\u0441\u0438\u0432","\u043B\u043E\u0433\u0438\u0447\u0435\u0441\u043A\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435","\u043A\u043B\u0430\u0441\u0441","\u043A\u043E\u043D\u0441\u0442\u0430\u043D\u0442\u0430","\u043A\u043E\u043D\u0441\u0442\u0440\u0443\u043A\u0442\u043E\u0440","\u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u0438\u0435","\u044D\u043B\u0435\u043C\u0435\u043D\u0442 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u0438\u044F","\u0441\u043E\u0431\u044B\u0442\u0438\u0435","\u043F\u043E\u043B\u0435","\u0444\u0430\u0439\u043B","\u0444\u0443\u043D\u043A\u0446\u0438\u044F","\u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441","\u043A\u043B\u044E\u0447","\u043C\u0435\u0442\u043E\u0434","\u043C\u043E\u0434\u0443\u043B\u044C","\u043F\u0440\u043E\u0441\u0442\u0440\u0430\u043D\u0441\u0442\u0432\u043E \u0438\u043C\u0435\u043D","NULL","\u0447\u0438\u0441\u043B\u043E","\u043E\u0431\u044A\u0435\u043A\u0442","\u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440","\u043F\u0430\u043A\u0435\u0442","\u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E","\u0441\u0442\u0440\u043E\u043A\u0430","\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430","\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0442\u0438\u043F\u0430","\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.ru.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.zh-cn",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\u6570\u7EC4","\u5E03\u5C14\u503C","\u7C7B","\u5E38\u6570","\u6784\u9020\u51FD\u6570","\u679A\u4E3E","\u679A\u4E3E\u6210\u5458","\u4E8B\u4EF6","\u5B57\u6BB5","\u6587\u4EF6","\u51FD\u6570","\u63A5\u53E3","\u952E","\u65B9\u6CD5","\u6A21\u5757","\u547D\u540D\u7A7A\u95F4","Null","\u6570\u5B57","\u5BF9\u8C61","\u8FD0\u7B97\u7B26","\u5305","\u5C5E\u6027","\u5B57\u7B26\u4E32","\u7ED3\u6784","\u7C7B\u578B\u53C2\u6570","\u53D8\u91CF","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.zh-cn.js.map

View file

@ -0,0 +1,8 @@
/*!-----------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
* Released under the MIT license
* https://github.com/microsoft/vscode/blob/main/LICENSE.txt
*-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.zh-tw",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\u9663\u5217","\u5E03\u6797\u503C","\u985E\u5225","\u5E38\u6578","\u5EFA\u69CB\u51FD\u5F0F","\u5217\u8209","\u5217\u8209\u6210\u54E1","\u4E8B\u4EF6","\u6B04\u4F4D","\u6A94\u6848","\u51FD\u5F0F","\u4ECB\u9762","\u7D22\u5F15\u9375","\u65B9\u6CD5","\u6A21\u7D44","\u547D\u540D\u7A7A\u9593","null","\u6578\u5B57","\u7269\u4EF6","\u904B\u7B97\u5B50","\u5957\u4EF6","\u5C6C\u6027","\u5B57\u4E32","\u7D50\u69CB","\u578B\u5225\u53C3\u6578","\u8B8A\u6578","{0} ({1})"]});
//# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.zh-tw.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3,7 +3,7 @@
<h1 class="text-lg font-semibold text-gray-900">
Creating new Healthcheck.
</h1>
<form class="max-w-sm mt-4" action="/settings/healthchecks/create" method="post">
<form class="mt-4" action="/settings/healthchecks/create" method="post">
<div class="mb-5">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">Name</label>
<input type="name" name="name" id="name" placeholder="Github.com" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"/>
@ -17,21 +17,51 @@
<input type="text" name="schedule" id="schedule" placeholder="* * * * *" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"/>
</div>
<div class="mb-5">
<label for="script" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your message</label>
<textarea id="script" name="script" rows="4" class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500" placeholder="import http from 'k6/http';
import { sleep } from 'k6';
<label for="script" class="block mb-2 text-sm font-medium text-gray-900">Script</label>
<input required type="hidden" id="script" name="script">
<div id="editor" class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"></div>
</div>
<button type="submit" onclick="save()" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center">Create</button>
</form>
</section>
<script src="/static/monaco/vs/loader.js"></script>
<script>
function save() {
const script = window.editor.getValue();
document.getElementById('script').value = script;
}
script = `import http from 'k6/http';
export const options = {
vus: 10, // not working atm
duration: '30s', // not working atm
thresholds: {
// http errors should be less than 1%
http_req_failed: ['rate<0.01'],
},
vus: 10,
duration: '5s',
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}"></textarea>
</div>
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center">Create</button>
</form>
</section>
http.get('https://example.com');
}
`
require.config({ paths: { vs: '/static/monaco/vs' } });
require(['vs/editor/editor.main'], function () {
window.editor = monaco.editor.create(document.getElementById('editor'), {
value: script,
language: 'javascript',
minimap: { enabled: false },
});
const divElem = document.getElementById('editor');
const resizeObserver = new ResizeObserver(entries => {
window.editor.layout();
});
resizeObserver.observe(divElem);
});
</script>
{{end}}