新增调试信息
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/hiddify/hiddify-core/config"
|
||||
"github.com/hiddify/hiddify-core/extension/ui"
|
||||
pb "github.com/hiddify/hiddify-core/hiddifyrpc"
|
||||
"github.com/hiddify/hiddify-core/v2/db"
|
||||
"github.com/jellydator/validation"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type Extension interface {
|
||||
GetUI() ui.Form
|
||||
SubmitData(button string, data map[string]string) error
|
||||
Close() error
|
||||
UpdateUI(form ui.Form) error
|
||||
|
||||
BeforeAppConnect(hiddifySettings *config.HiddifyOptions, singconfig *option.Options) error
|
||||
|
||||
StoreData()
|
||||
|
||||
init(id string)
|
||||
getQueue() chan *pb.ExtensionResponse
|
||||
getId() string
|
||||
}
|
||||
|
||||
type Base[T any] struct {
|
||||
id string
|
||||
// responseStream grpc.ServerStreamingServer[pb.ExtensionResponse]
|
||||
queue chan *pb.ExtensionResponse
|
||||
Data T
|
||||
}
|
||||
|
||||
// func (b *Base) mustEmbdedBaseExtension() {
|
||||
// }
|
||||
|
||||
func (b *Base[T]) BeforeAppConnect(hiddifySettings *config.HiddifyOptions, singconfig *option.Options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Base[T]) StoreData() {
|
||||
table := db.GetTable[extensionData]()
|
||||
ed, err := table.Get(b.id)
|
||||
if err != nil {
|
||||
log.Warn("error: ", err)
|
||||
return
|
||||
}
|
||||
res, err := json.Marshal(b.Data)
|
||||
if err != nil {
|
||||
log.Warn("error: ", err)
|
||||
return
|
||||
}
|
||||
ed.JsonData = (res)
|
||||
table.UpdateInsert(ed)
|
||||
}
|
||||
|
||||
func (b *Base[T]) init(id string) {
|
||||
b.id = id
|
||||
b.queue = make(chan *pb.ExtensionResponse, 1)
|
||||
table := db.GetTable[extensionData]()
|
||||
extdata, err := table.Get(b.id)
|
||||
if err != nil {
|
||||
log.Warn("error: ", err)
|
||||
return
|
||||
}
|
||||
if extdata == nil {
|
||||
log.Warn("extension data not found ", id)
|
||||
return
|
||||
}
|
||||
if extdata.JsonData != nil {
|
||||
var t T
|
||||
if err := json.Unmarshal(extdata.JsonData, &t); err != nil {
|
||||
log.Warn("error loading data of ", id, " : ", err)
|
||||
} else {
|
||||
b.Data = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Base[T]) getQueue() chan *pb.ExtensionResponse {
|
||||
return b.queue
|
||||
}
|
||||
|
||||
func (b *Base[T]) getId() string {
|
||||
return b.id
|
||||
}
|
||||
|
||||
func (e *Base[T]) ShowMessage(title string, msg string) error {
|
||||
return e.ShowDialog(ui.Form{
|
||||
Title: title,
|
||||
Description: msg,
|
||||
Fields: [][]ui.FormField{
|
||||
{{
|
||||
Type: ui.FieldButton,
|
||||
Key: ui.ButtonDialogOk,
|
||||
Label: "Ok",
|
||||
}},
|
||||
},
|
||||
// Buttons: []string{ui.Button_Ok},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Base[T]) UpdateUI(form ui.Form) error {
|
||||
p.queue <- &pb.ExtensionResponse{
|
||||
ExtensionId: p.id,
|
||||
Type: pb.ExtensionResponseType_UPDATE_UI,
|
||||
JsonUi: form.ToJSON(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Base[T]) ShowDialog(form ui.Form) error {
|
||||
p.queue <- &pb.ExtensionResponse{
|
||||
ExtensionId: p.id,
|
||||
Type: pb.ExtensionResponseType_SHOW_DIALOG,
|
||||
JsonUi: form.ToJSON(),
|
||||
}
|
||||
// log.Printf("Updated UI for extension %s: %s", err, p.id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (base *Base[T]) ValName(fieldPtr interface{}) string {
|
||||
val, err := validation.ErrorFieldName(&base.Data, fieldPtr)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return ""
|
||||
}
|
||||
if val == "" {
|
||||
log.Warn("Field not found")
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
type ExtensionFactory struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Builder func() Extension
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
pb "github.com/hiddify/hiddify-core/hiddifyrpc"
|
||||
"github.com/hiddify/hiddify-core/v2/db"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type ExtensionHostService struct {
|
||||
pb.UnimplementedExtensionHostServiceServer
|
||||
}
|
||||
|
||||
func (ExtensionHostService) ListExtensions(ctx context.Context, empty *pb.Empty) (*pb.ExtensionList, error) {
|
||||
extensionList := &pb.ExtensionList{
|
||||
Extensions: make([]*pb.Extension, 0),
|
||||
}
|
||||
allext, err := db.GetTable[extensionData]().All()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, dbext := range allext {
|
||||
if ext, ok := allExtensionsMap[dbext.Id]; ok {
|
||||
extensionList.Extensions = append(extensionList.Extensions, &pb.Extension{
|
||||
Id: ext.Id,
|
||||
Title: ext.Title,
|
||||
Description: ext.Description,
|
||||
Enable: dbext.Enable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return extensionList, nil
|
||||
}
|
||||
|
||||
func getExtension(id string) (*Extension, error) {
|
||||
if !isEnable(id) {
|
||||
return nil, fmt.Errorf("Extension with ID %s is not enabled", id)
|
||||
}
|
||||
if extension, ok := enabledExtensionsMap[id]; ok {
|
||||
return extension, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Extension with ID %s not found", id)
|
||||
}
|
||||
|
||||
func (e ExtensionHostService) Connect(req *pb.ExtensionRequest, stream grpc.ServerStreamingServer[pb.ExtensionResponse]) error {
|
||||
extension, err := getExtension(req.GetExtensionId())
|
||||
if err != nil {
|
||||
log.Printf("Error connecting stream for extension %s: %v", req.GetExtensionId(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Connecting stream for extension %s", req.GetExtensionId())
|
||||
log.Printf("Extension data: %+v", extension)
|
||||
|
||||
if err := (*extension).UpdateUI((*extension).GetUI()); err != nil {
|
||||
log.Printf("Error updating UI for extension %s: %v", req.GetExtensionId(), err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stream.Context().Done():
|
||||
return nil
|
||||
case info := <-(*extension).getQueue():
|
||||
stream.Send(info)
|
||||
if info.GetType() == pb.ExtensionResponseType_END {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e ExtensionHostService) SubmitForm(ctx context.Context, req *pb.SendExtensionDataRequest) (*pb.ExtensionActionResult, error) {
|
||||
extension, err := getExtension(req.GetExtensionId())
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return &pb.ExtensionActionResult{
|
||||
ExtensionId: req.ExtensionId,
|
||||
Code: pb.ResponseCode_FAILED,
|
||||
Message: err.Error(),
|
||||
}, err
|
||||
}
|
||||
(*extension).SubmitData(req.Button, req.GetData())
|
||||
|
||||
return &pb.ExtensionActionResult{
|
||||
ExtensionId: req.ExtensionId,
|
||||
Code: pb.ResponseCode_OK,
|
||||
Message: "Success",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e ExtensionHostService) Close(ctx context.Context, req *pb.ExtensionRequest) (*pb.ExtensionActionResult, error) {
|
||||
extension, err := getExtension(req.GetExtensionId())
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return &pb.ExtensionActionResult{
|
||||
ExtensionId: req.ExtensionId,
|
||||
Code: pb.ResponseCode_FAILED,
|
||||
Message: err.Error(),
|
||||
}, err
|
||||
}
|
||||
(*extension).Close()
|
||||
(*extension).StoreData()
|
||||
return &pb.ExtensionActionResult{
|
||||
ExtensionId: req.ExtensionId,
|
||||
Code: pb.ResponseCode_OK,
|
||||
Message: "Success",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e ExtensionHostService) EditExtension(ctx context.Context, req *pb.EditExtensionRequest) (*pb.ExtensionActionResult, error) {
|
||||
if !req.Enable {
|
||||
extension, _ := getExtension(req.GetExtensionId())
|
||||
if extension != nil {
|
||||
(*extension).Close()
|
||||
(*extension).StoreData()
|
||||
}
|
||||
delete(enabledExtensionsMap, req.GetExtensionId())
|
||||
}
|
||||
table := db.GetTable[extensionData]()
|
||||
data, err := table.Get(req.GetExtensionId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Enable = req.Enable
|
||||
table.UpdateInsert(data)
|
||||
|
||||
if req.Enable {
|
||||
loadExtension(allExtensionsMap[req.GetExtensionId()])
|
||||
}
|
||||
|
||||
return &pb.ExtensionActionResult{
|
||||
ExtensionId: req.ExtensionId,
|
||||
Code: pb.ResponseCode_OK,
|
||||
Message: "Success",
|
||||
}, nil
|
||||
}
|
||||
|
||||
type extensionData struct {
|
||||
Id string `json:"id"`
|
||||
Enable bool `json:"enable"`
|
||||
JsonData []byte
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
import * as a from "./rpc/extension_grpc_web_pb.js";
|
||||
const client = new ExtensionHostServiceClient('http://localhost:8080');
|
||||
const request = new GetHelloRequest();
|
||||
export const getHello = (name) => {
|
||||
request.setName(name)
|
||||
client.getHello(request, {}, (err, response) => {
|
||||
console.log(request.getName());
|
||||
console.log(response.toObject());
|
||||
});
|
||||
}
|
||||
getHello("D")
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hiddify Extensions</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" integrity="sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
|
||||
<style>
|
||||
pre {
|
||||
background-color: black !important; overflow: auto;
|
||||
color: white!important; }
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div id="connection-page" class="card p-4">
|
||||
<div id="connection-before-connect" class="card-body">
|
||||
<h2 class="card-title mb-4">Connection Settings</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="config-content" class="form-label">Config String</label>
|
||||
<textarea id="config-content" class="form-control" placeholder="Enter config string here..." rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="hiddify-settings" class="form-label">Hiddify Settings</label>
|
||||
<textarea id="hiddify-settings" class="form-control" placeholder="Enter Hiddify settings here..." rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<button id="connect-button" class="btn btn-success">Connect</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="connection-connecting" class="card-body d-none">
|
||||
<h2 id="connection-status" class="card-title mb-4">Connecting...</h2>
|
||||
<button id="disconnect-button" class="btn btn-danger">Disconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="extension-list-container" class="card p-4">
|
||||
<h1 class="mb-4">
|
||||
Extension List
|
||||
</h1>
|
||||
<div id="extension-list" class="list-group">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="extension-page-container" class="card p-4">
|
||||
|
||||
<div id="extension-page"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal fade" id="extension-dialog" style="display: none;" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalLabel">Extension List</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="extension-page-containerdialog"></div>
|
||||
</div>
|
||||
<div class="modal-footer" id="modal-footer">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://unpkg.com/ansi_up@5.0.0/ansi_up.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/protobufjs@7.X.X/dist/protobuf.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js" integrity="sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="rpc.js?1"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,460 @@
|
||||
// source: base.proto
|
||||
/**
|
||||
* @fileoverview
|
||||
* @enhanceable
|
||||
* @suppress {missingRequire} reports error on implicit type usages.
|
||||
* @suppress {messageConventions} JS Compiler reports an error if a variable or
|
||||
* field starts with 'MSG_' and isn't a translatable message.
|
||||
* @public
|
||||
*/
|
||||
// GENERATED CODE -- DO NOT EDIT!
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
var jspb = require('google-protobuf');
|
||||
var goog = jspb;
|
||||
var global =
|
||||
(typeof globalThis !== 'undefined' && globalThis) ||
|
||||
(typeof window !== 'undefined' && window) ||
|
||||
(typeof global !== 'undefined' && global) ||
|
||||
(typeof self !== 'undefined' && self) ||
|
||||
(function () { return this; }).call(null) ||
|
||||
Function('return this')();
|
||||
|
||||
goog.exportSymbol('proto.hiddifyrpc.Empty', null, global);
|
||||
goog.exportSymbol('proto.hiddifyrpc.HelloRequest', null, global);
|
||||
goog.exportSymbol('proto.hiddifyrpc.HelloResponse', null, global);
|
||||
goog.exportSymbol('proto.hiddifyrpc.ResponseCode', null, global);
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.hiddifyrpc.HelloRequest, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.displayName = 'proto.hiddifyrpc.HelloRequest';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.hiddifyrpc.HelloResponse, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.displayName = 'proto.hiddifyrpc.HelloResponse';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.hiddifyrpc.Empty = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.hiddifyrpc.Empty, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.hiddifyrpc.Empty.displayName = 'proto.hiddifyrpc.Empty';
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.hiddifyrpc.HelloRequest.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.hiddifyrpc.HelloRequest} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
name: jspb.Message.getFieldWithDefault(msg, 1, "")
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.hiddifyrpc.HelloRequest}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.hiddifyrpc.HelloRequest;
|
||||
return proto.hiddifyrpc.HelloRequest.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.hiddifyrpc.HelloRequest} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.hiddifyrpc.HelloRequest}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setName(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.hiddifyrpc.HelloRequest.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.hiddifyrpc.HelloRequest} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getName();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string name = 1;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.prototype.getName = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @return {!proto.hiddifyrpc.HelloRequest} returns this
|
||||
*/
|
||||
proto.hiddifyrpc.HelloRequest.prototype.setName = function(value) {
|
||||
return jspb.Message.setProto3StringField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.hiddifyrpc.HelloResponse.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.hiddifyrpc.HelloResponse} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
message: jspb.Message.getFieldWithDefault(msg, 1, "")
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.hiddifyrpc.HelloResponse}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.hiddifyrpc.HelloResponse;
|
||||
return proto.hiddifyrpc.HelloResponse.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.hiddifyrpc.HelloResponse} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.hiddifyrpc.HelloResponse}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setMessage(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.hiddifyrpc.HelloResponse.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.hiddifyrpc.HelloResponse} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getMessage();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string message = 1;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.prototype.getMessage = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @return {!proto.hiddifyrpc.HelloResponse} returns this
|
||||
*/
|
||||
proto.hiddifyrpc.HelloResponse.prototype.setMessage = function(value) {
|
||||
return jspb.Message.setProto3StringField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.hiddifyrpc.Empty.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.hiddifyrpc.Empty.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.hiddifyrpc.Empty} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.hiddifyrpc.Empty.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.hiddifyrpc.Empty}
|
||||
*/
|
||||
proto.hiddifyrpc.Empty.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.hiddifyrpc.Empty;
|
||||
return proto.hiddifyrpc.Empty.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.hiddifyrpc.Empty} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.hiddifyrpc.Empty}
|
||||
*/
|
||||
proto.hiddifyrpc.Empty.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.hiddifyrpc.Empty.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.hiddifyrpc.Empty.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.hiddifyrpc.Empty} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.hiddifyrpc.Empty.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
proto.hiddifyrpc.ResponseCode = {
|
||||
OK: 0,
|
||||
FAILED: 1
|
||||
};
|
||||
|
||||
goog.object.extend(exports, proto.hiddifyrpc);
|
||||
@@ -0,0 +1,8 @@
|
||||
const hiddify = require("./hiddify_grpc_web_pb.js");
|
||||
const extension = require("./extension_grpc_web_pb.js");
|
||||
|
||||
const grpcServerAddress = '/';
|
||||
const extensionClient = new extension.ExtensionHostServicePromiseClient(grpcServerAddress, null, null);
|
||||
const hiddifyClient = new hiddify.CorePromiseClient(grpcServerAddress, null, null);
|
||||
|
||||
module.exports = { extensionClient ,hiddifyClient};
|
||||
@@ -0,0 +1,109 @@
|
||||
const { hiddifyClient } = require('./client.js');
|
||||
const hiddify = require("./hiddify_grpc_web_pb.js");
|
||||
|
||||
function openConnectionPage() {
|
||||
|
||||
$("#extension-list-container").show();
|
||||
$("#extension-page-container").hide();
|
||||
$("#connection-page").show();
|
||||
connect();
|
||||
$("#connect-button").click(async () => {
|
||||
const hsetting_request = new hiddify.ChangeHiddifySettingsRequest();
|
||||
hsetting_request.setHiddifySettingsJson($("#hiddify-settings").val());
|
||||
try{
|
||||
const hres=await hiddifyClient.changeHiddifySettings(hsetting_request, {});
|
||||
}catch(err){
|
||||
$("#hiddify-settings").val("")
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
const parse_request = new hiddify.ParseRequest();
|
||||
parse_request.setContent($("#config-content").val());
|
||||
try{
|
||||
const pres=await hiddifyClient.parse(parse_request, {});
|
||||
if (pres.getResponseCode() !== hiddify.ResponseCode.OK){
|
||||
alert(pres.getMessage());
|
||||
return
|
||||
}
|
||||
$("#config-content").val(pres.getContent());
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
alert(JSON.stringify(err))
|
||||
return
|
||||
}
|
||||
|
||||
const request = new hiddify.StartRequest();
|
||||
|
||||
request.setConfigContent($("#config-content").val());
|
||||
request.setEnableRawConfig(false);
|
||||
try{
|
||||
const res=await hiddifyClient.start(request, {});
|
||||
console.log(res.getCoreState(),res.getMessage())
|
||||
handleCoreStatus(res.getCoreState());
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
alert(JSON.stringify(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
$("#disconnect-button").click(async () => {
|
||||
const request = new hiddify.Empty();
|
||||
try{
|
||||
const res=await hiddifyClient.stop(request, {});
|
||||
console.log(res.getCoreState(),res.getMessage())
|
||||
handleCoreStatus(res.getCoreState());
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
alert(JSON.stringify(err))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function connect(){
|
||||
const request = new hiddify.Empty();
|
||||
const stream = hiddifyClient.coreInfoListener(request, {});
|
||||
stream.on('data', (response) => {
|
||||
console.log('Receving ',response);
|
||||
handleCoreStatus(response);
|
||||
});
|
||||
|
||||
stream.on('error', (err) => {
|
||||
console.error('Error opening extension page:', err);
|
||||
// openExtensionPage(extensionId);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
console.log('Stream ended');
|
||||
setTimeout(connect, 1000);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handleCoreStatus(status){
|
||||
if (status == hiddify.CoreState.STOPPED){
|
||||
$("#connection-before-connect").show();
|
||||
$("#connection-connecting").hide();
|
||||
}else{
|
||||
$("#connection-before-connect").hide();
|
||||
$("#connection-connecting").show();
|
||||
if (status == hiddify.CoreState.STARTING){
|
||||
$("#connection-status").text("Starting");
|
||||
$("#connection-status").css("color", "yellow");
|
||||
}else if (status == hiddify.CoreState.STOPPING){
|
||||
$("#connection-status").text("Stopping");
|
||||
$("#connection-status").css("color", "red");
|
||||
}else if (status == hiddify.CoreState.STARTED){
|
||||
$("#connection-status").text("Connected");
|
||||
$("#connection-status").css("color", "green");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = { openConnectionPage };
|
||||
@@ -0,0 +1,8 @@
|
||||
const { listExtensions } = require('./extensionList.js');
|
||||
const { openConnectionPage } = require('./connectionPage.js');
|
||||
window.onload = () => {
|
||||
listExtensions();
|
||||
openConnectionPage();
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
const { extensionClient } = require('./client.js');
|
||||
const extension = require("./extension_grpc_web_pb.js");
|
||||
async function listExtensions() {
|
||||
$("#extension-list-container").show();
|
||||
$("#extension-page-container").hide();
|
||||
$("#connection-page").show();
|
||||
|
||||
try {
|
||||
const extensionListContainer = document.getElementById('extension-list');
|
||||
extensionListContainer.innerHTML = ''; // Clear previous entries
|
||||
const response = await extensionClient.listExtensions(new extension.Empty(), {});
|
||||
|
||||
const extensionList = response.getExtensionsList();
|
||||
extensionList.forEach(ext => {
|
||||
const listItem = createExtensionListItem(ext);
|
||||
extensionListContainer.appendChild(listItem);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error listing extensions:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function createExtensionListItem(ext) {
|
||||
const listItem = document.createElement('li');
|
||||
listItem.className = 'list-group-item d-flex justify-content-between align-items-center';
|
||||
listItem.setAttribute('data-extension-id', ext.getId());
|
||||
|
||||
const contentDiv = document.createElement('div');
|
||||
|
||||
const titleElement = document.createElement('span');
|
||||
titleElement.innerHTML = `<strong>${ext.getTitle()}</strong>`;
|
||||
contentDiv.appendChild(titleElement);
|
||||
|
||||
const descriptionElement = document.createElement('p');
|
||||
descriptionElement.className = 'mb-0';
|
||||
descriptionElement.textContent = ext.getDescription();
|
||||
contentDiv.appendChild(descriptionElement);
|
||||
contentDiv.style.width="100%";
|
||||
listItem.appendChild(contentDiv);
|
||||
|
||||
const switchDiv = createSwitchElement(ext);
|
||||
listItem.appendChild(switchDiv);
|
||||
const {openExtensionPage} = require('./extensionPage.js');
|
||||
|
||||
contentDiv.addEventListener('click', () =>{
|
||||
if (!ext.getEnable() ){
|
||||
alert("Extension is not enabled")
|
||||
return
|
||||
}
|
||||
openExtensionPage(ext.getId())
|
||||
});
|
||||
|
||||
return listItem;
|
||||
}
|
||||
|
||||
function createSwitchElement(ext) {
|
||||
const switchDiv = document.createElement('div');
|
||||
switchDiv.className = 'form-check form-switch';
|
||||
|
||||
const switchButton = document.createElement('input');
|
||||
switchButton.type = 'checkbox';
|
||||
switchButton.className = 'form-check-input';
|
||||
switchButton.checked = ext.getEnable();
|
||||
switchButton.addEventListener('change', (e) => {
|
||||
|
||||
toggleExtension(ext.getId(), switchButton.checked)
|
||||
});
|
||||
|
||||
switchDiv.appendChild(switchButton);
|
||||
return switchDiv;
|
||||
}
|
||||
|
||||
async function toggleExtension(extensionId, enable) {
|
||||
const request = new extension.EditExtensionRequest();
|
||||
request.setExtensionId(extensionId);
|
||||
request.setEnable(enable);
|
||||
|
||||
try {
|
||||
await extensionClient.editExtension(request, {});
|
||||
console.log(`Extension ${extensionId} updated to ${enable ? 'enabled' : 'disabled'}`);
|
||||
} catch (err) {
|
||||
console.error('Error updating extension status:', err);
|
||||
}
|
||||
listExtensions();
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = { listExtensions };
|
||||
@@ -0,0 +1,87 @@
|
||||
const { extensionClient } = require('./client.js');
|
||||
const extension = require("./extension_grpc_web_pb.js");
|
||||
|
||||
const { renderForm } = require('./formRenderer.js');
|
||||
const { listExtensions } = require('./extensionList.js');
|
||||
var currentExtensionId = undefined;
|
||||
function openExtensionPage(extensionId) {
|
||||
currentExtensionId = extensionId;
|
||||
$("#extension-list-container").hide();
|
||||
$("#extension-page-container").show();
|
||||
$("#connection-page").hide();
|
||||
connect()
|
||||
}
|
||||
|
||||
function connect() {
|
||||
const request = new extension.ExtensionRequest();
|
||||
request.setExtensionId(currentExtensionId);
|
||||
|
||||
const stream = extensionClient.connect(request, {});
|
||||
|
||||
stream.on('data', (response) => {
|
||||
console.log('Receiving ', response);
|
||||
if (response.getExtensionId() === currentExtensionId) {
|
||||
ui = JSON.parse(response.getJsonUi())
|
||||
if (response.getType() == proto.hiddifyrpc.ExtensionResponseType.SHOW_DIALOG) {
|
||||
renderForm(ui, "dialog", handleSubmitButtonClick, undefined);
|
||||
} else {
|
||||
renderForm(ui, "", handleSubmitButtonClick, handleStopButtonClick);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', (err) => {
|
||||
console.error('Error opening extension page:', err);
|
||||
// openExtensionPage(extensionId);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
console.log('Stream ended');
|
||||
setTimeout(connect, 1000);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmitButtonClick(event, button) {
|
||||
event.preventDefault();
|
||||
bootstrap.Modal.getOrCreateInstance("#extension-dialog").hide();
|
||||
const request = new extension.SendExtensionDataRequest();
|
||||
request.setButton(button);
|
||||
if (event.type != 'hidden.bs.modal') {
|
||||
const formData = new FormData(event.target.closest('form'));
|
||||
const datamap = request.getDataMap()
|
||||
formData.forEach((value, key) => {
|
||||
datamap.set(key, value);
|
||||
});
|
||||
}
|
||||
request.setExtensionId(currentExtensionId);
|
||||
|
||||
try {
|
||||
await extensionClient.submitForm(request, {});
|
||||
console.log('Form submitted successfully.');
|
||||
} catch (err) {
|
||||
console.error('Error submitting form:', err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleStopButtonClick(event) {
|
||||
event.preventDefault();
|
||||
const request = new extension.ExtensionRequest();
|
||||
request.setExtensionId(currentExtensionId);
|
||||
bootstrap.Modal.getOrCreateInstance("#extension-dialog").hide();
|
||||
try {
|
||||
await extensionClient.close(request, {});
|
||||
console.log('Extension stopped successfully.');
|
||||
currentExtensionId = undefined;
|
||||
listExtensions(); // Return to the extension list
|
||||
} catch (err) {
|
||||
console.error('Error stopping extension:', err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = { openExtensionPage };
|
||||
@@ -0,0 +1,441 @@
|
||||
/**
|
||||
* @fileoverview gRPC-Web generated client stub for hiddifyrpc
|
||||
* @enhanceable
|
||||
* @public
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-grpc-web. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-grpc-web v1.5.0
|
||||
// protoc v5.28.0
|
||||
// source: extension.proto
|
||||
|
||||
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
|
||||
|
||||
const grpc = {};
|
||||
grpc.web = require('grpc-web');
|
||||
|
||||
|
||||
var base_pb = require('./base_pb.js')
|
||||
const proto = {};
|
||||
proto.hiddifyrpc = require('./extension_pb.js');
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServiceClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {?Object} credentials
|
||||
* @param {?grpc.web.ClientOptions} options
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServicePromiseClient =
|
||||
function(hostname, credentials, options) {
|
||||
if (!options) options = {};
|
||||
options.format = 'text';
|
||||
|
||||
/**
|
||||
* @private @const {!grpc.web.GrpcWebClientBase} The client
|
||||
*/
|
||||
this.client_ = new grpc.web.GrpcWebClientBase(options);
|
||||
|
||||
/**
|
||||
* @private @const {string} The hostname
|
||||
*/
|
||||
this.hostname_ = hostname.replace(/\/+$/, '');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.hiddifyrpc.Empty,
|
||||
* !proto.hiddifyrpc.ExtensionList>}
|
||||
*/
|
||||
const methodDescriptor_ExtensionHostService_ListExtensions = new grpc.web.MethodDescriptor(
|
||||
'/hiddifyrpc.ExtensionHostService/ListExtensions',
|
||||
grpc.web.MethodType.UNARY,
|
||||
base_pb.Empty,
|
||||
proto.hiddifyrpc.ExtensionList,
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.Empty} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.hiddifyrpc.ExtensionList.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.Empty} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionList)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionList>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.listExtensions =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/ListExtensions',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_ListExtensions,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.Empty} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.hiddifyrpc.ExtensionList>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.listExtensions =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/ListExtensions',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_ListExtensions);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.hiddifyrpc.ExtensionRequest,
|
||||
* !proto.hiddifyrpc.ExtensionResponse>}
|
||||
*/
|
||||
const methodDescriptor_ExtensionHostService_Connect = new grpc.web.MethodDescriptor(
|
||||
'/hiddifyrpc.ExtensionHostService/Connect',
|
||||
grpc.web.MethodType.SERVER_STREAMING,
|
||||
proto.hiddifyrpc.ExtensionRequest,
|
||||
proto.hiddifyrpc.ExtensionResponse,
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.hiddifyrpc.ExtensionResponse.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request The request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionResponse>}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.connect =
|
||||
function(request, metadata) {
|
||||
return this.client_.serverStreaming(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/Connect',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_Connect);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request The request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionResponse>}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.connect =
|
||||
function(request, metadata) {
|
||||
return this.client_.serverStreaming(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/Connect',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_Connect);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.hiddifyrpc.EditExtensionRequest,
|
||||
* !proto.hiddifyrpc.ExtensionActionResult>}
|
||||
*/
|
||||
const methodDescriptor_ExtensionHostService_EditExtension = new grpc.web.MethodDescriptor(
|
||||
'/hiddifyrpc.ExtensionHostService/EditExtension',
|
||||
grpc.web.MethodType.UNARY,
|
||||
proto.hiddifyrpc.EditExtensionRequest,
|
||||
proto.hiddifyrpc.ExtensionActionResult,
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.EditExtensionRequest} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.EditExtensionRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.editExtension =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/EditExtension',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_EditExtension,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.EditExtensionRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.editExtension =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/EditExtension',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_EditExtension);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.hiddifyrpc.SendExtensionDataRequest,
|
||||
* !proto.hiddifyrpc.ExtensionActionResult>}
|
||||
*/
|
||||
const methodDescriptor_ExtensionHostService_SubmitForm = new grpc.web.MethodDescriptor(
|
||||
'/hiddifyrpc.ExtensionHostService/SubmitForm',
|
||||
grpc.web.MethodType.UNARY,
|
||||
proto.hiddifyrpc.SendExtensionDataRequest,
|
||||
proto.hiddifyrpc.ExtensionActionResult,
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.SendExtensionDataRequest} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.SendExtensionDataRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.submitForm =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/SubmitForm',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_SubmitForm,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.SendExtensionDataRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.submitForm =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/SubmitForm',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_SubmitForm);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.hiddifyrpc.ExtensionRequest,
|
||||
* !proto.hiddifyrpc.ExtensionActionResult>}
|
||||
*/
|
||||
const methodDescriptor_ExtensionHostService_Close = new grpc.web.MethodDescriptor(
|
||||
'/hiddifyrpc.ExtensionHostService/Close',
|
||||
grpc.web.MethodType.UNARY,
|
||||
proto.hiddifyrpc.ExtensionRequest,
|
||||
proto.hiddifyrpc.ExtensionActionResult,
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.close =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/Close',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_Close,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.close =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/Close',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_Close);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!grpc.web.MethodDescriptor<
|
||||
* !proto.hiddifyrpc.ExtensionRequest,
|
||||
* !proto.hiddifyrpc.ExtensionActionResult>}
|
||||
*/
|
||||
const methodDescriptor_ExtensionHostService_GetUI = new grpc.web.MethodDescriptor(
|
||||
'/hiddifyrpc.ExtensionHostService/GetUI',
|
||||
grpc.web.MethodType.UNARY,
|
||||
proto.hiddifyrpc.ExtensionRequest,
|
||||
proto.hiddifyrpc.ExtensionActionResult,
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
function(request) {
|
||||
return request.serializeBinary();
|
||||
},
|
||||
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>} metadata User defined
|
||||
* call metadata
|
||||
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
|
||||
* callback The callback function(error, response)
|
||||
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
|
||||
* The XHR Node Readable Stream
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.getUI =
|
||||
function(request, metadata, callback) {
|
||||
return this.client_.rpcCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/GetUI',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_GetUI,
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
|
||||
* request proto
|
||||
* @param {?Object<string, string>=} metadata User defined
|
||||
* call metadata
|
||||
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
|
||||
* Promise that resolves to the response
|
||||
*/
|
||||
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.getUI =
|
||||
function(request, metadata) {
|
||||
return this.client_.unaryCall(this.hostname_ +
|
||||
'/hiddifyrpc.ExtensionHostService/GetUI',
|
||||
request,
|
||||
metadata || {},
|
||||
methodDescriptor_ExtensionHostService_GetUI);
|
||||
};
|
||||
|
||||
|
||||
module.exports = proto.hiddifyrpc;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,239 @@
|
||||
|
||||
const ansi_up = new AnsiUp({
|
||||
escape_html: false,
|
||||
|
||||
});
|
||||
|
||||
|
||||
function renderForm(json, dialog, submitAction, stopAction) {
|
||||
const container = document.getElementById(`extension-page-container${dialog}`);
|
||||
const formId = `dynamicForm${json.id}${dialog}`;
|
||||
|
||||
const existingForm = document.getElementById(formId);
|
||||
if (existingForm) {
|
||||
existingForm.remove();
|
||||
}
|
||||
const form = document.createElement('form');
|
||||
container.appendChild(form);
|
||||
form.id = formId;
|
||||
|
||||
if (dialog === "dialog") {
|
||||
document.getElementById("modalLabel").textContent = json.title;
|
||||
} else {
|
||||
const titleElement = createTitleElement(json);
|
||||
const stopBtn = document.createElement('button');
|
||||
stopBtn.type = 'button';
|
||||
stopBtn.className = 'btn btn-danger';
|
||||
stopBtn.textContent = 'Close';
|
||||
stopBtn.addEventListener('click', stopAction);
|
||||
form.appendChild(stopBtn);
|
||||
form.appendChild(titleElement);
|
||||
}
|
||||
addElementsToForm(form, json,submitAction);
|
||||
|
||||
if (dialog === "dialog") {
|
||||
document.getElementById("modal-footer").innerHTML = '';
|
||||
// if ($(form.lastChild).find("button").length > 0) {
|
||||
|
||||
// document.getElementById("modal-footer").appendChild(form.lastChild);
|
||||
|
||||
// }
|
||||
const extensionDialog = document.getElementById("extension-dialog");
|
||||
const dialog = bootstrap.Modal.getOrCreateInstance(extensionDialog);
|
||||
dialog.show();
|
||||
extensionDialog.addEventListener("hidden.bs.modal", (e)=>submitAction(e,"CloseDialog"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function addElementsToForm(form, json,submitAction) {
|
||||
|
||||
|
||||
|
||||
const description = document.createElement('p');
|
||||
description.textContent = json.description;
|
||||
form.appendChild(description);
|
||||
if (json.fields) {
|
||||
json.fields.forEach(field => {
|
||||
div=document.createElement("div")
|
||||
div.classList.add("row")
|
||||
form.appendChild(div)
|
||||
for (let i = 0; i < field.length; i++) {
|
||||
const formGroup = createFormGroup(field[i], submitAction);
|
||||
formGroup.classList.add("col")
|
||||
div.appendChild(formGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
function createTitleElement(json) {
|
||||
const title = document.createElement('h1');
|
||||
title.textContent = json.title;
|
||||
return title;
|
||||
}
|
||||
|
||||
function createFormGroup(field, submitAction) {
|
||||
const formGroup = document.createElement('div');
|
||||
formGroup.classList.add('mb-3');
|
||||
if (field.type == "Button") {
|
||||
const button = document.createElement('button');
|
||||
button.textContent = field.label;
|
||||
button.name=field.key
|
||||
button.classList.add('btn');
|
||||
if (field.key == "Submit") {
|
||||
button.classList.add('btn-primary');
|
||||
} else if (field.key == "Cancel") {
|
||||
button.classList.add('btn-secondary');
|
||||
}else{
|
||||
button.classList.add('btn', 'btn-outline-secondary');
|
||||
}
|
||||
|
||||
button.addEventListener('click', (e) => submitAction(e,field.key));
|
||||
formGroup.appendChild(button);
|
||||
} else {
|
||||
if (field.label && !field.labelHidden) {
|
||||
const label = document.createElement('label');
|
||||
label.textContent = field.label;
|
||||
label.setAttribute('for', field.key);
|
||||
formGroup.appendChild(label);
|
||||
}
|
||||
|
||||
const input = createInputElement(field);
|
||||
formGroup.appendChild(input);
|
||||
}
|
||||
return formGroup;
|
||||
}
|
||||
|
||||
function createInputElement(field) {
|
||||
let input;
|
||||
|
||||
switch (field.type) {
|
||||
case "Console":
|
||||
input = document.createElement('pre');
|
||||
input.innerHTML = ansi_up.ansi_to_html(field.value || field.placeholder || '');
|
||||
input.style.maxHeight = field.lines * 20 + 'px';
|
||||
break;
|
||||
case "TextArea":
|
||||
input = document.createElement('textarea');
|
||||
input.rows = field.lines || 3;
|
||||
input.textContent = field.value || '';
|
||||
break;
|
||||
|
||||
case "Checkbox":
|
||||
case "RadioButton":
|
||||
input = createCheckboxOrRadioGroup(field);
|
||||
break;
|
||||
|
||||
case "Switch":
|
||||
input = createSwitchElement(field);
|
||||
break;
|
||||
|
||||
case "Select":
|
||||
input = document.createElement('select');
|
||||
field.items.forEach(item => {
|
||||
const option = document.createElement('option');
|
||||
option.value = item.value;
|
||||
option.text = item.label;
|
||||
input.appendChild(option);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
input = document.createElement('input');
|
||||
input.type = field.type.toLowerCase();
|
||||
input.value = field.value;
|
||||
break;
|
||||
}
|
||||
|
||||
input.id = field.key;
|
||||
input.name = field.key;
|
||||
if (field.readOnly) input.readOnly = true;
|
||||
if (field.type == "Checkbox" || field.type == "RadioButton" || field.type == "Switch") {
|
||||
|
||||
} else {
|
||||
if (field.required) input.required = true;
|
||||
input.classList.add('form-control');
|
||||
if (field.placeholder) input.placeholder = field.placeholder;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
function createCheckboxOrRadioGroup(field) {
|
||||
const wrapper = document.createDocumentFragment();
|
||||
|
||||
field.items.forEach(item => {
|
||||
const inputWrapper = document.createElement('div');
|
||||
inputWrapper.classList.add('form-check');
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = field.type === "Checkbox" ? 'checkbox' : 'radio';
|
||||
input.classList.add('form-check-input');
|
||||
input.id = `${field.key}_${item.value}`;
|
||||
input.name = field.key; // Grouping by name for radio buttons
|
||||
input.value = item.value;
|
||||
input.checked = field.value === item.value;
|
||||
|
||||
const itemLabel = document.createElement('label');
|
||||
itemLabel.classList.add('form-check-label');
|
||||
itemLabel.setAttribute('for', input.id);
|
||||
itemLabel.textContent = item.label;
|
||||
|
||||
inputWrapper.appendChild(input);
|
||||
inputWrapper.appendChild(itemLabel);
|
||||
wrapper.appendChild(inputWrapper);
|
||||
});
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function createSwitchElement(field) {
|
||||
const switchWrapper = document.createElement('div');
|
||||
switchWrapper.classList.add('form-check', 'form-switch');
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.classList.add('form-check-input');
|
||||
input.setAttribute('role', 'switch');
|
||||
input.id = field.key;
|
||||
input.checked = field.value === "true";
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.classList.add('form-check-label');
|
||||
label.setAttribute('for', field.key);
|
||||
label.textContent = field.label;
|
||||
|
||||
switchWrapper.appendChild(input);
|
||||
switchWrapper.appendChild(label);
|
||||
|
||||
return switchWrapper;
|
||||
}
|
||||
|
||||
function createButtonGroup(json, submitAction, cancelAction) {
|
||||
const buttonGroup = document.createElement('div');
|
||||
buttonGroup.classList.add('btn-group');
|
||||
json.buttons.forEach(buttonText => {
|
||||
const btn = document.createElement('button');
|
||||
btn.classList.add('btn', "btn-default");
|
||||
buttonGroup.appendChild(btn);
|
||||
btn.textContent = buttonText
|
||||
if (buttonText == "Cancel") {
|
||||
btn.classList.add('btn-secondary');
|
||||
btn.addEventListener('click', cancelAction);
|
||||
} else {
|
||||
if (buttonText == "Submit" || buttonText == "Ok")
|
||||
btn.classList.add('btn-primary');
|
||||
btn.addEventListener('click', submitAction);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
return buttonGroup;
|
||||
}
|
||||
|
||||
|
||||
module.exports = { renderForm };
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hiddify/hiddify-core/v2/db"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/hiddify/hiddify-core/v2/service_manager"
|
||||
)
|
||||
|
||||
var (
|
||||
allExtensionsMap = make(map[string]ExtensionFactory)
|
||||
enabledExtensionsMap = make(map[string]*Extension)
|
||||
)
|
||||
|
||||
func RegisterExtension(factory ExtensionFactory) error {
|
||||
if _, ok := allExtensionsMap[factory.Id]; ok {
|
||||
err := fmt.Errorf("Extension with ID %s already exists", factory.Id)
|
||||
log.Warn(err)
|
||||
return err
|
||||
}
|
||||
|
||||
allExtensionsMap[factory.Id] = factory
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isEnable(id string) bool {
|
||||
table := db.GetTable[extensionData]()
|
||||
extdata, err := table.Get(id)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return extdata.Enable
|
||||
}
|
||||
|
||||
func loadExtension(factory ExtensionFactory) error {
|
||||
if !isEnable(factory.Id) {
|
||||
return fmt.Errorf("Extension with ID %s is not enabled", factory.Id)
|
||||
}
|
||||
extension := factory.Builder()
|
||||
extension.init(factory.Id)
|
||||
|
||||
// fmt.Printf("Registered extension: %+v\n", extension)
|
||||
enabledExtensionsMap[factory.Id] = &extension
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type extensionService struct {
|
||||
// Storage *CacheFile
|
||||
}
|
||||
|
||||
func (s *extensionService) Start() error {
|
||||
table := db.GetTable[extensionData]()
|
||||
|
||||
for _, factory := range allExtensionsMap {
|
||||
data, err := table.Get(factory.Id)
|
||||
|
||||
if data == nil || err != nil {
|
||||
log.Warn("Data of Extension ", factory.Id, " not found, creating new one")
|
||||
data = &extensionData{Id: factory.Id, Enable: false}
|
||||
if err := table.UpdateInsert(data); err != nil {
|
||||
log.Warn("Failed to create new extension data: ", err, " ", factory.Id)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if data.Enable {
|
||||
if err := loadExtension(factory); err != nil {
|
||||
return fmt.Errorf("failed to load extension %s: %w", data.Id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *extensionService) Close() error {
|
||||
for _, extension := range enabledExtensionsMap {
|
||||
if err := (*extension).Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
service_manager.Register(&extensionService{})
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
_ "github.com/hiddify/hiddify-app-demo-extension/hiddify_extension"
|
||||
_ "github.com/hiddify/hiddify-ip-scanner-extension/hiddify_extension"
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hiddify/hiddify-core/config"
|
||||
v2 "github.com/hiddify/hiddify-core/v2"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func RunInstance(hiddifySettings *config.HiddifyOptions, singconfig *option.Options) (*v2.HiddifyService, error) {
|
||||
return v2.RunInstance(hiddifySettings, singconfig)
|
||||
}
|
||||
|
||||
func ParseConfig(hiddifySettings *config.HiddifyOptions, configStr string) (*option.Options, error) {
|
||||
if hiddifySettings == nil {
|
||||
hiddifySettings = config.DefaultHiddifyOptions()
|
||||
}
|
||||
if strings.HasPrefix(configStr, "http://") || strings.HasPrefix(configStr, "https://") {
|
||||
client := &http.Client{}
|
||||
configPath := strings.Split(configStr, "\n")[0]
|
||||
// Create a new request
|
||||
req, err := http.NewRequest("GET", configPath, nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating request:", err)
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "HiddifyNext/2.3.1 ("+runtime.GOOS+") like ClashMeta v2ray sing-box")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println("Error making GET request:", err)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config body: %w", err)
|
||||
}
|
||||
configStr = string(body)
|
||||
}
|
||||
return config.ParseConfigContentToOptions(configStr, true, hiddifySettings, false)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
v2 "github.com/hiddify/hiddify-core/v2"
|
||||
|
||||
"github.com/hiddify/hiddify-core/utils"
|
||||
"github.com/improbable-eng/grpc-web/go/grpcweb"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func StartTestExtensionServer() {
|
||||
v2.Setup("./tmp", "./", "./tmp", 0, false)
|
||||
StartExtensionServer()
|
||||
}
|
||||
|
||||
func StartExtensionServer() {
|
||||
grpc_server, _ := v2.StartCoreGrpcServer("127.0.0.1:12345")
|
||||
fmt.Printf("Waiting for CTRL+C to stop\n")
|
||||
runWebserver(grpc_server)
|
||||
}
|
||||
|
||||
func allowCors(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
resp.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
resp.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
if req.Method == "OPTIONS" {
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func runWebserver(grpcServer *grpc.Server) {
|
||||
// Context for cancellation
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Channels to signal termination
|
||||
grpcTerminated := make(chan struct{})
|
||||
grpcWebTerminated := make(chan struct{})
|
||||
|
||||
// Specify the directory to serve static files
|
||||
dir := "./extension/html/"
|
||||
|
||||
// Wrapping gRPC server with grpc-web
|
||||
grpcWeb := grpcweb.WrapServer(grpcServer)
|
||||
|
||||
// HTTP multiplexer
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
|
||||
allowCors(resp, req)
|
||||
if grpcWeb.IsGrpcWebRequest(req) || grpcWeb.IsAcceptableGrpcCorsRequest(req) {
|
||||
grpcWeb.ServeHTTP(resp, req)
|
||||
} else {
|
||||
http.DefaultServeMux.ServeHTTP(resp, req)
|
||||
}
|
||||
})
|
||||
|
||||
// File server for static files
|
||||
fs := http.FileServer(http.Dir(dir))
|
||||
http.Handle("/", http.StripPrefix("/", fs))
|
||||
|
||||
// HTTP server for grpc-web
|
||||
rpcWebServer := &http.Server{
|
||||
Handler: mux,
|
||||
Addr: ":12346",
|
||||
}
|
||||
log.Println("Serving grpc-web from https://localhost:12346/")
|
||||
|
||||
// Add a goroutine for the grpc-web server
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
utils.GenerateCertificate("cert/server-cert.pem", "cert/server-key.pem", true, true)
|
||||
if err := rpcWebServer.ListenAndServeTLS("cert/server-cert.pem", "cert/server-key.pem"); err != nil && err != http.ErrServerClosed {
|
||||
// if err := rpcWebServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
fmt.Printf("Web server (gRPC-web) shutdown with error: %s", err)
|
||||
}
|
||||
grpcServer.Stop()
|
||||
close(grpcWebTerminated) // Server terminated
|
||||
}()
|
||||
|
||||
// Signal handling to gracefully shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case <-ctx.Done(): // Context canceled
|
||||
log.Println("Context canceled, shutting down servers...")
|
||||
case sig := <-sigChan: // OS signal received
|
||||
log.Printf("Received signal: %s, shutting down servers...", sig)
|
||||
case <-grpcTerminated: // Unexpected gRPC termination
|
||||
log.Println("gRPC server terminated unexpectedly")
|
||||
case <-grpcWebTerminated: // Unexpected gRPC-web termination
|
||||
log.Println("gRPC-web server terminated unexpectedly")
|
||||
}
|
||||
|
||||
// Graceful shutdown of the servers
|
||||
if err := rpcWebServer.Shutdown(ctx); err != nil {
|
||||
log.Printf("gRPC-web server shutdown with error: %s", err)
|
||||
}
|
||||
<-grpcWebTerminated
|
||||
|
||||
// Ensure all routines finish
|
||||
wg.Wait()
|
||||
log.Println("Server shutdown complete")
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package ui
|
||||
|
||||
// // Field is an interface that all specific field types implement.
|
||||
// type Field interface {
|
||||
// GetType() string
|
||||
// }
|
||||
|
||||
// // GenericField holds common field properties.
|
||||
// const (
|
||||
// Select string = "Select"
|
||||
// Email string = "Email"
|
||||
// Input string = "Input"
|
||||
// Password string = "Password"
|
||||
// TextArea string = "TextArea"
|
||||
// Switch string = "Switch"
|
||||
// Checkbox string = "Checkbox"
|
||||
// RadioButton string = "RadioButton"
|
||||
// DigitsOnly string = "digitsOnly"
|
||||
// )
|
||||
|
||||
// // FormField extends GenericField with additional common properties.
|
||||
// type FormField struct {
|
||||
// Key string `json:"key"`
|
||||
// Type string `json:"type"`
|
||||
// Label string `json:"label,omitempty"`
|
||||
// LabelHidden bool `json:"labelHidden"`
|
||||
// Required bool `json:"required,omitempty"`
|
||||
// Placeholder string `json:"placeholder,omitempty"`
|
||||
// Readonly bool `json:"readonly,omitempty"`
|
||||
// Value string `json:"value"`
|
||||
// Validator string `json:"validator,omitempty"`
|
||||
// Items []SelectItem `json:"items,omitempty"`
|
||||
// Lines int `json:"lines,omitempty"`
|
||||
// VerticalScroll bool `json:"verticalScroll,omitempty"`
|
||||
// HorizontalScroll bool `json:"horizontalScroll,omitempty"`
|
||||
// Monospace bool `json:"monospace,omitempty"`
|
||||
// }
|
||||
|
||||
// // GetType returns the type of the field.
|
||||
// func (gf FormField) GetType() string {
|
||||
// return gf.Type
|
||||
// }
|
||||
@@ -0,0 +1,75 @@
|
||||
package ui
|
||||
|
||||
// import (
|
||||
// "encoding/json"
|
||||
// "testing"
|
||||
// )
|
||||
|
||||
// // Test UnmarshalJSON for different field types
|
||||
// func TestFormUnmarshalJSON(t *testing.T) {
|
||||
// formJSON := `{
|
||||
// "title": "Form Example",
|
||||
// "description": "This is a sample form.",
|
||||
// "fields": [
|
||||
// {
|
||||
// "key": "inputKey",
|
||||
// "type": "Input",
|
||||
// "label": "Hi Group",
|
||||
// "placeholder": "Hi Group flutter",
|
||||
// "required": true,
|
||||
// "value": "D"
|
||||
// },
|
||||
// {
|
||||
// "key": "passwordKey",
|
||||
// "type": "Password",
|
||||
// "label": "Password",
|
||||
// "required": true,
|
||||
// "value": "secret"
|
||||
// },
|
||||
// {
|
||||
// "key": "emailKey",
|
||||
// "type": "Email",
|
||||
// "label": "Email Label",
|
||||
// "placeholder": "Enter your email",
|
||||
// "required": true,
|
||||
// "value": "example@example.com"
|
||||
// }
|
||||
// ]
|
||||
// }`
|
||||
|
||||
// var form Form
|
||||
// err := json.Unmarshal([]byte(formJSON), &form)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Error unmarshaling form JSON: %v", err)
|
||||
// }
|
||||
|
||||
// if form.Title != "Form Example" {
|
||||
// t.Errorf("Expected Title to be 'Form Example', got '%s'", form.Title)
|
||||
// }
|
||||
// if form.Description != "This is a sample form." {
|
||||
// t.Errorf("Expected Description to be 'This is a sample form.', got '%s'", form.Description)
|
||||
// }
|
||||
|
||||
// if len(form.Fields) != 3 {
|
||||
// t.Fatalf("Expected 3 fields, got %d", len(form.Fields))
|
||||
// }
|
||||
|
||||
// for i, field := range form.Fields {
|
||||
// switch f := field.(type) {
|
||||
// case InputField:
|
||||
// if f.Type != "Input" {
|
||||
// t.Errorf("Field %d: Expected Type to be 'Input', got '%s'", i+1, f.Type)
|
||||
// }
|
||||
// case PasswordField:
|
||||
// if f.Type != "Password" {
|
||||
// t.Errorf("Field %d: Expected Type to be 'Password', got '%s'", i+1, f.Type)
|
||||
// }
|
||||
// case EmailField:
|
||||
// if f.Type != "Email" {
|
||||
// t.Errorf("Field %d: Expected Type to be 'Email', got '%s'", i+1, f.Type)
|
||||
// }
|
||||
// default:
|
||||
// t.Errorf("Field %d: Unexpected field type %T", i+1, f)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,88 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Field is an interface that all specific field types implement.
|
||||
type Field interface {
|
||||
GetType() string
|
||||
}
|
||||
|
||||
// GenericField holds common field properties.
|
||||
const (
|
||||
FieldSelect string = "Select"
|
||||
FieldEmail string = "Email"
|
||||
FieldInput string = "Input"
|
||||
FieldPassword string = "Password"
|
||||
FieldTextArea string = "TextArea"
|
||||
FieldSwitch string = "Switch"
|
||||
FieldCheckbox string = "Checkbox"
|
||||
FieldRadioButton string = "RadioButton"
|
||||
FieldConsole string = "Console"
|
||||
FieldButton string = "Button"
|
||||
ValidatorDigitsOnly string = "digitsOnly"
|
||||
|
||||
ButtonSubmit string = "Submit"
|
||||
ButtonCancel string = "Cancel"
|
||||
|
||||
ButtonDialogClose string = "CloseDialog"
|
||||
ButtonDialogOk string = "OkDialog"
|
||||
)
|
||||
|
||||
// FormField extends GenericField with additional common properties.
|
||||
type FormField struct {
|
||||
Key string `json:"key"`
|
||||
Type string `json:"type"`
|
||||
Label string `json:"label,omitempty"`
|
||||
LabelHidden bool `json:"labelHidden"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Readonly bool `json:"readonly,omitempty"`
|
||||
Value string `json:"value"`
|
||||
Validator string `json:"validator,omitempty"`
|
||||
Items []SelectItem `json:"items,omitempty"`
|
||||
Lines int `json:"lines,omitempty"`
|
||||
}
|
||||
|
||||
// GetType returns the type of the field.
|
||||
func (gf FormField) GetType() string {
|
||||
return gf.Type
|
||||
}
|
||||
|
||||
type InputField struct {
|
||||
FormField
|
||||
Validator string `json:"validator,omitempty"`
|
||||
}
|
||||
|
||||
type SelectItem struct {
|
||||
Label string `json:"label"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Form struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Fields [][]FormField `json:"fields"`
|
||||
// Buttons []string `json:"buttons"`
|
||||
}
|
||||
|
||||
func (f *Form) ToJSON() string {
|
||||
formJson, err := json.MarshalIndent(f, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("Error encoding to JSON:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return (string(formJson))
|
||||
}
|
||||
|
||||
// UnmarshalJSON custom unmarshals JSON data into a Form.
|
||||
func (f *Form) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package ui
|
||||
|
||||
// // ContentField represents a label with additional properties.
|
||||
// type ContentField struct {
|
||||
// GenericField
|
||||
// Lines int `json:"lines,omitempty"`
|
||||
// VerticalScroll bool `json:"verticalScroll,omitempty"`
|
||||
// HorizontalScroll bool `json:"horizontalScroll,omitempty"`
|
||||
// Monospace bool `json:"monospace,omitempty"`
|
||||
// }
|
||||
|
||||
// // NewContentField creates a new ContentField.
|
||||
// func NewContentField(key, label string, lines int, monospace, horizontalScroll, verticalScroll bool) ContentField {
|
||||
// return ContentField{
|
||||
// GenericField: GenericField{
|
||||
// Key: key,
|
||||
// Type: "Content",
|
||||
// Label: label,
|
||||
// },
|
||||
// Lines: lines,
|
||||
// VerticalScroll: verticalScroll,
|
||||
// HorizontalScroll: horizontalScroll,
|
||||
// Monospace: monospace,
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1 @@
|
||||
package ui
|
||||
@@ -0,0 +1,244 @@
|
||||
package ui
|
||||
|
||||
// import (
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// )
|
||||
|
||||
// // InputField represents a text input field.
|
||||
// type InputField struct {
|
||||
// FormField
|
||||
// Validator string `json:"validator,omitempty"`
|
||||
|
||||
// }
|
||||
|
||||
// // // NewInputField creates a new InputField.
|
||||
// // func NewInputField(key, label, placeholder string, required bool, value string) InputField {
|
||||
// // return InputField{
|
||||
// // FormField: FormField{
|
||||
// // GenericField: GenericField{
|
||||
// // Key: key,
|
||||
// // Type: "Input",
|
||||
// // Label: label,
|
||||
// // },
|
||||
// // Placeholder: placeholder,
|
||||
// // Required: required,
|
||||
// // Value: value,
|
||||
// // },
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // // PasswordField represents a password field.
|
||||
// // type PasswordField struct {
|
||||
// // FormField
|
||||
// // }
|
||||
|
||||
// // // NewPasswordField creates a new PasswordField.
|
||||
// // func NewPasswordField(key, label string, required bool, value string) PasswordField {
|
||||
// // return PasswordField{
|
||||
// // FormField: FormField{
|
||||
// // GenericField: GenericField{
|
||||
// // Key: key,
|
||||
// // Type: "Password",
|
||||
// // Label: label,
|
||||
// // },
|
||||
// // Required: required,
|
||||
// // Value: value,
|
||||
// // },
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // // EmailField represents an email field.
|
||||
// // type EmailField struct {
|
||||
// // FormField
|
||||
// // }
|
||||
|
||||
// // // NewEmailField creates a new EmailField.
|
||||
// // func NewEmailField(key, label, placeholder string, required bool, value string) EmailField {
|
||||
// // return EmailField{
|
||||
// // FormField: FormField{
|
||||
// // GenericField: GenericField{
|
||||
// // Key: key,
|
||||
// // Type: "Email",
|
||||
// // Label: label,
|
||||
// // },
|
||||
// // Placeholder: placeholder,
|
||||
// // Required: required,
|
||||
// // Value: value,
|
||||
// // },
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // // TextAreaField represents a multi-line text area field.
|
||||
// // type TextAreaField struct {
|
||||
// // FormField
|
||||
// // }
|
||||
|
||||
// // // NewTextAreaField creates a new TextAreaField.
|
||||
// // func NewTextAreaField(key, label, placeholder string, required bool, value string) TextAreaField {
|
||||
// // return TextAreaField{
|
||||
// // FormField: FormField{
|
||||
// // GenericField: GenericField{
|
||||
// // Key: key,
|
||||
// // Type: "TextArea",
|
||||
// // Label: label,
|
||||
// // },
|
||||
// // Placeholder: placeholder,
|
||||
// // Required: required,
|
||||
// // Value: value,
|
||||
// // },
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // // SelectField represents a dropdown selection field.
|
||||
// // type SelectField struct {
|
||||
// // FormField
|
||||
// // Items []SelectItem `json:"items"`
|
||||
// // }
|
||||
|
||||
// // // SelectItem represents an item in a dropdown.
|
||||
// type SelectItem struct {
|
||||
// Label string `json:"label"`
|
||||
// Value string `json:"value"`
|
||||
// }
|
||||
|
||||
// // // NewSelectField creates a new SelectField.
|
||||
// // func NewSelectField(key, label, value string, items []SelectItem) SelectField {
|
||||
// // return SelectField{
|
||||
// // FormField: FormField{
|
||||
// // GenericField: GenericField{
|
||||
// // Key: key,
|
||||
// // Type: "Select",
|
||||
// // Label: label,
|
||||
// // },
|
||||
// // Value: value,
|
||||
// // },
|
||||
// // Items: items,
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // Form represents a collection of fields with metadata.
|
||||
// type Form struct {
|
||||
// Title string `json:"title"`
|
||||
// Description string `json:"description"`
|
||||
// Fields []FormField `json:"fields"`
|
||||
// }
|
||||
|
||||
// func (f *Form) ToJSON() string {
|
||||
// formJson, err := json.MarshalIndent(f, "", " ")
|
||||
// if err != nil {
|
||||
// fmt.Println("Error encoding to JSON:", err)
|
||||
// return ""
|
||||
// }
|
||||
// return (string(formJson))
|
||||
// }
|
||||
|
||||
// // UnmarshalJSON custom unmarshals JSON data into a Form.
|
||||
// func (f *Form) UnmarshalJSON(data []byte) error {
|
||||
// if err := json.Unmarshal(data, &f); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // f.Title = raw.Title
|
||||
// // f.Description = raw.Description
|
||||
|
||||
// // for _, fieldData := range raw.Fields {
|
||||
// // var base FormField
|
||||
// // if err := json.Unmarshal(fieldData, &base); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
|
||||
// // var field Field
|
||||
// // switch base.Type {
|
||||
// // case "Input":
|
||||
// // var inputField InputField
|
||||
// // if err := json.Unmarshal(fieldData, &inputField); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // field = inputField
|
||||
// // case "Password":
|
||||
// // var passwordField PasswordField
|
||||
// // if err := json.Unmarshal(fieldData, &passwordField); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // field = passwordField
|
||||
// // case "Email":
|
||||
// // var emailField EmailField
|
||||
// // if err := json.Unmarshal(fieldData, &emailField); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // field = emailField
|
||||
// // case "TextArea":
|
||||
// // var textAreaField TextAreaField
|
||||
// // if err := json.Unmarshal(fieldData, &textAreaField); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // field = textAreaField
|
||||
// // case "Select":
|
||||
// // var selectField SelectField
|
||||
// // if err := json.Unmarshal(fieldData, &selectField); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // field = selectField
|
||||
// // case "Content":
|
||||
// // var contentField ContentField
|
||||
// // if err := json.Unmarshal(fieldData, &contentField); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // field = contentField
|
||||
// // default:
|
||||
// // return fmt.Errorf("unsupported field type: %s", base.Type)
|
||||
// // }
|
||||
|
||||
// // f.Fields = append(f.Fields, field)
|
||||
// // }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // func main() {
|
||||
// // // Example form JSON
|
||||
// // formJSON := `{
|
||||
// // "title": "Form Example",
|
||||
// // "description": "",
|
||||
// // "fields": [
|
||||
// // {
|
||||
// // "key": "inputKey",
|
||||
// // "type": "Input",
|
||||
// // "label": "Hi Group",
|
||||
// // "placeholder": "Hi Group flutter",
|
||||
// // "required": true,
|
||||
// // "value": "D"
|
||||
// // },
|
||||
// // {
|
||||
// // "key": "passwordKey",
|
||||
// // "type": "Password",
|
||||
// // "label": "Password",
|
||||
// // "required": true,
|
||||
// // "value": "secret"
|
||||
// // },
|
||||
// // {
|
||||
// // "key": "emailKey",
|
||||
// // "type": "Email",
|
||||
// // "label": "Email Label",
|
||||
// // "placeholder": "Enter your email",
|
||||
// // "required": true,
|
||||
// // "value": "example@example.com"
|
||||
// // }
|
||||
// // ]
|
||||
// // }`
|
||||
|
||||
// // var form Form
|
||||
|
||||
// // // Decode the form JSON
|
||||
// // if err := json.Unmarshal([]byte(formJSON), &form); err != nil {
|
||||
// // fmt.Println("Error decoding form:", err)
|
||||
// // return
|
||||
// // }
|
||||
|
||||
// // // Print decoded form fields
|
||||
// // fmt.Println("Form Title:", form.Title)
|
||||
// // for i, field := range form.Fields {
|
||||
// // fmt.Printf("Field %d: %T\n", i+1, field)
|
||||
// // }
|
||||
// // }
|
||||
Reference in New Issue
Block a user