SoftwareBrothers / adminjs

AdminJS is an admin panel for apps written in node.js
https://adminjs.co
MIT License
8.06k stars 646 forks source link

Upload files in admin panel #95

Closed Nikhiladiga closed 4 years ago

Nikhiladiga commented 5 years ago

Is there no way to upload files like images in the admin panel?

wojtek-krysiak commented 5 years ago

Right now we don't have an out-of-the-box solution for uploading files. To achieve that you have to override custom field: check out https://softwarebrothers.github.io/admin-bro-dev/tutorial-04-customizing-resources.html

doubletuna commented 4 years ago

@wojtek-krysiak Hi there would it be possible for you to provide a code example for image upload? I've tried to override that by using a simple text input and uploading a base64 but it kinda destroys everything.. PayloadTooLargeError. it can be solved, using app.use(bodyParser.json({ limit: "50mb" })) but still problematic

I've managed to add quilljs based on your example, is it possible to customize it and allow to add an image through the wysiwyg?

cheers

DT

wojtek-krysiak commented 4 years ago

working on the upload plugin right now. It should be ready at the weekend.

doubletuna commented 4 years ago

@wojtek-krysiak awesome, cheers for that!

captainko commented 4 years ago

@wojtek-krysiak I can't wait for it xD.

doubletuna commented 4 years ago

@wojtek-krysiak Hello, could you please update regarding the new feature eta? just need to plan dev on my end :-) Cheers

DT

captainko commented 4 years ago

Hi @wojtek-krysiak can I use the plugin now?

doubletuna commented 4 years ago

Hi @wojtek-krysiak, did you get the chance to complete this feature?

cheers

DT

wojtek-krysiak commented 4 years ago

Sorry for the late response.

So - I have one bad and one good news.

The bad: Unfortunately, we decided not to build a full-featured upload plugin because it will require to handle different storages (AWS S3, local file system etc).

The good: That is why we added DropArea component and change a way of how data is sent from the frontend to the API (from application-json to multipart) so files can be handled easily. Everything is in beta and we are still testing it - so in order to try it out you have to install:

"admin-bro": "^1.5.0-beta.5",
"admin-bro-expressjs": "^0.4.0-beta.2",

and install express-formidable - since it is now a peerDependency for admin-bro-expressjs

This is am actual component code in typescript we use:

import React, { FC } from 'react';
import { unflatten } from 'flat';

import { BasePropertyProps, DropArea, PropertyInEdit } from 'admin-bro';

import { MIME_TYPES, MAX_SIZE } from '../../../photo/photo.constants';

const EditImageProperty: FC<BasePropertyProps> = ({ property, record, onChange }) => {
  const params = unflatten(record.params);

  const fileObject = params.filename ? {
    name: params.filename,
    size: params.size,
    type: params.mimeType,
    file: params.file,
  } : null;

  const onUpload = (files: FileList): void => {
    const newRecord = { ...record };
    const [file] = files;
    onChange({
      ...newRecord,
      params: {
        ...newRecord.params,
        file,
        filename: file.name,
        size: file.size,
        mimeType: file.type,
      },
    });
  };

  return (
    <PropertyInEdit property={property}>
      <DropArea
        fileObject={fileObject}
        onUpload={onUpload}
        propertyName={property.name}
        validate={{
          mimeTypes: MIME_TYPES,
          maxSize: MAX_SIZE,
        }}
      />
    </PropertyInEdit>
  );
};

export default EditImageProperty;

example validation consts:

export const MIME_TYPES = ['image/jpeg', 'image/gif', 'image/tiff', 'image/png'];
export const MAX_SIZE = 1024 * 5000; // 5 MB;

and after hook for new and edit actions:

const uploadFile = async (
    response: NewActionResponse | EditActionResponse,
    request,
    context,
  ): Promise<NewActionResponse> => {
    const { record } = context;
    const { payload } = request;
    if (record.isValid() && payload?.file) {
      const { file } = payload;

      // here is the logic for uploading to S3 (in our case) - but you also can write this to
      // hdd or whatever else... file is a formidable object
      const photo = await service.findAndUpdateFileInfo(+record.id(), file);

      return {
        ...response,
        record: new BaseRecord(photo, context.resource).toJSON(context.currentAdmin),
      };
    }
    return response;
  };

file is a formidable object which has this interface: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/formidable/index.d.ts#L47

path is there the most important property - it is the path to tmp file created by formidable.

After more tests I will publish this as a stable release and add documentation.

If you have any issues - let me know.

wojtek-krysiak commented 4 years ago

and don't forget that in delete hook you will have to remove the file :)

doubletuna commented 4 years ago

@wojtek-krysiak cheers for that! Will give it a try

