diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx index 55f46a064b5..e2c0d343437 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx @@ -21,6 +21,8 @@ export interface Props { className?: string; /** Button size */ size?: ComponentSize; + /** Show the file name */ + showFileName?: boolean; } export const FileUpload: FC = ({ diff --git a/public/app/features/storage/FolderView.tsx b/public/app/features/storage/FolderView.tsx index 02d5d7a6545..8c48090f7f7 100644 --- a/public/app/features/storage/FolderView.tsx +++ b/public/app/features/storage/FolderView.tsx @@ -5,18 +5,14 @@ import AutoSizer from 'react-virtualized-auto-sizer'; import { DataFrame, GrafanaTheme2 } from '@grafana/data'; import { Table, useStyles2 } from '@grafana/ui'; -import { UploadView } from './UploadView'; import { StorageView } from './types'; interface Props { listing: DataFrame; - path: string; - onPathChange: (p: string, view?: StorageView) => void; view: StorageView; - fileNames: string[]; } -export function FolderView({ listing, path, onPathChange, view, fileNames }: Props) { +export function FolderView({ listing, view }: Props) { const styles = useStyles2(getStyles); switch (view) { @@ -24,21 +20,6 @@ export function FolderView({ listing, path, onPathChange, view, fileNames }: Pro return
CONFIGURE?
; case StorageView.Perms: return
Permissions
; - case StorageView.Upload: - return ( - { - console.log('Uploaded: ' + path); - if (rsp.path) { - onPathChange(rsp.path); - } else { - onPathChange(path); // back to data - } - }} - fileNames={fileNames} - /> - ); } return ( diff --git a/public/app/features/storage/StoragePage.tsx b/public/app/features/storage/StoragePage.tsx index ca6dd94bd8f..2e7019ea92f 100644 --- a/public/app/features/storage/StoragePage.tsx +++ b/public/app/features/storage/StoragePage.tsx @@ -4,7 +4,7 @@ import { useAsync } from 'react-use'; import { DataFrame, GrafanaTheme2, isDataFrame, ValueLinkConfig } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; -import { useStyles2, IconName, Spinner, TabsBar, Tab, Button, HorizontalGroup, LinkButton } from '@grafana/ui'; +import { useStyles2, IconName, Spinner, TabsBar, Tab, Button, HorizontalGroup, LinkButton, Alert } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; @@ -18,6 +18,7 @@ import { ExportView } from './ExportView'; import { FileView } from './FileView'; import { FolderView } from './FolderView'; import { RootView } from './RootView'; +import { UploadButton } from './UploadButton'; import { getGrafanaStorage, filenameAlreadyExists } from './storage'; import { StorageView } from './types'; @@ -57,6 +58,7 @@ export default function StoragePage(props: Props) { }; const [isAddingNewFolder, setIsAddingNewFolder] = useState(false); + const [errorMessages, setErrorMessages] = useState([]); const listing = useAsync((): Promise => { return getGrafanaStorage() @@ -153,18 +155,27 @@ export default function StoragePage(props: Props) { opts.push({ what: StorageView.History, text: 'History' }); } - // Hardcode the uploadable folder :) - if (isFolder && path.startsWith('resources')) { - opts.push({ - what: StorageView.Upload, - text: 'Upload', - }); - } const canAddFolder = isFolder && path.startsWith('resources'); const canDelete = path.startsWith('resources/'); const canViewDashboard = path.startsWith('devenv/') && config.featureToggles.dashboardsFromStorage && (isFolder || path.endsWith('.json')); + const getErrorMessages = () => { + return ( +
+ + {errorMessages.map((error) => { + return
{error}
; + })} +
+
+ ); + }; + + const clearAlert = () => { + setErrorMessages([]); + }; + return (
@@ -175,7 +186,13 @@ export default function StoragePage(props: Props) { Dashboard )} - {canAddFolder && } + + {canAddFolder && ( + <> + + + + )} {canDelete && (
+ } + title={'This file already exists'} + confirmText={'Replace'} + onConfirm={onOverwriteConfirm} + onDismiss={onOverwriteDismiss} + /> + )} + + ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + uploadButton: css` + margin-right: ${theme.spacing(2)}; + `, +}); diff --git a/public/app/features/storage/UploadView.tsx b/public/app/features/storage/UploadView.tsx deleted file mode 100644 index d7f0a7ec457..00000000000 --- a/public/app/features/storage/UploadView.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { css } from '@emotion/css'; -import React, { useState } from 'react'; -import SVG from 'react-inlinesvg'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Alert, Button, ButtonGroup, Checkbox, Field, FileDropzone, useStyles2 } from '@grafana/ui'; - -import { filenameAlreadyExists, getGrafanaStorage } from './storage'; -import { UploadReponse } from './types'; - -interface Props { - folder: string; - onUpload: (rsp: UploadReponse) => void; - fileNames: string[]; -} - -interface ErrorResponse { - message: string; -} - -const FileDropzoneCustomChildren = ({ secondaryText = 'Drag and drop here or browse' }) => { - const styles = useStyles2(getStyles); - - return ( -
- {secondaryText} -
- ); -}; - -export const UploadView = ({ folder, onUpload, fileNames }: Props) => { - const [file, setFile] = useState(undefined); - - const styles = useStyles2(getStyles); - - const [error, setError] = useState({ message: '' }); - const [overwriteExistingFile, setOverwriteExistingFile] = useState(false); - - const Preview = () => { - if (!file) { - return <>; - } - const isImage = file.type?.startsWith('image/'); - const isSvg = file.name?.endsWith('.svg'); - - const src = URL.createObjectURL(file); - return ( - -
- {isSvg && } - {isImage && !isSvg && } -
-
- ); - }; - - const doUpload = async () => { - if (!file) { - setError({ message: 'please select a file' }); - return; - } - - const rsp = await getGrafanaStorage().upload(folder, file, overwriteExistingFile); - if (rsp.status !== 200) { - setError(rsp); - } else { - onUpload(rsp); - } - }; - - const filenameExists = file ? filenameAlreadyExists(file.name, fileNames) : false; - const isUploadDisabled = !file || (filenameExists && !overwriteExistingFile); - - return ( -
- { - setFile(undefined); - }} - options={{ - accept: { 'image/*': ['.jpg', '.jpeg', '.png', '.gif', '.webp'] }, - multiple: false, - onDrop: (acceptedFiles: File[]) => { - setFile(acceptedFiles[0]); - }, - }} - > - {error.message !== '' ?

{error.message}

: Boolean(file) ? : } -
- - {file && filenameExists && ( -
- - setOverwriteExistingFile(!overwriteExistingFile)} - label="Overwrite existing file" - /> - -
- )} - - - - -
- ); -}; - -const getStyles = (theme: GrafanaTheme2) => ({ - resourcePickerPopover: css` - border-radius: ${theme.shape.borderRadius()}; - box-shadow: ${theme.shadows.z3}; - background: ${theme.colors.background.primary}; - border: 1px solid ${theme.colors.border.medium}; - `, - resourcePickerPopoverContent: css` - width: 315px; - font-size: ${theme.typography.bodySmall.fontSize}; - min-height: 184px; - padding: ${theme.spacing(1)}; - display: flex; - flex-direction: column; - `, - button: css` - margin: 12px 20px 5px; - `, - iconPreview: css` - width: 238px; - height: 198px; - border: 1px solid ${theme.colors.border.medium}; - display: flex; - align-items: center; - justify-content: center; - `, - img: css` - width: 147px; - height: 147px; - fill: ${theme.colors.text.primary}; - `, - iconWrapper: css` - display: flex; - flex-direction: column; - align-items: center; - `, - small: css` - color: ${theme.colors.text.secondary}; - margin-bottom: ${theme.spacing(2)}; - `, - alert: css` - padding-top: 10px; - `, -}); diff --git a/public/app/features/storage/types.ts b/public/app/features/storage/types.ts index 9a729dee3b9..120da9f7dec 100644 --- a/public/app/features/storage/types.ts +++ b/public/app/features/storage/types.ts @@ -2,7 +2,6 @@ export enum StorageView { Data = 'data', Config = 'config', Perms = 'perms', - Upload = 'upload', Export = 'export', History = 'history', AddRoot = 'add',