mirror of
https://github.com/ComfortablyCoding/strapi-plugin-website-builder.git
synced 2025-06-25 00:03:29 -04:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
47ed9f40a8 | ||
|
8eeac9a748 | ||
|
ba64fede45 | ||
|
d185e7e420 | ||
|
608c2f3f2a | ||
|
f59bfc8dcc | ||
|
9279a44fb1 | ||
|
eb5936fa2b | ||
|
74c9fb711c | ||
|
84c0d4cda7 | ||
|
0fadeb8162 | ||
|
ec9ca19a1c | ||
|
0dd80139c3 | ||
|
9c18c7a184 | ||
|
f8d7860a0b | ||
|
907759456b | ||
|
0a446b5a18 | ||
|
c87758949d | ||
|
a03218f61e | ||
|
a047226662 | ||
|
706124c70b | ||
|
e0b580d153 | ||
|
e4381771a1 | ||
|
04c9b89761 | ||
|
11c260d7c1 | ||
|
554a1e34f4 | ||
|
ba4d4ac809 | ||
|
8fc1e21e3f | ||
|
18408984ce | ||
|
462ed39c27 | ||
|
ba7c58799f | ||
|
fe0ba9b056 | ||
|
2b026ba584 | ||
|
8a699b264f | ||
|
34002b82d9 | ||
|
4f042eebd6 | ||
|
5405681926 | ||
|
beb6f03e53 | ||
|
801ef9759b | ||
|
e219ddc95b | ||
|
f564814680 | ||
|
bdadab5f69 | ||
|
ed2bfce4f4 |
@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
files: ['./server/**/*'],
|
||||
$schema: 'https://json.schemastore.org/eslintrc',
|
||||
env: {
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:node/recommended', 'prettier'],
|
||||
globals: {
|
||||
strapi: 'readonly',
|
||||
},
|
||||
};
|
||||
|
@ -1,23 +1,18 @@
|
||||
module.exports = {
|
||||
files: ['./admin/**/*'],
|
||||
$schema: 'https://json.schemastore.org/eslintrc',
|
||||
parser: '@babel/eslint-parser',
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
},
|
||||
plugins: ['react'],
|
||||
extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
sourceType: 'module',
|
||||
},
|
||||
env: { browser: true, es6: true },
|
||||
settings: {
|
||||
react: {
|
||||
version: '16.5.2',
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
29
.eslintrc.js
29
.eslintrc.js
@ -1,30 +1,5 @@
|
||||
const frontendESLint = require('./.eslintrc.frontend.js');
|
||||
const backendESLint = require('./.eslintrc.backend.js');
|
||||
|
||||
module.exports = {
|
||||
$schema: 'https://json.schemastore.org/eslintrc',
|
||||
// parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
rules: {
|
||||
indent: ['error', 'tab'],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always'],
|
||||
},
|
||||
globals: {
|
||||
strapi: 'readonly',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['/**/*'],
|
||||
excludedFiles: ['/admin/**/*'],
|
||||
...backendESLint,
|
||||
},
|
||||
{
|
||||
files: ['/admin/**/*'],
|
||||
...frontendESLint,
|
||||
},
|
||||
],
|
||||
root: true,
|
||||
overrides: [require('./.eslintrc.backend.js'), require('./.eslintrc.frontend.js')],
|
||||
};
|
||||
|
9
.github/workflows/npm-publish.yml
vendored
9
.github/workflows/npm-publish.yml
vendored
@ -13,17 +13,14 @@ jobs:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v14
|
||||
- name: Install Node v18
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '18.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Clean install deps
|
||||
run: yarn install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Publish to NPM
|
||||
run: npm publish
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -7,3 +7,7 @@ stats.json
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
.idea
|
||||
|
||||
# docs
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
@ -20,3 +20,6 @@ npm-debug.log
|
||||
|
||||
# github
|
||||
.github
|
||||
|
||||
# docs
|
||||
docs
|
||||
|
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@ -0,0 +1,3 @@
|
||||
package-lock.json
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
@ -7,5 +7,5 @@
|
||||
"useTabs": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 100
|
||||
"printWidth": 120
|
||||
}
|
||||
|
57
README.md
57
README.md
@ -1,54 +1,25 @@
|
||||
# Strapi plugin website-builder
|
||||
# strapi-plugin-website-builder
|
||||
|
||||
A plugin for [Strapi](https://github.com/strapi/strapi) that provides the ability to manually trigger website builds
|
||||
A plugin for [Strapi](https://github.com/strapi/strapi) that provides the ability to trigger website builds manually, periodically or through model events.
|
||||
|
||||
[](https://img.shields.io/npm/dm/strapi-plugin-website-builder?style=for-the-badge)
|
||||
[](https://img.shields.io/npm/l/strapi-plugin-website-builder?style=for-the-badge)
|
||||
[](https://img.shields.io/github/v/release/ComfortablyCoding/strapi-plugin-website-builder?style=for-the-badge)
|
||||
|
||||
## Requirements
|
||||
|
||||
Complete installation requirements the same as Strapi itself and can be found in the documentation under [Quick Start Guide](https://strapi.io/documentation/developer-docs/latest/getting-started/quick-start.html) Prerequisites info card.
|
||||
The installation requirements are the same as Strapi itself and can be found in the documentation on the [Quick Start](https://strapi.io/documentation/developer-docs/latest/getting-started/quick-start.html) page in the Prerequisites info card.
|
||||
|
||||
**Supported Strapi versions**:
|
||||
### Supported Strapi versions
|
||||
|
||||
- Strapi v3.6.8
|
||||
- v4.x.x
|
||||
|
||||
(ThWhile this plugin may work with the older Strapi versions, they are not supported.)
|
||||
**NOTE**: While this plugin may work with the older Strapi versions, they are not supported, it is always recommended to use the latest version of Strapi.
|
||||
|
||||
**It is recommended to always use the latest version of Strapi when starting new projects**.
|
||||
## Documentation
|
||||
|
||||
## Installation
|
||||
The documentation for this plugin can be viewed [here](https://strapi-plugin-website-builder.netlify.app/)
|
||||
|
||||
```bash
|
||||
npm install strapi-plugin-website-builder
|
||||
```
|
||||
## Bugs
|
||||
|
||||
**or**
|
||||
|
||||
```bash
|
||||
yarn add strapi-plugin-website-builder
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Generate a config file at `config/plugins.js` with the following structure,
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
'website-builder': {
|
||||
// This is the URL that will be POST to on trigger. Required
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
// Object of any headers you might need. Optional
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// Max number of logs to store. Defaults to 5
|
||||
maxNumOfLogs: 5,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**IMPORTANT NOTE**: Make sure any sensitive data is stored in env files.
|
||||
|
||||
## Usage
|
||||
|
||||
Once the plugin has been installed and configured, it will show in the sidebar as `Website Builder`.
|
||||
To trigger a manual build select the `Website Builder` menu item in the sidebar and click
|
||||
the `Trigger Build` button to start the build process.
|
||||
If any bugs are found please report them as a [Github Issue](https://github.com/ComfortablyCoding/strapi-plugin-website-builder/issues)
|
||||
|
@ -6,21 +6,21 @@
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { pluginId } from '../../utils/constants';
|
||||
import { PLUGIN_ID } from '../../utils/constants';
|
||||
|
||||
const Initializer = ({ updatePlugin }) => {
|
||||
const Initializer = ({ setPlugin }) => {
|
||||
const ref = useRef();
|
||||
ref.current = updatePlugin;
|
||||
ref.current = setPlugin;
|
||||
|
||||
useEffect(() => {
|
||||
ref.current(pluginId, 'isReady', true);
|
||||
ref.current(PLUGIN_ID);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Initializer.propTypes = {
|
||||
updatePlugin: PropTypes.func.isRequired,
|
||||
setPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Initializer;
|
1
admin/src/components/Initializer/index.js
Normal file
1
admin/src/components/Initializer/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Initializer';
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Log Table
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { useGlobalContext } from 'strapi-helper-plugin';
|
||||
|
||||
import { Table, Text } from '@buffetjs/core';
|
||||
|
||||
import getTrad from '../utils/getTrad';
|
||||
import { BUILD_LOG_TABLE_HEADERS } from '../utils/constants';
|
||||
|
||||
const LogTable = ({ rows }) => {
|
||||
const { formatMessage } = useGlobalContext();
|
||||
|
||||
// map log table headers back with their translated titles
|
||||
const headers = BUILD_LOG_TABLE_HEADERS.map(({ translation, value }) => ({
|
||||
name: formatMessage({ id: getTrad(translation) }),
|
||||
value,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text fontSize="lg">
|
||||
{formatMessage({
|
||||
id: getTrad('log.table.title'),
|
||||
})}
|
||||
</Text>
|
||||
<Table headers={headers} rows={rows} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LogTable);
|
14
admin/src/components/PluginIcon/PluginIcon.js
Normal file
14
admin/src/components/PluginIcon/PluginIcon.js
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
*
|
||||
* PluginIcon
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Play from '@strapi/icons/Play';
|
||||
|
||||
const PluginIcon = () => {
|
||||
return <Play />;
|
||||
};
|
||||
|
||||
export default PluginIcon;
|
1
admin/src/components/PluginIcon/index.js
Normal file
1
admin/src/components/PluginIcon/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './PluginIcon';
|
@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { NotFound } from 'strapi-helper-plugin';
|
||||
import { pluginId } from '../../utils/constants';
|
||||
|
||||
// Containers
|
||||
import HomePage from '../HomePage';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* HomePage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useGlobalContext } from 'strapi-helper-plugin';
|
||||
|
||||
import { Header } from '@buffetjs/custom';
|
||||
import { Padded } from '@buffetjs/core';
|
||||
import LogTable from '../../components/LogTable';
|
||||
|
||||
import pluginEndpointRequest from '../../utils/pluginEndpointRequest';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
|
||||
const HomePage = () => {
|
||||
const { formatMessage } = useGlobalContext();
|
||||
const [logs, setLogs] = useState([]);
|
||||
|
||||
async function handlePublish() {
|
||||
let logs;
|
||||
try {
|
||||
await pluginEndpointRequest('publish', {
|
||||
method: 'POST',
|
||||
});
|
||||
strapi.notification.success(getTrad('publish.success'));
|
||||
|
||||
const time = Date.now();
|
||||
logs = await pluginEndpointRequest('logs', {
|
||||
method: 'POST',
|
||||
params: {
|
||||
id: time,
|
||||
status: 'Success',
|
||||
timestamp: time,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
strapi.notification.error(getTrad('publish.error'));
|
||||
logs = await pluginEndpointRequest('logs', {
|
||||
method: 'POST',
|
||||
params: {
|
||||
id: time,
|
||||
status: 'Error',
|
||||
timestamp: time,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
setLogs(logs.data);
|
||||
}
|
||||
}
|
||||
|
||||
// set initial state for logs
|
||||
useEffect(() => {
|
||||
async function getBuildLogs() {
|
||||
try {
|
||||
const response = await pluginEndpointRequest('logs', {
|
||||
method: 'GET',
|
||||
});
|
||||
setLogs(response.data);
|
||||
} catch (error) {
|
||||
strapi.notification.error(getTrad('logs.get.error'));
|
||||
}
|
||||
}
|
||||
|
||||
getBuildLogs();
|
||||
|
||||
return () => {
|
||||
let ignore = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Padded size="md" top left bottom right>
|
||||
<Header
|
||||
title={{ label: formatMessage({ id: getTrad('home.title') }) }}
|
||||
content={formatMessage({ id: getTrad('home.description') })}
|
||||
actions={[
|
||||
{
|
||||
label: formatMessage({
|
||||
id: getTrad('home.button.trigger'),
|
||||
}),
|
||||
onClick: handlePublish,
|
||||
color: 'success',
|
||||
type: 'submit',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<LogTable rows={logs} />
|
||||
</Padded>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(HomePage);
|
45
admin/src/hooks/useBuild.js
Normal file
45
admin/src/hooks/useBuild.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
|
||||
import { PLUGIN_ID } from '../utils/constants';
|
||||
import { getTrad } from '../utils/common';
|
||||
|
||||
export const useBuild = () => {
|
||||
const { post, get } = useFetchClient();
|
||||
const toggleNotification = useNotification();
|
||||
|
||||
function getBuilds() {
|
||||
return useQuery({
|
||||
queryKey: [PLUGIN_ID, 'builds'],
|
||||
queryFn: function () {
|
||||
return get(`/${PLUGIN_ID}/builds`);
|
||||
},
|
||||
select: function ({ data }) {
|
||||
return data.data || false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { mutateAsync: triggerBuild } = useMutation({
|
||||
mutationFn: function (data) {
|
||||
return post(`/${PLUGIN_ID}/builds`, { data });
|
||||
},
|
||||
onSuccess: () => {
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: getTrad('build.notification.trigger.success') },
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: error.response?.error?.message || error.message || { id: 'notification.error' },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
triggerBuild,
|
||||
getBuilds,
|
||||
};
|
||||
};
|
47
admin/src/hooks/useLogs.js
Normal file
47
admin/src/hooks/useLogs.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
||||
import { useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
|
||||
import { PLUGIN_ID } from '../utils/constants';
|
||||
import { getTrad } from '../utils/common';
|
||||
|
||||
export const useLogs = () => {
|
||||
const { get, del } = useFetchClient();
|
||||
const toggleNotification = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
function getLogs({ page }) {
|
||||
return useQuery({
|
||||
queryKey: [PLUGIN_ID, 'logs'],
|
||||
queryFn: function () {
|
||||
return get(`/${PLUGIN_ID}/logs`, { params: { sort: ['id:desc'], pagination: { page } } });
|
||||
},
|
||||
select: function ({ data }) {
|
||||
return { ...data } || false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { mutateAsync: deleteLog } = useMutation({
|
||||
mutationFn: function (id) {
|
||||
return del(`/${PLUGIN_ID}/logs/${id}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([PLUGIN_ID, 'logs']);
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: getTrad('log.notification.delete.success') },
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: error.response?.error?.message || error.message || { id: 'notification.error' },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
getLogs,
|
||||
deleteLog,
|
||||
};
|
||||
};
|
@ -1,46 +1,51 @@
|
||||
import App from './containers/App';
|
||||
import Initializer from './containers/Initializer';
|
||||
import lifecycles from './lifecycles';
|
||||
import trads from './translations';
|
||||
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||
import { PLUGIN_ID } from './utils/constants';
|
||||
import Initializer from './components/Initializer';
|
||||
import PluginIcon from './components/PluginIcon';
|
||||
|
||||
import pluginPkg from '../../package.json';
|
||||
const pluginId = pluginPkg.name.replace(/^strapi-plugin-/i, '');
|
||||
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
|
||||
const icon = pluginPkg.strapi.icon;
|
||||
const name = pluginPkg.strapi.name;
|
||||
const isStrapiRequired = pluginPkg.strapi.required || false;
|
||||
const name = 'Website Builder';
|
||||
|
||||
export default (strapi) => {
|
||||
const plugin = {
|
||||
blockerComponent: null,
|
||||
blockerComponentProps: {},
|
||||
description: pluginDescription,
|
||||
icon,
|
||||
id: pluginId,
|
||||
export default {
|
||||
register(app) {
|
||||
app.addMenuLink({
|
||||
to: `/plugins/${PLUGIN_ID}`,
|
||||
icon: PluginIcon,
|
||||
intlLabel: {
|
||||
id: `${PLUGIN_ID}.plugin.name`,
|
||||
defaultMessage: name,
|
||||
},
|
||||
Component: async () => {
|
||||
const component = await import(/* webpackChunkName: "[website-builder-request]" */ './pages/App');
|
||||
|
||||
return component;
|
||||
},
|
||||
});
|
||||
app.registerPlugin({
|
||||
id: PLUGIN_ID,
|
||||
initializer: Initializer,
|
||||
injectedComponents: [],
|
||||
isReady: false,
|
||||
isRequired: isStrapiRequired,
|
||||
layout: null,
|
||||
lifecycles,
|
||||
mainComponent: App,
|
||||
name,
|
||||
preventComponentRendering: false,
|
||||
trads,
|
||||
menu: {
|
||||
pluginsSectionLinks: [
|
||||
{
|
||||
destination: `/plugins/${pluginId}`,
|
||||
icon,
|
||||
label: {
|
||||
id: pluginId,
|
||||
defaultMessage: 'Website Builder',
|
||||
},
|
||||
name,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
async registerTrads({ locales }) {
|
||||
const importedTrads = await Promise.all(
|
||||
locales.map((locale) => {
|
||||
return import(/* webpackChunkName: "translation-[website-builder-request]" */ `./translations/${locale}.json`)
|
||||
.then(({ default: data }) => {
|
||||
return {
|
||||
data: prefixPluginTranslations(data, PLUGIN_ID),
|
||||
locale,
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
data: {},
|
||||
locale,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return strapi.registerPlugin(plugin);
|
||||
return Promise.resolve(importedTrads);
|
||||
},
|
||||
};
|
||||
|
@ -1,3 +0,0 @@
|
||||
function lifecycles() {}
|
||||
|
||||
export default lifecycles;
|
25
admin/src/pages/App/index.js
Normal file
25
admin/src/pages/App/index.js
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
*
|
||||
* This component is the skeleton around the actual pages, and should only
|
||||
* contain code that should be seen on all pages. (e.g. navigation bar)
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { NotFound } from '@strapi/helper-plugin';
|
||||
import { PLUGIN_ID } from '../../utils/constants';
|
||||
import Builds from '../Builds/ListView';
|
||||
import BuildLogs from '../Logs/ListView';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`/plugins/${PLUGIN_ID}`} component={Builds} exact />
|
||||
<Route path={`/plugins/${PLUGIN_ID}/logs`} component={BuildLogs} exact />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
167
admin/src/pages/Builds/ListView/ListView.js
Normal file
167
admin/src/pages/Builds/ListView/ListView.js
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
*
|
||||
* BuildPage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Layout,
|
||||
Main,
|
||||
HeaderLayout,
|
||||
ContentLayout,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Th,
|
||||
Tr,
|
||||
Tbody,
|
||||
Td,
|
||||
EmptyStateLayout,
|
||||
Typography,
|
||||
Switch,
|
||||
LinkButton,
|
||||
Button,
|
||||
Flex,
|
||||
} from '@strapi/design-system';
|
||||
import { EmptyDocuments, Stack, Play } from '@strapi/icons';
|
||||
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
|
||||
import { PLUGIN_ID } from '../../../utils/constants';
|
||||
import { useBuild } from '../../../hooks/useBuild';
|
||||
import { getTrad } from '../../../utils/common';
|
||||
|
||||
const BuildPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [builds, setBuilds] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
const { triggerBuild, getBuilds } = useBuild();
|
||||
|
||||
const { isLoading: isLoadingBuilds, data, isRefetching: isRefetchingBuilds } = getBuilds();
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (!isLoadingBuilds && !isRefetchingBuilds) {
|
||||
if (data) {
|
||||
setBuilds(data);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isLoadingBuilds, isRefetchingBuilds]);
|
||||
|
||||
function handleTriggerBuild(name) {
|
||||
triggerBuild({ name });
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Main aria-busy={isLoading}>
|
||||
<HeaderLayout
|
||||
title={formatMessage({ id: getTrad('builds.header.title'), defaultMessage: 'Builds' })}
|
||||
primaryAction={
|
||||
<LinkButton variant="secondary" size="s" endIcon={<Stack />} to={`/plugins/${PLUGIN_ID}/logs`}>
|
||||
Logs
|
||||
</LinkButton>
|
||||
}
|
||||
/>
|
||||
<ContentLayout>
|
||||
{isLoading ? (
|
||||
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
|
||||
<LoadingIndicatorPage />
|
||||
</Box>
|
||||
) : builds.length > 0 ? (
|
||||
<Table colCount={5} rowCount={builds.length + 1}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th width="5%">
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.enabled'),
|
||||
defaultMessage: 'Enabled',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="5%">
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.trigger'),
|
||||
defaultMessage: 'Trigger',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="20%">
|
||||
<Typography variant="sigma" fontWeight="semiBold" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.name'),
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="60%">
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.url'),
|
||||
defaultMessage: 'URL',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="10%">
|
||||
{formatMessage({
|
||||
id: getTrad('table.header.actions'),
|
||||
defaultMessage: 'Actions',
|
||||
})}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{builds.map((build) => (
|
||||
<Tr key={btoa(build.name)}>
|
||||
<Td>
|
||||
<Switch label="Build Enabled" selected={build.enabled} />
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{build.trigger.type}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography fontWeight="semiBold" textColor="neutral800">
|
||||
{build.name}
|
||||
</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{build.url}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex gap={2}>
|
||||
{build.trigger.type === 'manual' && (
|
||||
<Button
|
||||
variant="default"
|
||||
size="S"
|
||||
disabled={build.enabled === false}
|
||||
endIcon={<Play />}
|
||||
onClick={() => handleTriggerBuild(build.name)}
|
||||
>
|
||||
Trigger
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<EmptyStateLayout
|
||||
icon={<EmptyDocuments width="160px" />}
|
||||
content={formatMessage({
|
||||
id: getTrad('builds.empty'),
|
||||
defaultMessage: 'No builds found',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BuildPage);
|
1
admin/src/pages/Builds/ListView/index.js
Normal file
1
admin/src/pages/Builds/ListView/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './ListView';
|
167
admin/src/pages/Logs/ListView/ListView.js
Normal file
167
admin/src/pages/Logs/ListView/ListView.js
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
*
|
||||
* LogsPage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Layout,
|
||||
Main,
|
||||
HeaderLayout,
|
||||
ContentLayout,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Th,
|
||||
Tr,
|
||||
Tbody,
|
||||
Td,
|
||||
EmptyStateLayout,
|
||||
Typography,
|
||||
VisuallyHidden,
|
||||
IconButton,
|
||||
} from '@strapi/design-system';
|
||||
import { EmptyDocuments, Trash } from '@strapi/icons';
|
||||
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
|
||||
import { useLogs } from '../../../hooks/useLogs';
|
||||
import { getTrad } from '../../../utils/common';
|
||||
|
||||
const LogsPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [logs, setLogs] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
const { getLogs, deleteLog } = useLogs();
|
||||
|
||||
const { isLoading: isLoadingLogs, data: response, isRefetching: isRefetchingLogs } = getLogs({ page: 1 });
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (!isLoadingLogs && !isRefetchingLogs) {
|
||||
if (response && !response.error) {
|
||||
setLogs(response.data);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isLoadingLogs, isRefetchingLogs]);
|
||||
|
||||
function isSuccessStatus(status) {
|
||||
return status >= 200 && 400 > status;
|
||||
}
|
||||
|
||||
function handleLogDelete(id) {
|
||||
deleteLog(id);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Main aria-busy={isLoading}>
|
||||
<HeaderLayout title={formatMessage({ id: getTrad('logs.header.title'), defaultMessage: 'Build Logs' })} />
|
||||
<ContentLayout>
|
||||
{isLoading ? (
|
||||
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
|
||||
<LoadingIndicatorPage />
|
||||
</Box>
|
||||
) : logs.length > 0 ? (
|
||||
<>
|
||||
<Table colCount={5} rowCount={logs.length + 1}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.id'),
|
||||
defaultMessage: 'ID',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.build'),
|
||||
defaultMessage: 'Build',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.trigger'),
|
||||
defaultMessage: 'Trigger',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.status'),
|
||||
defaultMessage: 'Status',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" fontWeight="semiBold" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.timestamp'),
|
||||
defaultMessage: 'Timestamp',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<VisuallyHidden>
|
||||
{formatMessage({
|
||||
id: getTrad('table.header.actions'),
|
||||
defaultMessage: 'Actions',
|
||||
})}
|
||||
</VisuallyHidden>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{logs.map((log) => (
|
||||
<Tr key={log.id}>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.id}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.attributes.trigger}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.attributes.build || 'unknown'}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography
|
||||
fontWeight="semiBold"
|
||||
textColor={isSuccessStatus(log.attributes.status) ? 'success500' : 'danger500'}
|
||||
>
|
||||
{log.attributes.status}
|
||||
</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.attributes.createdAt}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<IconButton onClick={() => handleLogDelete(log.id)} label="Delete" noBorder icon={<Trash />} />
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</>
|
||||
) : (
|
||||
<EmptyStateLayout
|
||||
icon={<EmptyDocuments width="160px" />}
|
||||
content={formatMessage({
|
||||
id: getTrad('logs.empty'),
|
||||
defaultMessage: 'No logs found',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LogsPage);
|
1
admin/src/pages/Logs/ListView/index.js
Normal file
1
admin/src/pages/Logs/ListView/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './ListView';
|
@ -1,13 +1,18 @@
|
||||
{
|
||||
"home.title": "Website Builder",
|
||||
"home.description": "Manually trigger website builds with a click of the button",
|
||||
"home.button.trigger": "Trigger Build",
|
||||
"log.table.title": "Build Logs",
|
||||
"log.table.header.id": "Id",
|
||||
"log.table.header.status": "Status",
|
||||
"log.table.header.timestamp": "Timestamp",
|
||||
"logs.get.error": "An error occured while fetching the build logs",
|
||||
"settings.get.error": "An error occured while fetching plugin settings",
|
||||
"publish.success": "The build has been sucessfully triggered",
|
||||
"publish.error": "The build has encountered an error"
|
||||
"table.header.actions": "Actions",
|
||||
"builds.header.title": "Builds",
|
||||
"builds.table.header.enabled": "Enabled",
|
||||
"builds.table.header.trigger": "Trigger",
|
||||
"builds.table.header.name": "Timestamp",
|
||||
"builds.table.header.url": "URL",
|
||||
"builds.empty": "No builds found",
|
||||
"build.notification.trigger.success": "Build has been triggered successfully",
|
||||
"logs.header.title": "Build Logs",
|
||||
"logs.table.header.id": "ID",
|
||||
"logs.table.header.trigger": "Trigger",
|
||||
"logs.table.header.status": "Status",
|
||||
"logs.table.header.timestamp": "Timestamp",
|
||||
"logs.empty": "No logs found",
|
||||
"log.notification.delete.success": "Build log deleted successfully",
|
||||
"plugin.name": "Website Builder"
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
import en from './en.json';
|
||||
|
||||
const trads = {
|
||||
en,
|
||||
};
|
||||
|
||||
export default trads;
|
5
admin/src/utils/common.js
Normal file
5
admin/src/utils/common.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { PLUGIN_ID } from './constants';
|
||||
|
||||
export function getTrad(id) {
|
||||
return `${PLUGIN_ID}.${id}`;
|
||||
}
|
@ -1,18 +1 @@
|
||||
import pluginPkg from '../../../package.json';
|
||||
|
||||
export const pluginId = pluginPkg.name.replace(/^strapi-plugin-/i, '');
|
||||
|
||||
export const BUILD_LOG_TABLE_HEADERS = [
|
||||
{
|
||||
translation: 'log.table.header.id',
|
||||
value: 'id',
|
||||
},
|
||||
{
|
||||
translation: 'log.table.header.status',
|
||||
value: 'status',
|
||||
},
|
||||
{
|
||||
translation: 'log.table.header.timestamp',
|
||||
value: 'timestamp',
|
||||
},
|
||||
];
|
||||
export const PLUGIN_ID = 'website-builder';
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { pluginId } from './constants';
|
||||
|
||||
/**
|
||||
* Auto prefix URLs with the plugin id
|
||||
*
|
||||
* @param {String} endpoint plugin specific endpoint
|
||||
* @returns {String} plugin id prefixed endpoint
|
||||
*/
|
||||
const getRequestURL = (endpoint) => `/${pluginId}/${endpoint}`;
|
||||
|
||||
export default getRequestURL;
|
@ -1,11 +0,0 @@
|
||||
import { pluginId } from './constants';
|
||||
|
||||
/**
|
||||
* Auto prefix translations with plugin id
|
||||
*
|
||||
* @param {String} id Translation key
|
||||
* @returns {String}
|
||||
*/
|
||||
const getTrad = (id) => `${pluginId}.${id}`;
|
||||
|
||||
export default getTrad;
|
@ -1,15 +0,0 @@
|
||||
import { request } from 'strapi-helper-plugin';
|
||||
import getRequestURL from './getRequestURL';
|
||||
|
||||
/**
|
||||
* Helper function to make requests to plugin specific endpoints
|
||||
*
|
||||
* @param {String} endpoint plugin specific endpoint
|
||||
* @returns {Promise<object>} request response
|
||||
*/
|
||||
const pluginEndpointRequest = (endpoint, data) => {
|
||||
const prefixedEndpoint = getRequestURL(endpoint);
|
||||
return request(prefixedEndpoint, data);
|
||||
};
|
||||
|
||||
export default pluginEndpointRequest;
|
29
config/functions/bootstrap.js
vendored
29
config/functions/bootstrap.js
vendored
@ -1,29 +0,0 @@
|
||||
const { pluginId } = require('../../utils/constants');
|
||||
|
||||
module.exports = async () => {
|
||||
// init plugin store
|
||||
const store = strapi.store({
|
||||
type: 'plugin',
|
||||
name: `${pluginId}_store`,
|
||||
});
|
||||
|
||||
// seed initial data from config
|
||||
let { url, headers, maxNumOfLogs = 5 } = strapi.plugins[pluginId].config;
|
||||
|
||||
// account for 0 indexed array
|
||||
const normalizedMaxNumOfLogs = maxNumOfLogs - 1;
|
||||
|
||||
await store.set({
|
||||
key: 'settings',
|
||||
value: {
|
||||
url,
|
||||
headers,
|
||||
maxNumOfLogs: normalizedMaxNumOfLogs,
|
||||
},
|
||||
});
|
||||
|
||||
await store.set({
|
||||
key: 'build_logs',
|
||||
value: [],
|
||||
});
|
||||
};
|
@ -1,36 +0,0 @@
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/publish",
|
||||
"handler": "publish.index",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/settings",
|
||||
"handler": "settings.get",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/logs",
|
||||
"handler": "logs.get",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/logs",
|
||||
"handler": "logs.update",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* logs.js controller
|
||||
*
|
||||
* @description: A set of functions called "actions" of the `website-builder` plugin.
|
||||
*/
|
||||
|
||||
const { pluginId } = require('../utils/constants');
|
||||
module.exports = {
|
||||
/**
|
||||
* Fetch the current logs
|
||||
*
|
||||
* @return {Array} logs
|
||||
*/
|
||||
|
||||
get: async (ctx) => {
|
||||
try {
|
||||
const logs = await strapi.plugins[pluginId].services.logs.get();
|
||||
|
||||
ctx.send({
|
||||
data: logs,
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.send({
|
||||
data: null,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new log
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
update: async (ctx) => {
|
||||
const log = ctx.query;
|
||||
try {
|
||||
const logs = await strapi.plugins[pluginId].services.logs.get();
|
||||
|
||||
// ensure we always have no more than maxNumOfLogs
|
||||
const { maxNumOfLogs } = await strapi.plugins[pluginId].services.settings.get();
|
||||
const values = logs.slice(-maxNumOfLogs);
|
||||
values.push(log);
|
||||
|
||||
await strapi.plugins[pluginId].services.logs.update(values);
|
||||
|
||||
ctx.send({
|
||||
data: values,
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.send({
|
||||
data: null,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* publish.js controller
|
||||
*
|
||||
* @description: A set of functions called "actions" of the `website-builder` plugin.
|
||||
*/
|
||||
|
||||
const { pluginId } = require('../utils/constants');
|
||||
module.exports = {
|
||||
/**
|
||||
* Trigger a website rebuild
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
index: async (ctx) => {
|
||||
try {
|
||||
const settings = await strapi.plugins[pluginId].services.settings.get();
|
||||
const response = await strapi.plugins[pluginId].services.publish.index(settings);
|
||||
|
||||
ctx.send({
|
||||
data: response.body,
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.send({
|
||||
data: null,
|
||||
error: {
|
||||
code: error.code,
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* settings.js controller
|
||||
*
|
||||
* @description: A set of functions called "actions" of the `website-builder` plugin.
|
||||
*/
|
||||
|
||||
const { pluginId } = require('../utils/constants');
|
||||
module.exports = {
|
||||
/**
|
||||
* Fetch the current plugin settings
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get: async (ctx) => {
|
||||
try {
|
||||
const settings = await strapi.plugins[pluginId].services.settings.get();
|
||||
|
||||
ctx.send({
|
||||
data: settings,
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.send({
|
||||
data: null,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
28
docs/.vitepress/config.mjs
Normal file
28
docs/.vitepress/config.mjs
Normal file
@ -0,0 +1,28 @@
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: 'Strapi Plugin Website Builder',
|
||||
description:
|
||||
'A plugin for Strapi Headless CMS that provides the ability to trigger website builds manually, periodically or through model events.',
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Quick Start', link: '/quick-start' },
|
||||
{ text: 'Configuration API', link: '/config-api' },
|
||||
],
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
items: [
|
||||
{ text: 'Quick Start', link: '/quick-start' },
|
||||
{ text: 'Build Triggers', link: '/build-triggers' },
|
||||
{ text: 'Configuration API', link: '/config-api' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/ComfortablyCoding/strapi-plugin-website-builder' }],
|
||||
},
|
||||
});
|
145
docs/build-triggers.md
Normal file
145
docs/build-triggers.md
Normal file
@ -0,0 +1,145 @@
|
||||
# Builds
|
||||
|
||||
The plugin supports multiple builds and build configurations to cover as many use cases as possible. Each build at minimum must specify a name,url and trigger type to be valid.
|
||||
|
||||
Their are currently three supported trigger types `manual`,`cron`, and `event`.
|
||||
|
||||
## Manual Trigger
|
||||
|
||||
The manual trigger will start a build whenever the trigger button in the admin panel is pressed for the respective build.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'manual'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Periodic Trigger
|
||||
|
||||
The periodic trigger will start a build at the interval specified by the cron expressions. The following example triggers a new build every hour.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'cron',
|
||||
expression: '0 */1 * * *'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Event Trigger
|
||||
|
||||
The event trigger wull start a build whenever one of the specific actions of the defined uid is emitted. The following example triggers a build every time a new article is created.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'event',
|
||||
events: [
|
||||
{
|
||||
uid: 'api::articles.articles',
|
||||
actions: ['create'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
::: info
|
||||
To trigger builds for image mutations use the uid `plugin::upload.file`.
|
||||
:::
|
||||
|
||||
## Multiple Builds
|
||||
|
||||
The plugin supports as many build configurations as you wish to use.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production-manual'
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'manual'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'development',
|
||||
enabled: env('NODE_ENV') !== 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'manual'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'production-edge'
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'cron',
|
||||
expression: '0 */1 * * *'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'production-automated'
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'event',
|
||||
events: [
|
||||
{
|
||||
uid: 'api::articles.articles',
|
||||
actions: ['create'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
196
docs/config-api.md
Normal file
196
docs/config-api.md
Normal file
@ -0,0 +1,196 @@
|
||||
# Configuration API
|
||||
|
||||
- default
|
||||
|
||||
```json
|
||||
{
|
||||
"shared": {},
|
||||
"builds": [],
|
||||
"hooks": {}
|
||||
}
|
||||
```
|
||||
|
||||
## shared
|
||||
|
||||
- type: `object`
|
||||
|
||||
> default values between all builds
|
||||
|
||||
- example
|
||||
|
||||
```json
|
||||
{
|
||||
"headers": {
|
||||
"X-Powered-By": "Strapi CMS"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### headers
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The default headers to be sent with the request for all builds. Any build specific headers with the same nproperty ame will override these. A record is only provided to the function for event triggers.
|
||||
|
||||
### params
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The default parameters to be sent with the request for all builds. Any build specific params with the same property name will override these. A record is only provided to the function for event triggers.
|
||||
|
||||
## build
|
||||
|
||||
- type `array`
|
||||
|
||||
> build configurations
|
||||
|
||||
### name
|
||||
|
||||
- type: `string`
|
||||
- description: The name for the build. **MUST BE UNIQUE**.
|
||||
|
||||
### url
|
||||
|
||||
- type: `string`
|
||||
- description: The url for the build.
|
||||
|
||||
### trigger
|
||||
|
||||
- type: `object`
|
||||
- description: The trigger configuration for the build.
|
||||
|
||||
- #### type
|
||||
|
||||
- type: `string`
|
||||
- description: The trigger type for the build. It can be `manual`,`cron`, or `event`.
|
||||
|
||||
- #### expression
|
||||
|
||||
- type: `string`
|
||||
- description: The cron expression to use for the `cron` tigger type. Required for the `cron` trigger type only.
|
||||
|
||||
- #### events
|
||||
|
||||
- type: `array`
|
||||
- description: The events to trigger new build on. Required for the `event` trigger type only.
|
||||
|
||||
- ##### uid
|
||||
|
||||
- type: `string`
|
||||
- description: The uid of the content type to watch mutations on.
|
||||
|
||||
- ##### actions
|
||||
|
||||
- type: `actions`
|
||||
- description: The actions/mutations to listen for on the content type. Possible values are `create`,`update`,`delete`,`publish`,`unpublish`. `publish` and `unpublish` are only available on content types, not media mutations.
|
||||
|
||||
### method
|
||||
|
||||
- type: `string`
|
||||
- default: `POST`
|
||||
- description: The method to use for the request that triggers the build. Supported methods are `GET` and `POST`.
|
||||
|
||||
### headers
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The headers to be sent with the request. A record is only provided to the function for event triggers.
|
||||
- example
|
||||
|
||||
:::: code-group
|
||||
|
||||
```js [function]
|
||||
{
|
||||
headers: () => ({
|
||||
'X-Powered-By': 'Lorem Ipsum',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```js [object]
|
||||
{
|
||||
headers: {
|
||||
'X-Powered-By': 'Lorem Ipsum',
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
### params
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The parameters to be sent with the request. A record is only provided to the function for event triggers.
|
||||
- example
|
||||
|
||||
:::: code-group
|
||||
|
||||
```js [function]
|
||||
{
|
||||
params: () => ({
|
||||
lorem: 'ipsum',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```js [object]
|
||||
{
|
||||
params: {
|
||||
'lorem': 'ipsum',
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
### body
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The parameters to be sent with the request. A record is only provided to the function for event triggers.
|
||||
- example
|
||||
|
||||
:::: code-group
|
||||
|
||||
```js [function]
|
||||
{
|
||||
body: () => ({
|
||||
lorem: 'ipsum',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```js [object]
|
||||
{
|
||||
body: {
|
||||
'lorem': 'ipsum',
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
## hooks
|
||||
|
||||
- type: `object`
|
||||
|
||||
> hook into specific areas to extend functionality
|
||||
|
||||
### beforeRequest
|
||||
|
||||
- type: `function`
|
||||
- arguments: `requestConfig`
|
||||
- description: Mutate the request before it is sent out
|
||||
- example
|
||||
|
||||
```js
|
||||
{
|
||||
hooks: {
|
||||
beforeRequest: (requestConfig) => {
|
||||
requestConfig.headers.custom = 'custom_value';
|
||||
return config;
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
23
docs/index.md
Normal file
23
docs/index.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: 'Strapi Plugin Website Builder'
|
||||
tagline: 'The only builder you will ever need'
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Quick Start
|
||||
link: /quick-start
|
||||
- theme: alt
|
||||
text: Configuration API
|
||||
link: /config-api
|
||||
|
||||
features:
|
||||
- title: Manual Builds
|
||||
details: Trigger builds manually via the strapi admin.
|
||||
- title: Periodic Builds
|
||||
details: Trigger builds periodically through cron jobs.
|
||||
- title: Events Builds
|
||||
details: Trigger builds based on content type actions.
|
||||
---
|
52
docs/quick-start.md
Normal file
52
docs/quick-start.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Quick Start
|
||||
|
||||
## Installation & Configuration
|
||||
|
||||
::: info
|
||||
This plugin is compatible with Strapi v4.x only. While it may work with the older Strapi versions, they are not supported. It is recommended to always use the latest version of Strapi.
|
||||
:::
|
||||
|
||||
1. Install the plugin in the root directory of your strapi project.
|
||||
|
||||
:::: code-group
|
||||
|
||||
```bash [npm]
|
||||
npm i strapi-plugin-website-builder
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add strapi-plugin-website-builder
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
2. Enable the plugin at `./config/plugins.js`.
|
||||
|
||||
```js
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'manual-build',
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'manual',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once the plugin has been installed and configured, it should show in the sidebar as `Website Builder`. To trigger a manual build select the `Website Builder` menu item in the sidebar and click the `Trigger` button fr the build process you wish to start.
|
||||
|
||||
::: warning
|
||||
If the plugin does not show in the admin sidebar after the plugin is enabled then a clean rebuild of the admin is required. This can be done by deleting the generated `.cache` and `build` folders and re-running the develop command.
|
||||
:::
|
18611
package-lock.json
generated
18611
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
83
package.json
83
package.json
@ -1,42 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package",
|
||||
"name": "strapi-plugin-website-builder",
|
||||
"version": "1.0.2",
|
||||
"description": "A plugin for Strapi Headless CMS that provides the ability to manually trigger website builds",
|
||||
"version": "3.0.1",
|
||||
"description": "A plugin for Strapi Headless CMS that provides the ability to trigger website builds manually, periodically or through model events.",
|
||||
"scripts": {
|
||||
"lint": "eslint --fix",
|
||||
"format": "prettier --write **/*.{ts,js,json,yml}"
|
||||
},
|
||||
"dependencies": {
|
||||
"got": "^11.8.2"
|
||||
"lint:fix": "eslint --fix \"./**/*.{js,yml}\"",
|
||||
"format:fix": "prettier --write \"./**/*.{js,json,yml,md}\"",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"author": {
|
||||
"name": "@ComfortablyCoding (https://github.com/ComfortablyCoding)"
|
||||
"name": "@ComfortablyCoding",
|
||||
"url": "https://github.com/ComfortablyCoding"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0 <=14.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.16.3",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-react": "^7.27.1",
|
||||
"prettier": "^2.4.1"
|
||||
},
|
||||
"keywords": [
|
||||
"publishing",
|
||||
"website-builder",
|
||||
"strapi",
|
||||
"strapi-plugin"
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "@ComfortablyCoding",
|
||||
"url": "https://github.com/ComfortablyCoding"
|
||||
}
|
||||
],
|
||||
"strapi": {
|
||||
"name": "website-builder",
|
||||
"icon": "plug",
|
||||
"description": "A plugin for Strapi Headless CMS that provides the ability to manually trigger website builds"
|
||||
},
|
||||
"homepage": "https://github.com/ComfortablyCoding/strapi-plugin-website-builder#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -44,5 +27,41 @@
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ComfortablyCoding/strapi-plugin-website-builder/issues"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0",
|
||||
"defu": "^6.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"react-query": "^3.39.3",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"prettier": "^2.5.1",
|
||||
"vitepress": "^1.0.0-rc.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@strapi/strapi": "^4.0.7"
|
||||
},
|
||||
"strapi": {
|
||||
"displayName": "Website Builder",
|
||||
"description": "A plugin for Strapi Headless CMS that provides the ability to trigger website builds manually, periodically or through model events.",
|
||||
"name": "website-builder",
|
||||
"kind": "plugin"
|
||||
},
|
||||
"keywords": [
|
||||
"strapi",
|
||||
"strapi-plugin",
|
||||
"plugin",
|
||||
"strapi plugin",
|
||||
"publishing",
|
||||
"website-builder",
|
||||
"website builder",
|
||||
"build website"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
|
20
server/bootstrap/bootstrap.js
vendored
Normal file
20
server/bootstrap/bootstrap.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
const { bootstrapCron } = require('./bootstrapCron');
|
||||
const { bootstrapEvents } = require('./bootstrapEvents');
|
||||
|
||||
module.exports = async ({ strapi }) => {
|
||||
const builds = getService({ strapi, name: 'settings' }).get({ path: 'builds' });
|
||||
|
||||
builds
|
||||
.filter((b) => b.enabled || typeof b.enabled === 'undefined')
|
||||
.forEach((build) => {
|
||||
if (build.trigger.type === 'cron') {
|
||||
bootstrapCron({ strapi, build });
|
||||
} else if (build.trigger.type === 'event') {
|
||||
bootstrapEvents({ strapi, build });
|
||||
}
|
||||
strapi.log.info(`[website builder] ${build.trigger.type} trigger enabled for ${build.name} build`);
|
||||
});
|
||||
};
|
21
server/bootstrap/bootstrapCron.js
vendored
Normal file
21
server/bootstrap/bootstrapCron.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
|
||||
function bootstrapCron({ strapi, build }) {
|
||||
// create cron check
|
||||
strapi.cron.add({
|
||||
[build.name]: {
|
||||
options: {
|
||||
rule: build.trigger.expression,
|
||||
},
|
||||
task: () => {
|
||||
getService({ strapi, name: 'build' }).trigger({ name: build.name, trigger: { type: 'cron' } });
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bootstrapCron,
|
||||
};
|
46
server/bootstrap/bootstrapEvents.js
vendored
Normal file
46
server/bootstrap/bootstrapEvents.js
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
const { FILE_UID, EMIT_ACTIONS } = require('../utils/constants');
|
||||
|
||||
function bootstrapEvents({ strapi, build }) {
|
||||
// build valid events
|
||||
const events = new Map();
|
||||
build.trigger.events.forEach((event) => {
|
||||
// setup actions
|
||||
let actions = new Set();
|
||||
if (event.actions === '*') {
|
||||
EMIT_ACTIONS.forEach((action) => actions.add(action));
|
||||
} else {
|
||||
event.actions.forEach((action) => actions.add(action));
|
||||
}
|
||||
|
||||
// setup events
|
||||
if (event.uid === '*') {
|
||||
Object.keys(strapi.contentTypes)
|
||||
.filter((uid) => /^api::/.test(uid) || uid === FILE_UID)
|
||||
.forEach((uid) => {
|
||||
events.set(uid, actions);
|
||||
});
|
||||
} else {
|
||||
events.set(event.uid, actions);
|
||||
}
|
||||
});
|
||||
|
||||
EMIT_ACTIONS.forEach((action) => {
|
||||
strapi.eventHub.on(`entry.${action}`, ({ uid, entry: record }) => {
|
||||
const entry = events.get(uid);
|
||||
if (entry && entry.has(action)) {
|
||||
getService({ strapi, name: 'build' }).trigger({
|
||||
name: build.name,
|
||||
record,
|
||||
trigger: { type: 'event' },
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bootstrapEvents,
|
||||
};
|
1
server/bootstrap/index.js
Normal file
1
server/bootstrap/index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./bootstrap');
|
12
server/config/index.js
Normal file
12
server/config/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { pluginConfigSchema } = require('./schema.js');
|
||||
|
||||
module.exports = {
|
||||
default: () => ({
|
||||
shared: {},
|
||||
builds: [],
|
||||
hooks: {},
|
||||
}),
|
||||
validator: async (config) => await pluginConfigSchema.validate(config),
|
||||
};
|
75
server/config/schema.js
Normal file
75
server/config/schema.js
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
const yup = require('yup');
|
||||
|
||||
const pluginConfigSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
shared: yup.object(),
|
||||
hooks: yup.object(),
|
||||
builds: yup.array().of(
|
||||
yup.object().shape({
|
||||
enabled: yup.bool(),
|
||||
name: yup.string().required('A build name must be provided'),
|
||||
url: yup.string().required('A URL is required'),
|
||||
trigger: yup
|
||||
.object()
|
||||
.shape({
|
||||
type: yup.string().oneOf(['manual', 'cron', 'event']).required('A trigger type is required'),
|
||||
expression: yup.string().when('type', {
|
||||
is: 'cron',
|
||||
then: yup.string().required('A cron expression must be entered'),
|
||||
}),
|
||||
events: yup.array().when('type', {
|
||||
is: 'event',
|
||||
then: yup
|
||||
.array()
|
||||
.of(
|
||||
yup.object().shape({
|
||||
uid: yup.string().required('A uid is required'),
|
||||
actions: yup.mixed().test({
|
||||
name: 'actions',
|
||||
exclusive: true,
|
||||
message: '${path} must be an string or valid actions',
|
||||
test: (value) =>
|
||||
typeof value === 'string' ||
|
||||
yup
|
||||
.array()
|
||||
.of(yup.string().oneOf(['create', 'update', 'delete', 'publish', 'unpublish']))
|
||||
.required('uid actions are required')
|
||||
.isValid(value),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.min(1, 'At least one event must be provided')
|
||||
.required('events is required'),
|
||||
}),
|
||||
})
|
||||
.required('A trigger is required'),
|
||||
method: yup.mixed().oneOf(['GET', 'POST']).optional(),
|
||||
params: yup.mixed().test({
|
||||
name: 'params',
|
||||
exclusive: true,
|
||||
message: '${path} must be an object or function',
|
||||
test: (value) => typeof value === 'function' || yup.object().isValid(value),
|
||||
}),
|
||||
headers: yup.mixed().test({
|
||||
name: 'headers',
|
||||
exclusive: true,
|
||||
message: '${path} must be an object or function',
|
||||
test: (value) => typeof value === 'function' || yup.object().isValid(value),
|
||||
}),
|
||||
body: yup.mixed().test({
|
||||
name: 'body',
|
||||
exclusive: true,
|
||||
message: '${path} must be an object or function',
|
||||
test: (value) => typeof value === 'function' || yup.object().isValid(value),
|
||||
}),
|
||||
})
|
||||
),
|
||||
})
|
||||
.required('A config is required');
|
||||
|
||||
module.exports = {
|
||||
pluginConfigSchema,
|
||||
};
|
7
server/content-types/index.js
Normal file
7
server/content-types/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const logContentType = require('./log-content-type');
|
||||
|
||||
module.exports = {
|
||||
log: { schema: logContentType },
|
||||
};
|
39
server/content-types/log-content-type/index.js
Normal file
39
server/content-types/log-content-type/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
kind: 'collectionType',
|
||||
collectionName: 'logs',
|
||||
info: {
|
||||
singularName: 'log',
|
||||
pluralName: 'logs',
|
||||
displayName: 'logs',
|
||||
},
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false,
|
||||
},
|
||||
'content-type-builder': {
|
||||
visible: false,
|
||||
},
|
||||
},
|
||||
options: {
|
||||
draftAndPublish: false,
|
||||
},
|
||||
attributes: {
|
||||
status: {
|
||||
type: 'integer',
|
||||
},
|
||||
build: {
|
||||
type: 'string',
|
||||
},
|
||||
trigger: {
|
||||
type: 'string',
|
||||
},
|
||||
method: {
|
||||
type: 'string',
|
||||
},
|
||||
response: {
|
||||
type: 'json',
|
||||
},
|
||||
},
|
||||
};
|
32
server/controllers/build-controller.js
Normal file
32
server/controllers/build-controller.js
Normal file
@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
/**
|
||||
* Trigger a website build
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
async trigger(ctx) {
|
||||
try {
|
||||
const { status } = await getService({ strapi, name: 'build' }).trigger({
|
||||
name: ctx.request.body.data.name,
|
||||
trigger: { type: 'manual' },
|
||||
});
|
||||
|
||||
ctx.send({ data: { status } });
|
||||
} catch (error) {
|
||||
ctx.badRequest();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all builds
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
async find(ctx) {
|
||||
ctx.send({ data: getService({ strapi, name: 'settings' }).get({ path: 'builds' }) });
|
||||
},
|
||||
});
|
9
server/controllers/index.js
Normal file
9
server/controllers/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const log = require('./log-controller');
|
||||
const build = require('./build-controller');
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
build,
|
||||
};
|
9
server/controllers/log-controller.js
Normal file
9
server/controllers/log-controller.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* controller
|
||||
*/
|
||||
|
||||
const { createCoreController } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreController('plugin::website-builder.log');
|
17
server/index.js
Normal file
17
server/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const bootstrap = require('./bootstrap');
|
||||
const config = require('./config');
|
||||
const contentTypes = require('./content-types');
|
||||
const controllers = require('./controllers');
|
||||
const routes = require('./routes');
|
||||
const services = require('./services');
|
||||
|
||||
module.exports = {
|
||||
bootstrap,
|
||||
config,
|
||||
controllers,
|
||||
routes,
|
||||
services,
|
||||
contentTypes,
|
||||
};
|
14
server/routes/build-routes.js
Normal file
14
server/routes/build-routes.js
Normal file
@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/builds',
|
||||
handler: 'build.trigger',
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/builds',
|
||||
handler: 'build.find',
|
||||
},
|
||||
];
|
6
server/routes/index.js
Normal file
6
server/routes/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const logRoutes = require('./log-routes');
|
||||
const buildRoutes = require('./build-routes');
|
||||
|
||||
module.exports = [...logRoutes, ...buildRoutes];
|
14
server/routes/log-routes.js
Normal file
14
server/routes/log-routes.js
Normal file
@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/logs',
|
||||
handler: 'log.find',
|
||||
},
|
||||
{
|
||||
method: 'DELETE',
|
||||
path: '/logs/:id',
|
||||
handler: 'log.delete',
|
||||
},
|
||||
];
|
32
server/services/build-service.js
Normal file
32
server/services/build-service.js
Normal file
@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
async trigger({ name, record, trigger }) {
|
||||
let log = { trigger: trigger.type, status: 500, build: name };
|
||||
|
||||
try {
|
||||
const request = await getService({ strapi, name: 'request' }).build({
|
||||
name,
|
||||
record,
|
||||
trigger,
|
||||
});
|
||||
const response = await getService({ strapi, name: 'request' }).execute(request);
|
||||
log.status = response.status;
|
||||
log.response = response.data;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
log.status = error.response.status;
|
||||
} else if (error.request) {
|
||||
log.response = {};
|
||||
} else {
|
||||
log.response = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
getService({ strapi, name: 'log' }).create({ data: log });
|
||||
|
||||
return { status: log.status };
|
||||
},
|
||||
});
|
13
server/services/index.js
Normal file
13
server/services/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const log = require('./log-service');
|
||||
const build = require('./build-service');
|
||||
const settings = require('./settings-service');
|
||||
const request = require('./request-service');
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
build,
|
||||
settings,
|
||||
request,
|
||||
};
|
9
server/services/log-service.js
Normal file
9
server/services/log-service.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* service
|
||||
*/
|
||||
|
||||
const { createCoreService } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreService('plugin::website-builder.log');
|
61
server/services/request-service.js
Normal file
61
server/services/request-service.js
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios').default;
|
||||
const { defu } = require('defu');
|
||||
const { getService, resolveValue } = require('../utils/common');
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
async build({ name, record, trigger }) {
|
||||
let request = {};
|
||||
const { shared, builds } = getService({ strapi, name: 'settings' }).get();
|
||||
|
||||
const build = builds.find((b) => b.name === name);
|
||||
|
||||
if (!build) {
|
||||
return;
|
||||
}
|
||||
|
||||
// method
|
||||
request.method = build.method || 'POST';
|
||||
|
||||
// url
|
||||
request.url = build.url;
|
||||
|
||||
// body
|
||||
if (build.body) {
|
||||
request.data = await resolveValue({ value: build.body, args: { record, trigger } });
|
||||
}
|
||||
|
||||
// params
|
||||
if (shared.params) {
|
||||
request.params = await resolveValue({ value: shared.params, args: { record, trigger } });
|
||||
}
|
||||
|
||||
if (build.params) {
|
||||
const paramsValue = await resolveValue({ value: build.params, args: { record, trigger } });
|
||||
|
||||
request.params = defu(paramsValue, request.params || {});
|
||||
}
|
||||
|
||||
// headers
|
||||
if (shared.headers) {
|
||||
request.headers = await resolveValue({ value: shared.headers, args: { record, trigger } });
|
||||
}
|
||||
|
||||
if (build.headers) {
|
||||
const headerValue = await resolveValue({ value: build.headers, args: { record, trigger } });
|
||||
request.headers = defu(headerValue, request.headers || {});
|
||||
}
|
||||
|
||||
return request;
|
||||
},
|
||||
async execute(request) {
|
||||
const hooks = getService({ strapi, name: 'settings' }).get({ path: 'hooks' });
|
||||
|
||||
if (hooks.beforeRequest) {
|
||||
axios.interceptors.request.use(hooks.beforeRequest);
|
||||
}
|
||||
|
||||
return axios(request);
|
||||
},
|
||||
});
|
16
server/services/settings-service.js
Normal file
16
server/services/settings-service.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const { PLUGIN_ID } = require('../utils/constants');
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
get({ path = '', defaultValue } = {}) {
|
||||
if (path.length) {
|
||||
path = `.${path}`;
|
||||
}
|
||||
|
||||
return strapi.config.get(`plugin.${PLUGIN_ID}${path}`, defaultValue);
|
||||
},
|
||||
set({ path = '', value }) {
|
||||
return strapi.config.set(`plugin.website-builder${path}`, value);
|
||||
},
|
||||
});
|
18
server/utils/common.js
Normal file
18
server/utils/common.js
Normal file
@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
function getService({ strapi, name, plugin = 'website-builder' }) {
|
||||
return strapi.plugin(plugin).service(name);
|
||||
}
|
||||
|
||||
async function resolveValue({ value, args }) {
|
||||
if (typeof value === 'function') {
|
||||
return await value(args);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getService,
|
||||
resolveValue,
|
||||
};
|
11
server/utils/constants.js
Normal file
11
server/utils/constants.js
Normal file
@ -0,0 +1,11 @@
|
||||
const PLUGIN_ID = 'website-builder';
|
||||
|
||||
const FILE_UID = 'plugin::upload.file';
|
||||
|
||||
const EMIT_ACTIONS = ['create', 'update', 'delete', 'publish', 'unpublish'];
|
||||
|
||||
module.exports = {
|
||||
PLUGIN_ID,
|
||||
FILE_UID,
|
||||
EMIT_ACTIONS,
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* logs.js service
|
||||
*
|
||||
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Returns the currently stored build logs
|
||||
*
|
||||
* @return {Promise<array>} logs
|
||||
*/
|
||||
get: () => {
|
||||
return strapi
|
||||
.store({
|
||||
type: 'plugin',
|
||||
name: 'website-builder_store',
|
||||
})
|
||||
.get({ key: 'build_logs' });
|
||||
},
|
||||
/**
|
||||
* Updates the build logs to a new set
|
||||
*
|
||||
* @param {array} logs The new set of build logs to update to.
|
||||
*
|
||||
* @return {Promise<array>} logs
|
||||
*/
|
||||
|
||||
update: (logs) => {
|
||||
return strapi
|
||||
.store({
|
||||
type: 'plugin',
|
||||
name: 'website-builder_store',
|
||||
})
|
||||
.set({ key: 'build_logs', value: logs });
|
||||
},
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* publish.js service
|
||||
*
|
||||
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
||||
*/
|
||||
|
||||
const got = require('got');
|
||||
module.exports = {
|
||||
/**
|
||||
* Makes a request to the url specified in the plugin config
|
||||
*
|
||||
* @param {object} settings The configuration settings for the plugin
|
||||
*
|
||||
* @return {Promise<object>} response The response data from the url
|
||||
*/
|
||||
index: (settings) => {
|
||||
const { url, headers } = settings;
|
||||
const options = {
|
||||
responseType: 'json',
|
||||
};
|
||||
|
||||
if (headers) {
|
||||
options.headers = headers;
|
||||
}
|
||||
|
||||
return got(url, options);
|
||||
},
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* settings.js service
|
||||
*
|
||||
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Returns the current settings object
|
||||
*
|
||||
* @return {Promise<object>} settings The configuration settings for the plugin
|
||||
*/
|
||||
get: () => {
|
||||
return strapi
|
||||
.store({
|
||||
type: 'plugin',
|
||||
name: 'website-builder_store',
|
||||
})
|
||||
.get({ key: 'settings' });
|
||||
},
|
||||
};
|
3
strapi-admin.js
Normal file
3
strapi-admin.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./admin/src').default;
|
3
strapi-server.js
Normal file
3
strapi-server.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./server');
|
@ -1,9 +0,0 @@
|
||||
const pluginPkg = require('../package.json');
|
||||
|
||||
module.exports = {
|
||||
pluginId: pluginPkg.name.replace(/^strapi-plugin-/i, ''),
|
||||
pluginDescription: pluginPkg.strapi.description || pluginPkg.description,
|
||||
icon: pluginPkg.strapi.icon,
|
||||
name: pluginPkg.strapi.name,
|
||||
isStrapiRequired: pluginPkg.strapi.required || false,
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user