Our state of the art Gantt chart


Post by Qwerty »

Hi there Bryntum,

I've spent some time implementing the PDF/PNG Export Feature for Gantt. Aside from some images, the export is working locally via Docker but I have some concerns about taking it to production.

My 1st question would be how to fulfil this requirement when the official demo fails to export (FF 94.0, Chromium 96.0)? If its a CORS issue, I'm not sure we can expect our users to change settings on their browsers.

Can the technologies used be run directly on the app or it has to be a network request? Any suggestions?


Post by alex.l »

There will be no CORS issues if you'll set up your export server on the same domain with your production application.
We used dev subdomain in our examples, that's why you may see warnings.

All the best,
Alex


Post by Qwerty »

Thanks for the quick reply Alex!
I see. I'll proceed testing that approach. I'm curious as to any other way around it? I was planning on releasing one export server per region, but if its per app I assume the resource drain would be multiplied.

Is there no idle resource drain since the workers destroy themselves? Are you aware of what the resource costs of this server are?


Post by alex.l »

Hi Qwerty,

Sorry, I am afraid it's out of scope of our support. We do not support server-side questions. I guess there is a way to create a proxy server and use params to redirect it further, and many other options, but actually it's not our side and we can't give any valuable advice here.
We do not limit you from using different URLs, but the only thing here is CORS policy that you should care about, same for all apps. https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

All the best,
Alex


Post by Maxim Gorkovsky »

Hello.

My 1st question would be how to fulfil this requirement when the official demo fails to export (FF 94.0, Chromium 96.0)? If its a CORS issue, I'm not sure we can expect our users to change settings on their browsers.

Our online demo was failing due to expired certificate on our export server. CORS is about loading resources to one origin from another and it is export server which has to load those. By default server loads resources to an empty page (without origin) so if your web server adds restrictive Access-Control-Allow-Origin header server will fail to render page properly. This is when clientUrl config comes handy: https://bryntum.com/docs/gantt/api/Gantt/feature/export/PdfExport#config-clientURL

I'll proceed testing that approach. I'm curious as to any other way around it? I was planning on releasing one export server per region, but if its per app I assume the resource drain would be multiplied

I believe there was a miscommunication there. It is not necessary to run export server and your app on the same domain. Export feature uses fetch to pass data to the export server so as long as you use same schema (http/https) it should be just fine because by default server does not put restrictive CORS header.
Using CORS you can restrict calling your server from a subset of domains. For example, if you have two apps https://foo.com and https://bar.com and you started you server like node ./src/server.js -c foo.com then only foo.com app will be able to run exports.

Is there no idle resource drain since the workers destroy themselves?

Normally workers (i.e. browser tabs) should be destroyed once export queue is empty. But sometimes puppeteer may fail and break server possible leaving tabs alive. It doesn't happen often and we were not able to narrow down this issue yet.
I should also note that we do not consider our server to be a production-grade solution and recommend to implement own server for robust execution. On the other hand we don't get any reports related to poor export server behavior.

Are you aware of what the resource costs of this server are?

Hard to tell. I'd go with chrome system requirements as a start: https://support.google.com/chrome/a/answer/7100626?hl=en
Then you can start increasing amount of workers (default is 5) while server is steady and you gain performance. Chrome likes RAM and max amount of workers is related to processor cores/threads count.


Post by Qwerty »

Thank you very much Maxim for the detailed reply.

I have been battling it out since then and have finally succeeded in setting up the export server (Docker) so that it exports a PDF on a production test. Unfortunately the output is plain text and I see varying export server log errors depending on the combination.

App: 'https://app.banana.com:443'
Export Server: 'https://export.banana.com:8082'