williamhqs commented 4 years ago

@wojtek-krysiak thanks for this great work!

I just start to use it, but I come across a few errors. Here is my error:

import React, { FC } from 'react';

Module "xxx/xxx/my-admin-app/node_moudles/react` has no exported memeber 'FC'

Screenshot 2019-12-11 at 5 23 55 PM Screenshot 2019-12-11 at 5 23 41 PM

Here are my steps:

  1. Create the project, and
    yarn add admin-bro@1.5.0-beta.5
    yarn add admin-bro-expressjs@0.4.0-beta.2
    yarn add express-formidable
  2. Create a new action and add component
const adminBro = new AdminBro({
  resources: [User, Admin, 
    { resource: WireFrame, options: { 
        actions: {
            show: {
                label: 'Show me that',
                icon: 'fas fa-eye',
                // isVisible: (resource, record) => record.param('email') !== '',
                isVisible: true
              },
              newAction: {
                actionType: ['record'],
                label: 'Publish',
                icon: 'fas fa-eye',
                isVisible: true,
                handler: async (request, response, data) => {
                    return {
                      record: data.record.toJSON()
                    }
                  },
                component: AdminBro.bundle('./components/upload_action_compontent')
              },
        } ,
        listProperties: ['name', 'version']} 
    }],
  rootPath: '/admin',

})
  1. Then added the upload_action_compontent.ts file there with your code example above.

See the errors. Not sure the file format should be .ts, or anything I missed, thanks a lot!

wojtek-krysiak commented 4 years ago

Hi, the example was written in typescript :) - FC is a type.

if you use raw JS in your project this code has to be transpiled. If you don't like to do this manually you can use http://www.typescriptlang.org/play/index.html.

The other solution would be to leave typescript on the components but you will have to install @types/react:

yarn add --dev @types/react
dmitryLagoda commented 4 years ago

Wojciech, there is the typo in the drop-area.tsx file. line 253: <h1>Dop Here</h1>

MariaRoblesSpin commented 4 years ago

@wojtek-krysiak than you in advance for your awesome admin panel! I have an issue uploading images with your comented component in the live demo code example. The component put inside the record params the file info with the upload function.


    const newRecord = {...record}
    const [file] = files
    console.log('valor de record después del onChange', file)
    onChange({
      ...newRecord,
      params: {
        ...newRecord.params,
        [`${property.name}.file`]: file,
        [`${property.name}.name`]: file.name,
        [`${property.name}.size`]: file.size,
        [`${property.name}.type`]: file.type,
      }
    })
    event.preventDefault()
  }

but when you get the payload inthe upload.js uploadphoto.file is always empty and I don't now why... any suggestions? thank you!

dmitryLagoda commented 4 years ago

@wojtek-krysiak than you in advance for your awesome admin panel! I have an issue uploading images with your comented component in the live demo code example. The component put inside the record params the file info with the upload function.


    const newRecord = {...record}
    const [file] = files
    console.log('valor de record después del onChange', file)
    onChange({
      ...newRecord,
      params: {
        ...newRecord.params,
        [`${property.name}.file`]: file,
        [`${property.name}.name`]: file.name,
        [`${property.name}.size`]: file.size,
        [`${property.name}.type`]: file.type,
      }
    })
    event.preventDefault()
  }

but when you get the payload inthe upload.js uploadphoto.file is always empty and I don't now why... any suggestions? thank you!

take a look on the example app code. It's commented, but still it should work. https://github.com/SoftwareBrothers/admin-bro-example-app/blob/master/admin/components/upload-photo.tsx

MariaRoblesSpin commented 4 years ago

Hi, @dmitryLagoda I'm using this component too... Into the component I have the file info, but when it arrives to the file "upload.js" the file info is empty inside the payload... I put everything like the code example, and I got a file write into the folder that I want, but the file is empty, because the file info into the payload is empty. I don't now why! Have you use the example and does it work for you? Thank you!

dmitryLagoda commented 4 years ago

@MariaRoblesSpin these are my files, and file uploading work fine for me. Also, please ensure you use the latest AdminBro version, they fixed file uploading in 1.6.3 ImageUpload.jsx

import React, { useState } from 'react'
import { DropArea, PropertyInEdit, BasePropertyProps } from 'admin-bro'
import { unflatten } from 'flat'

const TJImageUpload = (props) => {
    const { property, record, onChange } = props

    const fileObject = unflatten(record.params)[property.name]

    const onUpload = (files) => {
        const newRecord = {...record}
        const [file] = files

        onChange({
            ...newRecord,

            params: {
                ...newRecord.params,
                file: file,
                name: file.name,
                size: file.size,
                type: file.type,
            }
        })
        event.preventDefault()
    }

    return (
        <PropertyInEdit property={property}>
            <DropArea
                fileObject={fileObject}
                onUpload={onUpload}
                propertyName={property.name}
            />
        </PropertyInEdit>
    )
}
export default TJImageUpload;

