refactor: 插件化

pull/180/head
xiaojunnuo 2020-10-30 17:46:45 +08:00
parent 2be957968e
commit 3f81235e86
37 changed files with 1583 additions and 665 deletions

View File

@ -32,6 +32,9 @@ const configApi = {
getDefault () {
return lodash.cloneDeep(defConfig)
},
addDefault (key, defValue) {
lodash.set(defConfig, key, defValue)
},
resetDefault () {
configTarget = lodash.cloneDeep(defConfig)
},

View File

@ -1,145 +1,118 @@
module.exports = {
server: {
port: 1181
},
intercepts: {
'github.com': [
{
// "release archive 下载链接替换",
regexp: [
'/.*/.*/releases/download/',
'/.*/.*/archive/'
],
redirect: 'download.fastgit.org'
},
{
regexp: [
'/.*/.*/raw/',
'/.*/.*/blame/'
],
redirect: 'hub.fastgit.org'
}
],
// 'codeload.github.com': [
// {
// regexp: '.*',
// redirect:"download.fastgit.org"
// }
// ],
'raw.githubusercontent.com': [{ proxy: 'raw.fastgit.org' }],
'github.githubassets.com': [
{
proxy: 'assets.fastgit.org'
}
],
'customer-stories-feed.github.com': [
{
proxy: 'customer-stories-feed.fastgit.org'
}
],
enabled: true,
port: 1181,
intercepts: {
'github.com': [
{
// "release archive 下载链接替换",
regexp: [
'/.*/.*/releases/download/',
'/.*/.*/archive/'
],
redirect: 'download.fastgit.org'
},
{
regexp: [
'/.*/.*/raw/',
'/.*/.*/blame/'
],
redirect: 'hub.fastgit.org'
}
],
// 'codeload.github.com': [
// {
// regexp: '.*',
// redirect:"download.fastgit.org"
// }
// ],
'raw.githubusercontent.com': [{ proxy: 'raw.fastgit.org' }],
'github.githubassets.com': [
{
proxy: 'assets.fastgit.org'
}
],
'customer-stories-feed.github.com': [
{
proxy: 'customer-stories-feed.fastgit.org'
}
],
// google cdn
'ajax.googleapis.com': [
{
proxy: 'ajax.loli.net',
backup: ['ajax.proxy.ustclug.org'],
case: 'ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js'
}
],
'fonts.googleapis.com': [
{
proxy: 'fonts.loli.net',
backup: ['fonts.proxy.ustclug.org'],
case: 'https://fonts.googleapis.com/css?family=Oswald'
}
],
'themes.googleapis.com': [
{
proxy: 'themes.loli.net',
backup: ['themes.proxy.ustclug.org']
}
],
'themes.googleusercontent.com': [
{ proxy: 'google-themes.proxy.ustclug.org' }
],
'www.google.com': [
{
regexp: '/recaptcha/.*',
proxy: 'www.recaptcha.net'
}
],
'fonts.gstatic.com': [
{
proxy: 'fonts-gstatic.proxy.ustclug.org',
backup: ['gstatic.loli.net']
}
],
'clients*.google.com': [{ abort: true }],
'www.googleapis.com': [{ abort: true }],
'lh*.googleusercontent.com': [{ abort: true }],
// mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.0/napi-v3-win32-x64.tar.gz
'*.s3.amazonaws.com': [
{
regexp: '/sqlite3/.*',
redirect: 'npm.taobao.org/mirrors'
}
],
'registry-1.docker.io': [{ proxy: 'docker.mirrors.ustc.edu.cn' }],
'packages.elastic.co': [{ proxy: 'elastic.proxy.ustclug.org' }],
'ppa.launchpad.net': [{ proxy: 'launchpad.proxy.ustclug.org' }],
'archive.cloudera.com': [{ regexp: '/cdh5/.*', proxy: 'cloudera.proxy.ustclug.org' }],
'downloads.lede-project.org': [{ proxy: 'lede.proxy.ustclug.org' }],
'downloads.openwrt.org': [{ proxy: 'openwrt.proxy.ustclug.org' }],
'secure.gravatar.com': [{ proxy: 'gravatar.proxy.ustclug.org' }]
},
dns: {
providers: {
aliyun: {
type: 'https',
server: 'dns.alidns.com/dns-query',
cacheSize: 1000
},
usa: {
type: 'https',
server: 'cloudflare-dns.com/dns-query',
cacheSize: 1000
}
// google cdn
'ajax.googleapis.com': [
{
proxy: 'ajax.loli.net',
backup: ['ajax.proxy.ustclug.org'],
case: 'ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js'
}
],
'fonts.googleapis.com': [
{
proxy: 'fonts.loli.net',
backup: ['fonts.proxy.ustclug.org'],
case: 'https://fonts.googleapis.com/css?family=Oswald'
}
],
'themes.googleapis.com': [
{
proxy: 'themes.loli.net',
backup: ['themes.proxy.ustclug.org']
}
],
'themes.googleusercontent.com': [
{ proxy: 'google-themes.proxy.ustclug.org' }
],
'www.google.com': [
{
regexp: '/recaptcha/.*',
proxy: 'www.recaptcha.net'
}
],
'fonts.gstatic.com': [
{
proxy: 'fonts-gstatic.proxy.ustclug.org',
backup: ['gstatic.loli.net']
}
],
'clients*.google.com': [{ abort: true }],
'www.googleapis.com': [{ abort: true }],
'lh*.googleusercontent.com': [{ abort: true }],
// mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.0/napi-v3-win32-x64.tar.gz
'*.s3.amazonaws.com': [
{
regexp: '/sqlite3/.*',
redirect: 'npm.taobao.org/mirrors'
}
],
'registry-1.docker.io': [{ proxy: 'docker.mirrors.ustc.edu.cn' }],
'packages.elastic.co': [{ proxy: 'elastic.proxy.ustclug.org' }],
'ppa.launchpad.net': [{ proxy: 'launchpad.proxy.ustclug.org' }],
'archive.cloudera.com': [{ regexp: '/cdh5/.*', proxy: 'cloudera.proxy.ustclug.org' }],
'downloads.lede-project.org': [{ proxy: 'lede.proxy.ustclug.org' }],
'downloads.openwrt.org': [{ proxy: 'openwrt.proxy.ustclug.org' }],
'secure.gravatar.com': [{ proxy: 'gravatar.proxy.ustclug.org' }]
},
mapping: {
// "解决push的时候需要输入密码的问题",
'api.github.com': 'usa',
'gist.github.com': 'usa'
// "avatars*.githubusercontent.com": "usa"
}
},
variables: {
npm: {
SASS_BINARY_SITE: 'https://npm.taobao.org/mirrors/node-sass/',
PHANTOMJS_CDNURL: 'https://npm.taobao.org/mirrors/phantomjs/',
ELECTRON_MIRROR: 'https://npm.taobao.org/mirrors/electron/',
CYPRESS_DOWNLOAD_MIRROR: 'https://cdn.cypress.io',
NVM_NODEJS_ORG_MIRROR: 'https://npm.taobao.org/mirrors/node',
CHROMEDRIVER_CDNURL: 'https://npm.taobao.org/mirrors/chromedriver',
OPERADRIVER: 'https://npm.taobao.org/mirrors/operadriver',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://npm.taobao.org/mirrors/electron-builder-binaries/',
PYTHON_MIRROR: 'https://npm.taobao.org/mirrors/python'
},
system: {
// eslint-disable-next-line no-template-curly-in-string
NODE_EXTRA_CA_CERTS: '${ca_cert_path}'
}
},
setting: {
startup: { // 开机启动
server: true,
proxy: {
system: true,
npm: true,
yarn: true
dns: {
providers: {
aliyun: {
type: 'https',
server: 'dns.alidns.com/dns-query',
cacheSize: 1000
},
usa: {
type: 'https',
server: 'cloudflare-dns.com/dns-query',
cacheSize: 1000
}
},
variables: {
npm: true
mapping: {
// "解决push的时候需要输入密码的问题",
'api.github.com': 'usa',
'gist.github.com': 'usa'
// "avatars*.githubusercontent.com": "usa"
}
}
}
},
proxy: {},
plugin: {}
}

View File

@ -1,60 +1,134 @@
const server = require('./server/index.js')
const proxy = require('./switch/proxy/index.js')
const status = require('./status')
const config = require('./config')
const event = require('./event')
const shell = require('./shell')
async function proxyStartup ({ ip, port }) {
for (const key in proxy) {
if (config.get().setting.startup.proxy[key]) {
await proxy[key].open({ ip, port })
}
}
const modules = require('./modules')
const proxyConfig = require('./lib/proxy/common/config')
const lodash = require('lodash')
const context = {
config,
shell,
status,
event,
rootCaFile: proxyConfig.getDefaultCACertPath()
}
async function proxyShutdown () {
for (const key in proxy) {
if (status.proxy[key] === false) {
continue
}
await proxy[key].close()
}
function setupPlugin (key, plugin, context, config) {
const pluginConfig = plugin.config
const PluginClass = plugin.plugin
const pluginStatus = plugin.status
const api = PluginClass(context)
config.addDefault(key, pluginConfig)
lodash.set(status, key, pluginStatus)
return api
}
function fireStatus (target) {
event.fire('status', target)
}
const server = modules.server
const proxy = setupPlugin('proxy', modules.proxy, context, config)
const plugin = {}
for (const key in modules.plugin) {
const target = modules.plugin[key]
const api = setupPlugin('plugin.' + key, target, context, config)
plugin[key] = api
}
config.resetDefault()
module.exports = {
status,
api: {
server,
proxy,
config,
startup: async (newConfig) => {
if (newConfig) {
config.set(newConfig)
}
try {
const startup = config.get().setting.startup
if (startup.server) {
server.start(newConfig)
const conf = config.get()
if (conf.server.enabled) {
try {
const cfg = await server.start()
fireStatus({ key: 'server.enabled', value: true })
console.log('代理服务已启动127.0.0.1:' + cfg.port)
} catch (err) {
fireStatus({ key: 'server.enabled', value: false })
console.error('代理服务启动失败:', err)
}
await proxyStartup({ ip: '127.0.0.1', port: config.get().server.port })
if (startup.variables.npm) {
await config.setVariables('npm')
}
} catch (error) {
console.log(error)
}
if (conf.proxy.enabled) {
try {
const ret = await proxy.start()
fireStatus({ key: 'proxy.enabled', value: true })
console.log(`开启系统代理成功:${ret.ip}:${ret.port}`)
} catch (err) {
fireStatus({ key: 'proxy.enabled', value: false })
console.error('开启系统代理失败:', err)
}
}
const plugins = []
for (const key in plugin) {
if (conf.plugin[key].enabled) {
const start = async () => {
try {
await plugin[key].start()
console.log(`插件【${key}】已启动`)
} catch (err) {
console.log(`插件【${key}】启动失败`, err)
}
}
plugins.push(start())
}
}
await Promise.all(plugins)
},
shutdown: async () => {
try {
await proxyShutdown()
return new Promise(resolve => {
server.close()
resolve()
})
const plugins = []
for (const key in plugin) {
if (status.plugin[key].enabled && plugin[key].close) {
const close = async () => {
try {
await plugin[key].close()
console.log(`插件【${key}】已关闭`)
} catch (err) {
console.log(`插件【${key}】关闭失败`, err)
}
}
plugins.push(close())
}
}
await Promise.all(plugins)
if (status.proxy.enabled) {
try {
await proxy.close()
console.log('系统代理已关闭')
} catch (err) {
console.log('系统代理关闭失败', err)
}
}
if (status.server.enabled) {
try {
await server.close()
console.log('代理服务已关闭')
} catch (err) {
console.log('代理服务关闭失败', err)
}
}
} catch (error) {
console.log(error)
}
},
status: {
get () {
return status
}
},
config,
event,
shell
shell,
server,
proxy,
plugin
}
}

View File

@ -62,7 +62,7 @@ module.exports = function createRequestHandler (requestInterceptor, responseInte
function onFree () {
const url = `${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
const start = new Date().getTime()
console.log('代理请求:', url)
console.log('代理请求:', url, rOptions.method)
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
const end = new Date().getTime()
@ -73,7 +73,7 @@ module.exports = function createRequestHandler (requestInterceptor, responseInte
})
proxyReq.on('timeout', () => {
console.error('代理请求超时', rOptions.hostname, rOptions.path)
console.error('代理请求超时', rOptions.protocol, rOptions.hostname, rOptions.path)
reject(new Error(`${rOptions.host}:${rOptions.port}, 代理请求超时`))
})

View File

@ -0,0 +1,19 @@
class AbstractPlugin {
constructor (context) {
this._context = context
}
_getConfig () {
return this._context.config.get()
}
_getShell () {
return this._context.shell
}
_fireStatus (event) {
this._context.event.fire('status', event)
}
}
module.exports = AbstractPlugin

View File

@ -0,0 +1,11 @@
const server = require('./server')
const proxy = require('./proxy')
const plugin = require('./plugin')
module.exports = {
server,
proxy,
plugin
}

View File

@ -0,0 +1,4 @@
const node = require('./node')
module.exports = {
node
}

View File

@ -0,0 +1,29 @@
module.exports = {
name: 'NPM加速',
enabled: true,
startup: {
npm: true,
yarn: true,
variables: true
},
setting: {
'strict-ssl': false,
cafile: true,
NODE_EXTRA_CA_CERTS: true,
NODE_TLS_REJECT_UNAUTHORIZED: false
},
intercepts: {
'cdn.cypress.io': [{ regexp: '/desktop/.*', proxy: 'http://npm.taobao.org/mirrors/cypress/' }]
},
variables: {
SASS_BINARY_SITE: 'https://npm.taobao.org/mirrors/node-sass/',
PHANTOMJS_CDNURL: 'https://npm.taobao.org/mirrors/phantomjs/',
ELECTRON_MIRROR: 'https://npm.taobao.org/mirrors/electron/',
// CYPRESS_DOWNLOAD_MIRROR: 'https://cdn.cypress.io',
NVM_NODEJS_ORG_MIRROR: 'https://npm.taobao.org/mirrors/node',
CHROMEDRIVER_CDNURL: 'https://npm.taobao.org/mirrors/chromedriver',
OPERADRIVER: 'https://npm.taobao.org/mirrors/operadriver',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://npm.taobao.org/mirrors/electron-builder-binaries/',
PYTHON_MIRROR: 'https://npm.taobao.org/mirrors/python'
}
}

View File

@ -0,0 +1,145 @@
const nodeConfig = require('./config')
const NodePlugin = function (context) {
const { config, shell, event, rootCaFile } = context
const api = {
async start () {
try {
await api.setVariables()
} catch (err) {
console.warn('set variables error', err)
}
const ip = '127.0.0.1'
const port = config.get().server.port
await api.setProxy(ip, port)
return { ip, port }
},
async close () {
return api.unsetProxy()
},
async restart () {
await api.close()
await api.start()
},
async save (newConfig) {
api.setVariables()
},
async getNpmEnv () {
const ret = await shell.exec(['npm config list --json'], { type: 'cmd' })
if (ret != null) {
const json = ret.substring(ret.indexOf('{'))
return JSON.parse(json)
}
return {}
},
async setNpmEnv (list) {
const cmds = []
for (const item of list) {
cmds.push(`npm config set ${item.key} ${item.value}`)
}
const ret = await shell.exec(cmds, { type: 'cmd' })
return ret
},
async unsetNpmEnv (list) {
const cmds = []
for (const item of list) {
cmds.push(`npm config delete ${item} `)
}
const ret = await shell.exec(cmds, { type: 'cmd' })
return ret
},
async getVariables () {
const currentMap = await api.getNpmEnv()
const list = []
const map = config.get().plugin.node.variables
for (const key in map) {
const exists = currentMap[key] != null
list.push({
key,
value: map[key],
exists
})
}
return list
},
async setVariables () {
const list = await api.getVariables()
const noSetList = list.filter(item => {
return !item.exists
})
if (noSetList.length > 0) {
return api.setNpmEnv(noSetList)
}
},
async setProxy (ip, port) {
const cmds = [
`npm config set proxy=http://${ip}:${port}`,
`npm config set https-proxy=http://${ip}:${port}`
]
const env = []
/**
* 'strict-ssl': false,
cafile: true,
NODE_EXTRA_CA_CERTS: true,
NODE_TLS_REJECT_UNAUTHORIZED: false
*/
const nodeConfig = config.get().plugin.node
if (nodeConfig.setting['strict-ssl']) {
cmds.push('npm nodeConfig set strict-ssl false')
}
if (nodeConfig.setting.cafile) {
cmds.push(`npm config set cafile "${rootCaFile}"`)
}
if (nodeConfig.setting.NODE_EXTRA_CA_CERTS) {
cmds.push(`npm config set NODE_EXTRA_CA_CERTS "${rootCaFile}"`)
env.push({ key: 'NODE_EXTRA_CA_CERTS', value: rootCaFile })
}
if (nodeConfig.setting.NODE_TLS_REJECT_UNAUTHORIZED) {
cmds.push('npm nodeConfig set NODE_TLS_REJECT_UNAUTHORIZED 0')
env.push({ key: 'NODE_TLS_REJECT_UNAUTHORIZED', value: '0' })
}
const ret = await shell.exec(cmds, { type: 'cmd' })
if (env.length > 0) {
await shell.setSystemEnv({ list: env })
}
event.fire('status', { key: 'plugin.node.enabled', value: true })
console.info('开启【NPM】代理成功')
return ret
},
async unsetProxy () {
const cmds = [
'npm config delete proxy',
'npm config delete https-proxy',
'npm config delete NODE_EXTRA_CA_CERTS',
'npm config delete strict-ssl'
]
const ret = await shell.exec(cmds, { type: 'cmd' })
event.fire('status', { key: 'plugin.node.enabled', value: false })
return ret
}
}
return api
}
module.exports = {
key: 'node',
config: nodeConfig,
status: {
enabled: false
},
plugin: NodePlugin
}

View File

@ -0,0 +1,53 @@
const systemProxy = require('./system-proxy')
const ProxyPlugin = function (context) {
const { config, event } = context
const api = {
async start () {
return api.setProxy()
},
async close () {
return api.unsetProxy()
},
async setProxy () {
const ip = '127.0.0.1'
const port = config.get().server.port
await systemProxy.setProxy(ip, port)
event.fire('status', { key: 'proxy.enabled', value: true })
return { ip, port }
},
async unsetProxy () {
try {
systemProxy.unsetProxy()
event.fire('status', { key: 'proxy.enabled', vlaue: false })
console.log('关闭系统代理成功')
return true
} catch (err) {
console.error('关闭系统代理失败', err)
return false
}
}
}
return api
}
module.exports = {
key: 'proxy',
config: {
enabled: true,
name: '系统代理',
use: 'local',
other: {
host: undefined,
port: undefined,
username: undefined,
password: undefined
}
},
status: {
enabled: false,
proxyTarget: ''
},
plugin: ProxyPlugin
}

View File

@ -0,0 +1,14 @@
const script = `
$signature = @'
[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
'@
$INTERNET_OPTION_SETTINGS_CHANGED = 39
$INTERNET_OPTION_REFRESH = 37
$type = Add-Type -MemberDefinition $signature -Name wininet -Namespace pinvoke -PassThru
$a = $type::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
$b = $type::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
$a -and $b
`
module.exports = script

View File

@ -0,0 +1,74 @@
<#
.Synopsis
This function will set the proxy settings provided as input to the cmdlet.
.Description
This function will set the proxy server and (optinal) Automatic configuration script.
.Parameter ProxyServer
This parameter is set as the proxy for the system.
Data from. This parameter is Mandatory
.Example
Setting proxy information
Set-InternetProxy -proxy "proxy:7890"
.Example
Setting proxy information and (optinal) Automatic Configuration Script
Set-InternetProxy -proxy "proxy:7890" -acs "http://proxy:7892"
#>
Function Set-InternetProxy
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String[]]$Proxy,
[Parameter(Mandatory=$False,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[AllowEmptyString()]
[String[]]$acs
)
Begin
{
$regKey="HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
}
Process
{
Set-ItemProperty -path $regKey ProxyEnable -value 1
Set-ItemProperty -path $regKey ProxyServer -value $proxy
if($acs)
{
Set-ItemProperty -path $regKey AutoConfigURL -Value $acs
}
}
End
{
Write-Output "Proxy is now enabled"
Write-Output "Proxy Server : $proxy"
if ($acs)
{
Write-Output "Automatic Configuration Script : $acs"
}
else
{
Write-Output "Automatic Configuration Script : Not Defined"
}
}
}

View File

@ -0,0 +1,14 @@
$signature = @'
[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
public static extern bool InternetSetOption(IntPtr hInternet, int
dwOption, IntPtr lpBuffer, int dwBufferLength);
'@
$interopHelper = Add-Type -MemberDefinition $signature -Name MyInteropHelper -PassThru
$INTERNET_OPTION_SETTINGS_CHANGED = 39
$INTERNET_OPTION_REFRESH = 37
$result1 = $interopHelper::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
$result2 = $interopHelper::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
$result1 -and $result2

View File

@ -0,0 +1,61 @@
const script = `
Function Set-InternetProxy
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String[]]$Proxy,
[Parameter(Mandatory=$False,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[AllowEmptyString()]
[String[]]$acs
)
Begin
{
$regKey="HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
}
Process
{
Set-ItemProperty -path $regKey ProxyEnable -value 1
Set-ItemProperty -path $regKey ProxyServer -value $proxy
if($acs)
{
Set-ItemProperty -path $regKey AutoConfigURL -Value $acs
}
}
End
{
Write-Output "Proxy is now enabled"
Write-Output "Proxy Server : $proxy"
if ($acs)
{
Write-Output "Automatic Configuration Script : $acs"
}
else
{
Write-Output "Automatic Configuration Script : Not Defined"
}
}
}
`
module.exports = script

View File

@ -0,0 +1,191 @@
const util = require('util')
const os = require('os')
const childProcess = require('child_process')
const _exec = childProcess.exec
const Registry = require('winreg')
// const cmd = require('node-cmd')
const exec = util.promisify(_exec)
const refreshInternetPs = require('./refresh-internet')
const PowerShell = require('node-powershell')
const _lanIP = [
'localhost',
'127.*',
'10.*',
'172.16.*',
'172.17.*',
'172.18.*',
'172.19.*',
'172.20.*',
'172.21.*',
'172.22.*',
'172.23.*',
'172.24.*',
'172.25.*',
'172.26.*',
'172.27.*',
'172.28.*',
'172.29.*',
'172.30.*',
'172.31.*',
'192.168.*',
'<local>'
]
class SystemProxy {
static async setProxy (ip, port) {
throw new Error('You have to implement the method setProxy!')
}
static async unsetProxy () {
throw new Error('You have to implement the method unsetProxy!')
}
}
// TODO: Add path http_proxy and https_proxy
// TODO: Support for non-gnome
class LinuxSystemProxy extends SystemProxy {
static async setProxy (ip, port) {
await exec('gsettings set org.gnome.system.proxy mode manual')
await exec(`gsettings set org.gnome.system.proxy.http host ${ip}`)
await exec(`gsettings set org.gnome.system.proxy.http port ${port}`)
}
static async unsetProxy () {
await exec('gsettings set org.gnome.system.proxy mode none')
}
}
// TODO: Support for lan connections too
// TODO: move scripts to ../scripts/darwin
class DarwinSystemProxy extends SystemProxy {
static async setProxy (ip, port) {
const wifiAdaptor = (await exec('sh -c "networksetup -listnetworkserviceorder | grep `route -n get 0.0.0.0 | grep \'interface\' | cut -d \':\' -f2` -B 1 | head -n 1 | cut -d \' \' -f2"')).stdout.trim()
await exec(`networksetup -setwebproxy '${wifiAdaptor}' ${ip} ${port}`)
await exec(`networksetup -setsecurewebproxy '${wifiAdaptor}' ${ip} ${port}`)
}
static async unsetProxy () {
const wifiAdaptor = (await exec('sh -c "networksetup -listnetworkserviceorder | grep `route -n get 0.0.0.0 | grep \'interface\' | cut -d \':\' -f2` -B 1 | head -n 1 | cut -d \' \' -f2"')).stdout.trim()
await exec(`networksetup -setwebproxystate '${wifiAdaptor}' off`)
await exec(`networksetup -setsecurewebproxystate '${wifiAdaptor}' off`)
}
}
class WindowsSystemProxy extends SystemProxy {
static async setProxy (ip, port) {
const regKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
})
let lanIpStr = ''
for (const string of _lanIP) {
lanIpStr += string + ';'
}
// console.log('lanIps:', lanIpStr, ip, port)
await Promise.all([
WindowsSystemProxy._asyncRegSet(regKey, 'MigrateProxy', Registry.REG_DWORD, 1),
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyEnable', Registry.REG_DWORD, 1),
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyHttp1.1', Registry.REG_DWORD, 0),
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyServer', Registry.REG_SZ, `${ip}:${port}`),
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyOverride', Registry.REG_SZ, lanIpStr)
])
await WindowsSystemProxy._resetWininetProxySettings('echo refreshing') // 要执行以下这个才能生效
await WindowsSystemProxy._resetWininetProxySettings(refreshInternetPs)
}
static async unsetProxy () {
const regKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
})
await Promise.all([
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyEnable', Registry.REG_DWORD, 0),
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyServer', Registry.REG_SZ, '')
])
await WindowsSystemProxy._resetWininetProxySettings(refreshInternetPs)
}
static _asyncRegSet (regKey, name, type, value) {
return new Promise((resolve, reject) => {
regKey.set(name, type, value, e => {
if (e) {
reject(e)
} else {
resolve()
}
})
})
}
static _resetWininetProxySettings (script) {
return new Promise((resolve, reject) => {
const ps = new PowerShell({
executionPolicy: 'Bypass',
noProfile: true
})
// ps.addCommand(setproxyPs)
// ps.addCommand(`Set-InternetProxy -Proxy "${ip}:${port}"`)
ps.addCommand(script)
ps.invoke()
.then(output => {
console.log(output)
resolve()
})
.catch(err => {
console.log(err)
reject(err)
})
// const scriptPath = path.join(__dirname, '..', 'scripts', 'windows', 'wininet-reset-settings.ps1')
// const child = spawn('powershell.exe', [scriptPath])
// child.stdout.setEncoding('utf8')
// child.stdout.on('data', (data) => {
// console.log('data', data)
// if (data.includes('True')) {
// resolve()
// } else {
// reject(data)
// }
// })
//
// child.stderr.on('data', (err) => {
// console.log('data', err)
// reject(err)
// })
//
// child.stdin.end()
})
}
}
function getSystemProxy () {
switch (os.platform()) {
case 'darwin':
return DarwinSystemProxy
case 'linux':
return LinuxSystemProxy
case 'win32':
case 'win64':
return WindowsSystemProxy
case 'unknown os':
default:
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
}
}
module.exports = {
async setProxy (ip, port) {
const systemProxy = getSystemProxy()
await systemProxy.setProxy(ip, port)
},
async unsetProxy () {
const systemProxy = getSystemProxy()
await systemProxy.unsetProxy()
}
}

View File

@ -0,0 +1,82 @@
const ProxyOptions = require('./options')
const mitmproxy = require('../../lib/proxy')
const config = require('../../config')
const event = require('../../event')
const status = require('../../status')
const shell = require('../../shell')
let server
const serverApi = {
async startup () {
if (config.get().server.startup) {
return this.start(config.get().server)
}
},
async shutdown () {
if (status.server) {
return this.close()
}
},
async start (newConfig) {
if (server != null) {
server.close()
}
config.set(newConfig)
const proxyOptions = ProxyOptions(config.get())
const newServer = mitmproxy.createProxy(proxyOptions, () => {
event.fire('status', { key: 'server.enabled', value: true })
console.log(`代理服务已启动:127.0.0.1:${proxyOptions.port}`)
})
newServer.on('close', () => {
if (server === newServer) {
server = null
event.fire('status', { key: 'server.enabled', value: false })
}
})
newServer.on('error', (e) => {
console.log('server error', e)
// newServer = null
event.fire('error', { key: 'server', error: e })
})
newServer.config = proxyOptions
server = newServer
return { port: proxyOptions.port }
},
async close () {
return new Promise((resolve, reject) => {
if (server) {
server.close((err) => {
if (err) {
console.log('close error', err)
reject(err)
} else {
console.log('代理服务关闭')
resolve()
}
})
// 3秒后强制关闭
setTimeout(() => {
console.log('强制关闭')
shell.killByPort(config.get().server.port)
server = null
event.fire('status', { key: 'server.enabled', value: false })
resolve()
}, 3000)
} else {
console.log('server is null')
resolve()
}
})
},
async restart () {
try {
await serverApi.close()
} catch (err) {
console.log('stop error', err)
}
await serverApi.start()
},
getServer () {
return server
}
}
module.exports = serverApi

View File

@ -1,20 +1,18 @@
const getLogger = require('../lib/utils/logger')
const logger = getLogger('proxy')
const interceptors = require('../lib/interceptor')
const dnsUtil = require('../lib/dns')
const interceptors = require('../../lib/interceptor')
const dnsUtil = require('../../lib/dns')
const lodash = require('lodash')
function matchHostname (intercepts, hostname) {
const interceptOpts = intercepts[hostname]
if (interceptOpts) {
return interceptOpts
}
if (!interceptOpts) { // 该域名没有配置拦截器,直接过
if (!interceptOpts) {
for (const target in intercepts) {
if (target.indexOf('*') < 0) {
continue
}
// 正则表达式匹配
const regexp = target.replace('.', '\\.').replace('*', '.*')
if (hostname.match(regexp)) {
if (hostname.match(target)) {
return intercepts[target]
}
}
@ -25,19 +23,62 @@ function isMatched (url, regexp) {
return url.match(regexp)
}
function domainRegexply (target) {
return target.replace(/\./g, '\\.').replace(/\*/g, '.*')
}
// function test () {
// const ret = domainRegexply('*.aaa.com')
// console.log(ret)
// const success = 'aa.aaa.com'.match(ret)
// console.log(success)
// const fail = 'a.aaaa.com'.match(ret)
// console.log(fail)
// }
// test()
module.exports = (config) => {
let intercepts = lodash.cloneDeep(config.server.intercepts)
const dnsMapping = lodash.cloneDeep(config.server.dns.mapping)
if (config.plugin) {
lodash.each(config.plugin, (value) => {
const plugin = value
if (plugin.intercepts) {
lodash.merge(intercepts, plugin.intercepts)
}
if (plugin.dns) {
lodash.merge(dnsMapping, plugin.dns)
}
})
}
const regexpIntercepts = {}
lodash.each(intercepts, (value, domain) => {
if (domain.indexOf('*') >= 0) {
const regDomain = domainRegexply(domain)
regexpIntercepts[regDomain] = value
} else {
regexpIntercepts[domain] = value
}
})
intercepts = regexpIntercepts
const serverConfig = config.server
return {
port: config.server.port,
port: serverConfig.port,
dnsConfig: {
providers: dnsUtil.initDNS(config.dns.providers), mapping: config.dns.mapping
providers: dnsUtil.initDNS(serverConfig.dns.providers),
mapping: dnsMapping
},
sslConnectInterceptor: (req, cltSocket, head) => {
const hostname = req.url.split(':')[0]
return !!matchHostname(config.intercepts, hostname) // 配置了拦截的域名,将会被代理
return !!matchHostname(intercepts, hostname) // 配置了拦截的域名,将会被代理
},
requestInterceptor: (rOptions, req, res, ssl, next) => {
const hostname = rOptions.hostname
const interceptOpts = matchHostname(config.intercepts, hostname)
const interceptOpts = matchHostname(intercepts, hostname)
if (!interceptOpts) { // 该域名没有配置拦截器,直接过
next()
return
@ -45,20 +86,18 @@ module.exports = (config) => {
for (const interceptOpt of interceptOpts) { // 遍历拦截配置
let regexpList
if(interceptOpt.regexp!=null){
if (interceptOpt.regexp != null) {
if (interceptOpt.regexp instanceof Array) {
regexpList = interceptOpt.regexp
} else {
regexpList = [interceptOpt.regexp]
}
}else{
} else {
regexpList = [true]
}
for (const regexp of regexpList) { // 遍历regexp配置
if(regexp!==true){
if (regexp !== true) {
if (!isMatched(req.url, regexp)) {
continue
}
@ -75,7 +114,7 @@ module.exports = (config) => {
}
} catch (err) {
// 拦截失败
logger.error(err)
console.error(err)
}
}
}

View File

@ -1,55 +0,0 @@
const ProxyOptions = require('./options')
const mitmproxy = require('../lib/proxy')
const config = require('../config')
const event = require('../event')
let server
const serverApi = {
async start (newConfig) {
if (server != null) {
server.close()
}
config.set(newConfig)
const proxyOptions = ProxyOptions(config.get())
server = mitmproxy.createProxy(proxyOptions, () => {
event.fire('status', { key: 'server', value: true })
})
server.on('close', () => {
event.fire('status', { key: 'server', value: false })
})
server.on('error', (e) => {
event.fire('error', { key: 'server.start', error: e })
})
server.config = config.get()
return server.config
},
async close () {
return new Promise((resolve, reject) => {
if (server) {
server.close((err) => {
if (err) {
console.log('close error', err)
reject(err)
} else {
resolve()
}
})
server = null
} else {
console.log('server is null')
resolve()
}
})
},
async restart () {
try {
await serverApi.close()
} catch (err) {
console.log('stop error', err)
}
await serverApi.start()
},
getServer () {
return server
}
}
module.exports = serverApi

View File

@ -1,3 +1,4 @@
const shell = require('./shell')
const killByPort = require('./scripts/kill-by-port')
const setupCa = require('./scripts/setup-ca')
const getSystemEnv = require('./scripts/get-system-env')
@ -10,5 +11,8 @@ module.exports = {
getSystemEnv,
setSystemEnv,
getNpmEnv,
setNpmEnv
setNpmEnv,
exec (cmds, args) {
shell.getSystemShell().exec(cmds, args)
}
}

View File

@ -3,9 +3,9 @@ const lodash = require('lodash')
const status = {
server: false,
proxy: {
system: false,
npm: false,
git: false
},
plugin: {
}
}
@ -13,4 +13,5 @@ event.register('status', (event) => {
lodash.set(status, event.key, event.value)
console.log('status changed:', event)
}, -999)
module.exports = status

View File

@ -1,93 +1,26 @@
const cmd = require('node-cmd')
const util = require('util')
const winExec = util.promisify(cmd.get, { multiArgs: true, context: cmd })
const os = require('os')
const config = require('../../../lib/proxy/common/config')
class SystemProxy {
static async setProxy (ip, port) {
throw new Error('You have to implement the method setProxy!')
}
static async unsetProxy () {
throw new Error('You have to implement the method unsetProxy!')
}
}
class DarwinSystemProxy extends SystemProxy {
}
class LinuxSystemProxy extends SystemProxy {
}
class WindowsSystemProxy extends SystemProxy {
static async setProxy (ip, port) {
let ret = await winExec(`npm config set proxy=http://${ip}:${port}`)
console.log('npm http proxy set success', ret)
ret = await winExec(`npm config set https-proxy=http://${ip}:${port}`)
console.log('npm https proxy set success', ret)
// ret = await winExec(`npm config set cafile ${config.getDefaultCACertPath()}`)
// console.log('npm cafile set success', ret)
ret = await winExec(`npm config set NODE_EXTRA_CA_CERTS ${config.getDefaultCACertPath()}`)
console.log('npm NODE_EXTRA_CA_CERTS set success', ret)
ret = await winExec('npm config set strict-ssl false')
console.log('npm strict-ssl false success', ret)
}
static async unsetProxy () {
await winExec('npm config delete proxy')
console.log('npm https proxy unset success')
await winExec('npm config delete https-proxy')
console.log('npm https proxy unset success')
// await winExec('npm config delete cafile')
// console.log('npm ca unset success')
await winExec('npm config delete NODE_EXTRA_CA_CERTS')
console.log('npm NODE_EXTRA_CA_CERTS unset success')
await winExec(' npm config delete strict-ssl')
console.log('npm strict-ssl true success')
}
static _asyncRegSet (regKey, name, type, value) {
return new Promise((resolve, reject) => {
regKey.set(name, type, value, e => {
if (e) {
reject(e)
} else {
resolve()
}
})
})
}
}
function getSystemProxy () {
switch (os.platform()) {
case 'darwin':
return DarwinSystemProxy
case 'linux':
return LinuxSystemProxy
case 'win32':
case 'win64':
return WindowsSystemProxy
case 'unknown os':
default:
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
}
}
const Shell = require('../../../shell')
module.exports = {
async setProxy (ip, port) {
const systemProxy = getSystemProxy()
await systemProxy.setProxy(ip, port)
const cmds = [
`npm config set proxy=http://${ip}:${port}`,
`npm config set https-proxy=http://${ip}:${port}`,
`npm config set NODE_EXTRA_CA_CERTS ${config.getDefaultCACertPath()}`,
'npm config set strict-ssl false'
]
const ret = await Shell.exec(cmds)
return ret
},
async unsetProxy () {
const systemProxy = getSystemProxy()
await systemProxy.unsetProxy()
const cmds = [
'npm config delete proxy',
'npm config delete https-proxy',
'npm config delete NODE_EXTRA_CA_CERTS',
'npm config delete strict-ssl'
]
const ret = await Shell.exec(cmds)
return ret
}
}

View File

@ -6,8 +6,7 @@ const Registry = require('winreg')
// const cmd = require('node-cmd')
const exec = util.promisify(_exec)
const refreshInternetPs = require('./refresh-internet')
const Shell = require('node-powershell')
const PowerShell = require('node-powershell')
const _lanIP = [
'localhost',
'127.*',
@ -124,7 +123,7 @@ class WindowsSystemProxy extends SystemProxy {
static _resetWininetProxySettings (script) {
return new Promise((resolve, reject) => {
const ps = new Shell({
const ps = new PowerShell({
executionPolicy: 'Bypass',
noProfile: true
})

View File

@ -1901,8 +1901,7 @@
"@types/json-schema": {
"version": "7.0.6",
"resolved": "https://registry.npm.taobao.org/@types/json-schema/download/@types/json-schema-7.0.6.tgz?cache=0&sync_timestamp=1598910403749&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fjson-schema%2Fdownload%2F%40types%2Fjson-schema-7.0.6.tgz",
"integrity": "sha1-9MfsQ+gbMZqYFRFQMXCfJph4kfA=",
"dev": true
"integrity": "sha1-9MfsQ+gbMZqYFRFQMXCfJph4kfA="
},
"@types/json5": {
"version": "0.0.29",
@ -2372,16 +2371,6 @@
"integrity": "sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo=",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1601839122515&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
"integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"cacache": {
"version": "13.0.1",
"resolved": "https://registry.npm.taobao.org/cacache/download/cacache-13.0.1.tgz",
@ -2408,34 +2397,6 @@
"unique-filename": "^1.1.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz",
"integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
"integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
"integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
"dev": true,
"optional": true
},
"find-cache-dir": {
"version": "3.3.1",
"resolved": "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-3.3.1.tgz",
@ -2457,25 +2418,6 @@
"path-exists": "^4.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz",
"integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
"integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-5.0.0.tgz",
@ -2540,16 +2482,6 @@
"minipass": "^3.1.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1598611709087&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
"integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"terser-webpack-plugin": {
"version": "2.3.8",
"resolved": "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-2.3.8.tgz?cache=0&sync_timestamp=1602701885709&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-2.3.8.tgz",
@ -2566,18 +2498,6 @@
"terser": "^4.6.12",
"webpack-sources": "^1.4.3"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.0.0-beta.9",
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.0.0-beta.9.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.0.0-beta.9.tgz",
"integrity": "sha1-UlEsthwpaCfJnA1UOYvvhL5ESPw=",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
}
}
},
@ -2936,7 +2856,6 @@
"version": "6.12.6",
"resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.6.tgz?cache=0&sync_timestamp=1602353715225&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.6.tgz",
"integrity": "sha1-uvWmLoArB9l3A0WG+MO69a3ybfQ=",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@ -2953,8 +2872,7 @@
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.5.2.tgz?cache=0&sync_timestamp=1595907068314&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv-keywords%2Fdownload%2Fajv-keywords-3.5.2.tgz",
"integrity": "sha1-MfKdpatuANHC0yms97WSlhTVAU0=",
"dev": true
"integrity": "sha1-MfKdpatuANHC0yms97WSlhTVAU0="
},
"alphanum-sort": {
"version": "1.0.2",
@ -3101,7 +3019,6 @@
"version": "3.1.1",
"resolved": "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.1.tgz",
"integrity": "sha1-xV7PAhheJGklk5kxDBc84xIzsUI=",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@ -4396,14 +4313,12 @@
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npm.taobao.org/big.js/download/big.js-5.2.2.tgz",
"integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=",
"dev": true
"integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg="
},
"binary-extensions": {
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.1.0.tgz",
"integrity": "sha1-MPpAyef+B9vIlWeM0ocCTeokHdk=",
"dev": true
"integrity": "sha1-MPpAyef+B9vIlWeM0ocCTeokHdk="
},
"bindings": {
"version": "1.5.0",
@ -5229,7 +5144,6 @@
"version": "3.4.3",
"resolved": "https://registry.npm.taobao.org/chokidar/download/chokidar-3.4.3.tgz?cache=0&sync_timestamp=1602585381749&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-3.4.3.tgz",
"integrity": "sha1-wd84IxRI5FykrFiObHlXO6alfVs=",
"dev": true,
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
@ -5245,7 +5159,6 @@
"version": "3.0.2",
"resolved": "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz",
"integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
@ -5254,7 +5167,6 @@
"version": "7.0.1",
"resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-7.0.1.tgz",
"integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
@ -5262,14 +5174,12 @@
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz",
"integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=",
"dev": true
"integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss="
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz",
"integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
@ -7362,8 +7272,7 @@
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/emojis-list/download/emojis-list-3.0.0.tgz",
"integrity": "sha1-VXBmIEatKeLpFucariYKvf9Pang=",
"dev": true
"integrity": "sha1-VXBmIEatKeLpFucariYKvf9Pang="
},
"encodeurl": {
"version": "1.0.2",
@ -8318,8 +8227,7 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",
"integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",
"dev": true
"integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU="
},
"fast-glob": {
"version": "2.2.7",
@ -8361,8 +8269,7 @@
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/fast-json-stable-stringify/download/fast-json-stable-stringify-2.1.0.tgz?cache=0&sync_timestamp=1576340291001&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-json-stable-stringify%2Fdownload%2Ffast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM=",
"dev": true
"integrity": "sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM="
},
"fast-levenshtein": {
"version": "2.0.6",
@ -8780,7 +8687,6 @@
"version": "2.1.3",
"resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.3.tgz",
"integrity": "sha1-+3OHA66NL5/pAMM4Nt3r7ouX8j4=",
"dev": true,
"optional": true
},
"function-bind": {
@ -8848,7 +8754,6 @@
"version": "5.1.1",
"resolved": "https://registry.npm.taobao.org/glob-parent/download/glob-parent-5.1.1.tgz",
"integrity": "sha1-tsHvQXxOVmPqSY8cRa+saRa7wik=",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@ -9855,7 +9760,6 @@
"version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz",
"integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
@ -9964,8 +9868,7 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-finite": {
"version": "1.1.0",
@ -9993,7 +9896,6 @@
"version": "4.0.1",
"resolved": "https://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz",
"integrity": "sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw=",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
@ -10369,8 +10271,7 @@
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599333856086&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",
"integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",
"dev": true
"integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA="
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@ -10510,6 +10411,11 @@
"graceful-fs": "^4.1.9"
}
},
"klona": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
"integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA=="
},
"latest-version": {
"version": "5.1.0",
"resolved": "https://registry.npm.taobao.org/latest-version/download/latest-version-5.1.0.tgz",
@ -11305,8 +11211,7 @@
"neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npm.taobao.org/neo-async/download/neo-async-2.6.2.tgz",
"integrity": "sha1-tKr7k+OustgXTKU88WOrfXMIMF8=",
"dev": true
"integrity": "sha1-tKr7k+OustgXTKU88WOrfXMIMF8="
},
"nice-try": {
"version": "1.0.5",
@ -11465,8 +11370,7 @@
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz",
"integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=",
"dev": true
"integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU="
},
"normalize-range": {
"version": "0.1.2",
@ -12223,8 +12127,7 @@
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.2.tgz?cache=0&sync_timestamp=1584790434095&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpicomatch%2Fdownload%2Fpicomatch-2.2.2.tgz",
"integrity": "sha1-IfMz6ba46v8CRo9RRupAbTRfTa0=",
"dev": true
"integrity": "sha1-IfMz6ba46v8CRo9RRupAbTRfTa0="
},
"pify": {
"version": "4.0.1",
@ -13054,8 +12957,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npm.taobao.org/punycode/download/punycode-2.1.1.tgz",
"integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=",
"dev": true
"integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew="
},
"pupa": {
"version": "2.0.1",
@ -13300,7 +13202,6 @@
"version": "3.5.0",
"resolved": "https://registry.npm.taobao.org/readdirp/download/readdirp-3.5.0.tgz?cache=0&sync_timestamp=1602584331621&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freaddirp%2Fdownload%2Freaddirp-3.5.0.tgz",
"integrity": "sha1-m6dMAZsV02UnjS6Ru4xI17TULJ4=",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
@ -13761,6 +13662,53 @@
"truncate-utf8-bytes": "^1.0.0"
}
},
"sass": {
"version": "1.27.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.27.1.tgz",
"integrity": "sha512-Co5i3s4kN0AgXe8ZFfIl4pfjHjPgotT81O68m3buwdj7v3oHjYiWNqp0oXTKXnEqyKU30KAYC5u8uUF4x+BKfw==",
"requires": {
"chokidar": ">=2.0.0 <4.0.0"
}
},
"sass-loader": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.4.tgz",
"integrity": "sha512-zhdZ8qvZM4iL5XjLVEjJLvKWvC+MB+hHgzL2x/Nf7UHpUNmPYsJvypW79bW39g4LZ603dH/dRSsRYzJJIljtdA==",
"requires": {
"klona": "^2.0.4",
"loader-utils": "^2.0.0",
"neo-async": "^2.6.2",
"schema-utils": "^3.0.0",
"semver": "^7.3.2"
},
"dependencies": {
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"schema-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
"requires": {
"@types/json-schema": "^7.0.6",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
}
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
}
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npm.taobao.org/sax/download/sax-1.2.4.tgz",
@ -15725,7 +15673,6 @@
"version": "4.4.0",
"resolved": "https://registry.npm.taobao.org/uri-js/download/uri-js-4.4.0.tgz",
"integrity": "sha1-qnFCYd55PoqCNHp7zJznTobyhgI=",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
@ -16414,11 +16361,97 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.0.0-beta.9",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.9.tgz",
"integrity": "sha512-mu9pg6554GbXDSO8LlxkQM6qUJzUkb/A0FJc9LgRqnU9MCnhzEXwCt1Zx5NObvFpzs2mH2dH/uUCDwL8Qaz9sA==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-ref": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/vue-ref/download/vue-ref-2.0.0.tgz",
"integrity": "sha1-SDCE1zKr7RHaeWd4qCZqOvDqGpw="
},
"vue-router": {
"version": "3.4.8",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.8.tgz",
"integrity": "sha512-3BsR84AqarcmweXjItxw3jwQsiYNssYg090yi4rlzTnCJxmHtkyCvhNz9Z7qRSOkmiV485KkUCReTp5AjNY4wg=="
},
"vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npm.taobao.org/vue-style-loader/download/vue-style-loader-4.1.2.tgz",

View File

@ -22,8 +22,11 @@
"es-abstract": "^1.17.7",
"json5": "^2.1.3",
"lodash": "^4.17.20",
"sass": "^1.27.1",
"sass-loader": "^10.0.4",
"vue": "^2.6.11",
"vue-json-editor": "^1.4.2"
"vue-json-editor": "^1.4.2",
"vue-router": "^3.4.8"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",

View File

@ -58,7 +58,7 @@ function setTray (app) {
function createWindow () {
// Create the browser window.
win = new BrowserWindow({
width: 800,
width: 900,
height: 700,
webPreferences: {
enableRemoteModule: true,

View File

@ -132,6 +132,7 @@ export default {
// 合并用户配置
localApi.config.reload()
DevSidecar.api.startup()
},
devSidecar: DevSidecar
}

View File

@ -1,16 +1,37 @@
import Vue from 'vue'
import App from './view/components/App.vue'
import App from './view/App.vue'
import antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
import view from './view'
import { apiInit } from './view/api'
import VueRouter from 'vue-router'
import routes from './view/router'
import DsContainer from './view/components/container'
Vue.config.productionTip = false
Vue.use(antd)
Vue.use(VueRouter)
Vue.component(DsContainer)
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
apiInit().then(() => {
apiInit().then((api) => {
const app = new Vue({
router,
render: h => h(App)
}).$mount('#app')
view.register(app)
})
// fix vue-router NavigationDuplicated
const VueRouterPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return VueRouterPush.call(this, location).catch(err => err)
}
const VueRouterReplace = VueRouter.prototype.replace
VueRouter.prototype.replace = function replace (location) {
return VueRouterReplace.call(this, location).catch(err => err)
}

View File

@ -0,0 +1,122 @@
<template>
<div class="ds_layout">
<a-layout>
<a-layout-sider theme="light">
<div class="logo" >
<img height="60px" src="/logo/logo-lang.svg">
</div>
<div class="aside">
<a-menu
mode="inline"
:defaultSelectedKeys="[$route.fullPath]"
>
<template v-for="(item) of menus">
<a-sub-menu v-if="item.children && item.children.length>0" :key="item.path" @titleClick="titleClick(item)">
<span slot="title"><a-icon :type="item.icon?item.icon:'file'" /><span>{{item.title}}</span></span>
<a-menu-item v-for="(sub) of item.children" :key="sub.path" @click="menuClick(sub)" >
{{ sub.title }}
</a-menu-item>
</a-sub-menu>
<a-menu-item v-else :key="item.path" @click="menuClick(item)">
<a-icon :type="item.icon?item.icon:'file'"/>
<span class="nav-text">{{ item.title }}</span>
</a-menu-item>
</template>
</a-menu>
</div>
</a-layout-sider>
<a-layout>
<!-- <a-layout-header>Header</a-layout-header>-->
<a-layout-content>
<router-view></router-view>
</a-layout-content>
<a-layout-footer>
<div class="footer">
©2020 docmirror.cn
</div>
</a-layout-footer>
</a-layout>
</a-layout>
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
data () {
return {
menus: [
{ title: '首页', path: '/index', icon: 'home' },
{ title: '加速服务', path: '/server' },
{ title: '系统代理', path: '/proxy' },
{
title: '应用',
path: '/app',
children: [
{ title: 'NPM加速', path: '/app/node' }
]
}
]
}
},
computed: {
},
created () {
},
methods: {
handleClick (e) {
console.log('click', e)
},
titleClick (e) {
console.log('titleClick', e)
},
menuClick (item) {
console.log('menu click', item)
this.$router.push(item.path)
}
}
}
</script>
<style lang="scss">
body{
height: 100%;
}
.ds_layout {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
height: 100%;
.ant-layout-has-sider{
border:1px solid #eee;
}
.ant-layout-sider-children{
border-right:1px solid #eee;
}
.ant-layout{
height:100%
}
.logo{
padding:5px;
border-bottom: #eee solid 1px;
height:60px;
img{
height:100%
}
}
.ant-layout-footer{
padding:10px;
text-align: center;
border-top:#d6d4d4 solid 1px;
}
.ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left{
border:0;
}
}
</style>

View File

@ -26,6 +26,7 @@ export function apiInit () {
for (const item of list) {
bindApi(item)
}
console.log('api inited:', apiObj)
return apiObj
})
}

View File

@ -1,203 +0,0 @@
<template>
<div id="app">
<template>
<div style="margin:auto">
<div style="text-align: center"><img height="80px" src="/logo/logo-lang.svg"></div>
<a-card title="给开发者的辅助工具" style="width: 500px;margin:auto">
<div style="display: flex; align-items:center;justify-content:space-around;flex-direction: row">
<div style="text-align: center">
<div class="big_button" >
<a-button shape="circle" :type="startup.type()" :loading="startup.loading" @click="startup.doClick" >
<img v-if="!startup.loading && !status.server" width="50" src="/logo/logo-simple.svg">
<img v-if="!startup.loading && status.server" width="50" src="/logo/logo-fff.svg">
</a-button>
<div style="margin-top: 10px">{{status.server?'已开启':'已关闭'}}</div>
</div>
</div>
<div :span="12">
<a-form style="margin-top:20px" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }" >
<a-form-item label="代理服务">
<a-switch :loading="server.loading" v-model="status.server" default-checked v-on:click="server.doClick">
<a-icon slot="checkedChildren" type="check" />
<a-icon slot="unCheckedChildren" type="close" />
</a-switch>
</a-form-item>
<a-form-item v-for=" (item, key) in proxy" :key="key" :label="_lang(key,langSetting.proxy) ">
<a-switch :loading="item.loading" v-model="status.proxy[key]" default-checked v-on:click="item.doClick">
<a-icon slot="checkedChildren" type="check" />
<a-icon slot="unCheckedChildren" type="close" />
</a-switch>
</a-form-item>
</a-form>
</div>
</div>
<span slot="extra" >
<a-button style="margin-right:10px" @click="openSetupCa" >安装根证书</a-button>
<a-button v-if="config" @click="openSettings" icon="setting" ></a-button>
</span>
</a-card>
<setup-ca title="安装证书" :visible.sync="setupCa.visible"></setup-ca>
<settings v-if="config" title="设置" :config="config" :visible.sync="settings.visible" @change="onConfigChanged"></settings>
</div>
</template>
</div>
</template>
<script>
import api from '../api'
import status from '../status'
import lodash from 'lodash'
import Settings from './settings'
import setupCa from './setup-ca'
export default {
name: 'App',
components: {
Settings, setupCa
},
data () {
return {
langSetting: {
proxy: {
system: '系统代理',
npm: 'npm代理',
yarn: 'yarn代理'
}
},
status: status,
startup: {
loading: false,
type: () => {
return this.status.server ? 'primary' : 'default'
},
doClick: () => {
if (this.status.server) {
this.apiCall(this.startup, api.shutdown)
} else {
this.apiCall(this.startup, api.startup)
}
}
},
server: {
key: '代理服务',
loading: false,
doClick: (checked) => {
this.onServerClick(checked)
}
},
proxy: undefined,
config: undefined,
settings: {
visible: false
},
setupCa: {
visible: false
}
}
},
computed: {
_intercepts () {
return this.config.intercepts
}
},
created () {
this.proxy = this.createProxyBtns()
this.reloadConfig().then(() => {
this.start(true)
})
},
methods: {
reloadConfig () {
return api.config.reload().then(ret => {
this.config = ret
return ret
})
},
_lang (key, parent) {
const label = parent ? lodash.get(parent, key) : lodash.get(this.langSetting, key)
if (label) {
return label
}
return key
},
createProxyBtns () {
const btns = {}
for (const type in api.proxy) {
btns[type] = {
loading: false,
key: type,
doClick: (checked) => {
this.onSwitchClick(this.proxy[type], api.proxy[type].open, api.proxy[type].close, checked)
}
}
}
return btns
},
async apiCall (btn, api, param) {
btn.loading = true
try {
const ret = await api(param)
return ret
} catch (err) {
console.log('api invoke error:', err)
} finally {
btn.loading = false
}
},
onSwitchClick (btn, openApi, closeApi, checked) {
if (checked) {
return this.apiCall(btn, openApi)
} else {
return this.apiCall(btn, closeApi)
}
},
onServerClick (checked) {
return this.onSwitchClick(this.server, api.server.start, api.server.close, checked)
},
start (checked) {
this.apiCall(this.startup, api.startup)
},
openSettings () {
this.settings.visible = true
},
onConfigChanged (newConfig) {
console.log('config changed', newConfig)
this.reloadConfig().then(() => {
if (this.status.server) {
return api.server.restart()
}
})
},
openSetupCa () {
this.setupCa.visible = true
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
padding-top:60px;
}
.big_button >button{
width:100px;
height:100px;
border-radius: 100px;
}
.big_button >button i{
size:40px
}
div.ant-form-item{
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<div class="ds-container">
<div class="body-wrapper">
<div v-if="$slots.header" class="container-header"><slot name="header"></slot></div>
<div class="container-body"> <slot></slot></div>
<div class="container-footer"> <slot name="footer"></slot></div>
</div>
</div>
</template>
<script>
export default {
name: 'ds-container'
}
</script>
<style lang="scss">
.ds-container{
height:100%;
background: #fff;
display: flex;
position: relative;
.body-wrapper{
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.container-header{
padding:15px;
border-bottom: 1px solid #EEE;
background: #FFF;
height:60px;
display: flex;
align-items: center;
justify-content: space-between;
}
.container-body{
flex: 1;
height: 0;
overflow: auto;
position: relative;
padding:15px;
}
}
</style>

View File

@ -12,7 +12,7 @@ function register (app) {
api.on('error.core', (event, message) => {
console.error('view on error', message)
const key = message.key
if (key === 'server.start') {
if (key === 'server') {
handleServerStartError(message.error, app)
}
})

View File

@ -0,0 +1,3 @@
<template>
<div>node</div>
</template>

View File

@ -0,0 +1,195 @@
<template>
<ds-container>
<template slot="header">
给开发者的辅助工具
<span>
<a-button style="margin-right:10px" @click="openSetupCa"></a-button>
</span>
</template>
<div style="display: flex; align-items:center;justify-content:space-around;flex-direction: row">
<div style="text-align: center">
<div class="big_button">
<a-button shape="circle" :type="startup.type()" :loading="startup.loading" @click="startup.doClick">
<img v-if="!startup.loading && !status.server" width="50" src="/logo/logo-simple.svg">
<img v-if="!startup.loading && status.server" width="50" src="/logo/logo-fff.svg">
</a-button>
<div style="margin-top: 10px">{{ status.server ? '已开启' : '已关闭' }}</div>
</div>
</div>
<div :span="12">
<a-form style="margin-top:20px" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-form-item v-for=" (item, key) in switchBtns" :key="key" :label="item.label">
<a-switch :loading="item.loading" v-model="item.status[key].enabled" default-checked v-on:click="item.doClick">
<a-icon slot="checkedChildren" type="check"/>
<a-icon slot="unCheckedChildren" type="close"/>
</a-switch>
</a-form-item>
</a-form>
</div>
</div>
<setup-ca title="安装证书" :visible.sync="setupCa.visible"></setup-ca>
</ds-container>
</template>
<script>
import api from '../api'
import status from '../status'
import lodash from 'lodash'
import setupCa from '../components/setup-ca'
import DsContainer from '../components/container'
export default {
name: 'Index',
components: {
DsContainer,
setupCa
},
data () {
return {
status: {
proxy: {},
plugin: {
node: {}
}
},
startup: {
loading: false,
type: () => {
return this.status.server ? 'primary' : 'default'
},
doClick: () => {
if (this.status.server) {
this.apiCall(this.startup, api.shutdown)
} else {
this.apiCall(this.startup, api.startup)
}
}
},
server: {
key: '代理服务',
loading: false,
doClick: (checked) => {
this.onServerClick(checked)
}
},
switchBtns: undefined,
config: undefined,
setupCa: {
visible: false
}
}
},
created () {
console.log('index created')
this.reloadConfig().then(() => {
// this.start(true)
return api.status.get().then(ret => {
console.log('status', ret)
lodash.merge(status, ret)
this.$set(this, 'status', status)
})
}).then(() => {
this.switchBtns = this.createSwitchBtns()
})
},
mounted () {
console.log('index mounted')
},
methods: {
reloadConfig () {
return api.config.reload().then(ret => {
this.config = ret
return ret
})
},
createSwitchBtns () {
console.log('api,', api)
const btns = {}
btns.server = this.createSwitchBtn('server', '代理服务', api.server, status)
btns.proxy = this.createSwitchBtn('proxy', '系统代理', api.proxy, status)
lodash.forEach(this.status.plugin, (item, key) => {
btns[key] = this.createSwitchBtn(key, this.config.plugin[key].name, api.plugin[key], status.plugin)
})
return btns
},
createSwitchBtn (key, label, apiTarget, statusParent) {
return {
loading: false,
key: key,
label: label,
status: statusParent,
doClick: (checked) => {
this.onSwitchClick(this.switchBtns[key], apiTarget.start, apiTarget.close, checked)
}
}
},
async apiCall (btn, api, param) {
btn.loading = true
try {
const ret = await api(param)
return ret
} catch (err) {
console.log('api invoke error:', err)
} finally {
btn.loading = false
}
},
onSwitchClick (btn, openApi, closeApi, checked) {
if (checked) {
return this.apiCall(btn, openApi)
} else {
return this.apiCall(btn, closeApi)
}
},
onServerClick (checked) {
return this.onSwitchClick(this.server, api.server.start, api.server.close, checked)
},
start (checked) {
this.apiCall(this.startup, api.startup)
},
openSettings () {
this.settings.visible = true
},
onConfigChanged (newConfig) {
console.log('config changed', newConfig)
this.reloadConfig().then(() => {
if (this.status.server) {
return api.server.restart()
}
})
},
openSetupCa () {
this.setupCa.visible = true
}
}
}
</script>
<style>
.page_index {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
padding-top: 60px;
}
.big_button > button {
width: 100px;
height: 100px;
border-radius: 100px;
}
.big_button > button i {
size: 40px
}
div.ant-form-item {
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,3 @@
<template>
<div>proxy</div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div>server</div>
</template>

View File

@ -0,0 +1,15 @@
import Index from '../pages/index'
import Server from '../pages/server'
import Proxy from '../pages/proxy'
import Node from '../pages/app/node'
const routes = [
{ path: '/', redirect: '/index' },
{ path: '/index', component: Index },
{ path: '/server', component: Server },
{ path: '/proxy', component: Proxy },
{ path: '/app/node', component: Node }
]
export default routes