feat: supports subdirectory deployment (#3731)
* feat: supports subdirectory deployment * feat: auto publicPath * fix: buildIndexHtml * fix: format * fix: regexp * fix: test error * fix: nocobase.conf * fix: path * fix: nocobase.conf * fix: bugs * fix: resourcer prefix * fix: cas
This commit is contained in:
parent
126f60c959
commit
b359f9eac6
@ -30,7 +30,6 @@ RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \
|
|||||||
&& apt-get update && apt-get install -y nginx
|
&& apt-get update && apt-get install -y nginx
|
||||||
|
|
||||||
RUN rm -rf /etc/nginx/sites-enabled/default
|
RUN rm -rf /etc/nginx/sites-enabled/default
|
||||||
COPY ./nocobase.conf /etc/nginx/sites-enabled/nocobase.conf
|
|
||||||
COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz
|
COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz
|
||||||
|
|
||||||
WORKDIR /app/nocobase
|
WORKDIR /app/nocobase
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
nginx
|
|
||||||
echo 'nginx started';
|
|
||||||
|
|
||||||
if [ ! -d "/app/nocobase" ]; then
|
if [ ! -d "/app/nocobase" ]; then
|
||||||
mkdir nocobase
|
mkdir nocobase
|
||||||
fi
|
fi
|
||||||
@ -14,6 +11,13 @@ if [ ! -f "/app/nocobase/package.json" ]; then
|
|||||||
touch /app/nocobase/node_modules/@nocobase/app/dist/client/index.html
|
touch /app/nocobase/node_modules/@nocobase/app/dist/client/index.html
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cd /app/nocobase && yarn nocobase create-nginx-conf
|
||||||
|
rm -rf /etc/nginx/sites-enabled/nocobase.conf
|
||||||
|
ln -s /app/nocobase/storage/nocobase.conf /etc/nginx/sites-enabled/nocobase.conf
|
||||||
|
|
||||||
|
nginx
|
||||||
|
echo 'nginx started';
|
||||||
|
|
||||||
cd /app/nocobase && yarn start --quickstart
|
cd /app/nocobase && yarn start --quickstart
|
||||||
|
|
||||||
# Run command with node if the first argument contains a "-" or is not a system command. The last
|
# Run command with node if the first argument contains a "-" or is not a system command. The last
|
||||||
|
@ -19,16 +19,29 @@ indexGenerator.generate();
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: 'Loading...',
|
title: 'Loading...',
|
||||||
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false,
|
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false,
|
||||||
favicons: ['/favicon/favicon.ico'],
|
favicons: [`{{env.APP_PUBLIC_PATH}}favicon/favicon.ico`],
|
||||||
metas: [{ name: 'viewport', content: 'initial-scale=0.1' }],
|
metas: [{ name: 'viewport', content: 'initial-scale=0.1' }],
|
||||||
links: [
|
links: [
|
||||||
{ rel: 'apple-touch-icon', size: '180x180', ref: '/favicon/apple-touch-icon.png' },
|
{ rel: 'apple-touch-icon', size: '180x180', ref: `{{env.APP_PUBLIC_PATH}}favicon/apple-touch-icon.png` },
|
||||||
{ rel: 'icon', type: 'image/png', size: '32x32', ref: '/favicon/favicon-32x32.png' },
|
{ rel: 'icon', type: 'image/png', size: '32x32', ref: `{{env.APP_PUBLIC_PATH}}favicon/favicon-32x32.png` },
|
||||||
{ rel: 'icon', type: 'image/png', size: '16x16', ref: '/favicon/favicon-16x16.png' },
|
{ rel: 'icon', type: 'image/png', size: '16x16', ref: `{{env.APP_PUBLIC_PATH}}favicon/favicon-16x16.png` },
|
||||||
{ rel: 'manifest', href: '/favicon/site.webmanifest' },
|
{ rel: 'manifest', href: `{{env.APP_PUBLIC_PATH}}favicon/site.webmanifest` },
|
||||||
{ rel: 'stylesheet', href: '/global.css' },
|
{ rel: 'stylesheet', href: `{{env.APP_PUBLIC_PATH}}global.css` },
|
||||||
|
],
|
||||||
|
headScripts: [
|
||||||
|
{
|
||||||
|
src: `{{env.APP_PUBLIC_PATH}}browser-checker.js`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: `
|
||||||
|
window['__webpack_public_path__'] = '{{env.APP_PUBLIC_PATH}}';
|
||||||
|
window['__nocobase_api_base_url__'] = '{{env.API_BASE_URL}}';
|
||||||
|
window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}';
|
||||||
|
window['__nocobase_ws_url__'] = '{{env.WS_URL}}';
|
||||||
|
window['__nocobase_ws_path__'] = '{{env.WS_PATH}}';
|
||||||
|
`,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
headScripts: ['/browser-checker.js'],
|
|
||||||
outputPath: path.resolve(__dirname, '../dist/client'),
|
outputPath: path.resolve(__dirname, '../dist/client'),
|
||||||
hash: true,
|
hash: true,
|
||||||
alias: {
|
alias: {
|
||||||
@ -41,6 +54,7 @@ export default defineConfig({
|
|||||||
proxy: {
|
proxy: {
|
||||||
...umiConfig.proxy,
|
...umiConfig.proxy,
|
||||||
},
|
},
|
||||||
|
publicPath: 'auto',
|
||||||
fastRefresh: false, // 热更新会导致 Context 丢失,不开启
|
fastRefresh: false, // 热更新会导致 Context 丢失,不开启
|
||||||
mfsu: false,
|
mfsu: false,
|
||||||
esbuildMinifyIIFE: true,
|
esbuildMinifyIIFE: true,
|
||||||
|
@ -4,10 +4,18 @@ import devDynamicImport from '../.plugins/index';
|
|||||||
|
|
||||||
export const app = new Application({
|
export const app = new Application({
|
||||||
apiClient: {
|
apiClient: {
|
||||||
baseURL: process.env.API_BASE_URL,
|
// @ts-ignore
|
||||||
|
baseURL: window['__nocobase_api_base_url__'] || '/api/',
|
||||||
},
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
publicPath: window['__nocobase_public_path__'] || '/',
|
||||||
plugins: [NocoBaseClientPresetPlugin],
|
plugins: [NocoBaseClientPresetPlugin],
|
||||||
ws: true,
|
ws: {
|
||||||
|
// @ts-ignore
|
||||||
|
url: window['__nocobase_ws_url__'] || '',
|
||||||
|
// @ts-ignore
|
||||||
|
basename: window['__nocobase_ws_path__'] || '/ws',
|
||||||
|
},
|
||||||
loadRemotePlugins: true,
|
loadRemotePlugins: true,
|
||||||
devDynamicImport,
|
devDynamicImport,
|
||||||
});
|
});
|
||||||
|
89
packages/core/cli/nocobase.conf.tpl
Normal file
89
packages/core/cli/nocobase.conf.tpl
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
log_format apm '"$time_local" client=$remote_addr '
|
||||||
|
'method=$request_method request="$request" '
|
||||||
|
'request_length=$request_length '
|
||||||
|
'status=$status bytes_sent=$bytes_sent '
|
||||||
|
'body_bytes_sent=$body_bytes_sent '
|
||||||
|
'referer=$http_referer '
|
||||||
|
'user_agent="$http_user_agent" '
|
||||||
|
'upstream_addr=$upstream_addr '
|
||||||
|
'upstream_status=$upstream_status '
|
||||||
|
'request_time=$request_time '
|
||||||
|
'upstream_response_time=$upstream_response_time '
|
||||||
|
'upstream_connect_time=$upstream_connect_time '
|
||||||
|
'upstream_header_time=$upstream_header_time';
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root {{cwd}}/node_modules/@nocobase/app/dist/client;
|
||||||
|
index index.html;
|
||||||
|
client_max_body_size 1000M;
|
||||||
|
access_log /var/log/nginx/nocobase.log apm;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
# 不缓存 HTML 文件
|
||||||
|
# location ~ \.html$ {
|
||||||
|
# if_modified_since off;
|
||||||
|
# expires off;
|
||||||
|
# etag off;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# # 缓存 JavaScript 和 CSS 文件
|
||||||
|
# location ~* \.(js|css)$ {
|
||||||
|
# expires 365d;
|
||||||
|
# add_header Cache-Control "public";
|
||||||
|
# }
|
||||||
|
|
||||||
|
location {{publicPath}}storage/uploads/ {
|
||||||
|
alias {{cwd}}/storage/uploads/;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
access_log off;
|
||||||
|
autoindex off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location {{publicPath}} {
|
||||||
|
alias {{cwd}}/node_modules/@nocobase/app/dist/client/;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
add_header Last-Modified $date_gmt;
|
||||||
|
add_header Cache-Control 'no-store, no-cache';
|
||||||
|
if_modified_since off;
|
||||||
|
expires off;
|
||||||
|
etag off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ {{publicPath}}api/ {
|
||||||
|
proxy_pass http://127.0.0.1:{{apiPort}}{{publicPath}}api/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_connect_timeout 600;
|
||||||
|
proxy_send_timeout 600;
|
||||||
|
proxy_read_timeout 600;
|
||||||
|
send_timeout 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ {{publicPath}}static/plugins/ {
|
||||||
|
proxy_pass http://127.0.0.1:{{apiPort}}{{publicPath}}static/plugins/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_connect_timeout 600;
|
||||||
|
proxy_send_timeout 600;
|
||||||
|
proxy_read_timeout 600;
|
||||||
|
send_timeout 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
location {{publicPath}}ws {
|
||||||
|
proxy_pass http://127.0.0.1:{{apiPort}}{{publicPath}}ws;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
const { resolve } = require('path');
|
const { resolve } = require('path');
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { run, nodeCheck, isPackageValid } = require('../util');
|
const { run, nodeCheck, isPackageValid, buildIndexHtml } = require('../util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -31,5 +31,6 @@ module.exports = (cli) => {
|
|||||||
!options.dts ? '--no-dts' : '',
|
!options.dts ? '--no-dts' : '',
|
||||||
options.sourcemap ? '--sourcemap' : '',
|
options.sourcemap ? '--sourcemap' : '',
|
||||||
]);
|
]);
|
||||||
|
buildIndexHtml(true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
21
packages/core/cli/src/commands/create-nginx-conf.js
Normal file
21
packages/core/cli/src/commands/create-nginx-conf.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const { resolve } = require('path');
|
||||||
|
const { Command } = require('commander');
|
||||||
|
const { readFileSync, writeFileSync } = require('fs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Command} cli
|
||||||
|
*/
|
||||||
|
module.exports = (cli) => {
|
||||||
|
cli.command('create-nginx-conf').action(async (name, options) => {
|
||||||
|
const file = resolve(__dirname, '../../nocobase.conf.tpl');
|
||||||
|
const data = readFileSync(file, 'utf-8');
|
||||||
|
const replaced = data
|
||||||
|
.replace(/\{\{cwd\}\}/g, '/app/nocobase')
|
||||||
|
.replace(/\{\{publicPath\}\}/g, process.env.APP_PUBLIC_PATH)
|
||||||
|
.replace(/\{\{apiPort\}\}/g, process.env.APP_PORT);
|
||||||
|
|
||||||
|
const targetFile = resolve(process.cwd(), 'storage', 'nocobase.conf');
|
||||||
|
writeFileSync(targetFile, replaced);
|
||||||
|
});
|
||||||
|
};
|
@ -8,6 +8,7 @@ const { isPackageValid, generateAppDir } = require('../util');
|
|||||||
module.exports = (cli) => {
|
module.exports = (cli) => {
|
||||||
generateAppDir();
|
generateAppDir();
|
||||||
require('./global')(cli);
|
require('./global')(cli);
|
||||||
|
require('./create-nginx-conf')(cli);
|
||||||
require('./build')(cli);
|
require('./build')(cli);
|
||||||
require('./tar')(cli);
|
require('./tar')(cli);
|
||||||
require('./dev')(cli);
|
require('./dev')(cli);
|
||||||
|
@ -180,6 +180,7 @@ exports.generateAppDir = function generateAppDir() {
|
|||||||
} else {
|
} else {
|
||||||
process.env.APP_PACKAGE_ROOT = appPkgPath;
|
process.env.APP_PACKAGE_ROOT = appPkgPath;
|
||||||
}
|
}
|
||||||
|
buildIndexHtml();
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.genTsConfigPaths = function genTsConfigPaths() {
|
exports.genTsConfigPaths = function genTsConfigPaths() {
|
||||||
@ -257,6 +258,30 @@ function parseEnv(name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildIndexHtml(force = false) {
|
||||||
|
const file = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html`;
|
||||||
|
if (!fs.existsSync(file)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tpl = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html.tpl`;
|
||||||
|
if (force && fs.existsSync(tpl)) {
|
||||||
|
fs.rmSync(tpl);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(tpl)) {
|
||||||
|
fs.copyFileSync(file, tpl);
|
||||||
|
}
|
||||||
|
const data = fs.readFileSync(tpl, 'utf-8');
|
||||||
|
const replacedData = data
|
||||||
|
.replace(/\{\{env.APP_PUBLIC_PATH\}\}/g, process.env.APP_PUBLIC_PATH)
|
||||||
|
.replace(/\{\{env.API_BASE_URL\}\}/g, process.env.API_BASE_URL || process.env.API_BASE_PATH)
|
||||||
|
.replace(/\{\{env.WS_URL\}\}/g, process.env.WEBSOCKET_URL || '')
|
||||||
|
.replace(/\{\{env.WS_PATH\}\}/g, process.env.WS_PATH)
|
||||||
|
.replace('src="/umi.', `src="${process.env.APP_PUBLIC_PATH}umi.`);
|
||||||
|
fs.writeFileSync(file, replacedData, 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.buildIndexHtml = buildIndexHtml;
|
||||||
|
|
||||||
exports.initEnv = function initEnv() {
|
exports.initEnv = function initEnv() {
|
||||||
const env = {
|
const env = {
|
||||||
APP_ENV: 'development',
|
APP_ENV: 'development',
|
||||||
@ -280,7 +305,10 @@ exports.initEnv = function initEnv() {
|
|||||||
PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'),
|
PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'),
|
||||||
CACHE_DEFAULT_STORE: 'memory',
|
CACHE_DEFAULT_STORE: 'memory',
|
||||||
CACHE_MEMORY_MAX: 2000,
|
CACHE_MEMORY_MAX: 2000,
|
||||||
|
PLUGIN_STATICS_PATH: '/static/plugins/',
|
||||||
LOGGER_BASE_PATH: 'storage/logs',
|
LOGGER_BASE_PATH: 'storage/logs',
|
||||||
|
APP_SERVER_BASE_URL: '',
|
||||||
|
APP_PUBLIC_PATH: '/',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -319,4 +347,16 @@ exports.initEnv = function initEnv() {
|
|||||||
process.env[key] = env[key];
|
process.env[key] = env[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.APP_PUBLIC_PATH) {
|
||||||
|
const publicPath = process.env.APP_PUBLIC_PATH.replace(/\/$/g, '');
|
||||||
|
const keys = ['API_BASE_PATH', 'WS_PATH', 'PLUGIN_STATICS_PATH'];
|
||||||
|
for (const key of keys) {
|
||||||
|
process.env[key] = publicPath + process.env[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.APP_SERVER_BASE_URL && !process.env.API_BASE_URL) {
|
||||||
|
process.env.API_BASE_URL = process.env.APP_SERVER_BASE_URL + process.env.API_BASE_PATH;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { APIClient as APIClientSDK } from '@nocobase/sdk';
|
import { APIClient as APIClientSDK, getSubAppName } from '@nocobase/sdk';
|
||||||
import { Result } from 'ahooks/es/useRequest/src/types';
|
import { Result } from 'ahooks/es/useRequest/src/types';
|
||||||
import { notification } from 'antd';
|
import { notification } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -39,9 +39,9 @@ export class APIClient extends APIClientSDK {
|
|||||||
interceptors() {
|
interceptors() {
|
||||||
this.axios.interceptors.request.use((config) => {
|
this.axios.interceptors.request.use((config) => {
|
||||||
config.headers['X-With-ACL-Meta'] = true;
|
config.headers['X-With-ACL-Meta'] = true;
|
||||||
const match = location.pathname.match(/^\/apps\/([^/]*)\//);
|
const appName = this.app ? getSubAppName(this.app.getPublicPath()) : null;
|
||||||
if (match) {
|
if (appName) {
|
||||||
config.headers['X-App'] = match[1];
|
config.headers['X-App'] = appName;
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
@ -9,14 +9,14 @@ import { createRoot } from 'react-dom/client';
|
|||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import { Link, NavLink, Navigate } from 'react-router-dom';
|
import { Link, NavLink, Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { APIClient, APIClientProvider } from '../api-client';
|
||||||
import { CSSVariableProvider } from '../css-variable';
|
import { CSSVariableProvider } from '../css-variable';
|
||||||
import { AntdAppProvider, GlobalThemeProvider } from '../global-theme';
|
import { AntdAppProvider, GlobalThemeProvider } from '../global-theme';
|
||||||
|
import { i18n } from '../i18n';
|
||||||
import { PluginManager, PluginType } from './PluginManager';
|
import { PluginManager, PluginType } from './PluginManager';
|
||||||
import { PluginSettingOptions, PluginSettingsManager } from './PluginSettingsManager';
|
import { PluginSettingOptions, PluginSettingsManager } from './PluginSettingsManager';
|
||||||
import { ComponentTypeAndString, RouterManager, RouterOptions } from './RouterManager';
|
import { ComponentTypeAndString, RouterManager, RouterOptions } from './RouterManager';
|
||||||
import { WebSocketClient, WebSocketClientOptions } from './WebSocketClient';
|
import { WebSocketClient, WebSocketClientOptions } from './WebSocketClient';
|
||||||
import { APIClient, APIClientProvider } from '../api-client';
|
|
||||||
import { i18n } from '../i18n';
|
|
||||||
import { AppComponent, BlankComponent, defaultAppComponents } from './components';
|
import { AppComponent, BlankComponent, defaultAppComponents } from './components';
|
||||||
import { SchemaInitializer, SchemaInitializerManager } from './schema-initializer';
|
import { SchemaInitializer, SchemaInitializerManager } from './schema-initializer';
|
||||||
import * as schemaInitializerComponents from './schema-initializer/components';
|
import * as schemaInitializerComponents from './schema-initializer/components';
|
||||||
@ -25,10 +25,10 @@ import { compose, normalizeContainer } from './utils';
|
|||||||
import { defineGlobalDeps } from './utils/globalDeps';
|
import { defineGlobalDeps } from './utils/globalDeps';
|
||||||
import { getRequireJs } from './utils/requirejs';
|
import { getRequireJs } from './utils/requirejs';
|
||||||
|
|
||||||
import { type DataSourceManagerOptions, DataSourceManager } from '../data-source/data-source/DataSourceManager';
|
|
||||||
import { DataSourceApplicationProvider } from '../data-source/components/DataSourceApplicationProvider';
|
|
||||||
import { CollectionField } from '../data-source/collection-field/CollectionField';
|
import { CollectionField } from '../data-source/collection-field/CollectionField';
|
||||||
|
import { DataSourceApplicationProvider } from '../data-source/components/DataSourceApplicationProvider';
|
||||||
import { DataBlockProvider } from '../data-source/data-block/DataBlockProvider';
|
import { DataBlockProvider } from '../data-source/data-block/DataBlockProvider';
|
||||||
|
import { DataSourceManager, type DataSourceManagerOptions } from '../data-source/data-source/DataSourceManager';
|
||||||
|
|
||||||
import { AppSchemaComponentProvider } from './AppSchemaComponentProvider';
|
import { AppSchemaComponentProvider } from './AppSchemaComponentProvider';
|
||||||
import type { Plugin } from './Plugin';
|
import type { Plugin } from './Plugin';
|
||||||
@ -44,6 +44,7 @@ export type DevDynamicImport = (packageName: string) => Promise<{ default: typeo
|
|||||||
export type ComponentAndProps<T = any> = [ComponentType, T];
|
export type ComponentAndProps<T = any> = [ComponentType, T];
|
||||||
export interface ApplicationOptions {
|
export interface ApplicationOptions {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
publicPath?: string;
|
||||||
apiClient?: APIClientOptions | APIClient;
|
apiClient?: APIClientOptions | APIClient;
|
||||||
ws?: WebSocketClientOptions | boolean;
|
ws?: WebSocketClientOptions | boolean;
|
||||||
i18n?: i18next;
|
i18n?: i18next;
|
||||||
@ -116,9 +117,10 @@ export class Application {
|
|||||||
this.addReactRouterComponents();
|
this.addReactRouterComponents();
|
||||||
this.addProviders(options.providers || []);
|
this.addProviders(options.providers || []);
|
||||||
this.ws = new WebSocketClient(options.ws);
|
this.ws = new WebSocketClient(options.ws);
|
||||||
|
this.ws.app = this;
|
||||||
this.pluginSettingsManager = new PluginSettingsManager(options.pluginSettings, this);
|
this.pluginSettingsManager = new PluginSettingsManager(options.pluginSettings, this);
|
||||||
this.addRoutes();
|
this.addRoutes();
|
||||||
this.name = this.options.name || getSubAppName() || 'main';
|
this.name = this.options.name || getSubAppName(options.publicPath) || 'main';
|
||||||
}
|
}
|
||||||
|
|
||||||
private initRequireJs() {
|
private initRequireJs() {
|
||||||
@ -157,6 +159,18 @@ export class Application {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOptions() {
|
||||||
|
return this.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPublicPath() {
|
||||||
|
return this.options.publicPath || '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
getRouteUrl(pathname: string) {
|
||||||
|
return this.options.publicPath.replace(/\/$/g, '') + pathname;
|
||||||
|
}
|
||||||
|
|
||||||
getCollectionManager(dataSource?: string) {
|
getCollectionManager(dataSource?: string) {
|
||||||
return this.dataSourceManager.getDataSource(dataSource)?.collectionManager;
|
return this.dataSourceManager.getDataSource(dataSource)?.collectionManager;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { set, get } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
import React, { ComponentType } from 'react';
|
import React, { ComponentType } from 'react';
|
||||||
import {
|
import {
|
||||||
BrowserRouter,
|
BrowserRouter,
|
||||||
@ -10,8 +10,8 @@ import {
|
|||||||
RouteObject,
|
RouteObject,
|
||||||
useRoutes,
|
useRoutes,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { BlankComponent, RouterContextCleaner } from './components';
|
|
||||||
import { Application } from './Application';
|
import { Application } from './Application';
|
||||||
|
import { BlankComponent, RouterContextCleaner } from './components';
|
||||||
|
|
||||||
export interface BrowserRouterOptions extends Omit<BrowserRouterProps, 'children'> {
|
export interface BrowserRouterOptions extends Omit<BrowserRouterProps, 'children'> {
|
||||||
type?: 'browser';
|
type?: 'browser';
|
||||||
@ -98,6 +98,10 @@ export class RouterManager {
|
|||||||
this.options.type = type;
|
this.options.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBasename() {
|
||||||
|
return this.options.basename;
|
||||||
|
}
|
||||||
|
|
||||||
setBasename(basename: string) {
|
setBasename(basename: string) {
|
||||||
this.options.basename = basename;
|
this.options.basename = basename;
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,13 @@
|
|||||||
import { define, observable } from '@formily/reactive';
|
import { define, observable } from '@formily/reactive';
|
||||||
import { getSubAppName } from '@nocobase/sdk';
|
import { getSubAppName } from '@nocobase/sdk';
|
||||||
|
import { Application } from './Application';
|
||||||
export const getWebSocketURL = () => {
|
|
||||||
if (!process.env.API_BASE_URL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const subApp = getSubAppName();
|
|
||||||
const queryString = subApp ? `?__appName=${subApp}` : '';
|
|
||||||
const wsPath = process.env.WS_PATH || '/ws';
|
|
||||||
if (process.env.WEBSOCKET_URL) {
|
|
||||||
const url = new URL(process.env.WEBSOCKET_URL);
|
|
||||||
if (url.hostname === 'localhost') {
|
|
||||||
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
||||||
return `${protocol}://${location.hostname}:${url.port}${wsPath}${queryString}`;
|
|
||||||
}
|
|
||||||
return `${process.env.WEBSOCKET_URL}${queryString}`;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const url = new URL(process.env.API_BASE_URL);
|
|
||||||
return `${url.protocol === 'https:' ? 'wss' : 'ws'}://${url.host}${wsPath}${queryString}`;
|
|
||||||
} catch (error) {
|
|
||||||
return `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}${wsPath}${queryString}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export type WebSocketClientOptions = {
|
export type WebSocketClientOptions = {
|
||||||
reconnectInterval?: number;
|
reconnectInterval?: number;
|
||||||
reconnectAttempts?: number;
|
reconnectAttempts?: number;
|
||||||
pingInterval?: number;
|
pingInterval?: number;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
basename?: string;
|
||||||
protocols?: string | string[];
|
protocols?: string | string[];
|
||||||
onServerDown?: any;
|
onServerDown?: any;
|
||||||
};
|
};
|
||||||
@ -38,6 +17,7 @@ export class WebSocketClient {
|
|||||||
protected _reconnectTimes = 0;
|
protected _reconnectTimes = 0;
|
||||||
protected events = [];
|
protected events = [];
|
||||||
protected options: WebSocketClientOptions;
|
protected options: WebSocketClientOptions;
|
||||||
|
app: Application;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
connected = false;
|
connected = false;
|
||||||
serverDown = false;
|
serverDown = false;
|
||||||
@ -57,6 +37,34 @@ export class WebSocketClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getURL() {
|
||||||
|
if (!this.app) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const options = this.app.getOptions();
|
||||||
|
const apiBaseURL = options?.apiClient?.['baseURL'];
|
||||||
|
if (!apiBaseURL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const subApp = getSubAppName(this.app.getPublicPath());
|
||||||
|
const queryString = subApp ? `?__appName=${subApp}` : '';
|
||||||
|
const wsPath = this.options.basename || '/ws';
|
||||||
|
if (this.options.url) {
|
||||||
|
const url = new URL(this.options.url);
|
||||||
|
if (url.hostname === 'localhost') {
|
||||||
|
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
return `${protocol}://${location.hostname}:${url.port}${wsPath}${queryString}`;
|
||||||
|
}
|
||||||
|
return `${this.options.url}${queryString}`;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const url = new URL(apiBaseURL);
|
||||||
|
return `${url.protocol === 'https:' ? 'wss' : 'ws'}://${url.host}${wsPath}${queryString}`;
|
||||||
|
} catch (error) {
|
||||||
|
return `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}${wsPath}${queryString}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get reconnectAttempts() {
|
get reconnectAttempts() {
|
||||||
return this.options?.reconnectAttempts || 30;
|
return this.options?.reconnectAttempts || 30;
|
||||||
}
|
}
|
||||||
@ -90,7 +98,7 @@ export class WebSocketClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._reconnectTimes++;
|
this._reconnectTimes++;
|
||||||
const ws = new WebSocket(this.options.url || getWebSocketURL(), this.options.protocols);
|
const ws = new WebSocket(this.getURL(), this.options.protocols);
|
||||||
let pingIntervalTimer: any;
|
let pingIntervalTimer: any;
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.log('[nocobase-ws]: connected.');
|
console.log('[nocobase-ws]: connected.');
|
||||||
|
@ -10,6 +10,7 @@ import { useAPIClient } from '../api-client';
|
|||||||
import { Application } from '../application';
|
import { Application } from '../application';
|
||||||
import { Plugin } from '../application/Plugin';
|
import { Plugin } from '../application/Plugin';
|
||||||
import { BlockSchemaComponentPlugin } from '../block-provider';
|
import { BlockSchemaComponentPlugin } from '../block-provider';
|
||||||
|
import { CollectionPlugin } from '../collection-manager';
|
||||||
import { RemoteDocumentTitlePlugin } from '../document-title';
|
import { RemoteDocumentTitlePlugin } from '../document-title';
|
||||||
import { PinnedListPlugin } from '../plugin-manager';
|
import { PinnedListPlugin } from '../plugin-manager';
|
||||||
import { PMPlugin } from '../pm';
|
import { PMPlugin } from '../pm';
|
||||||
@ -22,7 +23,6 @@ import { BlockTemplateDetails, BlockTemplatePage } from '../schema-templates';
|
|||||||
import { SystemSettingsPlugin } from '../system-settings';
|
import { SystemSettingsPlugin } from '../system-settings';
|
||||||
import { CurrentUserProvider, CurrentUserSettingsMenuProvider } from '../user';
|
import { CurrentUserProvider, CurrentUserSettingsMenuProvider } from '../user';
|
||||||
import { LocalePlugin } from './plugins/LocalePlugin';
|
import { LocalePlugin } from './plugins/LocalePlugin';
|
||||||
import { CollectionPlugin } from '../collection-manager';
|
|
||||||
|
|
||||||
const AppSpin = () => {
|
const AppSpin = () => {
|
||||||
return (
|
return (
|
||||||
@ -36,7 +36,7 @@ const useErrorProps = (app: Application, error: any) => {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const err = error?.response?.data?.errors?.[0] || error;
|
const err = error?.response?.data?.errors?.[0] || error;
|
||||||
const subApp = getSubAppName();
|
const subApp = getSubAppName(app.getPublicPath());
|
||||||
switch (err.code) {
|
switch (err.code) {
|
||||||
case 'USER_HAS_NO_ROLES_ERR':
|
case 'USER_HAS_NO_ROLES_ERR':
|
||||||
return {
|
return {
|
||||||
|
@ -37,6 +37,7 @@ function getUmiConfig() {
|
|||||||
return memo;
|
return memo;
|
||||||
}, {}),
|
}, {}),
|
||||||
define: {
|
define: {
|
||||||
|
'process.env.APP_PUBLIC_PATH': process.env.APP_PUBLIC_PATH,
|
||||||
'process.env.WS_PATH': process.env.WS_PATH,
|
'process.env.WS_PATH': process.env.WS_PATH,
|
||||||
'process.env.API_BASE_URL': API_BASE_URL || API_BASE_PATH,
|
'process.env.API_BASE_URL': API_BASE_URL || API_BASE_PATH,
|
||||||
'process.env.APP_ENV': process.env.APP_ENV,
|
'process.env.APP_ENV': process.env.APP_ENV,
|
||||||
|
@ -48,7 +48,7 @@ export class Auth {
|
|||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const appName = getSubAppName();
|
const appName = getSubAppName(this.api['app'] ? this.api['app'].getPublicPath() : '/');
|
||||||
if (!appName) {
|
if (!appName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
const getSubAppName = () => {
|
const getSubAppName = (publicPath = '/') => {
|
||||||
const match = window.location.pathname.match(/^\/apps\/([^/]*)\/?/);
|
const prefix = `${publicPath}apps/`;
|
||||||
if (!match) {
|
if (!window.location.pathname.startsWith(prefix)) {
|
||||||
return '';
|
return;
|
||||||
}
|
}
|
||||||
return match[1];
|
const pathname = window.location.pathname.substring(prefix.length);
|
||||||
|
const args = pathname.split('/', 1);
|
||||||
|
return args[0] || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getSubAppName;
|
export default getSubAppName;
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export * from './APIClient';
|
export * from './APIClient';
|
||||||
export { default as getSubAppName } from './getSubAppName';
|
|
||||||
|
|
||||||
|
export { default as getSubAppName } from './getSubAppName';
|
||||||
|
@ -15,7 +15,7 @@ import handler from 'serve-handler';
|
|||||||
import { parse } from 'url';
|
import { parse } from 'url';
|
||||||
import { AppSupervisor } from '../app-supervisor';
|
import { AppSupervisor } from '../app-supervisor';
|
||||||
import { ApplicationOptions } from '../application';
|
import { ApplicationOptions } from '../application';
|
||||||
import { PLUGIN_STATICS_PATH, getPackageDirByExposeUrl, getPackageNameByExposeUrl } from '../plugin-manager';
|
import { getPackageDirByExposeUrl, getPackageNameByExposeUrl } from '../plugin-manager';
|
||||||
import { applyErrorWithArgs, getErrorWithCode } from './errors';
|
import { applyErrorWithArgs, getErrorWithCode } from './errors';
|
||||||
import { IPCSocketClient } from './ipc-socket-client';
|
import { IPCSocketClient } from './ipc-socket-client';
|
||||||
import { IPCSocketServer } from './ipc-socket-server';
|
import { IPCSocketServer } from './ipc-socket-server';
|
||||||
@ -168,14 +168,16 @@ export class Gateway extends EventEmitter {
|
|||||||
|
|
||||||
async requestHandler(req: IncomingMessage, res: ServerResponse) {
|
async requestHandler(req: IncomingMessage, res: ServerResponse) {
|
||||||
const { pathname } = parse(req.url);
|
const { pathname } = parse(req.url);
|
||||||
|
const { PLUGIN_STATICS_PATH, APP_PUBLIC_PATH } = process.env;
|
||||||
|
|
||||||
if (pathname === '/__umi/api/bundle-status') {
|
if (pathname.endsWith('/__umi/api/bundle-status')) {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.end('ok');
|
res.end('ok');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathname.startsWith('/storage/uploads/')) {
|
if (pathname.startsWith(APP_PUBLIC_PATH + 'storage/uploads/')) {
|
||||||
|
req.url = req.url.substring(APP_PUBLIC_PATH.length - 1);
|
||||||
await compress(req, res);
|
await compress(req, res);
|
||||||
return handler(req, res, {
|
return handler(req, res, {
|
||||||
public: resolve(process.cwd()),
|
public: resolve(process.cwd()),
|
||||||
@ -203,7 +205,8 @@ export class Gateway extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pathname.startsWith('/api')) {
|
if (!pathname.startsWith(process.env.API_BASE_PATH)) {
|
||||||
|
req.url = req.url.substring(APP_PUBLIC_PATH.length - 1);
|
||||||
await compress(req, res);
|
await compress(req, res);
|
||||||
return handler(req, res, {
|
return handler(req, res, {
|
||||||
public: `${process.env.APP_PACKAGE_ROOT}/dist/client`,
|
public: `${process.env.APP_PACKAGE_ROOT}/dist/client`,
|
||||||
|
@ -36,7 +36,7 @@ export function getPackageFilePathWithExistCheck(packageName: string, filePath:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getExposeUrl(packageName: string, filePath: string) {
|
export function getExposeUrl(packageName: string, filePath: string) {
|
||||||
return `${PLUGIN_STATICS_PATH}${packageName}/${filePath}`;
|
return `${process.env.PLUGIN_STATICS_PATH}${packageName}/${filePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExposeReadmeUrl(packageName: string, lang: string) {
|
export function getExposeReadmeUrl(packageName: string, lang: string) {
|
||||||
@ -63,7 +63,7 @@ export function getExposeChangelogUrl(packageName: string) {
|
|||||||
* getPluginNameByClientStaticUrl('/static/plugins/@nocobase/foo/README.md') => '@nocobase/foo'
|
* getPluginNameByClientStaticUrl('/static/plugins/@nocobase/foo/README.md') => '@nocobase/foo'
|
||||||
*/
|
*/
|
||||||
export function getPackageNameByExposeUrl(pathname: string) {
|
export function getPackageNameByExposeUrl(pathname: string) {
|
||||||
pathname = pathname.replace(PLUGIN_STATICS_PATH, '');
|
pathname = pathname.replace(process.env.PLUGIN_STATICS_PATH, '');
|
||||||
const pathArr = pathname.split('/');
|
const pathArr = pathname.split('/');
|
||||||
if (pathname.startsWith('@')) {
|
if (pathname.startsWith('@')) {
|
||||||
return pathArr.slice(0, 2).join('/');
|
return pathArr.slice(0, 2).join('/');
|
||||||
|
@ -5,7 +5,6 @@ import Application from '../../application';
|
|||||||
import { getExposeUrl } from '../clientStaticUtils';
|
import { getExposeUrl } from '../clientStaticUtils';
|
||||||
import PluginManager from '../plugin-manager';
|
import PluginManager from '../plugin-manager';
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import { version } from '../../../package.json';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'pm',
|
name: 'pm',
|
||||||
@ -131,7 +130,10 @@ export default {
|
|||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
...item.toJSON(),
|
...item.toJSON(),
|
||||||
url: `${getExposeUrl(item.packageName, PLUGIN_CLIENT_ENTRY_FILE)}?version=${item.version}`,
|
url: `${process.env.APP_SERVER_BASE_URL}${getExposeUrl(
|
||||||
|
item.packageName,
|
||||||
|
PLUGIN_CLIENT_ENTRY_FILE,
|
||||||
|
)}?version=${item.version}`,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { css, useAPIClient, useRequest } from '@nocobase/client';
|
import { css, useAPIClient, useApp, useRequest } from '@nocobase/client';
|
||||||
|
import { getSubAppName } from '@nocobase/sdk';
|
||||||
import { Select, Space, Spin, Typography } from 'antd';
|
import { Select, Space, Spin, Typography } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import SwaggerUIBundle from 'swagger-ui-dist/swagger-ui-bundle';
|
import SwaggerUIBundle from 'swagger-ui-dist/swagger-ui-bundle';
|
||||||
@ -13,11 +14,12 @@ const Documentation = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const swaggerUIRef = useRef();
|
const swaggerUIRef = useRef();
|
||||||
const { data: urls } = useRequest<{ data: { name: string; url: string }[] }>({ url: 'swagger:getUrls' });
|
const { data: urls } = useRequest<{ data: { name: string; url: string }[] }>({ url: 'swagger:getUrls' });
|
||||||
|
const app = useApp();
|
||||||
const requestInterceptor = (req) => {
|
const requestInterceptor = (req) => {
|
||||||
if (!req.headers['Authorization']) {
|
if (!req.headers['Authorization']) {
|
||||||
const match = location.pathname.match(/^\/apps\/([^/]*)\//);
|
const appName = getSubAppName(app.getPublicPath());
|
||||||
if (match?.[1]) {
|
if (appName) {
|
||||||
req.headers['X-App'] = match?.[1];
|
req.headers['X-App'] = appName;
|
||||||
}
|
}
|
||||||
req.headers['Authorization'] = `Bearer ${apiClient.auth.getToken()}`;
|
req.headers['Authorization'] = `Bearer ${apiClient.auth.getToken()}`;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import APIDocPlugin from '../server';
|
import APIDocPlugin from '../server';
|
||||||
import baseSwagger from './base-swagger';
|
import baseSwagger from './base-swagger';
|
||||||
|
import collectionToSwaggerObject from './collections';
|
||||||
import { SchemaTypeMapping } from './constants';
|
import { SchemaTypeMapping } from './constants';
|
||||||
import { createDefaultActionSwagger, getInterfaceCollection } from './helpers';
|
import { createDefaultActionSwagger, getInterfaceCollection } from './helpers';
|
||||||
import { getPluginsSwagger, getSwaggerDocument, loadSwagger } from './loader';
|
import { getPluginsSwagger, getSwaggerDocument, loadSwagger } from './loader';
|
||||||
import { merge } from './merge';
|
import { merge } from './merge';
|
||||||
import collectionToSwaggerObject from './collections';
|
|
||||||
|
|
||||||
export class SwaggerManager {
|
export class SwaggerManager {
|
||||||
private plugin: APIDocPlugin;
|
private plugin: APIDocPlugin;
|
||||||
@ -81,6 +81,10 @@ export class SwaggerManager {
|
|||||||
return merge(await this.getBaseSwagger(), await loadSwagger('@nocobase/server'));
|
return merge(await this.getBaseSwagger(), await loadSwagger('@nocobase/server'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getURL(pathname: string) {
|
||||||
|
return process.env.API_BASE_PATH + pathname;
|
||||||
|
}
|
||||||
|
|
||||||
async getUrls() {
|
async getUrls() {
|
||||||
const plugins = await getPluginsSwagger(this.db)
|
const plugins = await getPluginsSwagger(this.db)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -88,7 +92,7 @@ export class SwaggerManager {
|
|||||||
const schema = res[name];
|
const schema = res[name];
|
||||||
return {
|
return {
|
||||||
name: schema.info?.title || name,
|
name: schema.info?.title || name,
|
||||||
url: `/api/swagger:get?ns=${encodeURIComponent(`plugins/${name}`)}`,
|
url: this.getURL(`swagger:get?ns=${encodeURIComponent(`plugins/${name}`)}`),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -102,25 +106,25 @@ export class SwaggerManager {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'NocoBase API',
|
name: 'NocoBase API',
|
||||||
url: '/api/swagger:get',
|
url: this.getURL('swagger:get'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'NocoBase API - Core',
|
name: 'NocoBase API - Core',
|
||||||
url: '/api/swagger:get?ns=core',
|
url: this.getURL('swagger:get?ns=core'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'NocoBase API - All plugins',
|
name: 'NocoBase API - All plugins',
|
||||||
url: '/api/swagger:get?ns=plugins',
|
url: this.getURL('swagger:get?ns=plugins'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'NocoBase API - Custom collections',
|
name: 'NocoBase API - Custom collections',
|
||||||
url: '/api/swagger:get?ns=collections',
|
url: this.getURL('swagger:get?ns=collections'),
|
||||||
},
|
},
|
||||||
...plugins,
|
...plugins,
|
||||||
...collections.map((collection) => {
|
...collections.map((collection) => {
|
||||||
return {
|
return {
|
||||||
name: `Collection API - ${collection.title}`,
|
name: `Collection API - ${collection.title}`,
|
||||||
url: `/api/swagger:get?ns=${encodeURIComponent('collections/' + collection.name)}`,
|
url: this.getURL(`swagger:get?ns=${encodeURIComponent('collections/' + collection.name)}`),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import { LoginOutlined } from '@ant-design/icons';
|
import { LoginOutlined } from '@ant-design/icons';
|
||||||
import { Button, Space, message } from 'antd';
|
import { useApp } from '@nocobase/client';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { getSubAppName } from '@nocobase/sdk';
|
|
||||||
import { Authenticator } from '@nocobase/plugin-auth/client';
|
import { Authenticator } from '@nocobase/plugin-auth/client';
|
||||||
|
import { getSubAppName } from '@nocobase/sdk';
|
||||||
|
import { Button, Space, message } from 'antd';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
export const SigninPage = (props: { authenticator: Authenticator }) => {
|
export const SigninPage = (props: { authenticator: Authenticator }) => {
|
||||||
const authenticator = props.authenticator;
|
const authenticator = props.authenticator;
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const redirect = params.get('redirect');
|
const redirect = params.get('redirect');
|
||||||
|
const app = useApp();
|
||||||
|
|
||||||
const app = getSubAppName() || 'main';
|
const appName = getSubAppName(app.getPublicPath()) || 'main';
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
window.location.replace(`/api/cas:login?authenticator=${authenticator.name}&__appName=${app}&redirect=${redirect}`);
|
window.location.replace(
|
||||||
|
`/api/cas:login?authenticator=${authenticator.name}&__appName=${appName}&redirect=${redirect}`,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -9,7 +9,7 @@ export const service = async (ctx: Context, next: Next) => {
|
|||||||
if (appName && appName !== 'main') {
|
if (appName && appName !== 'main') {
|
||||||
const appSupervisor = AppSupervisor.getInstance();
|
const appSupervisor = AppSupervisor.getInstance();
|
||||||
if (appSupervisor?.runningMode !== 'single') {
|
if (appSupervisor?.runningMode !== 'single') {
|
||||||
prefix = `/apps/${appName}`;
|
prefix = process.env.APP_PUBLIC_PATH + `apps/${appName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export class CASAuth extends BaseAuth {
|
|||||||
const opts = this.options || {};
|
const opts = this.options || {};
|
||||||
return {
|
return {
|
||||||
...opts,
|
...opts,
|
||||||
serviceUrl: `${opts.serviceDomain}/api/cas:service`,
|
serviceUrl: `${opts.serviceDomain}${process.env.API_BASE_PATH}cas:service`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ export class FileModel extends Model {
|
|||||||
if (!json['thumbnailRule'] && storage?.type === 'ali-oss') {
|
if (!json['thumbnailRule'] && storage?.type === 'ali-oss') {
|
||||||
json['thumbnailRule'] = '?x-oss-process=image/auto-orient,1/resize,m_fill,w_94,h_94/quality,q_90';
|
json['thumbnailRule'] = '?x-oss-process=image/auto-orient,1/resize,m_fill,w_94,h_94/quality,q_90';
|
||||||
}
|
}
|
||||||
|
if (storage?.type === 'local' && process.env.APP_PUBLIC_PATH) {
|
||||||
|
json['url'] = process.env.APP_PUBLIC_PATH.replace(/\/$/g, '') + json.url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { observer, useField } from '@formily/react';
|
import { observer, useField } from '@formily/react';
|
||||||
import { useAPIClient } from '@nocobase/client';
|
import { useAPIClient, useApp } from '@nocobase/client';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -22,13 +22,16 @@ export const Iframe: any = observer(
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
|
const app = useApp();
|
||||||
|
|
||||||
const src = React.useMemo(() => {
|
const src = React.useMemo(() => {
|
||||||
if (mode === 'html') {
|
if (mode === 'html') {
|
||||||
return `/api/iframeHtml:getHtml/${htmlId}?token=${api.auth.getToken()}&v=${field.data?.v || ''}`;
|
const options = app.getOptions();
|
||||||
|
const apiBaseURL: string = options?.apiClient?.['baseURL'];
|
||||||
|
return `${apiBaseURL}iframeHtml:getHtml/${htmlId}?token=${api.auth.getToken()}&v=${field.data?.v || ''}`;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}, [url, mode, htmlId, field.data?.v]);
|
}, [app, url, mode, htmlId, field.data?.v]);
|
||||||
|
|
||||||
if ((mode === 'url' && !url) || (mode === 'html' && !htmlId)) {
|
if ((mode === 'url' && !url) || (mode === 'html' && !htmlId)) {
|
||||||
return <Card style={{ marginBottom: 24 }}>{t('Please fill in the iframe URL')}</Card>;
|
return <Card style={{ marginBottom: 24 }}>{t('Please fill in the iframe URL')}</Card>;
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
|
import { useApp } from '@nocobase/client';
|
||||||
import { Card, Form, Input } from 'antd';
|
import { Card, Form, Input } from 'antd';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../locale';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const AppConfiguration = () => {
|
export const AppConfiguration = () => {
|
||||||
|
const app = useApp();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
|
||||||
const targetUrl = useMemo(() => {
|
const targetUrl = useMemo(() => {
|
||||||
let baseUrl = '/mobile';
|
return app.getRouteUrl('/mobile');
|
||||||
if (location.pathname.startsWith('/apps')) {
|
}, [app]);
|
||||||
baseUrl = location.pathname.split('/').slice(0, 3).join('/');
|
|
||||||
}
|
|
||||||
return baseUrl;
|
|
||||||
}, [location.pathname]);
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
|
import { LinkOutlined } from '@ant-design/icons';
|
||||||
|
import { css, useApp } from '@nocobase/client';
|
||||||
|
import { Button } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../locale';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
|
||||||
import { css } from '@nocobase/client';
|
|
||||||
import { Button } from 'antd';
|
|
||||||
import { LinkOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
export const OpenInNewTab = () => {
|
export const OpenInNewTab = () => {
|
||||||
const location = useLocation();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const app = useApp();
|
||||||
|
|
||||||
const onOpenInNewTab = () => {
|
const onOpenInNewTab = () => {
|
||||||
let baseUrl = window.origin;
|
window.open(app.getRouteUrl('/mobile'));
|
||||||
if (window.location.pathname.startsWith('/apps')) {
|
|
||||||
baseUrl = window.origin + window.location.pathname.split('/').slice(0, 3).join('/');
|
|
||||||
}
|
|
||||||
window.open(`${baseUrl}${location.pathname}${location.search}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SchemaComponent, useRecord } from '@nocobase/client';
|
import { SchemaComponent, useApp, useRecord } from '@nocobase/client';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { schema } from './settings/schemas/applications';
|
import { schema } from './settings/schemas/applications';
|
||||||
@ -6,10 +6,11 @@ import { usePluginUtils } from './utils';
|
|||||||
|
|
||||||
const useLink = () => {
|
const useLink = () => {
|
||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
|
const app = useApp();
|
||||||
if (record.options?.standaloneDeployment && record.cname) {
|
if (record.options?.standaloneDeployment && record.cname) {
|
||||||
return `//${record.cname}`;
|
return `//${record.cname}`;
|
||||||
}
|
}
|
||||||
return `/apps/${record.name}/admin/`;
|
return app.getRouteUrl(`/apps/${record.name}/admin/`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppVisitor = () => {
|
const AppVisitor = () => {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { connect, mapReadPretty } from '@formily/react';
|
import { connect, mapReadPretty } from '@formily/react';
|
||||||
|
import { useApp } from '@nocobase/client';
|
||||||
import { Input as AntdInput } from 'antd';
|
import { Input as AntdInput } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const ReadPretty = (props) => {
|
const ReadPretty = (props) => {
|
||||||
|
const app = useApp();
|
||||||
const content = props.value && (
|
const content = props.value && (
|
||||||
<a target={'_blank'} href={`/apps/${props.value}/admin`} rel="noreferrer">
|
<a target={'_blank'} href={app.getRouteUrl(`/apps/${props.value}/admin`)} rel="noreferrer">
|
||||||
{props.value}
|
{props.value}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -20,10 +20,10 @@ const MultiAppManager = () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const { t } = usePluginUtils();
|
const { t } = usePluginUtils();
|
||||||
const app = useApp();
|
const instance = useApp();
|
||||||
const items = [
|
const items = [
|
||||||
...(data?.data || []).map((app) => {
|
...(data?.data || []).map((app) => {
|
||||||
let link = `/apps/${app.name}/admin/`;
|
let link = instance.getRouteUrl(`/apps/${app.name}/admin/`);
|
||||||
if (app.options?.standaloneDeployment && app.cname) {
|
if (app.options?.standaloneDeployment && app.cname) {
|
||||||
link = `//${app.cname}`;
|
link = `//${app.cname}`;
|
||||||
}
|
}
|
||||||
@ -38,7 +38,9 @@ const MultiAppManager = () => {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
key: '.manager',
|
key: '.manager',
|
||||||
label: <Link to={app.pluginSettingsManager.getRoutePath('multi-app-manager')}>{t('Manage applications')}</Link>,
|
label: (
|
||||||
|
<Link to={instance.pluginSettingsManager.getRoutePath('multi-app-manager')}>{t('Manage applications')}</Link>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
|
@ -115,7 +115,7 @@ const defaultAppOptionsFactory = (appName: string, mainApp: Application) => {
|
|||||||
},
|
},
|
||||||
plugins: ['nocobase'],
|
plugins: ['nocobase'],
|
||||||
resourcer: {
|
resourcer: {
|
||||||
prefix: '/api',
|
prefix: process.env.API_BASE_PATH,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -274,7 +274,7 @@ export class MultiAppShareCollectionPlugin extends Plugin {
|
|||||||
}),
|
}),
|
||||||
plugins: plugins.includes('nocobase') ? ['nocobase'] : plugins,
|
plugins: plugins.includes('nocobase') ? ['nocobase'] : plugins,
|
||||||
resourcer: {
|
resourcer: {
|
||||||
prefix: '/api',
|
prefix: process.env.API_BASE_PATH,
|
||||||
},
|
},
|
||||||
logger: {
|
logger: {
|
||||||
...mainApp.options.logger,
|
...mainApp.options.logger,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { CopyOutlined } from '@ant-design/icons';
|
import { CopyOutlined } from '@ant-design/icons';
|
||||||
import { ArrayItems, FormTab } from '@formily/antd-v5';
|
import { ArrayItems, FormTab } from '@formily/antd-v5';
|
||||||
import { observer } from '@formily/react';
|
import { observer } from '@formily/react';
|
||||||
import { FormItem, Input, SchemaComponent } from '@nocobase/client';
|
import { FormItem, Input, SchemaComponent, useApp } from '@nocobase/client';
|
||||||
import { Card, Space, message } from 'antd';
|
import { Card, Space, message } from 'antd';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { lang, useOidcTranslation } from './locale';
|
import { lang, useOidcTranslation } from './locale';
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
@ -327,9 +327,16 @@ const schema = {
|
|||||||
const Usage = observer(
|
const Usage = observer(
|
||||||
() => {
|
() => {
|
||||||
const { t } = useOidcTranslation();
|
const { t } = useOidcTranslation();
|
||||||
|
const app = useApp();
|
||||||
|
|
||||||
|
const url = useMemo(() => {
|
||||||
|
const options = app.getOptions();
|
||||||
|
const apiBaseURL: string = options?.apiClient?.['baseURL'];
|
||||||
const { protocol, host } = window.location;
|
const { protocol, host } = window.location;
|
||||||
const url = `${protocol}//${host}/api/oidc:redirect`;
|
return apiBaseURL.startsWith('http')
|
||||||
|
? `${apiBaseURL}oidc:redirect`
|
||||||
|
: `${protocol}//${host}${apiBaseURL}oidc:redirect`;
|
||||||
|
}, [app]);
|
||||||
|
|
||||||
const copy = (text: string) => {
|
const copy = (text: string) => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Context, Next } from '@nocobase/actions';
|
import { Context, Next } from '@nocobase/actions';
|
||||||
import { OIDCAuth } from '../oidc-auth';
|
|
||||||
import { AppSupervisor } from '@nocobase/server';
|
import { AppSupervisor } from '@nocobase/server';
|
||||||
|
import { OIDCAuth } from '../oidc-auth';
|
||||||
|
|
||||||
export const redirect = async (ctx: Context, next: Next) => {
|
export const redirect = async (ctx: Context, next: Next) => {
|
||||||
const {
|
const {
|
||||||
@ -14,7 +14,7 @@ export const redirect = async (ctx: Context, next: Next) => {
|
|||||||
if (appName && appName !== 'main') {
|
if (appName && appName !== 'main') {
|
||||||
const appSupervisor = AppSupervisor.getInstance();
|
const appSupervisor = AppSupervisor.getInstance();
|
||||||
if (appSupervisor?.runningMode !== 'single') {
|
if (appSupervisor?.runningMode !== 'single') {
|
||||||
prefix = `/apps/${appName}`;
|
prefix = process.env.APP_PUBLIC_PATH + `apps/${appName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as OIDCAuth;
|
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as OIDCAuth;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AuthConfig, BaseAuth } from '@nocobase/auth';
|
import { AuthConfig, BaseAuth } from '@nocobase/auth';
|
||||||
|
import { AuthModel } from '@nocobase/plugin-auth';
|
||||||
import { Issuer } from 'openid-client';
|
import { Issuer } from 'openid-client';
|
||||||
import { cookieName } from '../constants';
|
import { cookieName } from '../constants';
|
||||||
import { AuthModel } from '@nocobase/plugin-auth';
|
|
||||||
|
|
||||||
export class OIDCAuth extends BaseAuth {
|
export class OIDCAuth extends BaseAuth {
|
||||||
constructor(config: AuthConfig) {
|
constructor(config: AuthConfig) {
|
||||||
@ -17,7 +17,7 @@ export class OIDCAuth extends BaseAuth {
|
|||||||
const { http, port } = this.getOptions();
|
const { http, port } = this.getOptions();
|
||||||
const protocol = http ? 'http' : 'https';
|
const protocol = http ? 'http' : 'https';
|
||||||
const host = port ? `${ctx.hostname}${port ? `:${port}` : ''}` : ctx.host;
|
const host = port ? `${ctx.hostname}${port ? `:${port}` : ''}` : ctx.host;
|
||||||
return `${protocol}://${host}/api/oidc:redirect`;
|
return `${protocol}://${host}${process.env.API_BASE_PATH}oidc:redirect`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOptions() {
|
getOptions() {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
|
||||||
import { SchemaComponent } from '@nocobase/client';
|
|
||||||
import { Card, message } from 'antd';
|
|
||||||
import { CopyOutlined } from '@ant-design/icons';
|
import { CopyOutlined } from '@ant-design/icons';
|
||||||
import { observer, useForm } from '@formily/react';
|
import { observer, useForm } from '@formily/react';
|
||||||
import { useRecord, FormItem, Input } from '@nocobase/client';
|
import { FormItem, Input, SchemaComponent, useApp, useRecord } from '@nocobase/client';
|
||||||
import { lang, useSamlTranslation } from './locale';
|
|
||||||
import { getSubAppName } from '@nocobase/sdk';
|
import { getSubAppName } from '@nocobase/sdk';
|
||||||
|
import { Card, message } from 'antd';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { lang, useSamlTranslation } from './locale';
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -74,10 +73,19 @@ const Usage = observer(
|
|||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
const { t } = useSamlTranslation();
|
const { t } = useSamlTranslation();
|
||||||
|
|
||||||
const app = getSubAppName() || 'main';
|
const app = useApp();
|
||||||
const name = form.values.name ?? record.name;
|
const name = form.values.name ?? record.name;
|
||||||
|
|
||||||
|
const url = useMemo(() => {
|
||||||
|
const options = app.getOptions();
|
||||||
|
const apiBaseURL: string = options?.apiClient?.['baseURL'];
|
||||||
const { protocol, host } = window.location;
|
const { protocol, host } = window.location;
|
||||||
const url = `${protocol}//${host}/api/saml:redirect?authenticator=${name}&__appName=${app}`;
|
const appName = getSubAppName(app.getPublicPath()) || 'main';
|
||||||
|
|
||||||
|
return apiBaseURL.startsWith('http')
|
||||||
|
? `${apiBaseURL}saml:redirect?authenticator=${name}&__appName=${appName}`
|
||||||
|
: `${protocol}//${host}${apiBaseURL}saml:redirect?authenticator=${name}&__appName=${appName}`;
|
||||||
|
}, [app, name]);
|
||||||
|
|
||||||
const copy = (text: string) => {
|
const copy = (text: string) => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Context, Next } from '@nocobase/actions';
|
import { Context, Next } from '@nocobase/actions';
|
||||||
import { SAMLAuth } from '../saml-auth';
|
|
||||||
import { AppSupervisor } from '@nocobase/server';
|
import { AppSupervisor } from '@nocobase/server';
|
||||||
|
import { SAMLAuth } from '../saml-auth';
|
||||||
|
|
||||||
export const redirect = async (ctx: Context, next: Next) => {
|
export const redirect = async (ctx: Context, next: Next) => {
|
||||||
const { authenticator, __appName: appName } = ctx.action.params || {};
|
const { authenticator, __appName: appName } = ctx.action.params || {};
|
||||||
@ -9,7 +9,7 @@ export const redirect = async (ctx: Context, next: Next) => {
|
|||||||
if (appName && appName !== 'main') {
|
if (appName && appName !== 'main') {
|
||||||
const appSupervisor = AppSupervisor.getInstance();
|
const appSupervisor = AppSupervisor.getInstance();
|
||||||
if (appSupervisor?.runningMode !== 'single') {
|
if (appSupervisor?.runningMode !== 'single') {
|
||||||
prefix = `/apps/${appName}`;
|
prefix = process.env.APP_PUBLIC_PATH + `apps/${appName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as SAMLAuth;
|
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as SAMLAuth;
|
||||||
|
@ -24,7 +24,7 @@ export class SAMLAuth extends BaseAuth {
|
|||||||
const name = this.authenticator.get('name');
|
const name = this.authenticator.get('name');
|
||||||
const protocol = http ? 'http' : 'https';
|
const protocol = http ? 'http' : 'https';
|
||||||
return {
|
return {
|
||||||
callbackUrl: `${protocol}://${ctx.host}/api/saml:redirect?authenticator=${name}&__appName=${ctx.app.name}`,
|
callbackUrl: `${protocol}://${ctx.host}${process.env.API_BASE_PATH}saml:redirect?authenticator=${name}&__appName=${ctx.app.name}`,
|
||||||
entryPoint: ssoUrl,
|
entryPoint: ssoUrl,
|
||||||
issuer: name,
|
issuer: name,
|
||||||
cert: certificate,
|
cert: certificate,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { NocoBaseBuildInPlugin, Plugin } from '@nocobase/client';
|
import { Application, NocoBaseBuildInPlugin, Plugin } from '@nocobase/client';
|
||||||
|
|
||||||
const getCurrentTimezone = () => {
|
const getCurrentTimezone = () => {
|
||||||
const timezoneOffset = new Date().getTimezoneOffset() / -60;
|
const timezoneOffset = new Date().getTimezoneOffset() / -60;
|
||||||
@ -6,15 +6,17 @@ const getCurrentTimezone = () => {
|
|||||||
return (timezoneOffset > 0 ? '+' : '-') + timezone;
|
return (timezoneOffset > 0 ? '+' : '-') + timezone;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getBasename() {
|
function getBasename(app: Application) {
|
||||||
const match = location.pathname.match(/^\/apps\/([^/]*)\//);
|
const publicPath = app.getPublicPath();
|
||||||
return match ? match[0] : '/';
|
const pattern = `^${publicPath}apps/([^/]*)/`;
|
||||||
|
const match = location.pathname.match(new RegExp(pattern));
|
||||||
|
return match ? match[0] : publicPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NocoBaseClientPresetPlugin extends Plugin {
|
export class NocoBaseClientPresetPlugin extends Plugin {
|
||||||
async afterAdd() {
|
async afterAdd() {
|
||||||
this.router.setType('browser');
|
this.router.setType('browser');
|
||||||
this.router.setBasename(getBasename());
|
this.router.setBasename(getBasename(this.app));
|
||||||
this.app.apiClient.axios.interceptors.request.use((config) => {
|
this.app.apiClient.axios.interceptors.request.use((config) => {
|
||||||
config.headers['X-Hostname'] = window?.location?.hostname;
|
config.headers['X-Hostname'] = window?.location?.hostname;
|
||||||
config.headers['X-Timezone'] = getCurrentTimezone();
|
config.headers['X-Timezone'] = getCurrentTimezone();
|
||||||
|
1
storage/.gitignore
vendored
1
storage/.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
tmp
|
tmp
|
||||||
app.watch.ts
|
app.watch.ts
|
||||||
/e2e
|
/e2e
|
||||||
|
nocobase.conf
|
||||||
|
Loading…
Reference in New Issue
Block a user