handler for uploaded image

actions: {
        new: {
            after: uploadImageHandler
        },
}
const uploadImageHandler = async (response, request, context) => {  
    const { record } = context;
    const { payload } = request;
    const recordData = flat.unflatten(payload);
    // console.log(recordData)

    if (!(record.isValid() && recordData && recordData.image && recordData.image.file)) {
        return response;
    }

    const { file } = recordData.image;

    console.log(file)
};
MariaRoblesSpin commented 4 years ago

@dmitryLagoda thank you very much! I have to download the latest version! What a silly thing!

webface commented 4 years ago
ReferenceError: flat is not defined
backend     |     at Object.uploadImageHandler [as after] (/app/adminbro.js:16:21)
backend     |     at ActionDecorator.handler (/app/node_modules/admin-bro/lib/backend/decorators/action-decorator.js:69:31)
backend     |     at processTicksAndRejections (internal/process/task_queues.js:97:5)
backend     |     at async handler (/app/node_modules/admin-bro-expressjs/plugin.js:56:22)

This is what I get from the above code

webface commented 4 years ago

made some progress. https://www.npmjs.com/package/flat

georgebsmith-tech commented 4 years ago

Is there a way i can upload images while working with express? And i'm not using any frame work, just the ejs templating engine

wojtek-krysiak commented 4 years ago

take a look at this screencast: https://www.youtube.com/watch?v=7WtKcFqJHho it shows you how to add file upload to AdminBro with expressjs

akashhardia commented 3 years ago

How do upload image & send that image (in email via sendgrid) from a custom resource? image

I dont see any option to do that as there is not record there, its just a custom page.

SamanNsr commented 3 years ago

How do upload multiple images in adminbro and save them?

nelsonpoon commented 3 years ago

How do upload multiple images in adminbro and save them?

I also try to modify for upload multiple files...

wojtek-krysiak commented 3 years ago

take a look at @admin-bro/upload feature

mohamed-nazmi commented 3 years ago

Using @admin-bro/upload, I was only able to upload single images. The file-picker that opens after clicking on the DropZone forces me to select one image only. Is there any guide to select multi images?

wojtek-krysiak commented 3 years ago

@mohamed-nazmi

https://github.com/SoftwareBrothers/admin-bro-upload/blob/beta/src/index.doc.md

this is the latest beta with multi upload - but you have tot have it installed with the latest beta of admin-bro

mohamed-nazmi commented 3 years ago

Although I have installed

And added the following upload feature:

features: [
    uploadFeature({
        provider: {
            aws: {
                bucket: config.s3.bucket,
                accessKeyId: config.s3.accessKeyId,
                secretAccessKey: config.s3.secretAccessKey,
                region: config.s3.region
            }
        },
        properties: {
            key: 'path',
            filePath: 'imagePaths'
        },
        validation: {
            mimeTypes: ['image/png', 'image/jpg', 'image/jpeg']
        },
        multiple: true
    })
]

But still the DropZone allows me to select only one image!

And when I try to customize uploading images in the old way, the array of images fails to be passed to the request's payload. Here is a sample of my customized React component:

import React, { FC } from 'react';
import { BasePropertyProps, Box, DropZone, Label, OnDropZoneChange } from 'admin-bro';

