/* Copyright (C) 2019 Cloudbase Solutions SRL This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import React from 'react' import { observer } from 'mobx-react' import styled, { css } from 'styled-components' import InfoIcon from '@src/components/ui/InfoIcon' import { Close as InputClose } from '@src/components/ui/TextInput' import { Image as InstanceImage } from '@src/components/modules/WizardModule/WizardInstances' import StatusIcon from '@src/components/ui/StatusComponents/StatusIcon' import { ThemePalette, ThemeProps } from '@src/components/Theme' import FileUtils from '@src/utils/FileUtils' import type { Instance, InstanceScript } from '@src/@types/Instance' import { UserScriptData } from '@src/@types/MainItem' import DomUtils from '@src/utils/DomUtils' import scriptItemImage from './images/script-item.svg' const Wrapper = styled.div` width: 100%; display: flex; overflow: auto; flex-direction: column; min-height: 0; ` const Group = styled.div` display: flex; flex-direction: column; width: 100%; margin-bottom: 32px; &:last-child { margin-bottom: 0; } ` const Heading = styled.div` margin-bottom: 16px; font-size: ${props => (props.layout === 'modal' ? '16px' : '24px')}; font-weight: ${props => (props.layout === 'modal' ? ThemeProps.fontWeights.medium : ThemeProps.fontWeights.light)}; display: flex; ` const InfoIconStyled = styled(InfoIcon)` margin-top: ${props => (props.layout === 'modal' ? '1px' : '5px')}; margin-left: 8px; ` const Scripts = styled.div` width: 100%; display: flex; flex-direction: column; ` const Script = styled.div` width: 100%; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; border-top: 1px solid ${ThemePalette.grayscale[1]}; padding: 8px 0; &:last-child { border-bottom: 1px solid ${ThemePalette.grayscale[1]}; } ` const Name = styled.div` display: flex; align-items: center; ` const OsImage = styled.div` ${ThemeProps.exactSize('48px')} background: url('${scriptItemImage}') center no-repeat; ` const NameLabel = styled.div` display: flex; flex-direction: column; margin-left: 16px; ` const NameLabelTitle = styled.div` font-size: 16px; word-break: break-word; ` const NameLabelSubtitle = styled.div` font-size: 12px; color: ${ThemePalette.grayscale[5]}; margin-top: 1px; word-break: break-word; ` const LinkButton = styled.div` color: ${ThemePalette.primary}; flex-shrink: 0; margin: 0 8px 0 16px; cursor: pointer; :hover { text-decoration: underline; } ` const UploadedScript = styled.div` display: flex; position: relative; ` const UploadedScriptFileName = styled.div` max-width: 124px; text-overflow: ellipsis; overflow: hidden; margin-right: 32px; white-space: nowrap; ` const InputCloseStyled = styled(InputClose)` top: 0px; ` const FakeFileInput = styled.input` position: absolute; opacity: 0; top: -99999px; ` const ScriptDataActions = styled.div` display: flex; margin-left: -8px; margin-top: 8px; > div { margin-left: 8px; } ` const ScriptDataAction = styled.div<{ red?: boolean, disabled?: boolean }>` color: ${props => (props.red ? ThemePalette.alert : ThemePalette.primary)}; cursor: pointer; ${props => (props.disabled ? css` opacity: 0.6; cursor: default; ` : '')} font-size: 12px; ` type Props = { instances: Instance[], uploadedScripts: InstanceScript[], removedScripts: InstanceScript[], layout?: 'modal' | 'page', loadingInstances?: boolean, userScriptData: UserScriptData | null | undefined style?: React.CSSProperties onScriptUpload: (instanceScript: InstanceScript) => void, onCancelScript: (global: 'windows' | 'linux' | null, instanceName: string | null) => void, onScrollableRef?: (ref: HTMLElement) => void, scrollableRef?: (r: HTMLElement) => void onScriptDataRemove: (script: InstanceScript) => void } type FileInputRefs = { [prop: string]: { inputRef: HTMLInputElement, } } @observer class WizardScripts extends React.Component { fileInputRefs: FileInputRefs = {} async handleFileUpload( files: FileList | null, global: 'windows' | 'linux' | null, instanceId: string | null, ) { if (!files || !files.length) { return } const fileName = files[0].name const scriptContent = await FileUtils.readTextFromFirstFile(files) this.props.onScriptUpload({ instanceId, global, fileName, scriptContent: scriptContent || '', }) } handleScriptDataDownload(scriptData: string, fileName: string) { DomUtils.download(scriptData, fileName) } renderScriptItem(opts: { global?: 'windows' | 'linux', instanceId?: string, title: string, subtitle?: string, }) { const { global, instanceId, title, subtitle, } = opts const uploadedScript = this.props.uploadedScripts.find( s => (s.instanceId ? s.instanceId === instanceId : s.global ? s.global === global : false), ) let scriptData: string | null | undefined = null if (global) { scriptData = this.props.userScriptData?.global?.[global] } else if (instanceId) { scriptData = this.props.userScriptData?.instances?.[instanceId] } const isRemoved: boolean = Boolean(this.props.removedScripts .find(s => (global ? s.global === global : s.instanceId === instanceId))) return ( ) } renderScriptGroup(group: 'global' | 'instance') { if (group === 'global') { return ( Global Scripts {this.renderScriptItem({ global: 'windows', title: 'Windows Script File' })} {this.renderScriptItem({ global: 'linux', title: 'Linux Script File' })} ) } if (this.props.instances.length === 0 && !this.props.loadingInstances) { return null } return ( Instance Scripts {!this.props.loadingInstances ? ( ) : null} {this.props.loadingInstances ? ( ) : null} {this.props.instances.map(instance => { const id = instance.instance_name || instance.id const title = instance.name const osLabel = instance.os_type ? instance.os_type === 'windows' ? 'Windows' : instance.os_type === 'linux' ? 'Linux' : instance.os_type : '' const osType = osLabel ? `${osLabel} OS | ` : '' const subtitle = `${osType}${instance.num_cpu} vCPU | ${instance.memory_mb} MB RAM` return this.renderScriptItem({ instanceId: id, title, subtitle }) })} ) } render() { return ( { if (this.props.onScrollableRef) { this.props.onScrollableRef(r) } }} > {this.renderScriptGroup('global')} {this.renderScriptGroup('instance')} ) } } export default WizardScripts