Compare commits

...

15 Commits

Author SHA1 Message Date
nKwiatkowski
c322365dc2 test(docker): change the docker compose to reproduce a bug 2025-10-08 10:08:49 +02:00
Will Russell
a527271447 docs(templates): add mention to star repo (#11762) 2025-10-06 15:14:18 +01:00
Mustafa Tarek
3346f3a3f9 feat(tests): add test coverage for cli commands with no repo config (#11742)
* fix(core): Add warning logs for mismatched (Parent-Subflow) inputs for subflow plugin.

* feat(tests): add test coverage for cli commands with no repository configurations

* refactor(tests): enhance test assertions and namings

* refactor(tests): replace HelloCommand with Namespace KV helper in no-config tests

- Use Namespace KV command (with no params) to verify repo-independent CLI behavior
- Remove HelloCommand usage in NoConfigCommandTest
- Keep FlowCreateCommand case to assert repo-dependent commands fail without config
2025-10-06 15:00:23 +02:00
Gaurav Arora
4fc690b0d9 feat(frontend): convert DurationPicker component to TypeScript with Composition API (#11724)
Co-authored-by: Bart Ledoux <bledoux@kestra.io>
2025-10-06 14:18:26 +02:00
Piyush Bhaskar
4fe5c665bc chore(version): bump ui-libs version (#11759) 2025-10-06 17:36:28 +05:30
github-actions[bot]
777bc36d01 chore(core): localize to languages other than english (#11760)
Co-authored-by: GitHub Action <actions@github.com>
2025-10-06 17:35:28 +05:30
Emmanuel Adeniyi Adekeye
e5b3bea4d1 feat(executions): consolidate buttons in the execution header component (#11693)
Closes https://github.com/kestra-io/kestra/issues/8965.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-10-06 13:39:05 +02:00
Jacob
6fad3eb14f chore(core): separate page size values for each route (#11692)
Closes https://github.com/kestra-io/kestra/issues/11568.

Co-authored-by: Jakub Šašak <jakub.sasak@student.tuke.sk>
Co-authored-by: MilosPaunovic <paun992@hotmail.com>
2025-10-06 13:30:45 +02:00
Hemant M Mehta
8e91385080 chore(triggers): automatically update logs on trigger refresh (#11735)
Closes https://github.com/kestra-io/kestra/issues/11375.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-10-06 11:43:03 +02:00
Gaurav Arora
b593c51659 feat(frontend): convert LabelFilter component to TypeScript with Composition API (#11726)
Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-10-06 11:41:31 +02:00
github-actions[bot]
824a7597cd chore(core): localize to languages other than english (#11754)
Extended localization support by adding translations for multiple languages using English as the base. This enhances accessibility and usability for non-English-speaking users while keeping English as the source reference.

Co-authored-by: GitHub Action <actions@github.com>
2025-10-06 10:54:40 +02:00
Biplab Bera
7c292e2e70 fix: deprecated Property (#11719)
* fix: deprecated Property

* fixed failing test for WeekendTest file
2025-10-06 10:45:23 +02:00
Carlos Longhi
7e6918cefa fix(core): amend the code color variable value for light mode (#11736)
Closes https://github.com/kestra-io/kestra/issues/11682.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-10-06 10:45:05 +02:00
Dhivya G
d50d5b3231 refactor(core): convert vue component to typescript and composition api (#11720)
Closes https://github.com/kestra-io/kestra/issues/11717.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-10-06 10:38:48 +02:00
Matheus da Cunha da Fonseca
d6773e41ef refactor(core): convert vue component to typescript and composition api (#11728)
Closes https://github.com/kestra-io/kestra/issues/11713.

Co-authored-by: Miloš Paunović <paun992@hotmail.com>
2025-10-06 10:30:29 +02:00
48 changed files with 519 additions and 384 deletions

View File

@@ -4,7 +4,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for reporting an issue! Please provide a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) and share any additional information that may help reproduce, troubleshoot, and hopefully fix the issue, including screenshots, error traceback, and your Kestra server logs. For quick questions, you can contact us directly on [Slack](https://kestra.io/slack).
Thanks for reporting an issue! Please provide a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) and share any additional information that may help reproduce, troubleshoot, and hopefully fix the issue, including screenshots, error traceback, and your Kestra server logs. For quick questions, you can contact us directly on [Slack](https://kestra.io/slack). Don't forget to give us a star! ⭐
- type: textarea
attributes:
label: Describe the issue

View File

@@ -4,7 +4,7 @@ body:
- type: textarea
attributes:
label: Feature description
placeholder: Tell us more about your feature request
placeholder: Tell us more about your feature request. Don't forget to give us a star! ⭐
validations:
required: true
labels:

View File

@@ -35,4 +35,4 @@ Remove this section if this change applies to all flows or to the documentation
If there are no setup requirements, you can remove this section.
Thank you for your contribution. ❤️ -->
Thank you for your contribution. ❤️ Don't forget to give us a star! ⭐ -->

View File

@@ -43,7 +43,7 @@ import java.util.concurrent.Callable;
SysCommand.class,
ConfigCommand.class,
NamespaceCommand.class,
MigrationCommand.class,
MigrationCommand.class
}
)
@Introspected

View File

@@ -0,0 +1,76 @@
package io.kestra.cli.commands.configs.sys;
import io.kestra.cli.commands.flows.FlowCreateCommand;
import io.kestra.cli.commands.namespaces.kv.KvCommand;
import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.context.ApplicationContext;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Verifies CLI behavior without repository configuration:
* - Repo-independent commands succeed (e.g. KV with no params).
* - Repo-dependent commands fail with a clear error.
*/
class NoConfigCommandTest {
@Test
void shouldSucceedWithNamespaceKVCommandWithoutParamsAndConfig() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
try (ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start()) {
String[] args = {};
Integer call = PicocliRunner.call(KvCommand.class, ctx, args);
assertThat(call).isZero();
assertThat(out.toString()).contains("Usage: kestra namespace kv");
}
}
@Test
void shouldFailWithCreateFlowCommandWithoutConfig() throws URISyntaxException {
URL flowUrl = NoConfigCommandTest.class.getClassLoader().getResource("crudFlow/date.yml");
Objects.requireNonNull(flowUrl, "Test flow resource not found");
Path flowPath = Paths.get(flowUrl.toURI());
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err=new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
System.setErr(new PrintStream(err));
try (ApplicationContext ctx = ApplicationContext.builder()
.deduceEnvironment(false)
.start()) {
EmbeddedServer embeddedServer = ctx.getBean(EmbeddedServer.class);
embeddedServer.start();
String[] createArgs = {
"--server",
embeddedServer.getURL().toString(),
"--user",
"myuser:pass:word",
flowPath.toString(),
};
Integer exitCode = PicocliRunner.call(FlowCreateCommand.class, ctx, createArgs);
assertThat(exitCode).isNotZero();
assertThat(out.toString()).isEmpty();
assertThat(err.toString()).contains("No bean of type [io.kestra.core.repositories.FlowRepositoryInterface] exists");
}
}
}

View File

@@ -37,14 +37,14 @@ class PropertyTest {
@Test
void test() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.number(new Property<>("{{numberValue}}"))
.string(new Property<>("{{stringValue}}"))
.level(new Property<>("{{levelValue}}"))
.someDuration(new Property<>("{{durationValue}}"))
.withDefault(new Property<>("{{defaultValue}}"))
.items(new Property<>("""
.number(Property.ofExpression("{{numberValue}}"))
.string(Property.ofExpression("{{stringValue}}"))
.level(Property.ofExpression("{{levelValue}}"))
.someDuration(Property.ofExpression("{{durationValue}}"))
.withDefault(Property.ofExpression("{{defaultValue}}"))
.items(Property.ofExpression("""
["{{item1}}", "{{item2}}"]"""))
.properties(new Property<>("""
.properties(Property.ofExpression("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"
@@ -87,13 +87,13 @@ class PropertyTest {
@Test
void withDefaultsAndMessagesFromList() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.number(new Property<>("{{numberValue}}"))
.string(new Property<>("{{stringValue}}"))
.level(new Property<>("{{levelValue}}"))
.someDuration(new Property<>("{{durationValue}}"))
.items(new Property<>("""
.number(Property.ofExpression("{{numberValue}}"))
.string(Property.ofExpression("{{stringValue}}"))
.level(Property.ofExpression("{{levelValue}}"))
.someDuration(Property.ofExpression("{{durationValue}}"))
.items(Property.ofExpression("""
["{{item1}}", "{{item2}}"]"""))
.properties(new Property<>("""
.properties(Property.ofExpression("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"
@@ -156,14 +156,14 @@ class PropertyTest {
}
var task = DynamicPropertyExampleTask.builder()
.number(new Property<>("{{numberValue}}"))
.string(new Property<>("{{stringValue}}"))
.level(new Property<>("{{levelValue}}"))
.someDuration(new Property<>("{{durationValue}}"))
.withDefault(new Property<>("{{defaultValue}}"))
.items(new Property<>("""
.number(Property.ofExpression("{{numberValue}}"))
.string(Property.ofExpression("{{stringValue}}"))
.level(Property.ofExpression("{{levelValue}}"))
.someDuration(Property.ofExpression("{{durationValue}}"))
.withDefault(Property.ofExpression("{{defaultValue}}"))
.items(Property.ofExpression("""
["{{item1}}", "{{item2}}"]"""))
.properties(new Property<>("""
.properties(Property.ofExpression("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"
@@ -202,12 +202,12 @@ class PropertyTest {
@Test
void failingToRender() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.number(new Property<>("{{numberValue}}"))
.string(new Property<>("{{stringValue}}"))
.level(new Property<>("{{levelValue}}"))
.someDuration(new Property<>("{{durationValue}}"))
.withDefault(new Property<>("{{defaultValue}}"))
.items(new Property<>("""
.number(Property.ofExpression("{{numberValue}}"))
.string(Property.ofExpression("{{stringValue}}"))
.level(Property.ofExpression("{{levelValue}}"))
.someDuration(Property.ofExpression("{{durationValue}}"))
.withDefault(Property.ofExpression("{{defaultValue}}"))
.items(Property.ofExpression("""
["{{item1}}", "{{item2}}"]"""))
.from(Map.of("key", "{{mapValue}}"))
.build();
@@ -221,13 +221,13 @@ class PropertyTest {
var task = DynamicPropertyExampleTask.builder()
.id("dynamic")
.type(DynamicPropertyExampleTask.class.getName())
.number(new Property<>("{{numberValue}}"))
.string(new Property<>("{{stringValue}}"))
.level(new Property<>("{{levelValue}}"))
.someDuration(new Property<>("{{durationValue}}"))
.items(new Property<>("""
.number(Property.ofExpression("{{numberValue}}"))
.string(Property.ofExpression("{{stringValue}}"))
.level(Property.ofExpression("{{levelValue}}"))
.someDuration(Property.ofExpression("{{durationValue}}"))
.items(Property.ofExpression("""
["{{item1}}", "{{item2}}"]"""))
.properties(new Property<>("""
.properties(Property.ofExpression("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"
@@ -261,8 +261,8 @@ class PropertyTest {
@Test
void arrayAndMapToRender() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.items(new Property<>("{{renderOnce(listToRender)}}"))
.properties(new Property<>("{{renderOnce(mapToRender)}}"))
.items(Property.ofExpression("{{renderOnce(listToRender)}}"))
.properties(Property.ofExpression("{{renderOnce(mapToRender)}}"))
.build();
var runContext = runContextFactory.of(Map.ofEntries(
entry("arrayValueToRender", "arrayValue1"),
@@ -284,9 +284,9 @@ class PropertyTest {
@Test
void aListToRender() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.items(new Property<>("""
.items(Property.ofExpression("""
["python test.py --input1 \\"{{ item1 }}\\" --input2 \\"{{ item2 }}\\"", "'gs://{{ renderOnce(\\"bucket\\") }}/{{ 'table' }}/{{ 'file' }}_*.csv.gz'"]"""))
.properties(new Property<>("""
.properties(Property.ofExpression("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"
@@ -308,9 +308,9 @@ class PropertyTest {
@Test
void fromMessage() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.items(new Property<>("""
.items(Property.ofExpression("""
["python test.py --input1 \\"{{ item1 }}\\" --input2 \\"{{ item2 }}\\"", "'gs://{{ renderOnce(\\"bucket\\") }}/{{ 'table' }}/{{ 'file' }}_*.csv.gz'"]"""))
.properties(new Property<>("""
.properties(Property.ofExpression("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"
@@ -335,9 +335,9 @@ class PropertyTest {
@Test
void fromListOfMessages() throws Exception {
var task = DynamicPropertyExampleTask.builder()
.items(new Property<>("""
.items(Property.ofExpression("""
["python test.py --input1 \\"{{ item1 }}\\" --input2 \\"{{ item2 }}\\"", "'gs://{{ renderOnce(\\"bucket\\") }}/{{ 'table' }}/{{ 'file' }}_*.csv.gz'"]"""))
.properties(new Property<>("""
.properties(Property.ofExpression("""
{
"key1": "{{value1}}",
"key2": "{{value2}}"

View File

@@ -64,7 +64,7 @@ public class SkipExecutionCaseTest {
.tasks(Collections.singletonList(Return.builder()
.id("test")
.type(Return.class.getName())
.format(new Property<>("{{ inputs.testInputs }}"))
.format(Property.ofExpression("{{ inputs.testInputs }}"))
.build()))
.build();
}

View File

@@ -27,7 +27,7 @@ class ExecutionOutputsTest {
Map.of("test", "value"));
ExecutionOutputs build = ExecutionOutputs.builder()
.expression(new Property<>("{{ trigger.outputs.test == 'value' }}"))
.expression(Property.ofExpression("{{ trigger.outputs.test == 'value' }}"))
.build();
boolean test = conditionService.isValid(build, flow, execution);
@@ -44,7 +44,7 @@ class ExecutionOutputsTest {
Map.of("test", "value"));
ExecutionOutputs build = ExecutionOutputs.builder()
.expression(new Property<>("{{ unknown is defined }}"))
.expression(Property.ofExpression("{{ unknown is defined }}"))
.build();
boolean test = conditionService.isValid(build, flow, execution);
@@ -58,7 +58,7 @@ class ExecutionOutputsTest {
Execution execution = TestsUtils.mockExecution(flow, Map.of());
ExecutionOutputs build = ExecutionOutputs.builder()
.expression(new Property<>("{{ not evaluated }}"))
.expression(Property.ofExpression("{{ not evaluated }}"))
.build();
boolean test = conditionService.isValid(build, flow, execution);

View File

@@ -24,7 +24,7 @@ class ExpressionTest {
Execution execution = TestsUtils.mockExecution(flow, ImmutableMap.of("test", "value"));
Expression build = Expression.builder()
.expression(new Property<>("{{ flow.id }}"))
.expression(Property.ofExpression("{{ flow.id }}"))
.build();
boolean test = conditionService.isValid(build, flow, execution);
@@ -38,7 +38,7 @@ class ExpressionTest {
Execution execution = TestsUtils.mockExecution(flow, ImmutableMap.of("test", "value"));
Expression build = Expression.builder()
.expression(new Property<>("{{ unknown is defined }}"))
.expression(Property.ofExpression("{{ unknown is defined }}"))
.build();
boolean test = conditionService.isValid(build, flow, execution);

View File

@@ -37,7 +37,7 @@ class MultipleConditionTest {
.in(Property.ofValue(Collections.singletonList(State.Type.SUCCESS)))
.build(),
"second", Expression.builder()
.expression(new Property<>("{{ flow.id }}"))
.expression(Property.ofExpression("{{ flow.id }}"))
.build()
))
.build();

View File

@@ -38,7 +38,7 @@ class WeekendTest {
Execution execution = TestsUtils.mockExecution(flow, ImmutableMap.of());
Weekend build = Weekend.builder()
.date(new Property<>(date))
.date(date.startsWith("{{") ? Property.ofExpression(date) : Property.ofValue(date))
.build();
boolean test = conditionService.isValid(build, flow, execution);

View File

@@ -48,8 +48,8 @@ class CountTest {
new Flow(AbstractExecutionRepositoryTest.NAMESPACE, "third")
))
.expression("{{ count >= 5 }}")
.startDate(new Property<>("{{ now() | dateAdd (-30, 'DAYS') }}"))
.endDate(new Property<>("{{ now() }}"))
.startDate(Property.ofExpression("{{ now() | dateAdd (-30, 'DAYS') }}"))
.endDate(Property.ofExpression("{{ now() }}"))
.build();
RunContext runContext = runContextFactory.of("id", NAMESPACE, tenant);

View File

@@ -35,8 +35,8 @@ class DeleteTest {
Delete delete = Delete.builder()
.id(Delete.class.getSimpleName())
.type(Delete.class.getName())
.namespace(new Property<>("{{ inputs.namespace }}"))
.key(new Property<>("{{ inputs.key }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.key(Property.ofExpression("{{ inputs.key }}"))
.build();
final KVStore kv = runContext.namespaceKv(namespaceId);
@@ -61,8 +61,8 @@ class DeleteTest {
Delete delete = Delete.builder()
.id(Delete.class.getSimpleName())
.type(Delete.class.getName())
.namespace(new Property<>(namespaceId))
.key(new Property<>("my-key"))
.namespace(Property.ofValue(namespaceId))
.key(Property.ofValue("my-key"))
.build();
// When

View File

@@ -54,7 +54,7 @@ class GetKeysTest {
GetKeys getKeys = GetKeys.builder()
.id(GetKeys.class.getSimpleName())
.type(GetKeys.class.getName())
.prefix(new Property<>("{{ inputs.prefix }}"))
.prefix(Property.ofExpression("{{ inputs.prefix }}"))
.build();
final KVStore kv = runContext.namespaceKv(namespace);
@@ -79,7 +79,7 @@ class GetKeysTest {
GetKeys getKeys = GetKeys.builder()
.id(GetKeys.class.getSimpleName())
.type(GetKeys.class.getName())
.prefix(new Property<>("{{ inputs.prefix }}"))
.prefix(Property.ofExpression("{{ inputs.prefix }}"))
.build();
// When

View File

@@ -41,8 +41,8 @@ class GetTest {
Get get = Get.builder()
.id(Get.class.getSimpleName())
.type(Get.class.getName())
.namespace(new Property<>("{{ inputs.namespace }}"))
.key(new Property<>("{{ inputs.key }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.key(Property.ofExpression("{{ inputs.key }}"))
.build();
@@ -71,7 +71,7 @@ class GetTest {
Get get = Get.builder()
.id(Get.class.getSimpleName())
.type(Get.class.getName())
.key(new Property<>("{{ inputs.key }}"))
.key(Property.ofExpression("{{ inputs.key }}"))
.build();
@@ -99,8 +99,8 @@ class GetTest {
Get get = Get.builder()
.id(Get.class.getSimpleName())
.type(Get.class.getName())
.namespace(new Property<>(namespaceId))
.key(new Property<>("my-key"))
.namespace(Property.ofValue(namespaceId))
.key(Property.ofValue("my-key"))
.build();
// When

View File

@@ -38,9 +38,9 @@ class SetTest {
Set set = Set.builder()
.id(Set.class.getSimpleName())
.type(Set.class.getName())
.key(new Property<>("{{ inputs.key }}"))
.value(new Property<>("{{ inputs.value }}"))
.kvDescription(new Property<>("{{ inputs.description }}"))
.key(Property.ofExpression("{{ inputs.key }}"))
.value(Property.ofExpression("{{ inputs.value }}"))
.kvDescription(Property.ofExpression("{{ inputs.description }}"))
.build();
var value = Map.of("date", Instant.now().truncatedTo(ChronoUnit.MILLIS), "int", 1, "string", "string");
@@ -78,9 +78,9 @@ class SetTest {
Set set = Set.builder()
.id(Set.class.getSimpleName())
.type(Set.class.getName())
.key(new Property<>("{{ inputs.key }}"))
.value(new Property<>("{{ inputs.value }}"))
.namespace(new Property<>("io.kestra.test"))
.key(Property.ofExpression("{{ inputs.key }}"))
.value(Property.ofExpression("{{ inputs.value }}"))
.namespace(Property.ofValue("io.kestra.test"))
.build();
// When
@@ -105,9 +105,9 @@ class SetTest {
Set set = Set.builder()
.id(Set.class.getSimpleName())
.type(Set.class.getName())
.key(new Property<>("{{ inputs.key }}"))
.value(new Property<>("{{ inputs.value }}"))
.namespace(new Property<>("io.kestra"))
.key(Property.ofExpression("{{ inputs.key }}"))
.value(Property.ofExpression("{{ inputs.value }}"))
.namespace(Property.ofValue("io.kestra"))
.build();
// When
set.run(runContext);
@@ -131,9 +131,9 @@ class SetTest {
Set set = Set.builder()
.id(Set.class.getSimpleName())
.type(Set.class.getName())
.key(new Property<>("{{ inputs.key }}"))
.value(new Property<>("{{ inputs.value }}"))
.namespace(new Property<>("not-found"))
.key(Property.ofExpression("{{ inputs.key }}"))
.value(Property.ofExpression("{{ inputs.value }}"))
.namespace(Property.ofValue("not-found"))
.build();
// When - Then
@@ -146,8 +146,8 @@ class SetTest {
Set set = Set.builder()
.id(Set.class.getSimpleName())
.type(Set.class.getName())
.key(new Property<>("{{ inputs.key }}"))
.value(new Property<>("{{ inputs.value }}"))
.key(Property.ofExpression("{{ inputs.key }}"))
.value(Property.ofExpression("{{ inputs.value }}"))
.ttl(Property.ofValue(Duration.ofMinutes(5)))
.build();
@@ -174,8 +174,8 @@ class SetTest {
Set set = Set.builder()
.id(Set.class.getSimpleName())
.type(Set.class.getName())
.key(new Property<>("{{ inputs.key }}"))
.value(new Property<>("{{ inputs.value }}"))
.key(Property.ofExpression("{{ inputs.key }}"))
.value(Property.ofExpression("{{ inputs.value }}"))
.overwrite(Property.ofValue(false))
.build();

View File

@@ -32,7 +32,7 @@ class DeleteFilesTest {
.id(DeleteFiles.class.getSimpleName())
.type(DeleteFiles.class.getName())
.files(List.of("**test1*"))
.namespace(new Property<>("{{ inputs.namespace }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.build();
final RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, deleteFiles, Map.of("namespace", namespaceId));
@@ -59,7 +59,7 @@ class DeleteFilesTest {
.id(DeleteFiles.class.getSimpleName())
.type(DeleteFiles.class.getName())
.files(List.of("**/file.txt"))
.namespace(new Property<>("{{ inputs.namespace }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.deleteParentFolder(Property.ofValue(true))
.build();
@@ -87,7 +87,7 @@ class DeleteFilesTest {
.id(DeleteFiles.class.getSimpleName())
.type(DeleteFiles.class.getName())
.files(List.of("**/file.txt"))
.namespace(new Property<>("{{ inputs.namespace }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.deleteParentFolder(Property.ofValue(false))
.build();
@@ -115,7 +115,7 @@ class DeleteFilesTest {
.id(DeleteFiles.class.getSimpleName())
.type(DeleteFiles.class.getName())
.files(List.of("**/file1.txt"))
.namespace(new Property<>("{{ inputs.namespace }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.deleteParentFolder(Property.ofValue(true))
.build();

View File

@@ -34,7 +34,7 @@ public class DownloadFilesTest {
.id(DownloadFiles.class.getSimpleName())
.type(DownloadFiles.class.getName())
.files(List.of("**test1.txt"))
.namespace(new Property<>("{{ inputs.namespace }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.build();
final RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, downloadFiles, Map.of("namespace", namespaceId));

View File

@@ -76,7 +76,7 @@ public class UploadFilesTest {
.id(UploadFiles.class.getSimpleName())
.type(UploadFiles.class.getName())
.filesMap(Map.of("/path/file.txt", fileStorage.toString()))
.namespace(new Property<>("{{ inputs.namespace }}"))
.namespace(Property.ofExpression("{{ inputs.namespace }}"))
.destination(Property.ofValue("/folder"))
.build();

View File

@@ -38,7 +38,7 @@ class StateTest {
Set set = Set.builder()
.id(IdUtils.create())
.type(Set.class.toString())
.data(new Property<>(Map.of(
.data(Property.ofValue(Map.of(
"{{ inputs.key }}", "{{ inputs.inc }}"
)))
.build();
@@ -56,7 +56,7 @@ class StateTest {
set = Set.builder()
.id(IdUtils.create())
.type(Set.class.toString())
.data(new Property<>(Map.of(
.data(Property.ofValue(Map.of(
"{{ inputs.key }}", "2",
"test2", "3"
)))
@@ -100,7 +100,7 @@ class StateTest {
Delete task = Delete.builder()
.id(IdUtils.create())
.type(Get.class.getName())
.name(new Property<>(IdUtils.create()))
.name(Property.ofValue(IdUtils.create()))
.errorOnMissing(Property.ofValue(true))
.build();
@@ -114,7 +114,7 @@ class StateTest {
Get task = Get.builder()
.id(IdUtils.create())
.type(Get.class.getName())
.name(new Property<>(IdUtils.create()))
.name(Property.ofValue(IdUtils.create()))
.errorOnMissing(Property.ofValue(true))
.build();

View File

@@ -26,7 +26,7 @@ class TemplatedTaskTest {
TemplatedTask templatedTask = TemplatedTask.builder()
.id("template")
.type(TemplatedTask.class.getName())
.spec(new Property<>("""
.spec(Property.ofExpression("""
type: {{ type }}
format: It's alive!"""))
.build();

View File

@@ -20,6 +20,8 @@ services:
retries: 10
kestra:
env_file:
- kestra.env
image: kestra/kestra:latest
pull_policy: always
# Note that this setup with a root user is intended for development purpose.
@@ -39,10 +41,10 @@ services:
username: kestra
password: k3str4
kestra:
# server:
# basicAuth:
# username: admin@kestra.io # it must be a valid email address
# password: Admin1234 # it must be at least 8 characters long with uppercase letter and a number
server:
basicAuth:
username: user@kestra.io
password: DemoDemo1
repository:
type: postgres
storage:

8
ui/package-lock.json generated
View File

@@ -10,7 +10,7 @@
"hasInstallScript": true,
"dependencies": {
"@js-joda/core": "^5.6.5",
"@kestra-io/ui-libs": "^0.0.250",
"@kestra-io/ui-libs": "^0.0.253",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.2",
"@vue-flow/core": "^1.46.3",
@@ -3201,9 +3201,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@kestra-io/ui-libs": {
"version": "0.0.250",
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.250.tgz",
"integrity": "sha512-Y0ANjGn91f+3G6ZeH0niorf0ZCNe/BPWfur+yHni4AKHbyNUZjrE8UN9ETvOlYe5c2qQSyQdM9yK/LdG1Thtzw==",
"version": "0.0.253",
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.253.tgz",
"integrity": "sha512-Iixihy54osSAmQPlytQWokACcf2SzQjAu7k8KjuNAbgOyxp5XXXsozzA3YBoApS4cmTv3kdewXNr/Rgqg8/4Tg==",
"dependencies": {
"@nuxtjs/mdc": "^0.17.3",
"@popperjs/core": "^2.11.8",

View File

@@ -23,7 +23,7 @@
},
"dependencies": {
"@js-joda/core": "^5.6.5",
"@kestra-io/ui-libs": "^0.0.250",
"@kestra-io/ui-libs": "^0.0.253",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.2",
"@vue-flow/core": "^1.46.3",

View File

@@ -14,21 +14,30 @@
</div>
</template>
<script setup>
<script setup lang="ts">
import {useRoute} from "vue-router";
import {useDocStore} from "../../stores/doc";
const props = defineProps({
pageUrl: {
type: String,
default: undefined
},
interface ReleaseMetadata {
release: string;
title: string;
description?: string;
}
interface ResourcesWithMetadata {
[key: string]: ReleaseMetadata;
}
const props = withDefaults(defineProps<{
pageUrl?: string;
}>(), {
pageUrl: undefined
});
const docStore = useDocStore();
const route = useRoute();
let currentPage;
let currentPage: string;
if (props.pageUrl) {
currentPage = props.pageUrl;
@@ -38,7 +47,7 @@
currentPage = currentPage.endsWith("/") ? currentPage.slice(0, -1) : currentPage;
const resourcesWithMetadata = await docStore.children(currentPage);
const resourcesWithMetadata = await docStore.children(currentPage) as ResourcesWithMetadata;
const navigation = Object.entries(resourcesWithMetadata)
.filter(([_, metadata]) => metadata.release !== undefined)

View File

@@ -4,26 +4,25 @@
</component>
</template>
<script setup>
<script setup lang="ts">
import {computed, toRef} from "vue";
import {useRoute} from "vue-router";
import {useDocsLink} from "../docs/useDocsLink";
const route = useRoute();
const props = defineProps({
href: {
type: String,
default: ""
},
target: {
type: String,
default: undefined,
required: false
}
const props = withDefaults(defineProps<{
href?: string;
target?: string;
}>(), {
href: "",
target: undefined
});
const {href, isRemote} = useDocsLink(toRef(props.href), computed(() => route.path));
const {href, isRemote} = useDocsLink(
toRef(props, "href"),
computed(() => route.path)
);
const linkType = computed(() => {
return isRemote.value ? "a" : "router-link";
@@ -33,7 +32,7 @@
if (isRemote.value) {
return {
href: href.value,
target: "_blank"
target: props.target ?? "_blank"
};
}

View File

@@ -5,28 +5,60 @@
<Badge v-if="isATestExecution" :label="$t('test-badge-text')" :tooltip="$t('test-badge-tooltip')" />
</template>
<template #additional-right v-if="canDelete || isAllowedTrigger || isAllowedEdit">
<ul id="list">
<li v-if="isAllowedEdit">
<a :href="`${finalApiUrl}/executions/${execution.id}`" target="_blank">
<el-button :icon="Api">
{{ $t("api") }}
<div class="d-flex align-items-center gap-2">
<ul class="d-none d-xl-flex align-items-center">
<li v-if="isAllowedEdit">
<a :href="`${finalApiUrl}/executions/${execution.id}`" target="_blank">
<el-button :icon="Api">
{{ $t("api") }}
</el-button>
</a>
</li>
<li v-if="canDelete">
<el-button :icon="Delete" @click="deleteExecution">
{{ $t("delete") }}
</el-button>
</a>
</li>
<li v-if="canDelete">
<el-button :icon="Delete" @click="deleteExecution">
{{ $t("delete") }}
</li>
<li v-if="isAllowedEdit">
<el-button :icon="Pencil" @click="editFlow">
{{ $t("edit flow") }}
</el-button>
</li>
</ul>
<el-dropdown class="d-flex d-xl-none align-items-center">
<el-button>
<el-icon><DotsVerticalIcon /></el-icon>
<span class="d-none d-lg-inline-block">{{ $t("more_actions") }}</span>
</el-button>
</li>
<li v-if="isAllowedEdit">
<el-button :icon="Pencil" @click="editFlow">
{{ $t("edit flow") }}
</el-button>
</li>
<li v-if="isAllowedTrigger">
<TriggerFlow type="primary" :flowId="$route.params.flowId" :namespace="$route.params.namespace" />
</li>
</ul>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="isAllowedEdit">
<a :href="`${finalApiUrl}/executions/${execution.id}`" target="_blank">
<el-icon><Api /></el-icon>
{{ $t("api") }}
</a>
</el-dropdown-item>
<el-dropdown-item v-if="canDelete" @click="deleteExecution">
<el-icon><Delete /></el-icon>
{{ $t("delete") }}
</el-dropdown-item>
<el-dropdown-item v-if="isAllowedEdit" @click="editFlow">
<el-icon><Pencil /></el-icon>
{{ $t("edit flow") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div v-if="isAllowedTrigger">
<TriggerFlow
type="primary"
:flowId="$route.params.flowId"
:namespace="$route.params.namespace"
/>
</div>
</div>
</template>
</TopNavBar>
</template>
@@ -35,6 +67,7 @@
import Api from "vue-material-design-icons/Api.vue";
import Delete from "vue-material-design-icons/Delete.vue";
import Pencil from "vue-material-design-icons/Pencil.vue";
import DotsVerticalIcon from "vue-material-design-icons/DotsVertical.vue";
import Badge from "../global/Badge.vue";
</script>
@@ -160,30 +193,11 @@
};
</script>
<style>
@media (max-width: 768px) {
#list {
display:contents;
background-color: blue;
}
#list li:first-child {
grid-row:1;
grid-column:1;
}
#list li:nth-child(2){
grid-row:1;
grid-column:2;
}
#list li:nth-child(3){
grid-row:1;
grid-column:3;
}
#list li:nth-child(4){
grid-row:2;
grid-column:1;
}
}
@media (max-width: 575.98px) {
.sm-extra-padding {
padding: 0;
}
}
</style>

View File

@@ -19,7 +19,7 @@
>
<el-table-column type="expand">
<template #default="props">
<LogsWrapper class="m-3" :filters="{...props.row, triggerId: props.row.id}" purgeFilters :withCharts="false" embed />
<LogsWrapper class="m-3" :filters="{...props.row, triggerId: props.row.id}" purgeFilters :withCharts="false" :reloadLogs embed />
</template>
</el-table-column>
<el-table-column prop="id" :label="$t('id')">
@@ -305,7 +305,8 @@
end: null,
inputs: null,
labels: []
}
},
reloadLogs: undefined,
}
},
created() {
@@ -402,7 +403,8 @@
this.triggerStore
.find({namespace: this.flowStore.flow.namespace, flowId: this.flowStore.flow.id, size: this.triggersWithType.length, q: this.query})
.then(triggers => this.triggers = triggers.results);
.then(triggers => this.triggers = triggers.results)
.then(() => this.reloadLogs = Math.random());
},
setBackfillModal(trigger, bool) {
this.isBackfillOpen = bool

View File

@@ -77,120 +77,119 @@
</div>
</template>
<script>
<script setup lang="ts">
import {Duration, Period} from "@js-joda/core";
export default {
props: {
modelValue: {
type: String,
default: ""
import {ref, watch, onMounted, onUpdated} from "vue";
const props = defineProps<{
modelValue?: string;
}>();
const emit = defineEmits<{
"update:model-value": [value: string | null];
}>();
const years = ref<number>(0);
const months = ref<number>(0);
const weeks = ref<number>(0);
const days = ref<number>(0);
const hours = ref<number>(0);
const minutes = ref<number>(0);
const seconds = ref<number>(0);
const customDuration = ref<string>("");
const durationIssue = ref<string | null>(null);
const updateDuration = () => {
let duration = "P"
if (years.value > 0) {
duration += `${years.value}Y`;
}
if (months.value > 0) {
duration += `${months.value}M`;
}
if (weeks.value > 0) {
duration += `${weeks.value}W`;
}
if (days.value > 0) {
duration += `${days.value}D`;
}
if (hours.value > 0 || minutes.value > 0 || seconds.value > 0) {
duration += "T"
if (hours.value > 0) {
duration += `${hours.value}H`;
}
},
emits: ["update:model-value"],
mounted() {
this.parseDuration(this.modelValue);
this.updateDuration();
},
updated() {
if (this.modelValue) {
this.parseDuration(this.modelValue);
this.updateDuration();
if (minutes.value > 0) {
duration += `${minutes.value}M`;
}
},
data() {
return {
years: 0,
months: 0,
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
customDuration: "",
durationIssue: null
};
},
watch: {
years: "updateDuration",
months: "updateDuration",
weeks: "updateDuration",
days: "updateDuration",
hours: "updateDuration",
minutes: "updateDuration",
seconds: "updateDuration"
},
methods: {
updateDuration() {
let duration = "P"
if (this.years > 0) {
duration += `${this.years}Y`;
}
if (this.months > 0) {
duration += `${this.months}M`;
}
if (this.weeks > 0) {
duration += `${this.weeks}W`;
}
if (this.days > 0) {
duration += `${this.days}D`;
}
if (this.hours > 0 || this.minutes > 0 || this.seconds > 0) {
duration += "T"
if (this.hours > 0) {
duration += `${this.hours}H`;
}
if (this.minutes > 0) {
duration += `${this.minutes}M`;
}
if (this.seconds > 0) {
duration += `${this.seconds}S`;
}
}
if (duration === "P") {
duration = null;
}
this.customDuration = duration;
this.durationIssue = null;
this.$emit("update:model-value", duration);
},
parseDuration(durationString) {
this.customDuration = durationString;
const [datePart, timePart] = durationString.includes("T") ? durationString.split("T") : [durationString, null];
let durationIssueMessage = null;
try {
if (datePart && datePart !== "P") {
const period = Period.parse(datePart);
this.years = period.years();
this.months = period.months();
const days = period.days();
this.weeks = Math.floor(days / 7);
this.days = days % 7;
} else {
this.years = 0; this.months = 0; this.weeks = 0; this.days = 0;
}
if (timePart) {
const timeDuration = Duration.parse(`PT${timePart}`);
this.hours = timeDuration.toHours();
this.minutes = timeDuration.toMinutes() % 60;
this.seconds = timeDuration.seconds() % 60;
} else {
this.hours = 0; this.minutes = 0; this.seconds = 0;
}
} catch (e) {
durationIssueMessage = e.message;
this.$emit("update:model-value", null);
}
this.durationIssue = durationIssueMessage;
if (seconds.value > 0) {
duration += `${seconds.value}S`;
}
}
let finalDuration: string | null = duration;
if (duration === "P") {
finalDuration = null;
}
customDuration.value = finalDuration ?? "";
durationIssue.value = null;
emit("update:model-value", finalDuration);
};
const parseDuration = (durationString: string) => {
customDuration.value = durationString;
const [datePart, timePart] = durationString.includes("T") ? durationString.split("T") : [durationString, null];
let durationIssueMessage: string | null = null;
try {
if (datePart && datePart !== "P") {
const period = Period.parse(datePart);
years.value = period.years();
months.value = period.months();
const parsedDays = period.days();
weeks.value = Math.floor(parsedDays / 7);
days.value = parsedDays % 7;
} else {
years.value = 0; months.value = 0; weeks.value = 0; days.value = 0;
}
if (timePart) {
const timeDuration = Duration.parse(`PT${timePart}`);
hours.value = timeDuration.toHours();
minutes.value = timeDuration.toMinutes() % 60;
seconds.value = timeDuration.seconds() % 60;
} else {
hours.value = 0; minutes.value = 0; seconds.value = 0;
}
} catch (e) {
durationIssueMessage = (e as Error).message;
emit("update:model-value", null);
}
durationIssue.value = durationIssueMessage;
};
watch(years, updateDuration);
watch(months, updateDuration);
watch(weeks, updateDuration);
watch(days, updateDuration);
watch(hours, updateDuration);
watch(minutes, updateDuration);
watch(seconds, updateDuration);
onMounted(() => {
parseDuration(props.modelValue ?? "");
updateDuration();
});
onUpdated(() => {
if (props.modelValue) {
parseDuration(props.modelValue);
updateDuration();
}
});
</script>
<style scoped>

View File

@@ -24,51 +24,34 @@
</el-select>
</template>
<script>
const isValidLabel = (label) => {
<script setup lang="ts">
import {ref, watch} from "vue";
const isValidLabel = (label: string): boolean => {
return label.match(".+:.+") !== null;
};
const isValidLabels = (labels) => {
return labels.every((label) => isValidLabel(label));
const props = defineProps<{
modelValue?: string | string[];
}>();
const emit = defineEmits<{
"update:modelValue": [value: string[]];
}>();
const asArrayProp = (unknownValue: string | string[] | undefined): string[] => {
return (!Array.isArray(unknownValue) && unknownValue !== undefined) ? [unknownValue] : (unknownValue ?? []);
};
export default {
props: {
modelValue: {
type: [Array, String],
default: () => [],
validator(value) {
return typeof value === "string" ? isValidLabel(value) : isValidLabels(value);
}
}
},
emits: ["update:modelValue"],
created() {
this.labels = this.asArrayProp(this.modelValue);
},
data() {
return {
hover: false,
inputValue: undefined,
labels: [],
}
},
watch: {
modelValue: {
handler (newValue) {
this.labels = this.asArrayProp(newValue);
}
}
},
methods: {
asArrayProp(unknownValue) {
return (!Array.isArray(unknownValue) && unknownValue !== undefined) ? [unknownValue] : unknownValue;
},
onInput(value) {
this.labels = value.filter((label) => isValidLabel(label));
this.$emit("update:modelValue", this.labels)
},
}
const hover = ref<boolean>(false);
const labels = ref<string[]>(asArrayProp(props.modelValue));
watch(() => props.modelValue, (newValue) => {
labels.value = asArrayProp(newValue);
});
const onInput = (value: string[]) => {
labels.value = value.filter((label) => isValidLabel(label));
emit("update:modelValue", labels.value);
};
</script>

View File

@@ -52,6 +52,7 @@
}>();
const route = useRoute();
const PAGINATION_SIZE = `${storageKeys.PAGINATION_SIZE}__${String(route.name)}`;
const {t} = useI18n();
@@ -64,7 +65,7 @@
const internalSize = ref<number>(
parseInt(
localStorage.getItem(storageKeys.PAGINATION_SIZE) as string ||
localStorage.getItem(PAGINATION_SIZE) as string ||
(route.query.size as string) ||
props.size?.toString() ||
"25"
@@ -83,7 +84,7 @@
function pageSizeChange(value: number) {
internalPage.value = 1;
internalSize.value = value;
localStorage.setItem(storageKeys.PAGINATION_SIZE, value.toString());
localStorage.setItem(PAGINATION_SIZE, value.toString());
emit("page-changed", {
page: 1,
size: internalSize.value,
@@ -109,7 +110,7 @@
() => route.query,
() => {
internalSize.value = parseInt(
localStorage.getItem(storageKeys.PAGINATION_SIZE) as string ||
localStorage.getItem(PAGINATION_SIZE) as string ||
(route.query.size as string) ||
props.size?.toString() ||
"25"

View File

@@ -1,5 +1,5 @@
<template>
<nav data-component="FILENAME_PLACEHOLDER" class="d-flex w-100 gap-3 top-bar">
<nav data-component="FILENAME_PLACEHOLDER" class="d-flex align-items-center w-100 gap-3 top-bar">
<div class="d-flex flex-column flex-grow-1 flex-shrink-1 overflow-hidden top-title">
<div class="d-flex align-items-end gap-2">
<SidebarToggleButton

View File

@@ -83,6 +83,10 @@
type: Object,
default: null
},
reloadLogs: {
type: Number,
default: undefined
}
},
data() {
return {
@@ -206,7 +210,6 @@
load() {
this.isLoading = true
const data = {
page: this.filters ? this.internalPageNumber : this.$route.query.page || this.internalPageNumber,
size: this.filters ? this.internalPageSize : this.$route.query.size || this.internalPageSize,
@@ -224,6 +227,11 @@
},
},
watch: {
reloadLogs(newValue) {
if(newValue) this.refresh();
},
}
});
</script>
<style lang="scss" scoped>

View File

@@ -39,7 +39,7 @@
#{--el-color-alert-#{$type}}: var(--ks-content-#{$type});
}
--bs-code-color: $base-purple-600;
#{--bs-code-color}: $base-purple-600;
#{--card-bg}: var(--ks-background-card);
#{--input-bg}: var(--ks-background-input);

View File

@@ -778,6 +778,7 @@
"missingSource": "Fehlende Quelle",
"monogram": "Monogramm",
"months": "Monate",
"more_actions": "Weitere Aktionen",
"multi_panel_editor": {
"close_all_panels": "Alle Panels schließen",
"close_all_tabs": "Alle Registerkarten schließen",
@@ -806,11 +807,13 @@
"label": "Löschen"
},
"dialog": {
"confirm": "Bestätigen",
"file_deletion": "Datei-Löschung bestätigen",
"file_deletion_description": "Sind Sie sicher, dass Sie diese Datei löschen möchten?",
"folder_deletion": "Ordner-Löschung bestätigen",
"folder_deletion_description": "Sind Sie sicher, dass Sie diesen Ordner und alle seine Inhalte löschen möchten?",
"deletion": {
"confirm": "Bestätigen",
"files": "Möchten Sie wirklich <code>{count}</code> Datei(en) löschen?",
"folders": "Möchten Sie wirklich <code>{count}</code> Ordner und alle seine Inhalte löschen?",
"mixed": "Möchten Sie wirklich den Ordner <code>{folders}</code> und die Datei <code>{files}</code> löschen?",
"title": "Bestätigen Sie das Löschen des Inhalts"
},
"name": {
"file": "Name mit Erweiterung:",
"folder": "Ordnername:"

View File

@@ -576,6 +576,7 @@
},
"title": "Title",
"api": "API",
"more_actions": "More Actions",
"expand error": "Expand only failed tasks",
"expand all": "Expand all",
"collapse all": "Collapse all",

View File

@@ -778,6 +778,7 @@
"missingSource": "Fuente faltante",
"monogram": "Monograma",
"months": "Meses",
"more_actions": "Más acciones",
"multi_panel_editor": {
"close_all_panels": "Cerrar todos los paneles",
"close_all_tabs": "Cerrar todas las pestañas",
@@ -806,11 +807,13 @@
"label": "Eliminar"
},
"dialog": {
"confirm": "Confirmar",
"file_deletion": "Confirmar eliminación de archivo",
"file_deletion_description": "¿Estás seguro de que quieres eliminar este archivo?",
"folder_deletion": "Confirmar eliminación de carpeta",
"folder_deletion_description": "¿Estás seguro de que quieres eliminar esta carpeta y todo su contenido?",
"deletion": {
"confirm": "Confirmar",
"files": "¿Está seguro de que desea eliminar <code>{count}</code> archivo(s)?",
"folders": "¿Está seguro de que desea eliminar <code>{count}</code> carpeta(s) y todo su contenido?",
"mixed": "¿Está seguro de que desea eliminar la(s) carpeta(s) <code>{folders}</code> y el/los archivo(s) <code>{files}</code>?",
"title": "Confirmar eliminación de contenido"
},
"name": {
"file": "Nombre con extensión:",
"folder": "Nombre de la carpeta:"

View File

@@ -778,6 +778,7 @@
"missingSource": "Source manquant",
"monogram": "Monogramme",
"months": "Mois",
"more_actions": "Plus d'actions",
"multi_panel_editor": {
"close_all_panels": "Fermer tous les panneaux",
"close_all_tabs": "Fermer tous les onglets",
@@ -806,11 +807,13 @@
"label": "Supprimer"
},
"dialog": {
"confirm": "Confirmer",
"file_deletion": "Confirmer la suppression du fichier",
"file_deletion_description": "Êtes-vous sûr de vouloir supprimer ce fichier ?",
"folder_deletion": "Confirmer la suppression du dossier",
"folder_deletion_description": "Êtes-vous sûr de vouloir supprimer ce dossier et tous ses contenus ?",
"deletion": {
"confirm": "Confirmer",
"files": "Êtes-vous sûr de vouloir supprimer <code>{count}</code> fichier(s) ?",
"folders": "Êtes-vous sûr de vouloir supprimer <code>{count}</code> dossier(s) et tout son contenu ?",
"mixed": "Êtes-vous sûr de vouloir supprimer le(s) dossier(s) <code>{folders}</code> et le(s) fichier(s) <code>{files}</code> ?",
"title": "Confirmer la suppression du contenu"
},
"name": {
"file": "Nom avec extension :",
"folder": "Nom du dossier :"

View File

@@ -778,6 +778,7 @@
"missingSource": "स्रोत अनुपलब्ध",
"monogram": "मोनोग्राम",
"months": "महीने",
"more_actions": "अधिक क्रियाएँ",
"multi_panel_editor": {
"close_all_panels": "सभी पैनल बंद करें",
"close_all_tabs": "सभी टैब बंद करें",
@@ -806,11 +807,13 @@
"label": "हटाएं"
},
"dialog": {
"confirm": "पुष्टि करें",
"file_deletion": "फ़ाइल हटाने की पुष्टि करें",
"file_deletion_description": "क्या आप वाकई इस फ़ाइल को हटाना चाहते हैं?",
"folder_deletion": "फ़ोल्डर हटाने की पुष्टि करें",
"folder_deletion_description": "क्या आप वाकई इस फ़ोल्डर और इसकी सभी सामग्री को हटाना चाहते हैं?",
"deletion": {
"confirm": "पुष्टि करें",
"files": "क्या आप वाकई <code>{count}</code> फ़ाइल(फ़ाइलें) हटाना चाहते हैं?",
"folders": "क्या आप वाकई <code>{count}</code> फ़ोल्डर और उसकी सभी सामग्री को हटाना चाहते हैं?",
"mixed": "क्या आप वाकई <code>{folders}</code> फ़ोल्डर(ओं) और <code>{files}</code> फ़ाइल(ओं) को हटाना चाहते हैं?",
"title": "सामग्री को हटाने की पुष्टि करें"
},
"name": {
"file": "एक्सटेंशन के साथ नाम:",
"folder": "फ़ोल्डर का नाम:"

View File

@@ -778,6 +778,7 @@
"missingSource": "Sorgente mancante",
"monogram": "Monogramma",
"months": "Mesi",
"more_actions": "Altre azioni",
"multi_panel_editor": {
"close_all_panels": "Chiudi tutti i pannelli",
"close_all_tabs": "Chiudi tutte le tab",
@@ -806,11 +807,13 @@
"label": "Elimina"
},
"dialog": {
"confirm": "Conferma",
"file_deletion": "Conferma eliminazione file",
"file_deletion_description": "Sei sicuro di voler eliminare questo file?",
"folder_deletion": "Conferma eliminazione cartella",
"folder_deletion_description": "Sei sicuro di voler eliminare questa cartella e tutto il suo contenuto?",
"deletion": {
"confirm": "Conferma",
"files": "Sei sicuro di voler eliminare <code>{count}</code> file?",
"folders": "Sei sicuro di voler eliminare <code>{count}</code> cartella/e e tutto il suo contenuto?",
"mixed": "Sei sicuro di voler eliminare la/le cartella/e <code>{folders}</code> e il/i file <code>{files}</code>?",
"title": "Conferma eliminazione del contenuto"
},
"name": {
"file": "Nome con estensione:",
"folder": "Nome della cartella:"

View File

@@ -778,6 +778,7 @@
"missingSource": "ソースが見つかりません",
"monogram": "モノグラム",
"months": "月",
"more_actions": "その他のアクション",
"multi_panel_editor": {
"close_all_panels": "すべてのパネルを閉じる",
"close_all_tabs": "すべてのタブを閉じる",
@@ -806,11 +807,13 @@
"label": "削除"
},
"dialog": {
"confirm": "確認",
"file_deletion": "ファイル削除の確認",
"file_deletion_description": "このファイルを削除してもよろしいですか?",
"folder_deletion": "フォルダー削除の確認",
"folder_deletion_description": "このフォルダーとそのすべての内容を削除してもよろしいですか?",
"deletion": {
"confirm": "確認",
"files": "<code>{count}</code> 個のファイルを削除してもよろしいですか?",
"folders": "<code>{count}</code> 個のフォルダーとそのすべての内容を削除してもよろしいですか?",
"mixed": "<code>{folders}</code> フォルダーと <code>{files}</code> ファイルを削除してもよろしいですか?",
"title": "コンテンツの削除を確認"
},
"name": {
"file": "拡張子付きの名前:",
"folder": "フォルダー名:"

View File

@@ -778,6 +778,7 @@
"missingSource": "소스 누락",
"monogram": "모노그램",
"months": "개월",
"more_actions": "추가 작업",
"multi_panel_editor": {
"close_all_panels": "모든 패널 닫기",
"close_all_tabs": "모든 탭 닫기",
@@ -806,11 +807,13 @@
"label": "삭제"
},
"dialog": {
"confirm": "확인",
"file_deletion": "파일 삭제 확인",
"file_deletion_description": "이 파일을 삭제하시겠습니까?",
"folder_deletion": "폴더 삭제 확인",
"folder_deletion_description": "이 폴더와 모든 내용을 삭제하시겠습니까?",
"deletion": {
"confirm": "확인",
"files": "<code>{count}</code>개의 파일을 삭제하시겠습니까?",
"folders": "<code>{count}</code>개의 폴더와 모든 내용을 삭제하시겠습니까?",
"mixed": "<code>{folders}</code> 폴더와 <code>{files}</code> 파일을 삭제하시겠습니까?",
"title": "콘텐츠 삭제 확인"
},
"name": {
"file": "확장자를 포함한 이름:",
"folder": "폴더 이름:"

View File

@@ -778,6 +778,7 @@
"missingSource": "Brak źródła",
"monogram": "Monogram",
"months": "Miesiące",
"more_actions": "Więcej akcji",
"multi_panel_editor": {
"close_all_panels": "Zamknij wszystkie panele",
"close_all_tabs": "Zamknij wszystkie karty",
@@ -806,11 +807,13 @@
"label": "Usuń"
},
"dialog": {
"confirm": "Potwierdź",
"file_deletion": "Potwierdź usunięcie pliku",
"file_deletion_description": "Czy na pewno chcesz usunąć ten plik?",
"folder_deletion": "Potwierdź usunięcie folderu",
"folder_deletion_description": "Czy na pewno chcesz usunąć ten folder i całą jego zawartość?",
"deletion": {
"confirm": "Potwierdź",
"files": "Czy na pewno chcesz usunąć <code>{count}</code> plik(ów)?",
"folders": "Czy na pewno chcesz usunąć <code>{count}</code> folder(y) i całą jego zawartość?",
"mixed": "Czy na pewno chcesz usunąć folder(y) <code>{folders}</code> i plik(i) <code>{files}</code>?",
"title": "Potwierdź usunięcie zawartości"
},
"name": {
"file": "Nazwa z rozszerzeniem:",
"folder": "Nazwa folderu:"

View File

@@ -778,6 +778,7 @@
"missingSource": "Fonte ausente",
"monogram": "Monograma",
"months": "Meses",
"more_actions": "Mais Ações",
"multi_panel_editor": {
"close_all_panels": "Fechar todos os painéis",
"close_all_tabs": "Fechar todas as abas",
@@ -806,11 +807,13 @@
"label": "Deletar"
},
"dialog": {
"confirm": "Confirmar",
"file_deletion": "Confirmar deleção de arquivo",
"file_deletion_description": "Tem certeza de que deseja deletar este arquivo?",
"folder_deletion": "Confirmar deleção de pasta",
"folder_deletion_description": "Tem certeza de que deseja deletar esta pasta e todo o seu conteúdo?",
"deletion": {
"confirm": "Confirmar",
"files": "Tem certeza de que deseja excluir <code>{count}</code> arquivo(s)?",
"folders": "Tem certeza de que deseja excluir <code>{count}</code> pasta(s) e todo o seu conteúdo?",
"mixed": "Tem certeza de que deseja excluir a(s) pasta(s) <code>{folders}</code> e o(s) arquivo(s) <code>{files}</code>?",
"title": "Confirmar exclusão de conteúdo"
},
"name": {
"file": "Nome com extensão:",
"folder": "Nome da pasta:"

View File

@@ -778,6 +778,7 @@
"missingSource": "Fonte ausente",
"monogram": "Monograma",
"months": "Meses",
"more_actions": "Mais Ações",
"multi_panel_editor": {
"close_all_panels": "Fechar todos os painéis",
"close_all_tabs": "Fechar todas as abas",
@@ -806,11 +807,13 @@
"label": "Excluir"
},
"dialog": {
"confirm": "Confirmar",
"file_deletion": "Confirmar deleção de arquivo",
"file_deletion_description": "Tem certeza de que deseja excluir este arquivo?",
"folder_deletion": "Confirmar deleção de pasta",
"folder_deletion_description": "Tem certeza de que deseja excluir esta pasta e todo o seu conteúdo?",
"deletion": {
"confirm": "Confirmar",
"files": "Tem certeza de que deseja excluir <code>{count}</code> arquivo(s)?",
"folders": "Tem certeza de que deseja excluir <code>{count}</code> pasta(s) e todo o seu conteúdo?",
"mixed": "Tem certeza de que deseja excluir a(s) pasta(s) <code>{folders}</code> e o(s) arquivo(s) <code>{files}</code>?",
"title": "Confirmar exclusão de conteúdo"
},
"name": {
"file": "Nome com extensão:",
"folder": "Nome da pasta:"

View File

@@ -778,6 +778,7 @@
"missingSource": "Отсутствует источник",
"monogram": "Монограмма",
"months": "Месяцы",
"more_actions": "Больше действий",
"multi_panel_editor": {
"close_all_panels": "Закрыть все панели",
"close_all_tabs": "Закрыть все вкладки",
@@ -806,11 +807,13 @@
"label": "Удалить"
},
"dialog": {
"confirm": "Подтвердить",
"file_deletion": "Подтвердите удаление файла",
"file_deletion_description": "Вы уверены, что хотите удалить этот файл?",
"folder_deletion": "Подтвердите удаление папки",
"folder_deletion_description": "Вы уверены, что хотите удалить эту папку и все ее содержимое?",
"deletion": {
"confirm": "Подтвердить",
"files": "Вы уверены, что хотите удалить <code>{count}</code> файл(ы)?",
"folders": "Вы уверены, что хотите удалить <code>{count}</code> папку(и) и все её содержимое?",
"mixed": "Вы уверены, что хотите удалить папку(и) <code>{folders}</code> и файл(ы) <code>{files}</code>?",
"title": "Подтвердите удаление содержимого"
},
"name": {
"file": "Имя с расширением:",
"folder": "Имя папки:"

View File

@@ -778,6 +778,7 @@
"missingSource": "缺少源",
"monogram": "字母组合",
"months": "月份",
"more_actions": "更多操作",
"multi_panel_editor": {
"close_all_panels": "关闭所有面板",
"close_all_tabs": "关闭所有标签页",
@@ -806,11 +807,13 @@
"label": "删除"
},
"dialog": {
"confirm": "确认",
"file_deletion": "确认删除文件",
"file_deletion_description": "确定要删除文件吗?",
"folder_deletion": "确认删除文件夹",
"folder_deletion_description": "确定要删除此文件夹及其所有内容吗?",
"deletion": {
"confirm": "确认",
"files": "确定要删除 <code>{count}</code> 个文件吗?",
"folders": "您确定要删除 <code>{count}</code> 个文件夹及其所有内容吗?",
"mixed": "确定要删除 <code>{folders}</code> 个文件夹和 <code>{files}</code> 个文件吗?",
"title": "确认删除内容"
},
"name": {
"file": "文件名(带扩展名):",
"folder": "文件夹名:"

View File

@@ -106,6 +106,11 @@ export const InputTypes = {
"Seventh value",
"Eighth value"
]
},
{
id: "duration_field",
type: "DURATION",
displayName: "Duration select input",
}]}
/>;
}