const Image: FC<BasePropertyProps> = (props) => {
    const { property, record, onChange } = props;

    const onUpload: OnDropZoneChange = (files: File[]) => {
        const newRecord = { ...record };
        onChange({
            ...newRecord,
            params: {
                ...newRecord.params,
                [property.name]: files
            }
        });
        event.preventDefault();
    };

    return (
        <Box>
            <Label>{property.label}</Label>
            <DropZone
                multiple
                onChange={onUpload}
                validate={{ mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'] }}/>
        </Box>
    );
};

export default Image;
wojtek-krysiak commented 3 years ago

latest upload is "@admin-bro/upload": "^1.2.0-beta.3",

mohamed-nazmi commented 3 years ago

Using

And with the following code:

features: [
    uploadFeature({
        provider: {
            aws: {
                bucket: config.s3.bucket,
                accessKeyId: config.s3.accessKeyId,
                secretAccessKey: config.s3.secretAccessKey,
                region: config.s3.region
            }
        },
        properties: {
            key: 'keys',
            filePath: 'imagePaths'
        },
        validation: {
            mimeTypes: ['image/png', 'image/jpg', 'image/jpeg']
        },
        multiple: true
    })
]

I get the following error:

Error: You cannot upload file for not persisted record. Save record first
    at Object.exports.buildRemotePath (C:\Users\Mohamed\Desktop\Self\Identity\Projects\alexengsyndicate-backend\node_modules\@admin-bro\upload\build\features\upload-file\utils\build-remote-path.js:22:15)
    at Promise.all.uploadedFiles.map (C:\Users\Mohamed\Desktop\Self\Identity\Projects\alexengsyndicate-backend\node_modules\@admin-bro\upload\build\features\upload-file\factories\update-record-factory.js:36:53)
    at Array.map (<anonymous>)
    at updateRecord (C:\Users\Mohamed\Desktop\Self\Identity\Projects\alexengsyndicate-backend\node_modules\@admin-bro\upload\build\features\upload-file\factories\update-record-factory.js:35:62)
    at prevPromise.then.modifiedResponse (C:\Users\Mohamed\Desktop\Self\Identity\Projects\alexengsyndicate-backend\node_modules\admin-bro\lib\backend\decorators\action\action-decorator.js:147:99)
    at process._tickCallback (internal/process/next_tick.js:68:7)

Can you please give a full example for a multiple upload?

wojtek-krysiak commented 3 years ago

do you have some db validations - maybe they fail - like required name field or whatever????

  1. am example is here: https://github.com/SoftwareBrothers/admin-bro-upload/blob/beta/example-app/src/multi/multi.entity.ts
mohamed-nazmi commented 3 years ago

No, the error is still found even when I remove all model validations. Anyway, my model is here:

const NewsSchema: Schema = new Schema({
    title: {
        type: String,
        required: true
    },
    content: {
        type: String,
        required: true
    },
    keys: [{
        type: String
    }],
    imagePaths: [{
        type: String
    }]
}, { timestamps: true });

Do the "key" and "filePath" in the properties of the upload feature should have values that are attribute names in the model?

wojtek-krysiak commented 3 years ago

yes - in your case keys and filePaths. But it, you know it is still in beta, and we tested it a lot on typeorm and sequelize, but haven't on mongoose. There was an error in mongoose where it didn't update a record after update and this might cause the error. Can you bump up it to the latest version?

also, the error is triggered here: https://github.com/SoftwareBrothers/admin-bro-upload/blob/a68bfa23f85d1d6ecfd22e225d76191237d0b88e/src/features/upload-file/factories/update-record-factory.ts#L67-L67

it is thrown because reocrd.id() returns null for some reason. Can you debug why this happens (you can put console.log(record.params)

Nico205 commented 3 years ago

Hey @wojtek-krysiak ,

I've got the same error using NestJS with TypeORM and Admin-Bro.

"@admin-bro/typeorm": "^1.4.0-beta.1",
"@admin-bro/upload": "^1.2.0-beta.8",
"admin-bro": "^3.3.0-beta.33",

The model looks like this:

@Entity({
  name: 'product'
})
export class Product extends BaseEntity {

  @ObjectIdColumn()
  _id: ObjectID

  @Column()
  keys: string[]

  @Column()
  imagePaths: string[]

}

The output of console.log(record.params) is:

{ '_id._bsontype': 'ObjectID',
  '_id.id': <Buffer 5f 96 bb de b7 72 6d 17 92 50 66 72> }

Adding an additional @PrimaryColumn() to the MongoDB Entity doesn't help.

Are there any news on this bug?

Friendly Regards Nico :)

wojtek-krysiak commented 3 years ago

@Nico205 this is a @admin-bro/typeorm issue related to mongodb - can you post this error in https://github.com/SoftwareBrothers/admin-bro-typeorm?

Roeigr7 commented 3 years ago

so custom componenet dont work for me "....undefined"... so i try to do uploadFeature but why its not give me a dropzone fieldd ?

`const options = { image: { options: { listProperties: ["image", "mimeType"], },

    features: [
        uploadFeature({
            provider: {
                aws: {
                    accessKeyId: process.env.S3_ACCESS_KEY,
                    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
                    region: "EU (London) eu-west-2",
                    bucket: "battttkol",
                },
            },
            properties: {
                key: "image", // to this db field feature will safe S3 key,
                mimeType: "mimeType", // this property is important because allows to have previews,
            },
            validation: {
                mimeTypes: ["image/png", "image/jpg", "image/jpeg"],
            },
            multiple: true,
        }),
    ],
},

}; `

GithubMuler commented 1 year ago

component1 hello friends please help me I am fresh for node and adminbro I have developed front end application using ejs, and now I would like to write a code for backend using node js. which one is good ......admin panel templet or just coding from scratch? When I am using adminbro their is a lot of errors model1 for uploading images.

revskill10 commented 1 year ago

How to upload to cloudinary ? Do we have plan for it ? Thanks.