瀏覽代碼

SVI Уточнение интерфейсов WUI; 100.0%

SVI 1 年之前
父節點
當前提交
638117c8e0

+ 1 - 1
mds/mod_serv_http/btn_modules/btn_modules.go

@@ -38,7 +38,7 @@ var strBlockModules string
 var strBlockRow string
 
 // Событие клика по кнопке
-func (sf *BtnModules) clickMonolit() string {
+func (sf *BtnModules) clickMonolit(dict map[string]string) string {
 	mon := sf.kCtx.Get("monolit").Val().(IKernelMonolit)
 	chLst := mon.Ctx().SortedList()
 	strOut := ``

+ 1 - 1
mds/mod_serv_http/btn_modules/btn_modules_test.go

@@ -21,7 +21,7 @@ func TestBtnModules(t *testing.T) {
 	kMon := kmonolit.GetMonolit("test")
 	mod := kmodule.NewKernelModule("test")
 	kMon.Add(mod)
-	if str := btn.clickMonolit(); str == "" {
+	if str := btn.clickMonolit(map[string]string{}); str == "" {
 		t.Fatalf("strOut is empty")
 	}
 }

+ 1 - 1
mds/mod_serv_http/btn_monolit/btn_monolit.go

@@ -29,6 +29,6 @@ func (sf *BtnMonolit) Html() string {
 var strBlockMonolit string
 
 // Событие клика по кнопке
-func (sf *BtnMonolit) clickMonolit() string {
+func (sf *BtnMonolit) clickMonolit(dict map[string]string) string {
 	return strBlockMonolit
 }

+ 1 - 1
mds/mod_serv_http/btn_monolit/btn_monolit_test.go

@@ -21,7 +21,7 @@ func TestBtnModules(t *testing.T) {
 	kMon := kmonolit.GetMonolit("test")
 	mod := kmodule.NewKernelModule("test")
 	kMon.Add(mod)
-	if str := btn.clickMonolit(); str == "" {
+	if str := btn.clickMonolit(map[string]string{}); str == "" {
 		t.Fatalf("strOut is empty")
 	}
 }

+ 35 - 7
mds/mod_wui/mod_wui.go

@@ -3,9 +3,14 @@ package mod_wui
 
 import (
 	"fmt"
+	"net/http"
+	"strings"
 	"sync"
 
 	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/fiber/v2/middleware/adaptor"
+
+	. "gitp78su.ipnodns.ru/svi/kern/kc/helpers"
 	"gitp78su.ipnodns.ru/svi/kern/krn/kctx"
 	"gitp78su.ipnodns.ru/svi/kern/krn/kmodule"
 	"gitp78su.ipnodns.ru/svi/kern/krn/kserv_http"
@@ -50,7 +55,7 @@ func GetModuleWui() *ModuleWui {
 
 	_ = http_api.NewHttpApi()
 	fibApp := sf.kCtx.Get("fiberApp").Val().(*fiber.App)
-	fibApp.Post("/wui/click/:id", sf.wuiClick)
+	fibApp.Post("/wui/click/:id", adaptor.HTTPHandlerFunc(sf.wuiClick)) // adaptor.HTTPHandlerFunc(greet)
 	mod = sf
 	return sf
 }
@@ -72,20 +77,43 @@ func (sf *ModuleWui) IsWork() bool {
 }
 
 // Получает событие из сети
-func (sf *ModuleWui) wuiClick(ctx *fiber.Ctx) error {
-	id := ctx.Params("id")
+func (sf *ModuleWui) wuiClick(resp http.ResponseWriter, req *http.Request) {
+	url := req.RequestURI
+	id := strings.TrimPrefix(url, "/wui/click/")
 	widget0 := sf.wCtx.Get(id)
 	if widget0 == nil {
 		strOut := fmt.Sprintf("ModuleWui.wuiClick(): id(%v), widget not exists", id)
 		sf.log.Err(strOut)
-		return ctx.SendString(strOut)
+		fmt.Fprint(resp, strOut)
+		return
 	}
 	widget1, isOk := widget0.Val().(IWuiButton)
 	if !isOk {
 		strOut := fmt.Sprintf("ModuleWui.wuiClick(): widget(%T) not button", widget0.Val())
 		sf.log.Err(strOut)
-		return ctx.SendString(strOut)
+		fmt.Fprint(resp, strOut)
+		return
+	}
+	dict := map[string]string{}
+
+	// headers := ctx.GetReqHeaders()
+	for key, lstVal := range req.Header {
+		if len(lstVal) >= 1 {
+			dict[key] = lstVal[0]
+			continue
+		}
+	}
+
+	err := req.ParseForm()
+	Hassert(err == nil, "ModuleWui.wuiClick(): in parse form, err=\n\t%v", err)
+	// Получаем все form-значения
+	//values := req.ParseForm()
+	for key, lstVal := range req.Form {
+		if len(lstVal) >= 1 {
+			dict[key] = lstVal[0]
+			continue
+		}
 	}
-	strOut := widget1.Click()
-	return ctx.SendString(strOut)
+	strOut := widget1.Click(dict)
+	fmt.Fprint(resp, strOut)
 }

+ 58 - 1
mds/mod_wui/mod_wui_test.go

@@ -2,7 +2,9 @@ package mod_wui
 
 import (
 	"net/http"
+	"net/url"
 	"os"
+	"strings"
 	"testing"
 	"time"
 
@@ -40,8 +42,63 @@ func (sf *tester) click() {
 	sf.clickBad1()
 	sf.clickBad2()
 	sf.clickGood1()
+	sf.clickGood2()
+	sf.clickBad3()
 }
 
+// С кривой POST-формой
+func (sf *tester) clickBad3() {
+	sf.t.Log("clickBad3")
+	btn := wbutton.NewWuiButton("test_btn", sf.fnClick)
+	fiberApp := sf.serv.Fiber()
+	// Данные формы
+	reader := strings.NewReader("tra-ta-ta")
+	req, err := http.NewRequest("POST", "/wui/click/"+btn.Id(), reader)
+	if err != nil {
+		sf.t.Fatalf("clickBad3(): in net request, err=%v", err)
+	}
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+	req.Header.Add("Token-1", "123;original-1")
+	req.Header.Add("Token-2", "")
+	resp, err := fiberApp.Test(req)
+	if err != nil {
+		sf.t.Fatalf("clickBad3(): in make POST, err=%v", err)
+	}
+	if resp.StatusCode != 200 {
+		sf.t.Fatalf("clickBad3(): status(%v)!=200", resp.StatusCode)
+	}
+}
+
+// С POST-формой
+func (sf *tester) clickGood2() {
+	sf.t.Log("clickGood2")
+	btn := wbutton.NewWuiButton("test_btn", sf.fnClick)
+	fiberApp := sf.serv.Fiber()
+	// Данные формы
+	formData := url.Values{
+		"username": {"john_doe"},
+		"email":    {"john.doe@example.com"},
+		"password": {"s3cr3t", "valid"},
+	}
+	reader := strings.NewReader(formData.Encode())
+	req, err := http.NewRequest("POST", "/wui/click/"+btn.Id(), reader)
+	if err != nil {
+		sf.t.Fatalf("clickGood2(): in net request, err=%v", err)
+	}
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+	req.Header.Add("Token-1", "123;original-1")
+	req.Header.Add("Token-2", "")
+	req.Header.Clone().Add("Token-1", "555;original-2")
+	resp, err := fiberApp.Test(req)
+	if err != nil {
+		sf.t.Fatalf("clickGood2(): in make POST, err=%v", err)
+	}
+	if resp.StatusCode != 200 {
+		sf.t.Fatalf("clickGood2(): status(%v)!=200", resp.StatusCode)
+	}
+}
+
+// Без POST-формы
 func (sf *tester) clickGood1() {
 	sf.t.Log("clickGood1")
 	btn := wbutton.NewWuiButton("test_btn", sf.fnClick)
@@ -60,7 +117,7 @@ func (sf *tester) clickGood1() {
 }
 
 // Обратный вызов клика
-func (sf *tester) fnClick() string {
+func (sf *tester) fnClick(dict map[string]string) string {
 	sf.t.Log("fnClick")
 	return "test_click"
 }

+ 171 - 0
vendor/github.com/gofiber/fiber/v2/middleware/adaptor/adaptor.go

@@ -0,0 +1,171 @@
+package adaptor
+
+import (
+	"io"
+	"net"
+	"net/http"
+	"reflect"
+	"unsafe"
+
+	"github.com/valyala/fasthttp"
+	"github.com/valyala/fasthttp/fasthttpadaptor"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/fiber/v2/utils"
+)
+
+// HTTPHandlerFunc wraps net/http handler func to fiber handler
+func HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler {
+	return HTTPHandler(h)
+}
+
+// HTTPHandler wraps net/http handler to fiber handler
+func HTTPHandler(h http.Handler) fiber.Handler {
+	return func(c *fiber.Ctx) error {
+		handler := fasthttpadaptor.NewFastHTTPHandler(h)
+		handler(c.Context())
+		return nil
+	}
+}
+
+// ConvertRequest converts a fiber.Ctx to a http.Request.
+// forServer should be set to true when the http.Request is going to be passed to a http.Handler.
+func ConvertRequest(c *fiber.Ctx, forServer bool) (*http.Request, error) {
+	var req http.Request
+	if err := fasthttpadaptor.ConvertRequest(c.Context(), &req, forServer); err != nil {
+		return nil, err //nolint:wrapcheck // This must not be wrapped
+	}
+	return &req, nil
+}
+
+// CopyContextToFiberContext copies the values of context.Context to a fasthttp.RequestCtx
+func CopyContextToFiberContext(context interface{}, requestContext *fasthttp.RequestCtx) {
+	contextValues := reflect.ValueOf(context).Elem()
+	contextKeys := reflect.TypeOf(context).Elem()
+	if contextKeys.Kind() == reflect.Struct {
+		var lastKey interface{}
+		for i := 0; i < contextValues.NumField(); i++ {
+			reflectValue := contextValues.Field(i)
+			/* #nosec */
+			reflectValue = reflect.NewAt(reflectValue.Type(), unsafe.Pointer(reflectValue.UnsafeAddr())).Elem()
+
+			reflectField := contextKeys.Field(i)
+
+			if reflectField.Name == "noCopy" {
+				break
+			} else if reflectField.Name == "Context" {
+				CopyContextToFiberContext(reflectValue.Interface(), requestContext)
+			} else if reflectField.Name == "key" {
+				lastKey = reflectValue.Interface()
+			} else if lastKey != nil && reflectField.Name == "val" {
+				requestContext.SetUserValue(lastKey, reflectValue.Interface())
+			} else {
+				lastKey = nil
+			}
+		}
+	}
+}
+
+// HTTPMiddleware wraps net/http middleware to fiber middleware
+func HTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler {
+	return func(c *fiber.Ctx) error {
+		var next bool
+		nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			next = true
+			// Convert again in case request may modify by middleware
+			c.Request().Header.SetMethod(r.Method)
+			c.Request().SetRequestURI(r.RequestURI)
+			c.Request().SetHost(r.Host)
+			c.Request().Header.SetHost(r.Host)
+			for key, val := range r.Header {
+				for _, v := range val {
+					c.Request().Header.Set(key, v)
+				}
+			}
+			CopyContextToFiberContext(r.Context(), c.Context())
+		})
+
+		if err := HTTPHandler(mw(nextHandler))(c); err != nil {
+			return err
+		}
+
+		if next {
+			return c.Next()
+		}
+		return nil
+	}
+}
+
+// FiberHandler wraps fiber handler to net/http handler
+func FiberHandler(h fiber.Handler) http.Handler {
+	return FiberHandlerFunc(h)
+}
+
+// FiberHandlerFunc wraps fiber handler to net/http handler func
+func FiberHandlerFunc(h fiber.Handler) http.HandlerFunc {
+	return handlerFunc(fiber.New(), h)
+}
+
+// FiberApp wraps fiber app to net/http handler func
+func FiberApp(app *fiber.App) http.HandlerFunc {
+	return handlerFunc(app)
+}
+
+func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		// New fasthttp request
+		req := fasthttp.AcquireRequest()
+		defer fasthttp.ReleaseRequest(req)
+		// Convert net/http -> fasthttp request
+		if r.Body != nil {
+			n, err := io.Copy(req.BodyWriter(), r.Body)
+			req.Header.SetContentLength(int(n))
+
+			if err != nil {
+				http.Error(w, utils.StatusMessage(fiber.StatusInternalServerError), fiber.StatusInternalServerError)
+				return
+			}
+		}
+		req.Header.SetMethod(r.Method)
+		req.SetRequestURI(r.RequestURI)
+		req.SetHost(r.Host)
+		req.Header.SetHost(r.Host)
+		for key, val := range r.Header {
+			for _, v := range val {
+				req.Header.Set(key, v)
+			}
+		}
+		if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil && err.(*net.AddrError).Err == "missing port in address" { //nolint:errorlint, forcetypeassert // overlinting
+			r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
+		}
+		remoteAddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr)
+		if err != nil {
+			http.Error(w, utils.StatusMessage(fiber.StatusInternalServerError), fiber.StatusInternalServerError)
+			return
+		}
+
+		// New fasthttp Ctx
+		var fctx fasthttp.RequestCtx
+		fctx.Init(req, remoteAddr, nil)
+		if len(h) > 0 {
+			// New fiber Ctx
+			ctx := app.AcquireCtx(&fctx)
+			defer app.ReleaseCtx(ctx)
+			// Execute fiber Ctx
+			err := h[0](ctx)
+			if err != nil {
+				_ = app.Config().ErrorHandler(ctx, err) //nolint:errcheck // not needed
+			}
+		} else {
+			// Execute fasthttp Ctx though app.Handler
+			app.Handler()(&fctx)
+		}
+
+		// Convert fasthttp Ctx > net/http
+		fctx.Response.Header.VisitAll(func(k, v []byte) {
+			w.Header().Add(string(k), string(v))
+		})
+		w.WriteHeader(fctx.Response.StatusCode())
+		_, _ = w.Write(fctx.Response.Body()) //nolint:errcheck // not needed
+	}
+}

+ 159 - 0
vendor/github.com/valyala/fasthttp/fasthttpadaptor/adaptor.go

@@ -0,0 +1,159 @@
+// Package fasthttpadaptor provides helper functions for converting net/http
+// request handlers to fasthttp request handlers.
+package fasthttpadaptor
+
+import (
+	"bufio"
+	"io"
+	"net"
+	"net/http"
+	"sync"
+
+	"github.com/valyala/fasthttp"
+)
+
+// NewFastHTTPHandlerFunc wraps net/http handler func to fasthttp
+// request handler, so it can be passed to fasthttp server.
+//
+// While this function may be used for easy switching from net/http to fasthttp,
+// it has the following drawbacks comparing to using manually written fasthttp
+// request handler:
+//
+//   - A lot of useful functionality provided by fasthttp is missing
+//     from net/http handler.
+//   - net/http -> fasthttp handler conversion has some overhead,
+//     so the returned handler will be always slower than manually written
+//     fasthttp handler.
+//
+// So it is advisable using this function only for quick net/http -> fasthttp
+// switching. Then manually convert net/http handlers to fasthttp handlers
+// according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp .
+func NewFastHTTPHandlerFunc(h http.HandlerFunc) fasthttp.RequestHandler {
+	return NewFastHTTPHandler(h)
+}
+
+// NewFastHTTPHandler wraps net/http handler to fasthttp request handler,
+// so it can be passed to fasthttp server.
+//
+// While this function may be used for easy switching from net/http to fasthttp,
+// it has the following drawbacks comparing to using manually written fasthttp
+// request handler:
+//
+//   - A lot of useful functionality provided by fasthttp is missing
+//     from net/http handler.
+//   - net/http -> fasthttp handler conversion has some overhead,
+//     so the returned handler will be always slower than manually written
+//     fasthttp handler.
+//
+// So it is advisable using this function only for quick net/http -> fasthttp
+// switching. Then manually convert net/http handlers to fasthttp handlers
+// according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp .
+func NewFastHTTPHandler(h http.Handler) fasthttp.RequestHandler {
+	return func(ctx *fasthttp.RequestCtx) {
+		var r http.Request
+		if err := ConvertRequest(ctx, &r, true); err != nil {
+			ctx.Logger().Printf("cannot parse requestURI %q: %v", r.RequestURI, err)
+			ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
+			return
+		}
+		w := netHTTPResponseWriter{
+			w:   ctx.Response.BodyWriter(),
+			ctx: ctx,
+		}
+		h.ServeHTTP(&w, r.WithContext(ctx))
+
+		ctx.SetStatusCode(w.StatusCode())
+		haveContentType := false
+		for k, vv := range w.Header() {
+			if k == fasthttp.HeaderContentType {
+				haveContentType = true
+			}
+
+			for _, v := range vv {
+				ctx.Response.Header.Add(k, v)
+			}
+		}
+		if !haveContentType {
+			// From net/http.ResponseWriter.Write:
+			// If the Header does not contain a Content-Type line, Write adds a Content-Type set
+			// to the result of passing the initial 512 bytes of written data to DetectContentType.
+			l := 512
+			b := ctx.Response.Body()
+			if len(b) < 512 {
+				l = len(b)
+			}
+			ctx.Response.Header.Set(fasthttp.HeaderContentType, http.DetectContentType(b[:l]))
+		}
+	}
+}
+
+type netHTTPResponseWriter struct {
+	w          io.Writer
+	h          http.Header
+	ctx        *fasthttp.RequestCtx
+	statusCode int
+}
+
+func (w *netHTTPResponseWriter) StatusCode() int {
+	if w.statusCode == 0 {
+		return http.StatusOK
+	}
+	return w.statusCode
+}
+
+func (w *netHTTPResponseWriter) Header() http.Header {
+	if w.h == nil {
+		w.h = make(http.Header)
+	}
+	return w.h
+}
+
+func (w *netHTTPResponseWriter) WriteHeader(statusCode int) {
+	w.statusCode = statusCode
+}
+
+func (w *netHTTPResponseWriter) Write(p []byte) (int, error) {
+	return w.w.Write(p)
+}
+
+func (w *netHTTPResponseWriter) Flush() {}
+
+type wrappedConn struct {
+	net.Conn
+
+	wg   sync.WaitGroup
+	once sync.Once
+}
+
+func (c *wrappedConn) Close() (err error) {
+	c.once.Do(func() {
+		err = c.Conn.Close()
+		c.wg.Done()
+	})
+	return
+}
+
+func (w *netHTTPResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	// Hijack assumes control of the connection, so we need to prevent fasthttp from closing it or
+	// doing anything else with it.
+	w.ctx.HijackSetNoResponse(true)
+
+	conn := &wrappedConn{Conn: w.ctx.Conn()}
+	conn.wg.Add(1)
+	w.ctx.Hijack(func(net.Conn) {
+		conn.wg.Wait()
+	})
+
+	bufW := bufio.NewWriter(conn)
+
+	// Write any unflushed body to the hijacked connection buffer.
+	unflushedBody := w.ctx.Response.Body()
+	if len(unflushedBody) > 0 {
+		if _, err := bufW.Write(unflushedBody); err != nil {
+			conn.Close()
+			return nil, nil, err
+		}
+	}
+
+	return conn, &bufio.ReadWriter{Reader: bufio.NewReader(conn), Writer: bufW}, nil
+}

+ 11 - 0
vendor/github.com/valyala/fasthttp/fasthttpadaptor/b2s_new.go

@@ -0,0 +1,11 @@
+//go:build go1.20
+
+package fasthttpadaptor
+
+import "unsafe"
+
+// b2s converts byte slice to a string without memory allocation.
+// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
+func b2s(b []byte) string {
+	return unsafe.String(unsafe.SliceData(b), len(b))
+}

+ 14 - 0
vendor/github.com/valyala/fasthttp/fasthttpadaptor/b2s_old.go

@@ -0,0 +1,14 @@
+//go:build !go1.20
+
+package fasthttpadaptor
+
+import "unsafe"
+
+// b2s converts byte slice to a string without memory allocation.
+// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
+//
+// Note it may break if string and/or slice header will change
+// in the future go versions.
+func b2s(b []byte) string {
+	return *(*string)(unsafe.Pointer(&b))
+}

+ 70 - 0
vendor/github.com/valyala/fasthttp/fasthttpadaptor/request.go

@@ -0,0 +1,70 @@
+package fasthttpadaptor
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/valyala/fasthttp"
+)
+
+// ConvertRequest converts a fasthttp.Request to an http.Request.
+// forServer should be set to true when the http.Request is going to be passed to a http.Handler.
+//
+// The http.Request must not be used after the fasthttp handler has returned!
+// Memory in use by the http.Request will be reused after your handler has returned!
+func ConvertRequest(ctx *fasthttp.RequestCtx, r *http.Request, forServer bool) error {
+	body := ctx.PostBody()
+	strRequestURI := b2s(ctx.RequestURI())
+
+	rURL, err := url.ParseRequestURI(strRequestURI)
+	if err != nil {
+		return err
+	}
+
+	r.Method = b2s(ctx.Method())
+	r.Proto = b2s(ctx.Request.Header.Protocol())
+	if r.Proto == "HTTP/2" {
+		r.ProtoMajor = 2
+	} else {
+		r.ProtoMajor = 1
+	}
+	r.ProtoMinor = 1
+	r.ContentLength = int64(len(body))
+	r.RemoteAddr = ctx.RemoteAddr().String()
+	r.Host = b2s(ctx.Host())
+	r.TLS = ctx.TLSConnectionState()
+	r.Body = io.NopCloser(bytes.NewReader(body))
+	r.URL = rURL
+
+	if forServer {
+		r.RequestURI = strRequestURI
+	}
+
+	if r.Header == nil {
+		r.Header = make(http.Header)
+	} else if len(r.Header) > 0 {
+		for k := range r.Header {
+			delete(r.Header, k)
+		}
+	}
+
+	ctx.Request.Header.VisitAll(func(k, v []byte) {
+		sk := b2s(k)
+		sv := b2s(v)
+
+		switch sk {
+		case "Transfer-Encoding":
+			r.TransferEncoding = append(r.TransferEncoding, sv)
+		default:
+			if sk == fasthttp.HeaderCookie {
+				sv = strings.Clone(sv)
+			}
+			r.Header.Set(sk, sv)
+		}
+	})
+
+	return nil
+}

+ 2 - 0
vendor/modules.txt

@@ -47,6 +47,7 @@ github.com/gofiber/fiber/v2/internal/gopsutil/process
 github.com/gofiber/fiber/v2/internal/schema
 github.com/gofiber/fiber/v2/internal/wmi
 github.com/gofiber/fiber/v2/log
+github.com/gofiber/fiber/v2/middleware/adaptor
 github.com/gofiber/fiber/v2/middleware/compress
 github.com/gofiber/fiber/v2/middleware/filesystem
 github.com/gofiber/fiber/v2/middleware/monitor
@@ -94,6 +95,7 @@ github.com/valyala/bytebufferpool
 # github.com/valyala/fasthttp v1.59.0
 ## explicit; go 1.21
 github.com/valyala/fasthttp
+github.com/valyala/fasthttp/fasthttpadaptor
 github.com/valyala/fasthttp/fasthttputil
 github.com/valyala/fasthttp/reuseport
 github.com/valyala/fasthttp/stackless

+ 4 - 4
wui/wbutton/wbutton.go

@@ -17,12 +17,12 @@ import (
 type WuiButton struct {
 	IWuiWidget
 	text   IWuiText
-	fnBack func() string
+	fnBack func(map[string]string) string
 	hx     IWuiHx
 }
 
 // NewWuiButton -- возвращает новую WUI-кнопку
-func NewWuiButton(text string, fnBack func() string) *WuiButton {
+func NewWuiButton(text string, fnBack func(map[string]string) string) *WuiButton {
 	Hassert(fnBack != nil, "NewWuiButton(): fnBack==nil")
 	sf := &WuiButton{
 		IWuiWidget: wwidget.NewWuiWidget(),
@@ -47,8 +47,8 @@ func (sf *WuiButton) Text() IWuiText {
 }
 
 // Click -- событие нажатия
-func (sf *WuiButton) Click() string {
-	return sf.fnBack()
+func (sf *WuiButton) Click(dict map[string]string) string {
+	return sf.fnBack(dict)
 }
 
 const (

+ 2 - 2
wui/wbutton/wbutton_test.go

@@ -33,14 +33,14 @@ func (sf *tester) new() {
 	if hx := btn.Hx(); hx == nil {
 		sf.t.Fatalf("new(): IWuiHx==nil")
 	}
-	btn.Click()
+	btn.Click(map[string]string{})
 	if str := <-sf.chCall; str != "test" {
 		sf.t.Fatalf("new(): bad called")
 	}
 }
 
 // Функция обратного вызова
-func (sf *tester) fnBack() string {
+func (sf *tester) fnBack(dict map[string]string) string {
 	sf.chCall <- "test"
 	return "test_click"
 }

+ 1 - 1
wui/wtypes/iwui_button.go

@@ -6,7 +6,7 @@ type IWuiButton interface {
 	// Text -- возвращает текст кнопки
 	Text() IWuiText
 	// Click -- нажатие кнопки
-	Click() string
+	Click(map[string]string) string
 	// Hx -- атрибуты HTMX
 	Hx() IWuiHx
 }

+ 1 - 1
wui/wui.go

@@ -8,7 +8,7 @@ import (
 )
 
 // NewWuiButton -- возвращает новую WUI-кнопку
-func NewWuiButton(text string, fnClick func() string) IWuiButton {
+func NewWuiButton(text string, fnClick func(map[string]string) string) IWuiButton {
 	btn := wbutton.NewWuiButton(text, fnClick)
 	return btn
 }

+ 1 - 1
wui/wui_test.go

@@ -29,7 +29,7 @@ func (sf *tester) get() {
 }
 
 // Функция обратного вызова
-func (sf *tester) fnClick() string {
+func (sf *tester) fnClick(dict map[string]string) string {
 	sf.t.Log("fnClick")
 	return "test_click"
 }