What combination of Gantt configuration do I need?
exportServer: 'https://export.banana.com:8082'
translateURLsToAbsolute: true (default, not a URL because I can't put any assets on the exportServer)
clientURL: ? (leave it, app with port, app without port, export server with port)

ERRORS
Just exportServer: 'Failed to load resource: net::ERR_FAILED', 'Access to font at ... from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

exportServer + clientURL 'https://app.banana.com:443':
'Failed to load resource: the server responded with a status of 401 (Unauthorized)'

exportServer + clientURL 'https://export.banana.com:8082': 'puppeteer_evaluation_script', 'Page 1/1 reports: Refused to apply inline style because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash..., or a nonce ('nonce-...') is required to enable inline execution. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback.'

I can't get clientURL to work so I've allowed wildcard cors origin for app and the export server's apache. Do I need to enable wildcard cors origin inside the Docker container? Where's the only place I actually need to allow CORS?


Post by Qwerty »

I got further today by setting exportServer as "https://export.banana.com:8082" and translateURLsToAbsolute to "https://export.banana.com:8082/resources" as per the server example 'README.md' (also setting up the server with the files and running it with --resources=PATH).

Via Firefox I can see the asset at "https://export.banana.com:8082/resources/peel.css". Why does it ignore my translateURLsToAbsolute only when its the path that should work?

"https://export.banana.com:8082/" = Failed to load resource...8082/peel.css
When correctly "https://export.banana.com:8082/resources" = Failed to load resource...8082/font.ttf
"https://export.banana.com:8082/zzz" = Failed to load resource...8082/zzz/peel.css
"https://export.banana.com:8082/resources/resources" = Failed to load resource...8082/resources/resources/peel.css


Post by Qwerty »

I see its correct when '/resources' which is why it fails about fonts. What I don't see is why it doesn't look for fonts in resources? Do I need to copy these asses to a different directory on the server?


Post by Maxim Gorkovsky »

Just exportServer: 'Failed to load resource: net::ERR_FAILED', 'Access to font at ... from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

It looks like CORS policy is blocking loading resources with CORS header to an empty page with no address (no origin). This is where clientURL should help.

exportServer + clientURL 'https://app.banana.com:443': 'Failed to load resource: the server responded with a status of 401 (Unauthorized)'

It looks like export server cannot get access to your app server due to authentication error: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 You can try using curl or other command line tool to try testing access from export server machine to your app server.

exportServer + clientURL 'https://export.banana.com:8082': 'puppeteer_evaluation_script', 'Page 1/1 reports: Refused to apply inline style because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash..., or a nonce ('nonce-...') is required to enable inline execution. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback.'

In this case you make puppeteer to navigate to export.banana.com and then put HTML that links resources from app.banana.com. And CSP headers coming from your app server forbid page from processing those resources. You can try adding export.banana.com:8082 to a list of allowed sources for CSP: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

Why does it ignore my translateURLsToAbsolute only when its the path that should work?
"https://export.banana.com:8082/" = Failed to load resource...8082/peel.css
When correctly "https://export.banana.com:8082/resources" = Failed to load resource...8082/font.ttf
"https://export.banana.com:8082/zzz" = Failed to load resource...8082/zzz/peel.css
"https://export.banana.com:8082/resources/resources" = Failed to load resource...8082/resources/resources/peel.css

translateURLsToAbsolute replaces origin with the provided string. For instance, if resource URL on the app page is app.banana.com:443/css/fonts/font.ttf it will be transformed to export.banana.com:8082/resources/css/fonts/font.ttf. So next thing to do would be to configure export server to provide that resource. Let's say you took your css folder and put it to special folder in home dir, then you should serve it like this:

cp - /path/to/css/dir ~/resourceroot/
node ./src/server.js -r ~/resourceroot

Although this error here is suspicious: "https://export.banana.com:8082/resources" = Failed to load resource...8082/font.ttf. It looks like font is loaded from global URL, like @font-face { src: url('/font.ttf') } or makes assumptions of the app path. Can you check if that's true?


Post by Qwerty »

Thank you very much for persisting Maxim. I have oncemore met partial success.

So far today I have been able to confirm your suspicion that CSS had @font-face with absolute paths. Making sure the urls in the resources uploaded to exportServer had the /resources/ prefix caused the queue to complete without error.

Going down the clientURL path I found removing an image from the schedule, it also completes without error. I suspect I'll be able to solve this via CSP.

The issue is that the downloaded PDF (in both cases) looks nothing like the page, when it was nearly identical in my local tests. Instead of a basic Gantt with a task and milestone, the PDF is mostly blank, 2nd page entirely. The 1st page hard to read in the top left corner are the column names and row content as black text. To the right of it is a red rectangle with black 'Today' text for the Today line. I thought reading the CSS would fix this. What could be the cause?


Post Reply