#280: Added app documentation viewer within Workflows
This commit is contained in:
@@ -2,7 +2,7 @@ module shuffle
|
||||
|
||||
go 1.13
|
||||
|
||||
replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
|
||||
//replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
|
||||
|
||||
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
||||
|
||||
@@ -22,7 +22,7 @@ require (
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect
|
||||
github.com/frikky/kin-openapi v0.39.0
|
||||
github.com/frikky/shuffle-shared v0.0.93
|
||||
github.com/frikky/shuffle-shared v0.0.95
|
||||
github.com/fsouza/go-dockerclient v1.7.2
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-git/go-billy/v5 v5.0.0
|
||||
|
||||
+43
-19
@@ -3667,25 +3667,6 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
|
||||
continue
|
||||
}
|
||||
|
||||
readmeNames := []string{"readme", "readme.md"}
|
||||
for _, readmeName := range readmeNames {
|
||||
readmePath := fmt.Sprintf("%s/%s", extra, readmeName)
|
||||
readmeInfo, err := fs.Open(readmePath)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed to read README path %s", readmePath)
|
||||
continue
|
||||
}
|
||||
|
||||
dockerfileData, err := ioutil.ReadAll(dockerfile)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed to read readme file at %s", readmePath)
|
||||
continue
|
||||
} else {
|
||||
log.Printf("[INFO] Found file with name %s", readmeName)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
combined := []byte{}
|
||||
combined = append(combined, appfileData...)
|
||||
combined = append(combined, appPythonData...)
|
||||
@@ -3703,6 +3684,49 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
|
||||
newName := workflowapp.Name
|
||||
newName = strings.ReplaceAll(newName, " ", "-")
|
||||
|
||||
readmeNames := []string{"README.md", "README", "readme", "readme.md", "README.MD"}
|
||||
for _, readmeName := range readmeNames {
|
||||
readmePath := fmt.Sprintf("%s%s", extra, readmeName)
|
||||
readmeInfo, err := fs.Open(readmePath)
|
||||
if err != nil {
|
||||
//log.Printf("[WARNING] Failed to read README path %s", readmePath)
|
||||
continue
|
||||
}
|
||||
|
||||
fileData, err := ioutil.ReadAll(readmeInfo)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed to read readme file at %s", readmePath)
|
||||
continue
|
||||
} else {
|
||||
workflowapp.Documentation = string(fileData)
|
||||
log.Printf("[INFO] Found %s (README) file of length %d for %s:%s", readmePath, len(workflowapp.Documentation), newName, workflowapp.AppVersion)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(workflowapp.Documentation) == 0 {
|
||||
for _, readmeName := range readmeNames {
|
||||
readmePath := fmt.Sprintf("%s../%s", extra, readmeName)
|
||||
readmeInfo, err := fs.Open(readmePath)
|
||||
if err != nil {
|
||||
//log.Printf("[WARNING] Failed to read README path %s", readmePath)
|
||||
continue
|
||||
}
|
||||
|
||||
fileData, err := ioutil.ReadAll(readmeInfo)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed to read readme file at %s", readmePath)
|
||||
continue
|
||||
} else {
|
||||
workflowapp.Documentation = string(fileData)
|
||||
log.Printf("[INFO] Found %s (README) file of length %d for %s:%s", readmePath, len(workflowapp.Documentation), newName, workflowapp.AppVersion)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workflowapp.ReferenceInfo.GithubUrl = fmt.Sprintf("https://github.com/frikky/shuffle-apps/tree/master/%s/%s", strings.ToLower(newName), workflowapp.AppVersion)
|
||||
|
||||
tags := []string{
|
||||
fmt.Sprintf("%s:%s_%s", baseDockerName, strings.ToLower(newName), workflowapp.AppVersion),
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
version: '3'
|
||||
services:
|
||||
frontend:
|
||||
#build: ./frontend
|
||||
build: ./frontend
|
||||
image: ghcr.io/frikky/shuffle-frontend:nightly
|
||||
container_name: shuffle-frontend
|
||||
hostname: shuffle-frontend
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
depends_on:
|
||||
- backend
|
||||
backend:
|
||||
#build: ./backend
|
||||
build: ./backend
|
||||
image: ghcr.io/frikky/shuffle-backend:nightly
|
||||
container_name: shuffle-backend
|
||||
hostname: ${BACKEND_HOSTNAME}
|
||||
|
||||
@@ -176,6 +176,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
//console.log(
|
||||
return (
|
||||
<div>
|
||||
<DialogTitle><div style={{color: "white"}}>Authentication for {selectedApp.name}</div></DialogTitle>
|
||||
<DialogContent>
|
||||
<span style={{}}>
|
||||
<b>Oauth2 requires a client ID and secret to authenticate. This is usually made in the remote system.</b>
|
||||
|
||||
@@ -9,7 +9,7 @@ import NestedMenuItem from "material-ui-nested-menu-item";
|
||||
//import NestedMenuItem from "./NestedMenu.jsx";
|
||||
|
||||
import {Popper, TextField, TextareaAutosize, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core';
|
||||
import {GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
|
||||
import {HelpOutline as HelpOutlineIcon, Description as DescriptionIcon, GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
@@ -1459,7 +1459,7 @@ const ParsedAction = (props) => {
|
||||
<div style={{display: "flex", minHeight: 40, marginBottom: 30}}>
|
||||
<div style={{flex: 1}}>
|
||||
<h3 style={{marginBottom: 5}}>{(selectedAction.app_name.charAt(0).toUpperCase()+selectedAction.app_name.substring(1)).replaceAll("_", " ")}</h3>
|
||||
<div style={{display: "flex",}}>
|
||||
<div style={{display: "flex", marginTop: 10, }}>
|
||||
<IconButton style={{marginTop: "auto", marginBottom: "auto", height: 30, paddingLeft: 0, paddingRight: 0}} onClick={() => {
|
||||
console.log("FIND EXAMPLE RESULTS FOR ", selectedAction)
|
||||
if (workflowExecutions.length > 0) {
|
||||
@@ -1488,15 +1488,20 @@ const ParsedAction = (props) => {
|
||||
<ArrowLeftIcon style={{color: "white"}}/>
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<span style={{}}>
|
||||
<Typography style={{marginTop: 5,}}><a rel="norefferer" href="https://shuffler.io/docs/workflows#nodes" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>What are actions?</a></Typography>
|
||||
{selectedAction.errors !== undefined && selectedAction.errors !== null && selectedAction.errors.length > 0 ?
|
||||
<div>
|
||||
Errors: {selectedAction.errors.join("\n")}
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</span>
|
||||
<IconButton style={{marginTop: "auto", marginBottom: "auto", height: 30, paddingLeft: 10, paddingRight: 0}} onClick={() => {
|
||||
setAuthenticationModalOpen(true)
|
||||
}}>
|
||||
<Tooltip color="primary" title="Read app docs" placement="top">
|
||||
<DescriptionIcon style={{color: "white"}} />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<IconButton style={{marginTop: "auto", marginBottom: "auto", height: 30, paddingLeft: 25, paddingRight: 0}} onClick={() => {}}>
|
||||
<a rel="norefferer" href="https://shuffler.io/docs/workflows#nodes" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
|
||||
<Tooltip color="primary" title="What are actions?" placement="top">
|
||||
<HelpOutlineIcon style={{color: "white"}}/>
|
||||
</Tooltip>
|
||||
</a>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{display: "flex", flexDirection: "column",}}>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Prompt } from 'react-router'
|
||||
import { useBeforeunload } from 'react-beforeunload';
|
||||
import ReactJson from 'react-json-view'
|
||||
import NestedMenuItem from "material-ui-nested-menu-item";
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
|
||||
import {TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core';
|
||||
@@ -336,6 +337,38 @@ const AngularWorkflow = (props) => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function OuterLink(props) {
|
||||
if (props.href.includes("http") || props.href.includes("mailto")) {
|
||||
return <a href={props.href} style={{color: "#f85a3e", textDecoration: "none"}}>{props.children}</a>
|
||||
}
|
||||
return <Link to={props.href} style={{color: "#f85a3e", textDecoration: "none"}}>{props.children}</Link>
|
||||
}
|
||||
|
||||
function Img(props) {
|
||||
return <img style={{maxWidth: "100%"}} alt={props.alt} src={props.src}/>
|
||||
}
|
||||
|
||||
function CodeHandler(props) {
|
||||
return (
|
||||
<pre style={{padding: 15, minWidth: "50%", maxWidth: "100%", backgroundColor: theme.palette.inputColor, overflowX: "auto", overflowY: "hidden",}}>
|
||||
<code>
|
||||
{props.value}
|
||||
</code>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
function Heading(props) {
|
||||
const element = React.createElement(`h${props.level}`, {style: {marginTop: 40}}, props.children)
|
||||
return (
|
||||
<Typography>
|
||||
{props.level !== 1 ? <Divider style={{width: "90%", marginTop: 40, backgroundColor: theme.palette.inputColor}} /> : null}
|
||||
{element}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
const generateApikey = () => {
|
||||
fetch(globalUrl+"/api/v1/generateapikey", {
|
||||
method: 'GET',
|
||||
@@ -8794,12 +8827,14 @@ const AngularWorkflow = (props) => {
|
||||
active: true,
|
||||
})
|
||||
|
||||
if (selectedApp.authentication === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (selectedApp.authentication.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0) {
|
||||
return null
|
||||
if (selectedApp.authentication === undefined || selectedApp.authentication.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0) {
|
||||
return (
|
||||
<DialogContent style={{textAlign: "center", marginTop: 50,}}>
|
||||
<Typography variant="h4">
|
||||
{selectedApp.name} does not require authentication
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
|
||||
authenticationOption.app.actions = []
|
||||
@@ -8883,6 +8918,7 @@ const AngularWorkflow = (props) => {
|
||||
//console.log(
|
||||
return (
|
||||
<div>
|
||||
<DialogTitle><div style={{color: "white"}}>Authentication for {selectedApp.name}</div></DialogTitle>
|
||||
<DialogContent>
|
||||
<a target="_blank" rel="norefferer" href="https://shuffler.io/docs/apps#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is app authentication?</a><div/>
|
||||
These are required fields for authenticating with {selectedApp.name}
|
||||
@@ -9044,7 +9080,7 @@ const AngularWorkflow = (props) => {
|
||||
|
||||
|
||||
// This whole part is redundant. Made it part of Arguments instead.
|
||||
//console.log("TYPE: ", authenticationType)
|
||||
console.log(selectedApp)
|
||||
const authenticationModal = authenticationModalOpen ?
|
||||
<Dialog
|
||||
open={authenticationModalOpen}
|
||||
@@ -9059,12 +9095,26 @@ const AngularWorkflow = (props) => {
|
||||
style: {
|
||||
backgroundColor: surfaceColor,
|
||||
color: "white",
|
||||
minWidth: 600,
|
||||
minWidth: 1100,
|
||||
minHeight: 700,
|
||||
maxHeight: 700,
|
||||
padding: 15,
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<IconButton style={{zIndex: 5000, position: "absolute", top: 14, right: 14, color: "grey"}} onClick={() => {
|
||||
<div style={{zIndex: 5000, position: "absolute", top: 20, right: 54, height: 50, width: 50,}}>
|
||||
{selectedApp.reference_info === undefined || selectedApp.reference_info === null || selectedApp.reference_info.github_url === undefined || selectedApp.reference_info.github_url === null || selectedApp.reference_info.github_url.length === 0 ?
|
||||
<a rel="norefferer" target="_blank" href={"https://github.com/frikky/shuffle-apps"} style={{textDecoration: "none", color: "#f86a3e"}}>
|
||||
<img alt={`Documentation image for ${selectedApp.name}`} src={selectedApp.large_image} style={{width: 30, height: 30, border: "2px solid rgba(255,255,255,0.6)", borderRadius: theme.palette.borderRadius,}} />
|
||||
</a>
|
||||
:
|
||||
<a rel="norefferer" target="_blank" href={selectedApp.reference_info.github_url} style={{textDecoration: "none", color: "#f86a3e"}}>
|
||||
<img alt={`Documentation image for ${selectedApp.name}`} src={selectedApp.large_image} style={{width: 30, height: 30, border: "2px solid rgba(255,255,255,0.6)", borderRadius: theme.palette.borderRadius,}} />
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<IconButton style={{zIndex: 5000, position: "absolute", top: 14, right: 18, color: "grey"}} onClick={() => {
|
||||
setAuthenticationModalOpen(false)
|
||||
if (configureWorkflowModalOpen) {
|
||||
setSelectedAction({})
|
||||
@@ -9072,12 +9122,56 @@ const AngularWorkflow = (props) => {
|
||||
}}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<DialogTitle><div style={{color: "white"}}>Authentication for {selectedApp.name}</div></DialogTitle>
|
||||
{authenticationType.type === "oauth2" ?
|
||||
<AuthenticationOauth2 saveWorkflow={saveWorkflow} selectedApp={selectedApp} workflow={workflow} selectedAction={selectedAction} authenticationType={authenticationType} getAppAuthentication={getAppAuthentication} appAuthentication={appAuthentication} setSelectedAction={setSelectedAction} setNewAppAuth={setNewAppAuth} setAuthenticationModalOpen={setAuthenticationModalOpen} />
|
||||
:
|
||||
<AuthenticationData app={selectedApp} />
|
||||
}
|
||||
<div style={{display: "flex", flexDirection: "row",}}>
|
||||
<div style={{flex: 1, padding: 0, minHeight: 650, maxHeight: 650, overflowY: "auto", overflowX: "hidden", }}>
|
||||
{authenticationType.type === "oauth2" ?
|
||||
<AuthenticationOauth2 saveWorkflow={saveWorkflow} selectedApp={selectedApp} workflow={workflow} selectedAction={selectedAction} authenticationType={authenticationType} getAppAuthentication={getAppAuthentication} appAuthentication={appAuthentication} setSelectedAction={setSelectedAction} setNewAppAuth={setNewAppAuth} setAuthenticationModalOpen={setAuthenticationModalOpen} />
|
||||
:
|
||||
<AuthenticationData app={selectedApp} />
|
||||
}
|
||||
</div>
|
||||
<div style={{flex: 2, borderLeft: `1px solid ${inputColor}`, padding: "70px 30px 30px 30px", maxHeight: 630, minHeight: 630, overflowY: "auto", overflowX: "hidden"}}>
|
||||
{selectedApp.documentation === undefined || selectedApp.documentation === null || selectedApp.documentation.length === 0 ?
|
||||
<span style={{textAlign: "center", }}>
|
||||
<Typography variant="h4">
|
||||
There is currently no documentation for this app.
|
||||
</Typography>
|
||||
<Typography variant="h6" style={{marginTop: 25}}>
|
||||
Want help with this app? <a rel="norefferer" target="_blank" href="https://discord.gg/B2CBzUm" style={{textDecoration: "none", color: "#f86a3e"}}>Join the Discord!</a>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h4" style={{marginTop: 50,}}>
|
||||
Want to help change this app?
|
||||
</Typography>
|
||||
{selectedApp.reference_info === undefined || selectedApp.reference_info === null || selectedApp.reference_info.github_url === undefined || selectedApp.reference_info.github_url === null || selectedApp.reference_info.github_url.length === 0 ?
|
||||
<span>
|
||||
<Typography variant="h6" style={{marginTop: 25}}>
|
||||
<a rel="norefferer" target="_blank" href={"https://github.com/frikky/shuffle-apps"} style={{textDecoration: "none", color: "#f86a3e"}}>Check it out on Github!</a>
|
||||
</Typography>
|
||||
</span>
|
||||
:
|
||||
<span>
|
||||
<Typography variant="h6" style={{marginTop: 25}}>
|
||||
<a rel="norefferer" target="_blank" href={selectedApp.reference_info.github_url} style={{textDecoration: "none", color: "#f86a3e"}}>Check it out on Github!</a>
|
||||
</Typography>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
:
|
||||
<ReactMarkdown
|
||||
id="markdown_wrapper"
|
||||
escapeHtml={true}
|
||||
source={selectedApp.documentation}
|
||||
renderers={{
|
||||
link: OuterLink,
|
||||
image: Img,
|
||||
code: CodeHandler,
|
||||
heading: Heading,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog> : null
|
||||
|
||||
//const loadedCheck = isLoaded && isLoggedIn && workflowDone ?
|
||||
|
||||
Reference in New Issue
Block a user