Compare commits

...

9 Commits

Author SHA1 Message Date
YannC
6a03dc9e41 chore(version): update to version 'v0.13.2' 2023-11-24 08:50:56 +01:00
Loïc Mathieu
b2a107bc55 fix(ui): the workers API is global to all tenants 2023-11-24 08:50:40 +01:00
YannC
97a6afbe2e chore(version): update to version 'v0.13.1' 2023-11-21 19:44:48 +01:00
brian.mulier
cc800b4528 fix(ui): every unwanted things are now hidden in VSCode editor
closes #2336
closes #2338
closes #2339
2023-11-21 19:43:29 +01:00
YannC
da50cf4287 fix(): display file preview (#2569) 2023-11-21 19:42:55 +01:00
YannC
1ecfed1559 fix(): apply class correctly (#2578)
close #2576
2023-11-21 19:42:25 +01:00
brian.mulier
4a3e250019 fix(core): input streams are now properly closed to prevent exhausting connections on remote storages
closes kestra-io/storage-s3#33
2023-11-21 19:39:00 +01:00
YannC
2917c178a5 fix(): display actions buttons on topology view (#2575)
* fix(): display actions buttons on topology view

* fix(): better button position
2023-11-21 19:37:50 +01:00
YannC
0ceaeb4a97 fix(): subflow logs not working (#2552) 2023-11-21 19:36:50 +01:00
17 changed files with 249 additions and 106 deletions

View File

@@ -107,7 +107,6 @@ public class NamespaceFilesService {
private void copy(String tenantId, String namespace, Path basePath, List<URI> files) throws IOException {
files
.forEach(throwConsumer(f -> {
InputStream inputStream = storageInterface.get(tenantId, uri(namespace, f));
Path destination = Paths.get(basePath.toString(), f.getPath());
if (!destination.getParent().toFile().exists()) {
@@ -115,7 +114,9 @@ public class NamespaceFilesService {
destination.getParent().toFile().mkdirs();
}
Files.copy(inputStream, destination);
try (InputStream inputStream = storageInterface.get(tenantId, uri(namespace, f))) {
Files.copy(inputStream, destination);
}
}));
}
}

View File

@@ -1,7 +1,6 @@
package io.kestra.core.runners.pebble.functions;
import io.kestra.core.storages.StorageInterface;
import io.kestra.core.tenant.TenantService;
import io.kestra.core.utils.Slugify;
import io.pebbletemplates.pebble.error.PebbleException;
import io.pebbletemplates.pebble.extension.Function;
@@ -11,6 +10,7 @@ import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
@@ -47,7 +47,9 @@ public class ReadFileFunction implements Function {
private String readFromNamespaceFile(EvaluationContext context, String path) throws IOException {
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
URI namespaceFile = URI.create(storageInterface.namespaceFilePrefix(flow.get("namespace")) + "/" + path);
return new String(storageInterface.get(flow.get("tenantId"), namespaceFile).readAllBytes(), StandardCharsets.UTF_8);
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), namespaceFile)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
private String readFromInternalStorageUri(EvaluationContext context, String path) throws IOException {
@@ -69,7 +71,9 @@ public class ReadFileFunction implements Function {
}
}
URI internalStorageFile = URI.create(path);
return new String(storageInterface.get(flow.get("tenantId"), internalStorageFile).readAllBytes(), StandardCharsets.UTF_8);
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), internalStorageFile)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
private boolean validateFileUri(String namespace, String flowId, String executionId, String path) {

View File

@@ -4,10 +4,7 @@ import io.kestra.core.runners.RunContext;
import io.kestra.core.storages.StorageSplitInterface;
import io.micronaut.core.convert.format.ReadableBytesTypeConverter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.*;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

View File

@@ -42,8 +42,7 @@ public interface StorageInterface {
* @return true if the uri points to a file/object that exist in the internal storage.
*/
default boolean exists(String tenantId, URI uri) {
try {
get(tenantId, uri);
try (InputStream ignored = get(tenantId, uri)){
return true;
} catch (IOException ieo) {
return false;

View File

@@ -54,9 +54,9 @@ public abstract class AbstractState extends Task {
protected Map<String, Object> get(RunContext runContext) throws IllegalVariableEvaluationException, IOException {
InputStream taskStateFile = runContext.getTaskStateFile("tasks-states", runContext.render(this.name), this.namespace, this.taskrunValue);
return JacksonMapper.ofJson(false).readValue(taskStateFile, TYPE_REFERENCE);
try (InputStream taskStateFile = runContext.getTaskStateFile("tasks-states", runContext.render(this.name), this.namespace, this.taskrunValue)) {
return JacksonMapper.ofJson(false).readValue(taskStateFile, TYPE_REFERENCE);
}
}
protected Pair<URI, Map<String, Object>> merge(RunContext runContext, Map<String, Object> map) throws IllegalVariableEvaluationException, IOException {

View File

@@ -16,6 +16,7 @@ import io.kestra.core.runners.RunContext;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
@@ -128,7 +129,9 @@ public class Concat extends Task implements RunnableTask<Concat.Output> {
finalFiles.forEach(throwConsumer(s -> {
URI from = new URI(runContext.render(s));
IOUtils.copyLarge(runContext.uriToInputStream(from), fileOutputStream);
try (InputStream inputStream = runContext.uriToInputStream(from)) {
IOUtils.copyLarge(inputStream, fileOutputStream);
}
if (separator != null) {
IOUtils.copy(new ByteArrayInputStream(this.separator.getBytes()), fileOutputStream);

View File

@@ -1,4 +1,4 @@
version=0.13.0
version=0.13.2
jacksonVersion=2.15.2
micronautVersion=3.10.1

View File

@@ -30,6 +30,31 @@ const extensionsToFetch = Object.entries(versionByExtensionIdToFetch).map(([exte
}));
// used to configure VSCode startup
const sidebarTabs = [
{"id": "workbench.view.explorer", "pinned": true, "visible": true, "order": 0},
{"id": "workbench.view.search", "pinned": true, "visible": true, "order": 1},
{"id": "workbench.view.scm", "pinned": false, "visible": false, "order": 2},
{"id": "workbench.view.debug", "pinned": false,"visible": false,"order": 3},
{"id": "workbench.view.extensions", "pinned": true,"visible": true,"order": 4},
{"id": "workbench.view.remote", "pinned": false,"visible": false,"order": 4},
{"id": "workbench.view.extension.test", "pinned": false,"visible": false,"order": 6},
{"id": "workbench.view.extension.references-view", "pinned": false,"visible": false,"order": 7},
{"id": "workbench.panel.chatSidebar", "pinned": false,"visible": false,"order": 100},
{"id": "userDataProfiles", "pinned": false, "visible": false},
{"id": "workbench.view.sync", "pinned": false,"visible": false},
{"id": "workbench.view.editSessions", "pinned": false, "visible": false}
];
const bottomBarTabs = [
{"id":"workbench.panel.markers", "pinned": false,"visible": false,"order": 0},
{"id":"workbench.panel.output", "pinned": false,"visible": false,"order": 1},
{"id":"workbench.panel.repl", "pinned": false,"visible": false,"order": 2},
{"id":"terminal", "pinned": false,"visible": false,"order": 3},
{"id":"workbench.panel.testResults", "pinned": false,"visible": false,"order": 3},
{"id":"~remote.forwardedPortsContainer", "pinned": false,"visible": false,"order": 5},
{"id":"refactorPreview", "pinned": false,"visible": false}
];
window.product = {
productConfiguration: {
nameShort: "Kestra VSCode",
@@ -77,5 +102,17 @@ window.product = {
"workbench.colorTheme": THEME === "dark" ? "Sweet Dracula" : "Default Light Modern",
// provide the Kestra root URL to extension
"kestra.api.url": KESTRA_API_URL
},
profile: {
name: "Kestra VSCode",
contents: JSON.stringify({
globalState: JSON.stringify({
"storage": {
"workbench.activity.pinnedViewlets2": sidebarTabs,
"workbench.activity.showAccounts": "false",
"workbench.panel.pinnedPanels": bottomBarTabs
}
})
})
}
};

View File

@@ -142,7 +142,7 @@
executionId: this.executionId,
path: this.value,
maxRows: this.maxPreview,
encoding: this.encoding
encoding: this.encoding.value
})
.then(() => {
this.isPreviewOpen = true;

View File

@@ -456,15 +456,15 @@
}
.bottom-right {
bottom: var(--spacer);
right: var(--spacer);
bottom: 0px;
right: 0px;
ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
gap: calc(var(--spacer) / 2);
//gap: calc(var(--spacer) / 2);
}
}
}

View File

@@ -0,0 +1,121 @@
<template>
<div class="to-action-button">
<div v-if="isAllowedEdit || canDelete" class="mx-2">
<el-dropdown>
<el-button type="default" :disabled="isReadOnly">
<DotsVertical title=""/>
{{ $t("actions") }}
</el-button>
<template #dropdown>
<el-dropdown-menu class="m-dropdown-menu">
<el-dropdown-item
v-if="!isCreating && canDelete"
:icon="Delete"
size="large"
@click="forwardEvent('delete-flow', $event)"
>
{{ $t("delete") }}
</el-dropdown-item>
<el-dropdown-item
v-if="!isCreating"
:icon="ContentCopy"
size="large"
@click="forwardEvent('copy', $event)"
>
{{ $t("copy") }}
</el-dropdown-item>
<el-dropdown-item
v-if="isAllowedEdit"
:icon="Exclamation"
size="large"
@click="forwardEvent('open-new-error', null)"
:disabled="!flowHaveTasks"
>
{{ $t("add global error handler") }}
</el-dropdown-item>
<el-dropdown-item
v-if="isAllowedEdit"
:icon="LightningBolt"
size="large"
@click="forwardEvent('open-new-trigger', null)"
:disabled="!flowHaveTasks"
>
{{ $t("add trigger") }}
</el-dropdown-item>
<el-dropdown-item
v-if="isAllowedEdit"
:icon="FileEdit"
size="large"
@click="forwardEvent('open-edit-metadata', null)"
>
{{ $t("edit metadata") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div>
<el-button
:icon="ContentSave"
@click="forwardEvent('save', $event)"
v-if="isAllowedEdit"
:type="flowError ? 'danger' : 'primary'"
:disabled="!haveChange && !isCreating"
class="edit-flow-save-button"
>
{{ $t("save") }}
</el-button>
</div>
</div>
</template>
<script setup>
import DotsVertical from "vue-material-design-icons/DotsVertical.vue";
import Delete from "vue-material-design-icons/Delete.vue";
import ContentCopy from "vue-material-design-icons/ContentCopy.vue";
import Exclamation from "vue-material-design-icons/Exclamation.vue";
import LightningBolt from "vue-material-design-icons/LightningBolt.vue";
import FileEdit from "vue-material-design-icons/FileEdit.vue";
import ContentSave from "vue-material-design-icons/ContentSave.vue";
</script>
<script>
import {defineComponent} from "vue";
export default defineComponent({
props: {
isCreating: {
type: Boolean,
default: false
},
isReadOnly: {
type: Boolean,
default: false
},
canDelete: {
type: Boolean,
default: false
},
isAllowedEdit: {
type: Boolean,
default: false
},
haveChange: {
type: Boolean,
default: false
},
flowHaveTasks: {
type: Boolean,
default: false
},
flowError: {
type: String,
default: null
}
},
methods: {
forwardEvent(type, event) {
this.$emit(type, event);
}
}
})
</script>

View File

@@ -28,6 +28,7 @@
import {editorViewTypes} from "../../utils/constants";
import Utils from "@kestra-io/ui-libs/src/utils/Utils";
import {apiUrl} from "override/utils/route";
import EditorButtons from "./EditorButtons.vue";
const store = useStore();
const router = getCurrentInstance().appContext.config.globalProperties.$router;
@@ -675,75 +676,22 @@
<ValidationError ref="validationDomElement" tooltip-placement="bottom-start" size="small" class="ms-2" :error="flowError" :warnings="flowWarnings" />
</template>
<template #buttons>
<ul>
<li v-if="isAllowedEdit || canDelete">
<el-dropdown>
<el-button type="default" :disabled="isReadOnly">
<DotsVertical title="" />
{{ $t("actions") }}
</el-button>
<template #dropdown>
<el-dropdown-menu class="m-dropdown-menu">
<el-dropdown-item
v-if="!props.isCreating && canDelete"
:icon="Delete"
size="large"
@click="deleteFlow"
>
{{ $t("delete") }}
</el-dropdown-item>
<el-dropdown-item
v-if="!props.isCreating"
:icon="ContentCopy"
size="large"
@click="() => router.push({name: 'flows/create', query: {copy: true}})"
>
{{ $t("copy") }}
</el-dropdown-item>
<el-dropdown-item
v-if="isAllowedEdit"
:icon="Exclamation"
size="large"
@click="isNewErrorOpen = true;"
:disabled="!flowHaveTasks()"
>
{{ $t("add global error handler") }}
</el-dropdown-item>
<el-dropdown-item
v-if="isAllowedEdit"
:icon="LightningBolt"
size="large"
@click="isNewTriggerOpen = true;"
:disabled="!flowHaveTasks()"
>
{{ $t("add trigger") }}
</el-dropdown-item>
<el-dropdown-item
v-if="isAllowedEdit"
:icon="FileEdit"
size="large"
@click="isEditMetadataOpen = true;"
>
{{ $t("edit metadata") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</li>
<li>
<el-button
:icon="ContentSave"
@click="save"
v-if="isAllowedEdit"
:type="flowError ? 'danger' : 'primary'"
:disabled="!haveChange && !isCreating"
class="edit-flow-save-button"
>
{{ $t("save") }}
</el-button>
</li>
</ul>
<EditorButtons
v-if="![editorViewTypes.TOPOLOGY, editorViewTypes.SOURCE_TOPOLOGY].includes(viewType)"
:is-creating="props.isCreating"
:is-read-only="props.isReadOnly"
:can-delete="canDelete"
:is-allowed-edit="isAllowedEdit"
:have-change="haveChange"
:flow-have-tasks="flowHaveTasks()"
:flow-error="flowError"
@delete-flow="deleteFlow"
@save="save"
@copy="() => router.push({name: 'flows/create', query: {copy: true}})"
@open-new-error="isNewErrorOpen = true;"
@open-new-trigger="isNewTriggerOpen = true;"
@open-edit-metadata="isEditMetadataOpen = true;"
/>
</template>
</editor>
<div class="slider" @mousedown="dragEditor" v-if="combinedEditor" />
@@ -859,6 +807,22 @@
class="to-topology-button"
@switch-view="switchViewType"
/>
<EditorButtons
v-if="[editorViewTypes.TOPOLOGY, editorViewTypes.SOURCE_TOPOLOGY].includes(viewType)"
:is-creating="props.isCreating"
:is-read-only="props.isReadOnly"
:can-delete="canDelete"
:is-allowed-edit="isAllowedEdit"
:have-change="haveChange"
:flow-have-tasks="flowHaveTasks()"
:flow-error="flowError"
@delete-flow="deleteFlow"
@save="save"
@copy="() => router.push({name: 'flows/create', query: {copy: true}})"
@open-new-error="isNewErrorOpen = true;"
@open-new-trigger="isNewTriggerOpen = true;"
@open-edit-metadata="isEditMetadataOpen = true;"
/>
</el-card>
</template>
@@ -879,6 +843,13 @@
right: 45px;
}
.to-action-button {
position: absolute;
bottom: 30px;
right: 45px;
display: flex;
}
.editor-combined {
height: 100%;
width: 50%;

View File

@@ -53,6 +53,8 @@
:allow-auto-expand-subflows="false"
:target-execution-id="currentTaskRun.outputs.executionId"
:class="$el.classList.contains('even') ? '' : 'even'"
:show-progress-bar="showProgressBar"
:show-logs="showLogs"
/>
</DynamicScrollerItem>
</template>
@@ -315,6 +317,16 @@
this.logsWithIndexByAttemptUid[this.attemptUid(taskRun.id, this.selectedAttemptNumberByTaskRunId[taskRun.id])])) &&
this.showLogs
},
followExecution(executionId) {
this.$store
.dispatch("execution/followExecution", {id: executionId})
.then(sse => {
this.executionSSE = sse;
this.executionSSE.onmessage = async (event) => {
this.followedExecution = JSON.parse(event.data);
}
});
},
followLogs(executionId) {
this.$store
.dispatch("execution/followLogs", {id: executionId})

View File

@@ -1,11 +1,11 @@
import {apiUrl} from "override/utils/route";
import {apiUrlWithoutTenants} from "override/utils/route";
export default {
namespaced: true,
actions: {
findAll(_, __) {
return this.$http.get(`${apiUrl(this)}/workers`).then(response => {
return this.$http.get(`${apiUrlWithoutTenants(this)}/workers`).then(response => {
return response.data;
})
}

View File

@@ -413,11 +413,6 @@ form.ks-horizontal {
&.is-dark {
color: var(--bs-gray-100);
html:not(.dark) & {
* {
color: var(--bs-gray-100);
}
}
background: var(--bs-gray-900);
border: 1px solid var(--bs-border-color);

View File

@@ -1009,12 +1009,11 @@ public class ExecutionController {
@Parameter(description = "The execution id") @PathVariable String executionId,
@Parameter(description = "The internal storage uri") @QueryValue URI path,
@Parameter(description = "The max row returns") @QueryValue @Nullable Integer maxRows,
@Parameter(description = "The file encoding as Java charset name. Defaults to UTF-8", example = "ISO-8859-1") @QueryValue @Nullable String encoding
@Parameter(description = "The file encoding as Java charset name. Defaults to UTF-8", example = "ISO-8859-1") @QueryValue(defaultValue = "UTF-8") String encoding
) throws IOException {
this.validateFile(executionId, path, "/api/v1/executions/{executionId}/file?path=" + path);
String extension = FilenameUtils.getExtension(path.toString());
InputStream fileStream = storageInterface.get(tenantService.resolveTenant(), path);
Optional<Charset> charset;
try {
@@ -1023,13 +1022,15 @@ public class ExecutionController {
throw new IllegalArgumentException("Unable to preview using encoding '" + encoding + "'");
}
FileRender fileRender = FileRenderBuilder.of(
extension,
fileStream,
charset,
maxRows == null ? this.initialPreviewRows : (maxRows > this.maxPreviewRows ? this.maxPreviewRows : maxRows)
);
try (InputStream fileStream = storageInterface.get(tenantService.resolveTenant(), path)){
FileRender fileRender = FileRenderBuilder.of(
extension,
fileStream,
charset,
maxRows == null ? this.initialPreviewRows : (maxRows > this.maxPreviewRows ? this.maxPreviewRows : maxRows)
);
return HttpResponse.ok(fileRender);
return HttpResponse.ok(fileRender);
}
}
}

View File

@@ -138,7 +138,9 @@ public class NamespaceFileController {
) throws IOException, URISyntaxException {
ensureWritableFile(path);
storageInterface.put(tenantService.resolveTenant(), toNamespacedStorageUri(namespace, path), new BufferedInputStream(fileContent.getInputStream()));
try(BufferedInputStream inputStream = new BufferedInputStream(fileContent.getInputStream())) {
storageInterface.put(tenantService.resolveTenant(), toNamespacedStorageUri(namespace, path), inputStream);
}
}
@ExecuteOn(TaskExecutors.IO)