mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 00:01:34 +00:00
feat(frontend): Add HTML result rendering (#1160)
* feat(frontend): Add HTML result rendering * fix(frontend): Add reference for XSS * fix(frontend): Require approval only in apps * fix(frontend): Require approval in public apps
This commit is contained in:
@@ -7,11 +7,13 @@
|
||||
import autosize from 'svelte-autosize'
|
||||
|
||||
export let result: any
|
||||
export let requireHtmlApproval = false
|
||||
|
||||
let resultKind:
|
||||
| 'json'
|
||||
| 'table-col'
|
||||
| 'table-row'
|
||||
| 'html'
|
||||
| 'png'
|
||||
| 'file'
|
||||
| 'jpeg'
|
||||
@@ -19,9 +21,11 @@
|
||||
| 'error'
|
||||
| 'approval'
|
||||
| undefined
|
||||
|
||||
$: resultKind = inferResultKind(result)
|
||||
|
||||
let forceJson = false
|
||||
let enableHtml = false
|
||||
|
||||
function isRectangularArray(obj: any) {
|
||||
if (!Array.isArray(obj) || obj.length == 0) {
|
||||
@@ -53,6 +57,8 @@
|
||||
return 'table-row'
|
||||
} else if (keys.map((k) => Array.isArray(result[k])).reduce((a, b) => a && b)) {
|
||||
return 'table-col'
|
||||
} else if (keys.length == 1 && keys[0] == 'html') {
|
||||
return 'html'
|
||||
} else if (keys.length == 1 && keys[0] == 'png') {
|
||||
return 'png'
|
||||
} else if (keys.length == 1 && keys[0] == 'jpeg') {
|
||||
@@ -119,6 +125,34 @@
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'html'}
|
||||
<div class="h-full">
|
||||
{#if !requireHtmlApproval || enableHtml}
|
||||
{@html result.html}
|
||||
{:else}
|
||||
<div class="font-main text-sm">
|
||||
<div class="flex flex-col">
|
||||
<div class="bg-red-400 py-1 rounded-t text-white font-bold text-center">
|
||||
Warning
|
||||
</div>
|
||||
<p
|
||||
class="text-gray-600 mb-2 text-left border-2 !border-t-0 rounded-b border-red-400 overflow-auto p-1"
|
||||
>Rendering HTML can expose you to <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noreferrer" class="hover:underline">XSS attacks</a>.
|
||||
Only enable it if you trust the author of the script.
|
||||
</p>
|
||||
</div>
|
||||
<div class="center-center">
|
||||
<Button
|
||||
size="sm"
|
||||
color="dark"
|
||||
on:click={() => enableHtml = true}
|
||||
>
|
||||
Enable HTML rendering
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'png'}
|
||||
<div class="h-full"
|
||||
><img alt="png rendered" class="w-auto h-full" src="data:image/png;base64,{result.png}" />
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY } from '../types'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
@@ -16,6 +19,6 @@
|
||||
Results
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<DisplayResult {result} />
|
||||
<DisplayResult {result} {requireHtmlApproval} />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -179,4 +179,6 @@ export type AppEditorContext = {
|
||||
export type EditorMode = 'dnd' | 'preview'
|
||||
export type EditorBreakpoint = 'sm' | 'lg'
|
||||
|
||||
export const IS_APP_PUBLIC_CONTEXT_KEY = 'isAppPublicContext' as const
|
||||
|
||||
type ComponentID = string
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
import { browser } from '$app/environment'
|
||||
import { page } from '$app/stores'
|
||||
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
|
||||
import type { EditorBreakpoint } from '$lib/components/apps/types'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY, type EditorBreakpoint } from '$lib/components/apps/types'
|
||||
|
||||
import { Alert, Skeleton } from '$lib/components/common'
|
||||
import { WindmillIcon } from '$lib/components/icons'
|
||||
import { AppService, AppWithLastVersion, GlobalUserInfo, UserService } from '$lib/gen'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { setContext } from 'svelte'
|
||||
import github from 'svelte-highlight/styles/github'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
@@ -15,6 +16,8 @@
|
||||
let user: GlobalUserInfo | undefined = undefined
|
||||
let notExists = false
|
||||
|
||||
setContext(IS_APP_PUBLIC_CONTEXT_KEY, true)
|
||||
|
||||
async function loadApp() {
|
||||
try {
|
||||
app = await AppService.getPublicAppBySecret({
|
||||
|
||||
Reference in New Issue
Block a user