diff --git a/controller/task_plugin.go b/controller/task_plugin.go
index 0b27c1183..45db4813d 100644
--- a/controller/task_plugin.go
+++ b/controller/task_plugin.go
@@ -26,7 +26,7 @@ import (
"gorm.io/gorm"
)
-const maxTaskPluginSourceBytes = 1024 * 1024
+const maxTaskPluginSourceBytes = 8 << 20
func taskPluginCompileError(c *gin.Context, err error) {
var unknownField *jsplugin.UnknownMetaFieldError
@@ -55,7 +55,7 @@ func UploadTaskPlugin(c *gin.Context) {
return
}
if len(request.Source) > maxTaskPluginSourceBytes {
- common.ApiErrorMsg(c, "plugin source exceeds 1 MiB")
+ common.ApiErrorMsg(c, "plugin source exceeds 8 MiB")
return
}
if expected := strings.TrimSpace(request.SourceSha256); expected != "" {
@@ -94,8 +94,8 @@ func UploadTaskPlugin(c *gin.Context) {
}
plugin := model.TaskPlugin{
Key: loaded.Meta.Key, APIVersion: loaded.Meta.APIVersion, Version: loaded.Meta.Version,
- Source: request.Source, SourceHash: fmt.Sprintf("%x", sha256.Sum256([]byte(request.Source))),
- Icon: icon, Enabled: enabled, Remark: request.Remark,
+ Source: model.LongText(request.Source), SourceHash: fmt.Sprintf("%x", sha256.Sum256([]byte(request.Source))),
+ Icon: model.LongText(icon), Enabled: enabled, Remark: request.Remark,
}
if err = model.SaveTaskPlugin(&plugin); err != nil {
common.ApiError(c, err)
@@ -105,7 +105,7 @@ func UploadTaskPlugin(c *gin.Context) {
common.ApiError(c, err)
return
}
- common.ApiSuccess(c, taskPluginDetail{Plugin: &plugin, Meta: loaded.Meta, Source: plugin.Source, Layer: "override", HasIcon: plugin.HasIcon()})
+ common.ApiSuccess(c, taskPluginDetail{Plugin: &plugin, Meta: loaded.Meta, Source: string(plugin.Source), Layer: "override", HasIcon: plugin.HasIcon()})
}
func GetTaskPluginVersions(c *gin.Context) {
@@ -195,7 +195,7 @@ func ListTaskPlugins(c *gin.Context) {
item.FactoryMeta = &factoryCopy
}
item.Meta = jsplugin.Meta{Key: row.Key, Version: row.Version, APIVersion: row.APIVersion}
- if compiled, compileErr := jsplugin.NewRegistry().Register(row.Source, jsplugin.Options{Key: row.Key, Version: row.Version}); compileErr == nil {
+ if compiled, compileErr := jsplugin.NewRegistry().Register(string(row.Source), jsplugin.Options{Key: row.Key, Version: row.Version}); compileErr == nil {
item.Meta = compiled.Meta
}
item.Enabled = row.Enabled
@@ -316,7 +316,7 @@ func GetTaskPluginIcon(c *gin.Context) {
icon := ""
plugin, err := model.GetTaskPluginVersion(key, c.Query("version"))
if err == nil {
- icon = plugin.Icon
+ icon = string(plugin.Icon)
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
common.ApiError(c, err)
return
@@ -344,12 +344,12 @@ func GetTaskPlugin(c *gin.Context) {
version := c.Query("version")
plugin, err := model.GetTaskPluginVersion(key, version)
if err == nil {
- loaded, compileErr := jsplugin.NewRegistry().Register(plugin.Source, jsplugin.Options{Key: plugin.Key, Version: plugin.Version})
+ loaded, compileErr := jsplugin.NewRegistry().Register(string(plugin.Source), jsplugin.Options{Key: plugin.Key, Version: plugin.Version})
if compileErr != nil {
taskPluginCompileError(c, compileErr)
return
}
- common.ApiSuccess(c, taskPluginDetail{Plugin: plugin, Meta: loaded.Meta, Source: plugin.Source, Layer: "override", HasIcon: plugin.HasIcon()})
+ common.ApiSuccess(c, taskPluginDetail{Plugin: plugin, Meta: loaded.Meta, Source: string(plugin.Source), Layer: "override", HasIcon: plugin.HasIcon()})
return
}
if !errors.Is(err, gorm.ErrRecordNotFound) || version != "" {
@@ -385,7 +385,7 @@ func DryRunTaskPlugin(c *gin.Context) {
detailSource := ""
plugin, err := model.GetTaskPluginVersion(c.Param("key"), "")
if err == nil {
- detailSource = plugin.Source
+ detailSource = string(plugin.Source)
} else if errors.Is(err, gorm.ErrRecordNotFound) {
detailSource, err = plugins.Source(c.Param("key"))
}
@@ -483,7 +483,7 @@ func ActivateTaskPlugin(c *gin.Context) {
common.ApiErrorMsg(c, "plugin version not found")
return
}
- if _, err = jsplugin.NewRegistry().Register(target.Source, jsplugin.Options{Key: target.Key, Version: target.Version}); err != nil {
+ if _, err = jsplugin.NewRegistry().Register(string(target.Source), jsplugin.Options{Key: target.Key, Version: target.Version}); err != nil {
taskPluginCompileError(c, err)
return
}
@@ -770,7 +770,7 @@ func syncTaskPluginsOnceContext(ctx context.Context) error {
plugin.Key,
plugin.Version,
)
- compiled, compileErr := jsplugin.CompilePlugin(plugin.Source, jsplugin.Options{Key: plugin.Key, Version: plugin.Version})
+ compiled, compileErr := jsplugin.CompilePlugin(string(plugin.Source), jsplugin.Options{Key: plugin.Key, Version: plugin.Version})
if compileErr != nil {
retainedIncumbent := false
if current := currentOverrides[plugin.Key]; current != nil {
diff --git a/controller/task_plugin_test.go b/controller/task_plugin_test.go
index 33edfd481..380dc6bff 100644
--- a/controller/task_plugin_test.go
+++ b/controller/task_plugin_test.go
@@ -272,7 +272,7 @@ func TestDisableFactoryOverrideRowSuppressesBothLayers(t *testing.T) {
t.Cleanup(func() { jsplugin.DefaultRegistry.Unregister("kling") })
plugin := model.TaskPlugin{
Key: "kling", APIVersion: loaded.Meta.APIVersion, Version: loaded.Meta.Version,
- Source: overrideSource, SourceHash: "test-hash", Enabled: true,
+ Source: model.LongText(overrideSource), SourceHash: "test-hash", Enabled: true,
}
require.NoError(t, model.SaveTaskPlugin(&plugin))
require.NoError(t, syncTaskPluginsOnce())
@@ -326,6 +326,64 @@ export function parseTaskResult() { return {}; }
assert.True(t, ok)
}
+// The shipped alibaba plugin is already larger than a MySQL TEXT column
+// (64 KiB), so storing it through the model path the upload endpoint uses and
+// reading it back unchanged proves the source and icon columns are not
+// truncated. Set TEST_TASK_DB_DIALECT plus TEST_MYSQL_DSN or TEST_POSTGRES_DSN
+// to run the same contract against a real MySQL or PostgreSQL server.
+func TestTaskPluginSourceAboveMySQLTextLimitRoundTrips(t *testing.T) {
+ database, _ := openTaskDialectDatabase(t, &model.TaskPlugin{})
+ originalDB := model.DB
+ model.DB = database
+ t.Cleanup(func() { model.DB = originalDB })
+
+ source, err := plugins.Source("alibaba")
+ require.NoError(t, err)
+ require.Greater(t, len(source), 65535, "fixture must exceed the MySQL TEXT limit")
+ icon := "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(strings.Repeat(``, 2048)))
+ require.Greater(t, len(icon), 65535, "icon fixture must exceed the MySQL TEXT limit")
+
+ plugin := model.TaskPlugin{Key: "alibaba", APIVersion: 1, Version: "roundtrip", Source: model.LongText(source), SourceHash: "hash", Enabled: true}
+ require.NoError(t, model.SaveTaskPlugin(&plugin))
+ // Re-saving the same version attaches the icon through the update path.
+ withIcon := model.TaskPlugin{Key: "alibaba", APIVersion: 1, Version: "roundtrip", Source: model.LongText(source), SourceHash: "hash", Icon: model.LongText(icon), Enabled: true}
+ require.NoError(t, model.SaveTaskPlugin(&withIcon))
+
+ stored, err := model.GetTaskPluginVersion("alibaba", "roundtrip")
+ require.NoError(t, err)
+ assert.Equal(t, source, string(stored.Source))
+ assert.Equal(t, icon, string(stored.Icon))
+}
+
+func TestUploadTaskPluginAcceptsSourcesUpToEightMiB(t *testing.T) {
+ setupTaskPluginControllerTest(t)
+ cleanupTaskPluginControllerRuntime(t, "large-source")
+ upload := func(source string) *httptest.ResponseRecorder {
+ body, err := common.Marshal(map[string]any{"source": source})
+ require.NoError(t, err)
+ recorder := httptest.NewRecorder()
+ context, _ := gin.CreateTestContext(recorder)
+ context.Request = httptest.NewRequest(http.MethodPost, "/api/plugin/task", bytes.NewReader(body))
+ context.Request.Header.Set("Content-Type", "application/json")
+ UploadTaskPlugin(context)
+ return recorder
+ }
+
+ // A trailing comment pads a valid plugin past the former 1 MiB cap.
+ accepted := taskPluginControllerTestSource("large-source", "1.0.0") + "// " + strings.Repeat("x", 2<<20) + "\n"
+ recorder := upload(accepted)
+ require.Contains(t, recorder.Body.String(), `"success":true`)
+ stored, err := model.GetTaskPluginVersion("large-source", "1.0.0")
+ require.NoError(t, err)
+ assert.Equal(t, accepted, string(stored.Source))
+
+ rejected := upload(accepted + strings.Repeat("x", 6<<20))
+ assert.Contains(t, rejected.Body.String(), `"success":false`)
+ assert.Contains(t, rejected.Body.String(), "plugin source exceeds 8 MiB")
+ _, err = model.GetTaskPluginVersion("large-source", "2.0.0")
+ assert.ErrorIs(t, err, gorm.ErrRecordNotFound)
+}
+
func TestListTaskPluginsIncludesFactoryWithoutDatabaseRows(t *testing.T) {
setupTaskPluginControllerTest(t)
recorder := httptest.NewRecorder()
@@ -443,7 +501,7 @@ func TestDeleteActiveOverrideFallsBackToFactoryAndDeletesRecord(t *testing.T) {
t.Cleanup(func() { jsplugin.DefaultRegistry.Unregister("kling") })
plugin := model.TaskPlugin{
Key: "kling", APIVersion: loaded.Meta.APIVersion, Version: loaded.Meta.Version,
- Source: overrideSource, SourceHash: "test-hash", Enabled: true,
+ Source: model.LongText(overrideSource), SourceHash: "test-hash", Enabled: true,
}
require.NoError(t, model.SaveTaskPlugin(&plugin))
recorder := httptest.NewRecorder()
@@ -469,10 +527,10 @@ func TestDeleteActiveTaskPluginPromotesEnabledVersionInRuntime(t *testing.T) {
v1Source := taskPluginControllerTestSource(key, "1.0.0")
v2Source := taskPluginControllerTestSource(key, "2.0.0")
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: key, APIVersion: 1, Version: "1.0.0", Source: v1Source, SourceHash: "hash-v1", Enabled: true,
+ Key: key, APIVersion: 1, Version: "1.0.0", Source: model.LongText(v1Source), SourceHash: "hash-v1", Enabled: true,
}))
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: key, APIVersion: 1, Version: "2.0.0", Source: v2Source, SourceHash: "hash-v2", Enabled: true,
+ Key: key, APIVersion: 1, Version: "2.0.0", Source: model.LongText(v2Source), SourceHash: "hash-v2", Enabled: true,
}))
_, err := jsplugin.DefaultRegistry.Register(v1Source, jsplugin.Options{Key: key, Version: "1.0.0"})
require.NoError(t, err)
@@ -538,10 +596,10 @@ func TestActivateTaskPluginRefreshesRuntimeSyncState(t *testing.T) {
v1Source := taskPluginControllerTestSource(key, "1.0.0")
v2Source := taskPluginControllerTestSource(key, "2.0.0")
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: key, APIVersion: 1, Version: "1.0.0", Source: v1Source, SourceHash: "hash-v1", Enabled: true,
+ Key: key, APIVersion: 1, Version: "1.0.0", Source: model.LongText(v1Source), SourceHash: "hash-v1", Enabled: true,
}))
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: key, APIVersion: 1, Version: "2.0.0", Source: v2Source, SourceHash: "hash-v2", Enabled: true,
+ Key: key, APIVersion: 1, Version: "2.0.0", Source: model.LongText(v2Source), SourceHash: "hash-v2", Enabled: true,
}))
_, err := jsplugin.DefaultRegistry.Register(v1Source, jsplugin.Options{Key: key, Version: "1.0.0"})
require.NoError(t, err)
@@ -578,10 +636,10 @@ func TestSyncTaskPluginsPublishesOneGenerationForWholeBatch(t *testing.T) {
firstSource := taskPluginControllerTestSource(firstKey, "1.0.0")
secondSource := taskPluginControllerTestSource(secondKey, "1.0.0")
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: firstKey, APIVersion: 1, Version: "1.0.0", Source: firstSource, SourceHash: "first-hash", Enabled: true,
+ Key: firstKey, APIVersion: 1, Version: "1.0.0", Source: model.LongText(firstSource), SourceHash: "first-hash", Enabled: true,
}))
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: secondKey, APIVersion: 1, Version: "1.0.0", Source: secondSource, SourceHash: "second-hash", Enabled: true,
+ Key: secondKey, APIVersion: 1, Version: "1.0.0", Source: model.LongText(secondSource), SourceHash: "second-hash", Enabled: true,
}))
before := jsplugin.DefaultRegistry.Generation().Number
@@ -614,7 +672,7 @@ func TestTaskPluginRuntimeExposesDatabaseRevisionAheadOfLocalGeneration(t *testi
v1Source := taskPluginControllerTestSource(key, "1.0.0")
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
Key: key, APIVersion: 1, Version: "1.0.0",
- Source: v1Source, SourceHash: "runtime-v1", Enabled: true,
+ Source: model.LongText(v1Source), SourceHash: "runtime-v1", Enabled: true,
}))
require.NoError(t, syncTaskPluginsOnce())
localGeneration := jsplugin.DefaultRegistry.Generation().Number
@@ -625,7 +683,7 @@ func TestTaskPluginRuntimeExposesDatabaseRevisionAheadOfLocalGeneration(t *testi
v2Source := taskPluginControllerTestSource(key, "2.0.0")
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
Key: key, APIVersion: 1, Version: "2.0.0",
- Source: v2Source, SourceHash: "runtime-v2", Enabled: true,
+ Source: model.LongText(v2Source), SourceHash: "runtime-v2", Enabled: true,
}))
require.NoError(t, model.ActivateTaskPlugin(key, "2.0.0"))
@@ -705,7 +763,7 @@ func TestTaskPluginRuntimeSurvivesDatabaseSyncFailure(t *testing.T) {
source := taskPluginControllerTestSource(key, "1.0.0")
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
Key: key, APIVersion: 1, Version: "1.0.0",
- Source: source, SourceHash: "runtime-database-v1", Enabled: true,
+ Source: model.LongText(source), SourceHash: "runtime-database-v1", Enabled: true,
}))
require.NoError(t, syncTaskPluginsOnce())
generation := jsplugin.DefaultRegistry.Generation().Number
@@ -747,17 +805,17 @@ func TestSyncTaskPluginsCachesRejectedDesiredSourceWithoutLosingIncumbent(t *tes
v2Source := taskPluginControllerChannelSource(pluginKey, "2.0.0", 9002)
ownerSource := taskPluginControllerChannelSource(ownerKey, "1.0.0", 9002)
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: pluginKey, APIVersion: 1, Version: "1.0.0", Source: v1Source, SourceHash: "retained-v1", Enabled: true,
+ Key: pluginKey, APIVersion: 1, Version: "1.0.0", Source: model.LongText(v1Source), SourceHash: "retained-v1", Enabled: true,
}))
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: ownerKey, APIVersion: 1, Version: "1.0.0", Source: ownerSource, SourceHash: "owner-v1", Enabled: true,
+ Key: ownerKey, APIVersion: 1, Version: "1.0.0", Source: model.LongText(ownerSource), SourceHash: "owner-v1", Enabled: true,
}))
require.NoError(t, syncTaskPluginsOnce())
incumbent, ok := jsplugin.DefaultRegistry.Get(pluginKey)
require.True(t, ok)
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
- Key: pluginKey, APIVersion: 1, Version: "2.0.0", Source: v2Source, SourceHash: "retained-v2", Enabled: true,
+ Key: pluginKey, APIVersion: 1, Version: "2.0.0", Source: model.LongText(v2Source), SourceHash: "retained-v2", Enabled: true,
}))
require.NoError(t, model.ActivateTaskPlugin(pluginKey, "2.0.0"))
@@ -779,7 +837,7 @@ func TestSyncTaskPluginsCachesRejectedDesiredSourceWithoutLosingIncumbent(t *tes
require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{
Key: ownerKey, APIVersion: 1, Version: "2.0.0",
- Source: taskPluginControllerChannelSource(ownerKey, "2.0.0", 9003), SourceHash: "owner-v2", Enabled: true,
+ Source: model.LongText(taskPluginControllerChannelSource(ownerKey, "2.0.0", 9003)), SourceHash: "owner-v2", Enabled: true,
}))
require.NoError(t, model.ActivateTaskPlugin(ownerKey, "2.0.0"))
require.NoError(t, syncTaskPluginsOnce())
@@ -1198,7 +1256,7 @@ func TestTaskPluginDisplayOrderAndMetadata(t *testing.T) {
_, err := jsplugin.DefaultRegistry.Register(source, jsplugin.Options{})
require.NoError(t, err)
cleanupTaskPluginControllerRuntime(t, key)
- require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{Key: key, Version: "1.0.0", APIVersion: 1, Source: source, Enabled: true}))
+ require.NoError(t, model.SaveTaskPlugin(&model.TaskPlugin{Key: key, Version: "1.0.0", APIVersion: 1, Source: model.LongText(source), Enabled: true}))
}
for _, endpoint := range []string{"list", "options"} {
t.Run(endpoint, func(t *testing.T) {
@@ -1268,7 +1326,7 @@ func TestUploadTaskPluginStoresSidecarIconAndServesIt(t *testing.T) {
assert.NotContains(t, accepted.Body.String(), "base64,", "icon bytes never travel inside detail JSON")
stored, err := model.GetTaskPluginVersion(key, "1.0.0")
require.NoError(t, err)
- assert.Equal(t, svgIcon, stored.Icon)
+ assert.Equal(t, svgIcon, string(stored.Icon))
item := listTaskPluginItem(t, key)
assert.True(t, item.HasIcon)
diff --git a/model/task_plugin.go b/model/task_plugin.go
index adcfd24c7..9f2bdb943 100644
--- a/model/task_plugin.go
+++ b/model/task_plugin.go
@@ -12,6 +12,7 @@ import (
"github.com/QuantumNous/new-api/constant"
"gorm.io/gorm"
"gorm.io/gorm/clause"
+ "gorm.io/gorm/schema"
)
type TaskPluginChannelRef struct {
@@ -57,25 +58,40 @@ func UnbindTaskPlugin(channelID int, key string) (bool, error) {
return true, DB.Model(&Channel{}).Where("id = ?", channelID).Update("setting", channel.Setting).Error
}
+// LongText is a string column sized for plugin payloads on every supported
+// database: longtext on MySQL, text on PostgreSQL and SQLite. A bare
+// `type:text` tag stops at 64 KiB on MySQL. A `size:` tag above that becomes
+// varchar(N) on PostgreSQL, which rejects sizes past 10485760, and a NOT NULL
+// column declared that way is re-altered by AutoMigrate on every MySQL start
+// because the reported column length never equals N. Neither type chosen here
+// carries a length, so an up-to-date column is left alone, and MySQL widens a
+// shipped text column once, without data loss.
+type LongText string
+
+func (LongText) GormDBDataType(db *gorm.DB, _ *schema.Field) string {
+ if db.Dialector.Name() == string(common.DatabaseTypeMySQL) {
+ return "longtext"
+ }
+ return "text"
+}
+
type TaskPlugin struct {
- Id int64 `json:"id"`
- Key string `json:"key" gorm:"size:128;not null;uniqueIndex:uk_task_plugin_key_version,priority:1"`
- APIVersion int `json:"api_version" gorm:"not null"`
- Version string `json:"version" gorm:"size:64;not null;uniqueIndex:uk_task_plugin_key_version,priority:2"`
- Source string `json:"source" gorm:"type:text;not null"`
- SourceHash string `json:"source_hash" gorm:"size:64;not null"`
+ Id int64 `json:"id"`
+ Key string `json:"key" gorm:"size:128;not null;uniqueIndex:uk_task_plugin_key_version,priority:1"`
+ APIVersion int `json:"api_version" gorm:"not null"`
+ Version string `json:"version" gorm:"size:64;not null;uniqueIndex:uk_task_plugin_key_version,priority:2"`
+ Source LongText `json:"source" gorm:"not null"`
+ SourceHash string `json:"source_hash" gorm:"size:64;not null"`
// Icon is the plugin logo shipped as a sidecar icon.svg / icon.png next to
// plugin.js, stored as a data URI so one column carries both the media
// type and the bytes. It never travels inside list or detail JSON; the UI
- // loads it through GET /api/plugin/task/:key/icon. size matches the
- // 512 KiB icon cap and makes GORM emit mediumtext on MySQL (a bare TEXT
- // column there holds only 64 KiB), varchar(524288) on PostgreSQL, and text
- // on SQLite.
- Icon string `json:"-" gorm:"size:524288"`
- Enabled bool `json:"enabled" gorm:"not null"`
- Active bool `json:"active" gorm:"not null;index"`
- CreatedAt int64 `json:"created_at" gorm:"not null"`
- Remark string `json:"remark" gorm:"type:text"`
+ // loads it through GET /api/plugin/task/:key/icon. LongText keeps the
+ // 512 KiB icon cap storable on MySQL, where a bare TEXT holds only 64 KiB.
+ Icon LongText `json:"-"`
+ Enabled bool `json:"enabled" gorm:"not null"`
+ Active bool `json:"active" gorm:"not null;index"`
+ CreatedAt int64 `json:"created_at" gorm:"not null"`
+ Remark string `json:"remark" gorm:"type:text"`
}
// HasIcon reports whether this version ships a logo.
diff --git a/web/src/features/task-plugins/__tests__/plugin-url.test.ts b/web/src/features/task-plugins/__tests__/plugin-url.test.ts
index efa517ffd..3f2e17603 100644
--- a/web/src/features/task-plugins/__tests__/plugin-url.test.ts
+++ b/web/src/features/task-plugins/__tests__/plugin-url.test.ts
@@ -17,6 +17,7 @@ along with this program. If not, see
{t( - 'Single .js file, up to 1 MiB. Its source is shown below before upload.' + 'Single .js file, up to 8 MiB. Its source is shown below before upload.' )}
diff --git a/web/src/features/task-plugins/components/plugin-url-import-field.tsx b/web/src/features/task-plugins/components/plugin-url-import-field.tsx index c8e4299be..c766869ad 100644 --- a/web/src/features/task-plugins/components/plugin-url-import-field.tsx +++ b/web/src/features/task-plugins/components/plugin-url-import-field.tsx @@ -73,7 +73,7 @@ export function PluginUrlImportField(props: PluginUrlImportFieldProps) { return } if (error.reason === 'too_large') { - props.onError(t('Plugin source exceeds the 1 MiB limit.')) + props.onError(t('Plugin source exceeds the 8 MiB limit.')) return } if (error.reason === 'not_found') { diff --git a/web/src/features/task-plugins/components/upload-dialog.tsx b/web/src/features/task-plugins/components/upload-dialog.tsx index 9894e37d6..1207e6788 100644 --- a/web/src/features/task-plugins/components/upload-dialog.tsx +++ b/web/src/features/task-plugins/components/upload-dialog.tsx @@ -108,7 +108,7 @@ export function UploadDialog(props: UploadDialogProps) { const handleFile = async (file: File) => { if (file.size > MAX_PLUGIN_SOURCE_BYTES) { - setImportError(t('Plugin source exceeds the 1 MiB limit.')) + setImportError(t('Plugin source exceeds the 8 MiB limit.')) return } setImportError('') diff --git a/web/src/features/task-plugins/lib/plugin-url.ts b/web/src/features/task-plugins/lib/plugin-url.ts index 9d932aff1..6b3e3cf22 100644 --- a/web/src/features/task-plugins/lib/plugin-url.ts +++ b/web/src/features/task-plugins/lib/plugin-url.ts @@ -21,7 +21,7 @@ For commercial licensing, please contact support@quantumnous.com * controller/task_plugin.go). Enforced client-side too so an oversized fetch * fails with a readable message instead of a server rejection. */ -export const MAX_PLUGIN_SOURCE_BYTES = 1024 * 1024 +export const MAX_PLUGIN_SOURCE_BYTES = 8 * 1024 * 1024 export function pluginSourceByteLength(source: string): number { return new TextEncoder().encode(source).length diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 12faa663d..8ce9da227 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -4371,7 +4371,7 @@ "Plugin sandbox": "Plugin sandbox", "Plugin saved, activation required": "Plugin saved, activation required", "Plugin source": "Plugin source", - "Plugin source exceeds the 1 MiB limit.": "Plugin source exceeds the 1 MiB limit.", + "Plugin source exceeds the 8 MiB limit.": "Plugin source exceeds the 8 MiB limit.", "Plugin uploaded successfully": "Plugin uploaded successfully", "Plugin version activated": "Plugin version activated", "Plugin version deleted": "Plugin version deleted", @@ -5429,7 +5429,7 @@ "Simulation context is missing.": "Simulation context is missing.", "Simulation time": "Simulation time", "Since previous sample": "Since previous sample", - "Single .js file, up to 1 MiB. Its source is shown below before upload.": "Single .js file, up to 1 MiB. Its source is shown below before upload.", + "Single .js file, up to 8 MiB. Its source is shown below before upload.": "Single .js file, up to 8 MiB. Its source is shown below before upload.", "Single Key": "Single Key", "Site & Branding": "Site & Branding", "Site currency ({{currency}})": "Site currency ({{currency}})", diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 1dfdf8ead..a239f2062 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -4371,7 +4371,7 @@ "Plugin sandbox": "Bac à sable du plugin", "Plugin saved, activation required": "Plugin enregistré, activation requise", "Plugin source": "Source du plugin", - "Plugin source exceeds the 1 MiB limit.": "Le code du plugin dépasse la limite de 1 Mio.", + "Plugin source exceeds the 8 MiB limit.": "Le code du plugin dépasse la limite de 8 Mio.", "Plugin uploaded successfully": "Plugin importé avec succès", "Plugin version activated": "Version du plugin activée", "Plugin version deleted": "Version du plugin supprimée", @@ -5429,7 +5429,7 @@ "Simulation context is missing.": "Le contexte nécessaire à la simulation est manquant.", "Simulation time": "Heure de simulation", "Since previous sample": "Depuis le relevé précédent", - "Single .js file, up to 1 MiB. Its source is shown below before upload.": "Un seul fichier .js, jusqu’à 1 Mio. Sa source est affichée ci-dessous avant l’envoi.", + "Single .js file, up to 8 MiB. Its source is shown below before upload.": "Un seul fichier .js, jusqu’à 8 Mio. Sa source est affichée ci-dessous avant l’envoi.", "Single Key": "Clé unique", "Site & Branding": "Site et marque", "Site currency ({{currency}})": "Devise du site ({{currency}})", diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index b6c85ea38..e3e092617 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -4371,7 +4371,7 @@ "Plugin sandbox": "プラグインサンドボックス", "Plugin saved, activation required": "プラグインを保存しました。有効化が必要です", "Plugin source": "プラグインソース", - "Plugin source exceeds the 1 MiB limit.": "プラグインのソースが 1 MiB の上限を超えています。", + "Plugin source exceeds the 8 MiB limit.": "プラグインのソースが 8 MiB の上限を超えています。", "Plugin uploaded successfully": "プラグインをアップロードしました", "Plugin version activated": "プラグインバージョンを有効化しました", "Plugin version deleted": "プラグインバージョンを削除しました", @@ -5429,7 +5429,7 @@ "Simulation context is missing.": "シミュレーションに必要なコンテキストがありません。", "Simulation time": "シミュレーション時刻", "Since previous sample": "前回のサンプル以降", - "Single .js file, up to 1 MiB. Its source is shown below before upload.": "単一の .js ファイル、最大 1 MiB。アップロード前に下部でソースを確認できます。", + "Single .js file, up to 8 MiB. Its source is shown below before upload.": "単一の .js ファイル、最大 8 MiB。アップロード前に下部でソースを確認できます。", "Single Key": "単一キー", "Site & Branding": "サイトとブランド", "Site currency ({{currency}})": "サイト通貨({{currency}})", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index e9614fb14..af18b036b 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -4371,7 +4371,7 @@ "Plugin sandbox": "Песочница плагина", "Plugin saved, activation required": "Плагин сохранён, требуется активация", "Plugin source": "Исходный код плагина", - "Plugin source exceeds the 1 MiB limit.": "Код плагина превышает лимит 1 МиБ.", + "Plugin source exceeds the 8 MiB limit.": "Код плагина превышает лимит 8 МиБ.", "Plugin uploaded successfully": "Плагин успешно загружен", "Plugin version activated": "Версия плагина активирована", "Plugin version deleted": "Версия плагина удалена", @@ -5429,7 +5429,7 @@ "Simulation context is missing.": "Отсутствует контекст для моделирования.", "Simulation time": "Время моделирования", "Since previous sample": "С предыдущей выборки", - "Single .js file, up to 1 MiB. Its source is shown below before upload.": "Один файл .js размером до 1 МиБ. Его исходный код показан ниже перед загрузкой.", + "Single .js file, up to 8 MiB. Its source is shown below before upload.": "Один файл .js размером до 8 МиБ. Его исходный код показан ниже перед загрузкой.", "Single Key": "Одиночный ключ", "Site & Branding": "Сайт и брендинг", "Site currency ({{currency}})": "Валюта сайта ({{currency}})", diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 5387378fb..c7d3e7199 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -4371,7 +4371,7 @@ "Plugin sandbox": "Môi trường thử nghiệm plugin", "Plugin saved, activation required": "Đã lưu plugin, cần kích hoạt", "Plugin source": "Mã nguồn plugin", - "Plugin source exceeds the 1 MiB limit.": "Mã nguồn plugin vượt giới hạn 1 MiB.", + "Plugin source exceeds the 8 MiB limit.": "Mã nguồn plugin vượt giới hạn 8 MiB.", "Plugin uploaded successfully": "Đã tải plugin lên", "Plugin version activated": "Đã kích hoạt phiên bản plugin", "Plugin version deleted": "Đã xóa phiên bản plugin", @@ -5429,7 +5429,7 @@ "Simulation context is missing.": "Thiếu ngữ cảnh cần thiết để mô phỏng.", "Simulation time": "Thời gian mô phỏng", "Since previous sample": "Kể từ mẫu trước", - "Single .js file, up to 1 MiB. Its source is shown below before upload.": "Một tệp .js duy nhất, tối đa 1 MiB. Mã nguồn được hiển thị bên dưới trước khi tải lên.", + "Single .js file, up to 8 MiB. Its source is shown below before upload.": "Một tệp .js duy nhất, tối đa 8 MiB. Mã nguồn được hiển thị bên dưới trước khi tải lên.", "Single Key": "Khóa đơn", "Site & Branding": "Trang web & thương hiệu", "Site currency ({{currency}})": "Tiền tệ trang web ({{currency}})", diff --git a/web/src/i18n/locales/zh-TW.json b/web/src/i18n/locales/zh-TW.json index 784a7046f..3eca7eea6 100644 --- a/web/src/i18n/locales/zh-TW.json +++ b/web/src/i18n/locales/zh-TW.json @@ -4371,7 +4371,7 @@ "Plugin sandbox": "外掛沙盒", "Plugin saved, activation required": "外掛已儲存,待啟用", "Plugin source": "外掛原始碼", - "Plugin source exceeds the 1 MiB limit.": "外掛原始碼超過 1 MiB 上限。", + "Plugin source exceeds the 8 MiB limit.": "外掛原始碼超過 8 MiB 上限。", "Plugin uploaded successfully": "外掛上傳成功", "Plugin version activated": "外掛版本已激活", "Plugin version deleted": "外掛版本已刪除", @@ -5429,7 +5429,7 @@ "Simulation context is missing.": "缺少模擬所需的內容。", "Simulation time": "模擬時間", "Since previous sample": "自上次取樣以來", - "Single .js file, up to 1 MiB. Its source is shown below before upload.": "單一 .js 檔案,最大 1 MiB。上傳前會在下方顯示其原始碼。", + "Single .js file, up to 8 MiB. Its source is shown below before upload.": "單一 .js 檔案,最大 8 MiB。上傳前會在下方顯示其原始碼。", "Single Key": "單金鑰", "Site & Branding": "站點與品牌", "Site currency ({{currency}})": "站點貨幣({{currency}})", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 2b4a5e85a..23de155c0 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -4371,7 +4371,7 @@ "Plugin sandbox": "插件沙盒", "Plugin saved, activation required": "插件已保存,待激活", "Plugin source": "插件源码", - "Plugin source exceeds the 1 MiB limit.": "插件源码超过 1 MiB 上限。", + "Plugin source exceeds the 8 MiB limit.": "插件源码超过 8 MiB 上限。", "Plugin uploaded successfully": "插件上传成功", "Plugin version activated": "插件版本已激活", "Plugin version deleted": "插件版本已删除", @@ -5429,7 +5429,7 @@ "Simulation context is missing.": "缺少模拟所需的上下文。", "Simulation time": "模拟时间", "Since previous sample": "自上次采样以来", - "Single .js file, up to 1 MiB. Its source is shown below before upload.": "单个 .js 文件,最大 1 MiB。上传前会在下方显示其源码。", + "Single .js file, up to 8 MiB. Its source is shown below before upload.": "单个 .js 文件,最大 8 MiB。上传前会在下方显示其源码。", "Single Key": "单密钥", "Site & Branding": "站点与品牌", "Site currency ({{currency}})": "站点货币({{currency}})",