@silexlabs/silex 3.0.0-alpha.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.default +19 -0
- package/.silex.js +37 -0
- package/LICENSE +661 -0
- package/README.md +96 -0
- package/dist/client/client/api.d.ts +85 -0
- package/dist/client/client/assetUrl.d.ts +51 -0
- package/dist/client/client/config.d.ts +58 -0
- package/dist/client/client/events.d.ts +14 -0
- package/dist/client/client/expose.d.ts +8 -0
- package/dist/client/client/grapesjs/LoginDialog.d.ts +15 -0
- package/dist/client/client/grapesjs/PublicationManager.d.ts +72 -0
- package/dist/client/client/grapesjs/PublicationUi.d.ts +62 -0
- package/dist/client/client/grapesjs/blocks.d.ts +1 -0
- package/dist/client/client/grapesjs/breadcrumbs.d.ts +1 -0
- package/dist/client/client/grapesjs/footer.d.ts +2 -0
- package/dist/client/client/grapesjs/index.d.ts +5 -0
- package/dist/client/client/grapesjs/internal-links.d.ts +1 -0
- package/dist/client/client/grapesjs/new-page-dialog.d.ts +2 -0
- package/dist/client/client/grapesjs/openImport.d.ts +9 -0
- package/dist/client/client/grapesjs/page-panel.d.ts +3 -0
- package/dist/client/client/grapesjs/project-bar.d.ts +1 -0
- package/dist/client/client/grapesjs/rich-text.d.ts +1 -0
- package/dist/client/client/grapesjs/semantic.d.ts +1 -0
- package/dist/client/client/grapesjs/settings.d.ts +2 -0
- package/dist/client/client/grapesjs/storage.d.ts +1 -0
- package/dist/client/client/grapesjs/symbolDialogs.d.ts +3 -0
- package/dist/client/client/index.d.ts +5 -0
- package/dist/client/client/publication-transformers.d.ts +41 -0
- package/dist/client/client/utils.d.ts +2 -0
- package/dist/client/constants.d.ts +29 -0
- package/dist/client/css/admin.css +4491 -0
- package/dist/client/index.html +9 -0
- package/dist/client/js/main.js +321 -0
- package/dist/client/js/main.js.LICENSE.txt +24 -0
- package/dist/client/page.d.ts +2 -0
- package/dist/client/types.d.ts +334 -0
- package/dist/plugins/client/client/api.d.ts +85 -0
- package/dist/plugins/client/client/api.js +114 -0
- package/dist/plugins/client/client/api.js.map +1 -0
- package/dist/plugins/client/client/assetUrl.d.ts +51 -0
- package/dist/plugins/client/client/assetUrl.js +224 -0
- package/dist/plugins/client/client/assetUrl.js.map +1 -0
- package/dist/plugins/client/client/config.d.ts +58 -0
- package/dist/plugins/client/client/config.js +90 -0
- package/dist/plugins/client/client/config.js.map +1 -0
- package/dist/plugins/client/client/events.d.ts +14 -0
- package/dist/plugins/client/client/events.js +34 -0
- package/dist/plugins/client/client/events.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/LoginDialog.d.ts +15 -0
- package/dist/plugins/client/client/grapesjs/LoginDialog.js +195 -0
- package/dist/plugins/client/client/grapesjs/LoginDialog.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/PublicationManager.d.ts +72 -0
- package/dist/plugins/client/client/grapesjs/PublicationManager.js +356 -0
- package/dist/plugins/client/client/grapesjs/PublicationManager.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/PublicationUi.d.ts +62 -0
- package/dist/plugins/client/client/grapesjs/PublicationUi.js +336 -0
- package/dist/plugins/client/client/grapesjs/PublicationUi.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/blocks.d.ts +1 -0
- package/dist/plugins/client/client/grapesjs/blocks.js +37 -0
- package/dist/plugins/client/client/grapesjs/blocks.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/breadcrumbs.d.ts +1 -0
- package/dist/plugins/client/client/grapesjs/breadcrumbs.js +75 -0
- package/dist/plugins/client/client/grapesjs/breadcrumbs.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/footer.d.ts +2 -0
- package/dist/plugins/client/client/grapesjs/footer.js +28 -0
- package/dist/plugins/client/client/grapesjs/footer.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/index.d.ts +5 -0
- package/dist/plugins/client/client/grapesjs/index.js +313 -0
- package/dist/plugins/client/client/grapesjs/index.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/internal-links.d.ts +1 -0
- package/dist/plugins/client/client/grapesjs/internal-links.js +219 -0
- package/dist/plugins/client/client/grapesjs/internal-links.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/new-page-dialog.d.ts +2 -0
- package/dist/plugins/client/client/grapesjs/new-page-dialog.js +83 -0
- package/dist/plugins/client/client/grapesjs/new-page-dialog.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/openImport.d.ts +9 -0
- package/dist/plugins/client/client/grapesjs/openImport.js +74 -0
- package/dist/plugins/client/client/grapesjs/openImport.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/page-panel.d.ts +3 -0
- package/dist/plugins/client/client/grapesjs/page-panel.js +113 -0
- package/dist/plugins/client/client/grapesjs/page-panel.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/project-bar.d.ts +1 -0
- package/dist/plugins/client/client/grapesjs/project-bar.js +104 -0
- package/dist/plugins/client/client/grapesjs/project-bar.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/rich-text.d.ts +1 -0
- package/dist/plugins/client/client/grapesjs/rich-text.js +31 -0
- package/dist/plugins/client/client/grapesjs/rich-text.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/semantic.d.ts +1 -0
- package/dist/plugins/client/client/grapesjs/semantic.js +110 -0
- package/dist/plugins/client/client/grapesjs/semantic.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/settings.d.ts +2 -0
- package/dist/plugins/client/client/grapesjs/settings.js +198 -0
- package/dist/plugins/client/client/grapesjs/settings.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/storage.d.ts +1 -0
- package/dist/plugins/client/client/grapesjs/storage.js +126 -0
- package/dist/plugins/client/client/grapesjs/storage.js.map +1 -0
- package/dist/plugins/client/client/grapesjs/symbolDialogs.d.ts +3 -0
- package/dist/plugins/client/client/grapesjs/symbolDialogs.js +36 -0
- package/dist/plugins/client/client/grapesjs/symbolDialogs.js.map +1 -0
- package/dist/plugins/client/client/publication-transformers.d.ts +41 -0
- package/dist/plugins/client/client/publication-transformers.js +238 -0
- package/dist/plugins/client/client/publication-transformers.js.map +1 -0
- package/dist/plugins/client/client/utils.d.ts +2 -0
- package/dist/plugins/client/client/utils.js +25 -0
- package/dist/plugins/client/client/utils.js.map +1 -0
- package/dist/plugins/client/constants.d.ts +29 -0
- package/dist/plugins/client/constants.js +46 -0
- package/dist/plugins/client/constants.js.map +1 -0
- package/dist/plugins/client/page.d.ts +2 -0
- package/dist/plugins/client/page.js +31 -0
- package/dist/plugins/client/page.js.map +1 -0
- package/dist/plugins/client/plugins/client/11ty.d.ts +17 -0
- package/dist/plugins/client/plugins/client/11ty.js +86 -0
- package/dist/plugins/client/plugins/client/11ty.js.map +1 -0
- package/dist/plugins/client/plugins/client/publicationRenderer.d.ts +2 -0
- package/dist/plugins/client/plugins/client/publicationRenderer.js +170 -0
- package/dist/plugins/client/plugins/client/publicationRenderer.js.map +1 -0
- package/dist/plugins/client/plugins/client/template.d.ts +2 -0
- package/dist/plugins/client/plugins/client/template.js +183 -0
- package/dist/plugins/client/plugins/client/template.js.map +1 -0
- package/dist/plugins/client/plugins/client/website-info.d.ts +1 -0
- package/dist/plugins/client/plugins/client/website-info.js +72 -0
- package/dist/plugins/client/plugins/client/website-info.js.map +1 -0
- package/dist/plugins/client/types.d.ts +334 -0
- package/dist/plugins/client/types.js +74 -0
- package/dist/plugins/client/types.js.map +1 -0
- package/dist/plugins/server/constants.d.ts +29 -0
- package/dist/plugins/server/constants.js +49 -0
- package/dist/plugins/server/constants.js.map +1 -0
- package/dist/plugins/server/plugins/server/AuthPlugin.d.ts +1 -0
- package/dist/plugins/server/plugins/server/AuthPlugin.js +40 -0
- package/dist/plugins/server/plugins/server/AuthPlugin.js.map +1 -0
- package/dist/plugins/server/plugins/server/Cache.d.ts +2 -0
- package/dist/plugins/server/plugins/server/Cache.js +32 -0
- package/dist/plugins/server/plugins/server/Cache.js.map +1 -0
- package/dist/plugins/server/plugins/server/DownloadConnector.d.ts +29 -0
- package/dist/plugins/server/plugins/server/DownloadConnector.js +129 -0
- package/dist/plugins/server/plugins/server/DownloadConnector.js.map +1 -0
- package/dist/plugins/server/plugins/server/FtpConnector.d.ts +86 -0
- package/dist/plugins/server/plugins/server/FtpConnector.js +526 -0
- package/dist/plugins/server/plugins/server/FtpConnector.js.map +1 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.d.ts +87 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.js +564 -0
- package/dist/plugins/server/plugins/server/GitlabConnector.js.map +1 -0
- package/dist/plugins/server/plugins/server/PublicationOptimizer.d.ts +0 -0
- package/dist/plugins/server/plugins/server/PublicationOptimizer.js +14 -0
- package/dist/plugins/server/plugins/server/PublicationOptimizer.js.map +1 -0
- package/dist/plugins/server/plugins/server/SslPlugin.d.ts +1 -0
- package/dist/plugins/server/plugins/server/SslPlugin.js +86 -0
- package/dist/plugins/server/plugins/server/SslPlugin.js.map +1 -0
- package/dist/plugins/server/plugins/server/StaticPlugin.d.ts +1 -0
- package/dist/plugins/server/plugins/server/StaticPlugin.js +78 -0
- package/dist/plugins/server/plugins/server/StaticPlugin.js.map +1 -0
- package/dist/plugins/server/server/config.d.ts +59 -0
- package/dist/plugins/server/server/config.js +181 -0
- package/dist/plugins/server/server/config.js.map +1 -0
- package/dist/plugins/server/server/connectors/FsHosting.d.ts +13 -0
- package/dist/plugins/server/server/connectors/FsHosting.js +54 -0
- package/dist/plugins/server/server/connectors/FsHosting.js.map +1 -0
- package/dist/plugins/server/server/connectors/FsStorage.d.ts +43 -0
- package/dist/plugins/server/server/connectors/FsStorage.js +257 -0
- package/dist/plugins/server/server/connectors/FsStorage.js.map +1 -0
- package/dist/plugins/server/server/connectors/connectors.d.ts +87 -0
- package/dist/plugins/server/server/connectors/connectors.js +127 -0
- package/dist/plugins/server/server/connectors/connectors.js.map +1 -0
- package/dist/plugins/server/server/events.d.ts +26 -0
- package/dist/plugins/server/server/events.js +31 -0
- package/dist/plugins/server/server/events.js.map +1 -0
- package/dist/plugins/server/server/jobs.d.ts +12 -0
- package/dist/plugins/server/server/jobs.js +77 -0
- package/dist/plugins/server/server/jobs.js.map +1 -0
- package/dist/plugins/server/server/utils/validation.d.ts +7 -0
- package/dist/plugins/server/server/utils/validation.js +38 -0
- package/dist/plugins/server/server/utils/validation.js.map +1 -0
- package/dist/plugins/server/types.d.ts +334 -0
- package/dist/plugins/server/types.js +78 -0
- package/dist/plugins/server/types.js.map +1 -0
- package/dist/server/constants.js +29 -0
- package/dist/server/plugins/server/Cache.js +15 -0
- package/dist/server/server/api/api.js +20 -0
- package/dist/server/server/api/connectorApi.js +216 -0
- package/dist/server/server/api/publicationApi.js +108 -0
- package/dist/server/server/api/websiteApi.js +296 -0
- package/dist/server/server/cli.js +68 -0
- package/dist/server/server/config.js +120 -0
- package/dist/server/server/connectors/FsHosting.js +35 -0
- package/dist/server/server/connectors/FsStorage.js +225 -0
- package/dist/server/server/connectors/connectors.js +89 -0
- package/dist/server/server/events.js +14 -0
- package/dist/server/server/expose.js +27 -0
- package/dist/server/server/express.js +47 -0
- package/dist/server/server/index.js +90 -0
- package/dist/server/server/jobs.js +48 -0
- package/dist/server/server/utils/validation.js +15 -0
- package/dist/server/types.js +34 -0
- package/package.json +209 -0
- package/public/assets/container.png +0 -0
- package/public/assets/favicon.png +0 -0
- package/public/assets/logo-silex-small.png +0 -0
- package/public/robots.txt +2 -0
- package/public/silex.js +24 -0
- package/src/css/_mixins.scss +8 -0
- package/src/css/_variables.scss +33 -0
- package/src/css/dialog.scss +41 -0
- package/src/css/footer.scss +12 -0
- package/src/css/grapesjs-light.scss +180 -0
- package/src/css/grapesjs.scss +152 -0
- package/src/css/index.scss +32 -0
- package/src/css/light.scss +7 -0
- package/src/css/loader.scss +12 -0
- package/src/css/page-panel.scss +65 -0
- package/src/css/project-bar.scss +102 -0
- package/src/css/silex.scss +189 -0
- package/src/css/website-info.scss +14 -0
- package/src/html/debug.pug +8 -0
- package/src/html/index.pug +39 -0
- package/src/html/templates/includes.pug +1 -0
- package/src/html/templates/workspace.pug +2 -0
- package/src/ts/client/api.ts +144 -0
- package/src/ts/client/assetUrl.test.ts +125 -0
- package/src/ts/client/assetUrl.ts +235 -0
- package/src/ts/client/config.ts +105 -0
- package/src/ts/client/events.ts +34 -0
- package/src/ts/client/expose.ts +30 -0
- package/src/ts/client/grapesjs/LoginDialog.ts +204 -0
- package/src/ts/client/grapesjs/PublicationManager.ts +370 -0
- package/src/ts/client/grapesjs/PublicationUi.ts +342 -0
- package/src/ts/client/grapesjs/blocks.ts +37 -0
- package/src/ts/client/grapesjs/breadcrumbs.ts +79 -0
- package/src/ts/client/grapesjs/eleventy.ts +47 -0
- package/src/ts/client/grapesjs/footer.ts +27 -0
- package/src/ts/client/grapesjs/index.ts +335 -0
- package/src/ts/client/grapesjs/internal-links.test.ts +105 -0
- package/src/ts/client/grapesjs/internal-links.ts +225 -0
- package/src/ts/client/grapesjs/new-page-dialog.ts +90 -0
- package/src/ts/client/grapesjs/openImport.ts +81 -0
- package/src/ts/client/grapesjs/page-panel.ts +118 -0
- package/src/ts/client/grapesjs/project-bar.ts +107 -0
- package/src/ts/client/grapesjs/rich-text.ts +33 -0
- package/src/ts/client/grapesjs/semantic.ts +114 -0
- package/src/ts/client/grapesjs/settings.ts +208 -0
- package/src/ts/client/grapesjs/storage.ts +121 -0
- package/src/ts/client/grapesjs/symbolDialogs.ts +38 -0
- package/src/ts/client/index.ts +98 -0
- package/src/ts/client/publication-transformers.test.ts +199 -0
- package/src/ts/client/publication-transformers.ts +263 -0
- package/src/ts/client/utils.ts +28 -0
- package/src/ts/constants.ts +52 -0
- package/src/ts/externs.ts +18 -0
- package/src/ts/page.ts +31 -0
- package/src/ts/plugins/client/11ty.ts +104 -0
- package/src/ts/plugins/client/publicationRenderer.ts +174 -0
- package/src/ts/plugins/client/template.ts +192 -0
- package/src/ts/plugins/client/website-info.ts +84 -0
- package/src/ts/plugins/server/AuthPlugin.ts +38 -0
- package/src/ts/plugins/server/Cache.ts +28 -0
- package/src/ts/plugins/server/DownloadConnector.ts +148 -0
- package/src/ts/plugins/server/FtpConnector.test.ts +82 -0
- package/src/ts/plugins/server/FtpConnector.ts +609 -0
- package/src/ts/plugins/server/GitlabConnector.ts +644 -0
- package/src/ts/plugins/server/PublicationOptimizer.ts +14 -0
- package/src/ts/plugins/server/SslPlugin.ts +84 -0
- package/src/ts/plugins/server/StaticPlugin.ts +79 -0
- package/src/ts/server/api/api.ts +19 -0
- package/src/ts/server/api/connectorApi.ts +345 -0
- package/src/ts/server/api/publicationApi.ts +159 -0
- package/src/ts/server/api/websiteApi.ts +432 -0
- package/src/ts/server/cli.ts +65 -0
- package/src/ts/server/config.ts +189 -0
- package/src/ts/server/connectors/FsHosting.ts +60 -0
- package/src/ts/server/connectors/FsStorage.test.ts +51 -0
- package/src/ts/server/connectors/FsStorage.ts +261 -0
- package/src/ts/server/connectors/connectors.ts +203 -0
- package/src/ts/server/events.ts +37 -0
- package/src/ts/server/expose.ts +19 -0
- package/src/ts/server/express.ts +70 -0
- package/src/ts/server/index.ts +100 -0
- package/src/ts/server/jobs.test.ts +75 -0
- package/src/ts/server/jobs.ts +87 -0
- package/src/ts/server/utils/BackwardCompat.ts +552 -0
- package/src/ts/server/utils/validation.ts +34 -0
- package/src/ts/types.ts +339 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Silex website builder, free/libre no-code tool for makers.
|
|
3
|
+
* Copyright (c) 2023 lexoyo and Silex Labs foundation
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Client } from 'basic-ftp'
|
|
19
|
+
import { PassThrough, Readable, Writable } from 'stream'
|
|
20
|
+
import { ConnectorFile, ConnectorFileContent, HostingConnector, StatusCallback, StorageConnector, contentToReadable, contentToString, toConnectorData, toConnectorEnum} from '../../server/connectors/connectors'
|
|
21
|
+
import { requiredParam } from '../../server/utils/validation'
|
|
22
|
+
import { WEBSITE_DATA_FILE, WEBSITE_META_DATA_FILE } from '../../constants'
|
|
23
|
+
import { ConnectorType, ConnectorUser, WebsiteMeta, FileMeta, JobData, JobStatus, WebsiteId, PublicationJobData, WebsiteMetaFileContent, defaultWebsiteData, WebsiteData, ConnectorOptions } from '../../types'
|
|
24
|
+
import { ServerConfig } from '../../server/config'
|
|
25
|
+
import { join } from 'path'
|
|
26
|
+
import { type } from 'os'
|
|
27
|
+
import { v4 as uuid } from 'uuid'
|
|
28
|
+
import { JobManager } from '../../server/jobs'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @fileoverview FTP connector for Silex
|
|
32
|
+
* This is a connector for Silex, which allows to store the website on a FTP server
|
|
33
|
+
* and to publish the website on a FTP server
|
|
34
|
+
* FIXME: As a hosting connector, this connector should only store the password in the session and the rest in the website data
|
|
35
|
+
* to prevent publishing a site on the wrong server + multiple sites on 1 server
|
|
36
|
+
* FIXME: Allow reuse of FTP session between calls
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export interface FtpSessionData {
|
|
40
|
+
host: string
|
|
41
|
+
user: string
|
|
42
|
+
pass: string
|
|
43
|
+
port: number
|
|
44
|
+
secure: boolean
|
|
45
|
+
// Storage options
|
|
46
|
+
storageRootPath?: string
|
|
47
|
+
// Hosting options
|
|
48
|
+
publicationPath?: string
|
|
49
|
+
websiteUrl?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface FtpSession {
|
|
53
|
+
[ConnectorType.STORAGE]?: FtpSessionData
|
|
54
|
+
[ConnectorType.HOSTING]?: FtpSessionData
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface FileStatus {
|
|
58
|
+
file: ConnectorFile
|
|
59
|
+
message: string
|
|
60
|
+
status: JobStatus
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface FtpOptions {
|
|
64
|
+
type: ConnectorType
|
|
65
|
+
path: string
|
|
66
|
+
assetsFolder: string
|
|
67
|
+
cssFolder: string // For publication only
|
|
68
|
+
authorizeUrl: string
|
|
69
|
+
authorizePath: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// **
|
|
73
|
+
// Utils methos
|
|
74
|
+
function formHtml(redirectTo: string, type: ConnectorType, { host, user, pass, port, secure, storageRootPath, publicationPath, websiteUrl}: FtpSessionData, err = '') {
|
|
75
|
+
return `
|
|
76
|
+
${ err && `<div class="error">${err || ''}</div>` }
|
|
77
|
+
<form method="POST" action="${redirectTo}">
|
|
78
|
+
<label for="host">Host</label>
|
|
79
|
+
<input placeholder="ftp.example.com" type="text" name="host" value="${host || ''}" />
|
|
80
|
+
<label for="user">User</label>
|
|
81
|
+
<input placeholder="user" type="text" name="user" value="${user || ''}" />
|
|
82
|
+
<label for="pass">Pass</label>
|
|
83
|
+
<input placeholder="****" type="password" name="pass" value="${pass || ''}" />
|
|
84
|
+
<label for="port">Port</label>
|
|
85
|
+
<input placeholder="21" type="number" name="port" value="${port || '21'}" />
|
|
86
|
+
<div class="checkbox-container">
|
|
87
|
+
<input class="checkbox" type="checkbox" name="secure" value="true" ${secure ? 'checked' : ''} />
|
|
88
|
+
<label for="secure">Secure</label>
|
|
89
|
+
</div>
|
|
90
|
+
${ type === ConnectorType.STORAGE ? `
|
|
91
|
+
<details>
|
|
92
|
+
<summary>Storage options</summary>
|
|
93
|
+
<p>If you are not sure, don't change this</p>
|
|
94
|
+
<label for="storageRootPath">Root path where to store the website files</label>
|
|
95
|
+
<input placeholder="/silex/" type="text" name="storageRootPath" value="${storageRootPath || '/silex/'}" />
|
|
96
|
+
</details>
|
|
97
|
+
` : ''}
|
|
98
|
+
${ type === ConnectorType.HOSTING ? `
|
|
99
|
+
<fieldset>
|
|
100
|
+
<legend>Publication options</legend>
|
|
101
|
+
<label for="publicationPath">Path where to publish</label>
|
|
102
|
+
<input placeholder="/public_html/" type="text" name="publicationPath" value="${publicationPath || ''}" />
|
|
103
|
+
<label for="websiteUrl">URL where to the site will be accessible</label>
|
|
104
|
+
<input placeholder="https://mysite.com/" type="text" name="websiteUrl" value="${websiteUrl || ''}" />
|
|
105
|
+
</fieldset>
|
|
106
|
+
` : ''}
|
|
107
|
+
<div class="button-container">
|
|
108
|
+
<button type="submit" class="primary-button">Login</button>
|
|
109
|
+
<button type="button" class="secondary-button">Cancel</button>
|
|
110
|
+
</div>
|
|
111
|
+
</form>
|
|
112
|
+
`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const formCss = `
|
|
116
|
+
/* Reset default form styles */
|
|
117
|
+
form {
|
|
118
|
+
margin: 0;
|
|
119
|
+
padding: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Center the form */
|
|
123
|
+
body {
|
|
124
|
+
display: flex;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
align-items: center;
|
|
127
|
+
height: 100vh;
|
|
128
|
+
background-color: #f5f5f5;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Style form container */
|
|
132
|
+
form {
|
|
133
|
+
width: 400px;
|
|
134
|
+
padding: 20px;
|
|
135
|
+
border-radius: 4px;
|
|
136
|
+
background-color: white;
|
|
137
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Style form labels */
|
|
141
|
+
label {
|
|
142
|
+
font-weight: bold;
|
|
143
|
+
margin-bottom: 5px;
|
|
144
|
+
color: #333;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Style form inputs */
|
|
148
|
+
input {
|
|
149
|
+
width: 100%;
|
|
150
|
+
padding: 8px;
|
|
151
|
+
margin-bottom: 10px;
|
|
152
|
+
border: 1px solid #ccc;
|
|
153
|
+
border-radius: 4px;
|
|
154
|
+
}
|
|
155
|
+
input.checkbox {
|
|
156
|
+
width: 20px;
|
|
157
|
+
height: 20px;
|
|
158
|
+
}
|
|
159
|
+
fieldset {
|
|
160
|
+
padding: 20px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Style form buttons */
|
|
164
|
+
.button-container {
|
|
165
|
+
display: flex;
|
|
166
|
+
justify-content: space-between;
|
|
167
|
+
margin-top: 20px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.primary-button,
|
|
171
|
+
.secondary-button {
|
|
172
|
+
width: 48%;
|
|
173
|
+
padding: 10px;
|
|
174
|
+
border: none;
|
|
175
|
+
border-radius: 4px;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
transition: background-color 0.3s ease;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.primary-button {
|
|
181
|
+
background-color: #4caf50;
|
|
182
|
+
color: white;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.secondary-button {
|
|
186
|
+
background-color: #e0e0e0;
|
|
187
|
+
color: #333;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.primary-button:hover,
|
|
191
|
+
.secondary-button:hover {
|
|
192
|
+
background-color: #388e3c;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Style form checkbox */
|
|
196
|
+
.checkbox-container {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
margin-bottom: 10px;
|
|
200
|
+
margin: 20px 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.checkbox-container label {
|
|
204
|
+
margin-left: 5px;
|
|
205
|
+
color: #333;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Style details and summary elements */
|
|
209
|
+
details {
|
|
210
|
+
margin-top: 20px;
|
|
211
|
+
color: #333;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
summary {
|
|
215
|
+
font-weight: bold;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
details > p {
|
|
220
|
+
margin-top: 10px;
|
|
221
|
+
}
|
|
222
|
+
`
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @class FtpConnector
|
|
226
|
+
* @implements {HostingConnector}
|
|
227
|
+
* @implements {StorageConnector}
|
|
228
|
+
*/
|
|
229
|
+
export default class FtpConnector implements StorageConnector<FtpSession> {
|
|
230
|
+
connectorId = 'ftp'
|
|
231
|
+
displayName = 'Ftp'
|
|
232
|
+
//icon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDIwMDEwOTA0Ly9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy1TVkctMjAwMTA5MDQvRFREL3N2ZzEwLmR0ZCI+CjxzdmcgdmVyc2lvbj0iMS4wIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiB3aWR0aD0iNjQuMDAwMDAwcHQiIGhlaWdodD0iNjQuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCA2NC4wMDAwMDAgNjQuMDAwMDAwIgogcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgbWVldCI+Cgo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLjAwMDAwMCw2NC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiMwMDAwMDAiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0yNDQgNTYxIGMtMjMgLTEwIC01MSAtMzIgLTYzIC00OSAtMTIgLTE3IC0zNCAtMzMgLTUwIC0zNyAtNzQgLTE2Ci0xMTYgLTEyNyAtNzYgLTIwMSAyNCAtNDUgODYgLTc0IDE1NyAtNzQgNDkgMCA1NiAtMyA2NiAtMjUgNiAtMTQgMTYgLTI1IDIyCi0yNSA2IDAgMTAgLTEyIDggLTI3IGwtMyAtMjggLTExMiAtMyBjLTkxIC0yIC0xMTMgLTUgLTExMyAtMTcgMCAtMTMgMzYgLTE1CjI0NiAtMTUgMjIwIDAgMjQ1IDIgMjQyIDE2IC0yIDEzIC0yMSAxNiAtMTEzIDE4IGwtMTEwIDEgLTMgMjggYy0yIDE1IDIgMjcgOAoyNyA2IDAgMTUgMTEgMjAgMjUgOSAyMyAxNSAyNSA3MCAyNSAxMTAgMCAxNjAgMzggMTYwIDEyMSAwIDQwIC01IDUzIC0zMSA3OAotMTcgMTcgLTM5IDMxIC01MCAzMSAtMTIgMCAtMjMgMTAgLTI5IDI2IC0xOSA0OCAtNTMgODYgLTk0IDEwNSAtNTIgMjQgLTEwMAoyNCAtMTUyIDB6IG0xMTkgLTM1IGM0NSAtMTggODcgLTcxIDg3IC0xMDcgMCAtMjYgMyAtMjkgMzIgLTI5IDg2IDAgMTA4IC0xMTEKMjcgLTEzOSAtNDAgLTE0IC0zMDIgLTE0IC0zNTEgLTEgLTc4IDIyIC0xMDMgMTA2IC00OSAxNjEgMTYgMTYgNDAgMjkgNTMgMjkKMTQgMCAzNCAxMyA1MiAzNiAyNyAzNCA3NCA2MyAxMDQgNjQgNyAwIDI3IC02IDQ1IC0xNHoiLz4KPHBhdGggZD0iTTIwNyAzOTMgYy0xMSAtMTAgLTggLTkzIDMgLTkzIDYgMCAxMCA5IDEwIDIwIDAgMTMgNyAyMCAyMCAyMCAxMSAwCjIwIDUgMjAgMTAgMCA2IC05IDEwIC0yMCAxMCAtMTEgMCAtMjAgNSAtMjAgMTAgMCA2IDkgMTAgMjAgMTAgMTEgMCAyMCA1IDIwCjEwIDAgMTEgLTQzIDE0IC01MyAzeiIvPgo8cGF0aCBkPSJNMjgwIDM5MCBjMCAtNSA3IC0xMCAxNSAtMTAgMTEgMCAxNSAtMTEgMTUgLTQwIDAgLTIyIDMgLTQwIDggLTQwIDQKMCA2IDE4IDQgNDAgLTMgMzIgLTEgNDAgMTIgNDAgOSAwIDE2IDUgMTYgMTAgMCA2IC0xNiAxMCAtMzUgMTAgLTE5IDAgLTM1IC00Ci0zNSAtMTB6Ii8+CjxwYXRoIGQ9Ik0zNzAgMzUwIGMwIC0yNyA1IC01MCAxMCAtNTAgNiAwIDEwIDkgMTAgMjAgMCAxMyA3IDIwIDE5IDIwIDIzIDAKMzUgMjggMjAgNDYgLTYgOCAtMjIgMTQgLTM1IDE0IC0yMiAwIC0yNCAtNCAtMjQgLTUweiBtNTAgMTUgYzAgLTggLTcgLTE1Ci0xNSAtMTUgLTggMCAtMTUgNyAtMTUgMTUgMCA4IDcgMTUgMTUgMTUgOCAwIDE1IC03IDE1IC0xNXoiLz4KPC9nPgo8L3N2Zz4K'
|
|
233
|
+
icon = 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' height=\'1em\' viewBox=\'0 0 512 512\'%3E%3Cpath d=\'M64 32C28.7 32 0 60.7 0 96v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm48 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zM64 288c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V352c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm56 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z\'/%3E%3C/svg%3E'
|
|
234
|
+
options: FtpOptions
|
|
235
|
+
connectorType: ConnectorType
|
|
236
|
+
color = '#ffffff'
|
|
237
|
+
background = '#0066CC'
|
|
238
|
+
|
|
239
|
+
constructor(config: ServerConfig, opts: Partial<FtpOptions>) {
|
|
240
|
+
this.options = {
|
|
241
|
+
path: '',
|
|
242
|
+
assetsFolder: 'assets',
|
|
243
|
+
cssFolder: 'css',
|
|
244
|
+
authorizeUrl: './api/authorize/ftp/',
|
|
245
|
+
authorizePath: './api/authorize/ftp/',
|
|
246
|
+
...opts,
|
|
247
|
+
} as FtpOptions
|
|
248
|
+
if(!this.options.type) throw new Error('missing type in option of FtpConnector')
|
|
249
|
+
this.connectorType = toConnectorEnum(this.options.type)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// **
|
|
253
|
+
// Utils
|
|
254
|
+
sessionData(session: FtpSession): FtpSessionData {
|
|
255
|
+
return session[`ftp-${this.options.type}`] ?? {} as FtpSessionData
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
rootPath(session: FtpSession): string {
|
|
259
|
+
return this.connectorType === ConnectorType.STORAGE ?
|
|
260
|
+
requiredParam<string>(this.sessionData(session).storageRootPath, 'storage root path') :
|
|
261
|
+
requiredParam<string>(this.sessionData(session).publicationPath, 'publication path')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// **
|
|
265
|
+
// FTP methods
|
|
266
|
+
private async write(ftp: Client, path: string, content: ConnectorFileContent, progress?: (message: string) => void): Promise<void> {
|
|
267
|
+
ftp.trackProgress(info => {
|
|
268
|
+
progress && progress(`Uploading ${info.bytes / 1000} KB}`)
|
|
269
|
+
})
|
|
270
|
+
progress && progress('Upload started')
|
|
271
|
+
await ftp.uploadFrom(contentToReadable(content), path)
|
|
272
|
+
progress && progress('Upload complete')
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private async read(ftp: Client, path: string): Promise<Readable> {
|
|
276
|
+
const stream = new PassThrough()
|
|
277
|
+
await ftp.downloadTo(stream, path)
|
|
278
|
+
return stream
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private async readdir(ftp: Client, path: string): Promise<FileMeta[]> {
|
|
282
|
+
const list = await ftp.list(path)
|
|
283
|
+
return list.map((file) => ({
|
|
284
|
+
name: file.name,
|
|
285
|
+
isDir: file.isDirectory,
|
|
286
|
+
size: file.size,
|
|
287
|
+
createdAt: file.modifiedAt,
|
|
288
|
+
updatedAt: file.modifiedAt,
|
|
289
|
+
metaData: file,
|
|
290
|
+
} as FileMeta))
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private async mkdir(ftp: Client, path: string) {
|
|
294
|
+
return ftp.ensureDir(path)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private async rmdir(ftp: Client, path: string) {
|
|
298
|
+
return ftp.removeDir(path)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private async unlink(ftp: Client, path: string) {
|
|
302
|
+
return ftp.remove(path)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private async getClient({host, user, pass, port, secure}): Promise<Client> {
|
|
306
|
+
const ftp = new Client()
|
|
307
|
+
await ftp.access({
|
|
308
|
+
host,
|
|
309
|
+
port,
|
|
310
|
+
user,
|
|
311
|
+
password: pass,
|
|
312
|
+
secure,
|
|
313
|
+
})
|
|
314
|
+
return ftp
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private closeClient(ftp: Client) {
|
|
318
|
+
if(ftp && !ftp.closed) {
|
|
319
|
+
ftp.close()
|
|
320
|
+
} else {
|
|
321
|
+
console.warn('ftp already closed')
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// **
|
|
326
|
+
// Connector interface
|
|
327
|
+
getOptions(formData: object): ConnectorOptions {
|
|
328
|
+
return {
|
|
329
|
+
// Storage
|
|
330
|
+
storageRootPath: formData['storageRootPath'],
|
|
331
|
+
// Hosting
|
|
332
|
+
publicationPath: formData['publicationPath'],
|
|
333
|
+
websiteUrl: formData['websiteUrl'],
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async getOAuthUrl(session: FtpSession): Promise<null> { return null }
|
|
338
|
+
|
|
339
|
+
async getLoginForm(session: FtpSession, redirectTo: string): Promise<string | null> {
|
|
340
|
+
const { host, user, pass, port, secure, publicationPath, storageRootPath, websiteUrl } = this.sessionData(session)
|
|
341
|
+
requiredParam(type, 'connector type')
|
|
342
|
+
return `
|
|
343
|
+
<style>
|
|
344
|
+
${formCss}
|
|
345
|
+
</style>
|
|
346
|
+
${formHtml(redirectTo, this.connectorType, { host, user, pass, port, secure, publicationPath, storageRootPath, websiteUrl })}
|
|
347
|
+
`
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async getSettingsForm(session: FtpSession, redirectTo: string): Promise<string | null> { return null }
|
|
351
|
+
|
|
352
|
+
async setToken(session: FtpSession, token: object): Promise<void> {
|
|
353
|
+
// Check all required params are present
|
|
354
|
+
const { host, user, pass, port, secure = false, publicationPath, storageRootPath, websiteUrl } = token as FtpSessionData
|
|
355
|
+
requiredParam(session, 'session')
|
|
356
|
+
requiredParam(host, 'host')
|
|
357
|
+
requiredParam(user, 'user')
|
|
358
|
+
requiredParam(pass, 'pass')
|
|
359
|
+
requiredParam(port, 'port')
|
|
360
|
+
|
|
361
|
+
// Check if the connection is valid
|
|
362
|
+
const ftp = await this.getClient({ host, user, pass, port, secure })
|
|
363
|
+
// Save the token
|
|
364
|
+
session[`ftp-${this.connectorType}`] = { host, user, pass, port, secure, publicationPath, storageRootPath, websiteUrl }
|
|
365
|
+
// Clean up
|
|
366
|
+
this.closeClient(ftp)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async logout(session: FtpSession) {
|
|
370
|
+
delete session[`ftp-${this.options.type}`]
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async getUser(session: FtpSession): Promise<ConnectorUser> {
|
|
374
|
+
return {
|
|
375
|
+
name: this.sessionData(session).user,
|
|
376
|
+
picture: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' height=\'1em\' viewBox=\'0 0 448 512\'%3E%3C!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --%3E%3Cpath d=\'M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z\'/%3E%3C/svg%3E',
|
|
377
|
+
storage: await toConnectorData(session, this)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async isLoggedIn(session: FtpSession) {
|
|
382
|
+
try {
|
|
383
|
+
if (!session[`ftp-${this.options.type}`]) {
|
|
384
|
+
return false
|
|
385
|
+
}
|
|
386
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
387
|
+
this.closeClient(ftp)
|
|
388
|
+
return true
|
|
389
|
+
} catch(err) {
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async setWebsiteMeta(session: FtpSession, id: string, data: WebsiteMetaFileContent): Promise<void> {
|
|
395
|
+
const websiteId = requiredParam<WebsiteId>(id, 'website id')
|
|
396
|
+
const path = join(this.rootPath(session), websiteId, WEBSITE_META_DATA_FILE)
|
|
397
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
398
|
+
await this.write(ftp, path, JSON.stringify(data))
|
|
399
|
+
this.closeClient(ftp)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async getWebsiteMeta(session: FtpSession, id: WebsiteId): Promise<WebsiteMeta> {
|
|
403
|
+
try {
|
|
404
|
+
const websiteId = requiredParam<WebsiteId>(id, 'website id')
|
|
405
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
406
|
+
// Get stats for the website folder
|
|
407
|
+
const folder = join(this.rootPath(session), websiteId)
|
|
408
|
+
const lastMod = await ftp.lastMod(folder)
|
|
409
|
+
// Read the meta data file to get the meta data set by the user
|
|
410
|
+
const path = join(this.rootPath(session), websiteId, WEBSITE_META_DATA_FILE)
|
|
411
|
+
const readable = await this.read(ftp, path)
|
|
412
|
+
const meta = JSON.parse(await contentToString(readable)) as WebsiteMetaFileContent
|
|
413
|
+
this.closeClient(ftp)
|
|
414
|
+
// Return all meta
|
|
415
|
+
return {
|
|
416
|
+
websiteId,
|
|
417
|
+
//url: await this.getFileUrl(session, websiteId, WEBSITE_DATA_FILE_NAME),
|
|
418
|
+
createdAt: lastMod,
|
|
419
|
+
updatedAt: lastMod,
|
|
420
|
+
...meta,
|
|
421
|
+
}
|
|
422
|
+
} catch(err) {
|
|
423
|
+
console.error(err)
|
|
424
|
+
throw err
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// **
|
|
429
|
+
// Storage interface
|
|
430
|
+
/**
|
|
431
|
+
* Create necessary folders
|
|
432
|
+
* Assets and root folders
|
|
433
|
+
*/
|
|
434
|
+
async createWebsite(session: FtpSession): Promise<WebsiteId> {
|
|
435
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
436
|
+
// Generate a random id
|
|
437
|
+
const id = uuid()
|
|
438
|
+
// // create root folder
|
|
439
|
+
// const rootPath = join(this.storageRootPath(session), id)
|
|
440
|
+
// await this.mkdir(ftp, rootPath)
|
|
441
|
+
// create assets folder
|
|
442
|
+
const assetsPath = join(this.rootPath(session), id, '/assets')
|
|
443
|
+
await this.mkdir(ftp, assetsPath)
|
|
444
|
+
// create website data file
|
|
445
|
+
const websiteDataPath = join(this.rootPath(session), id, WEBSITE_DATA_FILE)
|
|
446
|
+
await this.write(ftp, websiteDataPath, JSON.stringify(defaultWebsiteData))
|
|
447
|
+
// create website meta data file
|
|
448
|
+
const websiteMetaDataPath = join(this.rootPath(session), id, WEBSITE_META_DATA_FILE)
|
|
449
|
+
await this.write(ftp, websiteMetaDataPath, JSON.stringify({}))
|
|
450
|
+
// Clean up
|
|
451
|
+
this.closeClient(ftp)
|
|
452
|
+
// All good
|
|
453
|
+
return id
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async listWebsites(session: FtpSession): Promise<WebsiteMeta[]> {
|
|
457
|
+
const storageRootPath = this.rootPath(session)
|
|
458
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
459
|
+
const files = await this.readdir(ftp, storageRootPath)
|
|
460
|
+
const list = await Promise.all(files.map(async file => {
|
|
461
|
+
const websiteId = file.name
|
|
462
|
+
const websiteMeta = await this.getWebsiteMeta(session, websiteId)
|
|
463
|
+
return websiteMeta
|
|
464
|
+
}))
|
|
465
|
+
this.closeClient(ftp)
|
|
466
|
+
return list
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async readWebsite(session: FtpSession, websiteId: string): Promise<WebsiteData | Readable> {
|
|
470
|
+
const storageRootPath = this.rootPath(session)
|
|
471
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
472
|
+
const websiteDataPath = join(storageRootPath, websiteId, WEBSITE_DATA_FILE)
|
|
473
|
+
const data = await this.read(ftp, websiteDataPath)
|
|
474
|
+
this.closeClient(ftp)
|
|
475
|
+
return data
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async updateWebsite(session: FtpSession, websiteId: string, data: WebsiteData): Promise<void> {
|
|
479
|
+
const storageRootPath = this.rootPath(session)
|
|
480
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
481
|
+
const websiteDataPath = join(storageRootPath, websiteId, WEBSITE_DATA_FILE)
|
|
482
|
+
await this.write(ftp, websiteDataPath, JSON.stringify(data))
|
|
483
|
+
this.closeClient(ftp)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async deleteWebsite(session: FtpSession, websiteId: string): Promise<void> {
|
|
487
|
+
const storageRootPath = this.rootPath(session)
|
|
488
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
489
|
+
const websitePath = join(storageRootPath, websiteId)
|
|
490
|
+
await this.rmdir(ftp, websitePath)
|
|
491
|
+
this.closeClient(ftp)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async writeAssets(
|
|
495
|
+
session: any,
|
|
496
|
+
id: WebsiteId,
|
|
497
|
+
files: ConnectorFile[],
|
|
498
|
+
statusCbk?: StatusCallback,
|
|
499
|
+
): Promise<void> {
|
|
500
|
+
return this.writeFile(session, id, files, this.options.assetsFolder, statusCbk)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async writeFile(
|
|
504
|
+
session: any,
|
|
505
|
+
id: WebsiteId,
|
|
506
|
+
files: ConnectorFile[],
|
|
507
|
+
relativePath: string,
|
|
508
|
+
statusCbk?: StatusCallback,
|
|
509
|
+
): Promise<void> {
|
|
510
|
+
// Connect to FTP server
|
|
511
|
+
statusCbk && statusCbk({
|
|
512
|
+
message: 'Connecting to FTP server',
|
|
513
|
+
status: JobStatus.IN_PROGRESS,
|
|
514
|
+
})
|
|
515
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
516
|
+
const rootPath = this.rootPath(session)
|
|
517
|
+
// Make sure that root folder exists
|
|
518
|
+
statusCbk && statusCbk({
|
|
519
|
+
message: 'Making sure that root folder exists',
|
|
520
|
+
status: JobStatus.IN_PROGRESS,
|
|
521
|
+
})
|
|
522
|
+
// Useless as ftp write will create the folder
|
|
523
|
+
// await this.mkdir(ftp, rootPath)
|
|
524
|
+
// Write files
|
|
525
|
+
let lastFile: ConnectorFile | undefined
|
|
526
|
+
try {
|
|
527
|
+
// Sequentially write files
|
|
528
|
+
for(const file of files) {
|
|
529
|
+
statusCbk && statusCbk({
|
|
530
|
+
message: `Writing file ${file.path}`,
|
|
531
|
+
status: JobStatus.IN_PROGRESS,
|
|
532
|
+
})
|
|
533
|
+
const dstPath = join(this.options.path, rootPath, id, relativePath, file.path)
|
|
534
|
+
lastFile = file
|
|
535
|
+
const result = await this.write(ftp, dstPath, file.content, message => {
|
|
536
|
+
statusCbk && statusCbk({
|
|
537
|
+
message: `Writing file ${file.path.split('/').pop()} to ${dstPath.split('/').slice(0, -1).join('/')}: ${message}`,
|
|
538
|
+
status: JobStatus.IN_PROGRESS,
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
this.closeClient(ftp)
|
|
543
|
+
statusCbk && statusCbk({
|
|
544
|
+
message: `Finished writing ${files.length} files to ${rootPath}`,
|
|
545
|
+
status: JobStatus.SUCCESS,
|
|
546
|
+
})
|
|
547
|
+
} catch(err) {
|
|
548
|
+
// Not sure why it never gets here
|
|
549
|
+
statusCbk && statusCbk({
|
|
550
|
+
message: `Error writing file ${lastFile?.path}: ${err.message}`,
|
|
551
|
+
status: JobStatus.ERROR,
|
|
552
|
+
})
|
|
553
|
+
this.closeClient(ftp)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async readAsset(session: FtpSession, id: string, path: string): Promise<ConnectorFileContent> {
|
|
558
|
+
if (!this.sessionData(session)) throw new Error('Not logged in')
|
|
559
|
+
const storageRootPath = this.rootPath(session)
|
|
560
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
561
|
+
const dirPath = join(this.options.path, storageRootPath, id, this.options.assetsFolder, path)
|
|
562
|
+
const asset = this.read(ftp, dirPath)
|
|
563
|
+
this.closeClient(ftp)
|
|
564
|
+
return asset
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async deleteAssets(session: FtpSession, id: WebsiteId, paths: string[]): Promise<void> {
|
|
568
|
+
const storageRootPath = this.rootPath(session)
|
|
569
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
570
|
+
await Promise.all(
|
|
571
|
+
paths.map((path) => this.unlink(ftp, join(this.options.path, storageRootPath, id, this.options.assetsFolder, path)))
|
|
572
|
+
)
|
|
573
|
+
this.closeClient(ftp)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// **
|
|
577
|
+
// Hosting interface
|
|
578
|
+
async getUrl(session: FtpSession, id: WebsiteId): Promise<string> {
|
|
579
|
+
// FIXME: do not store websiteUrl in the session, but in the website data
|
|
580
|
+
console.warn('FIXME: do not store websiteUrl in the session, but in the website data')
|
|
581
|
+
return this.sessionData(session).websiteUrl ?? ''
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async publish(session: FtpSession, id: WebsiteId, files: ConnectorFile[], {startJob, jobSuccess, jobError}: JobManager): Promise<JobData> {
|
|
585
|
+
const job = startJob(`Publishing to ${this.displayName}`) as PublicationJobData
|
|
586
|
+
job.logs = [[`Publishing to ${this.displayName}`]]
|
|
587
|
+
job.errors = [[]]
|
|
588
|
+
// Create folders
|
|
589
|
+
const rootPath = this.rootPath(session)
|
|
590
|
+
const ftp = await this.getClient(this.sessionData(session))
|
|
591
|
+
await this.mkdir(ftp, rootPath)
|
|
592
|
+
await this.mkdir(ftp, join(rootPath, this.options.assetsFolder))
|
|
593
|
+
await this.mkdir(ftp, join(rootPath, this.options.cssFolder))
|
|
594
|
+
// Write files
|
|
595
|
+
this.writeFile(session, '', files, '', async ({status, message}) => {
|
|
596
|
+
// Update the job status
|
|
597
|
+
job.status = status
|
|
598
|
+
job.message = message
|
|
599
|
+
job.logs[0].push(message)
|
|
600
|
+
if(status === JobStatus.SUCCESS) {
|
|
601
|
+
jobSuccess(job.jobId, message)
|
|
602
|
+
} else if(status === JobStatus.ERROR) {
|
|
603
|
+
job.errors[0].push(message)
|
|
604
|
+
jobError(job.jobId, message)
|
|
605
|
+
}
|
|
606
|
+
})
|
|
607
|
+
return job
|
|
608
|
+
}
|
|
609
|
+
}
|