diff --git a/backend/services/rpc/base.go b/backend/services/rpc/base.go index 4b2b8653..ea4c7a62 100644 --- a/backend/services/rpc/base.go +++ b/backend/services/rpc/base.go @@ -7,6 +7,7 @@ import ( "crawlab/model" "crawlab/utils" "encoding/json" + "errors" "fmt" "github.com/apex/log" "github.com/gomodule/redigo/redis" @@ -49,6 +50,11 @@ func ClientFunc(msg entity.RpcMessage) func() (entity.RpcMessage, error) { return replyMsg, err } + // 如果返回消息有错误,返回错误 + if replyMsg.Error != "" { + return replyMsg, errors.New(replyMsg.Error) + } + return } } diff --git a/backend/services/rpc/install_dep.go b/backend/services/rpc/install_dep.go index cb4ecf82..e615688a 100644 --- a/backend/services/rpc/install_dep.go +++ b/backend/services/rpc/install_dep.go @@ -19,7 +19,8 @@ func (s *InstallDepService) ServerHandle() (entity.RpcMessage, error) { lang := utils.GetRpcParam("lang", s.msg.Params) depName := utils.GetRpcParam("dep_name", s.msg.Params) if err := InstallDepLocal(lang, depName); err != nil { - return entity.RpcMessage{}, err + s.msg.Error = err.Error() + return s.msg, err } s.msg.Result = "success" return s.msg, nil diff --git a/backend/services/rpc/uninstall_dep.go b/backend/services/rpc/uninstall_dep.go index 7a2b6eab..1b8b8ecb 100644 --- a/backend/services/rpc/uninstall_dep.go +++ b/backend/services/rpc/uninstall_dep.go @@ -19,7 +19,8 @@ func (s *UninstallDepService) ServerHandle() (entity.RpcMessage, error) { lang := utils.GetRpcParam("lang", s.msg.Params) depName := utils.GetRpcParam("dep_name", s.msg.Params) if err := UninstallDepLocal(lang, depName); err != nil { - return entity.RpcMessage{}, err + s.msg.Error = err.Error() + return s.msg, err } s.msg.Result = "success" return s.msg, nil diff --git a/frontend/src/components/Node/NodeInstallation.vue b/frontend/src/components/Node/NodeInstallation.vue index 46653256..d5813baa 100644 --- a/frontend/src/components/Node/NodeInstallation.vue +++ b/frontend/src/components/Node/NodeInstallation.vue @@ -39,7 +39,7 @@ - + + + + + + + + {{$t('Search')}} + + + + + + + + + + + + + + + {{$t('Install')}} + + + + + + {{scope.column.label}} + + + + + {{$t('Installed')}} + + + {{$t('Uninstall')}} + + + + + + {{$t('Installing')}} + + + + + {{$t('Not Installed')}} + + + {{$t('Install')}} + + + + + @@ -108,37 +220,78 @@ export default { data () { return { langs: [ - { label: 'Python', name: 'python' }, - { label: 'Node.js', name: 'node' }, - { label: 'Java', name: 'java' }, - { label: '.Net Core', name: 'dotnet' }, - { label: 'PHP', name: 'php' } + { label: 'Python', name: 'python', hasDeps: true }, + { label: 'Node.js', name: 'node', hasDeps: true }, + { label: 'Java', name: 'java', hasDeps: false }, + { label: '.Net Core', name: 'dotnet', hasDeps: false }, + { label: 'PHP', name: 'php', hasDeps: false } ], - dataDict: {}, + langsDataDict: {}, handle: undefined, - activeTabName: 'lang' + activeTabName: 'lang', + depsDataDict: {}, + depsSet: new Set(), + activeLang: 'python', + isDepsLoading: false, + depName: '', + isShowInstalled: true, + depList: [] } }, computed: { ...mapState('node', [ 'nodeList' - ]) + ]), + activeNodes () { + return this.nodeList.filter(d => d.status === 'online') + }, + computedDepsSet () { + return Array.from(this.depsSet).map(d => { + return { + name: d + } + }) + }, + langsWithDeps () { + return this.langs.filter(l => l.hasDeps) + } + }, + watch: { + activeLang () { + this.getDepsData() + } }, methods: { - async getData () { - for (let i = 0; i < this.nodeList.length; i++) { - const n = this.nodeList[i] - if (n.status !== 'online') continue + async getLangsData () { + await Promise.all(this.nodeList.map(async n => { + if (n.status !== 'online') return const res = await this.$request.get(`/nodes/${n._id}/langs`) res.data.data.forEach(l => { const key = n._id + '|' + l.executable_name - this.$set(this.dataDict, key, l) + this.$set(this.langsDataDict, key, l) }) - } + })) + }, + async getDepsData () { + this.isDepsLoading = true + this.depsDataDict = {} + this.depsSet = new Set() + const depsSet = new Set() + await Promise.all(this.nodeList.map(async n => { + if (n.status !== 'online') return + const res = await this.$request.get(`/nodes/${n._id}/deps/installed`, { lang: this.activeLang }) + res.data.data.forEach(d => { + depsSet.add(d.name) + const key = n._id + '|' + d.name + this.$set(this.depsDataDict, key, 'installed') + }) + })) + this.depsSet = depsSet + this.isDepsLoading = false }, getLang (nodeId, langName) { const key = nodeId + '|' + langName - return this.dataDict[key] + return this.langsDataDict[key] }, getLangInstallStatus (nodeId, langName) { const lang = this.getLang(nodeId, langName) @@ -162,9 +315,9 @@ export default { lang: lang.name }) const key = nodeId + '|' + lang.name - this.$set(this.dataDict[key], 'install_status', 'installing') + this.$set(this.langsDataDict[key], 'install_status', 'installing') setTimeout(() => { - this.getData() + this.getLangsData() }, 1000) }, async onInstallAll (langLabel, ev) { @@ -174,28 +327,105 @@ export default { if (n.status !== 'online') return false const lang = this.getLangFromLabel(langLabel) const key = n._id + '|' + lang.name - if (!this.dataDict[key]) return false - if (['installing', 'installed'].includes(this.dataDict[key].install_status)) return false + if (!this.langsDataDict[key]) return false + if (['installing', 'installed'].includes(this.langsDataDict[key].install_status)) return false return true }) .forEach(n => { this.onInstall(n._id, langLabel, ev) }) setTimeout(() => { - this.getData() + this.getLangsData() }, 1000) }, onLangTableRowClick (row) { this.$router.push(`/nodes/${row._id}`) + }, + getDepStatus (node, dep) { + const key = node._id + '|' + dep.name + if (!this.depsDataDict[key]) { + return 'uninstalled' + } else { + return this.depsDataDict[key] + } + }, + async installDep (node, dep) { + const key = node._id + '|' + dep.name + this.$set(this.depsDataDict, key, 'installing') + const data = await this.$request.post(`/nodes/${node._id}/deps/install`, { + lang: this.activeLang, + dep_name: dep.name + }) + if (!data || data.error) { + this.$notify.error({ + title: this.$t('Installing dependency failed'), + message: this.$t('The dependency installation is unsuccessful: ') + name + }) + this.$set(this.depsDataDict, key, 'uninstalled') + } else { + this.$notify.success({ + title: this.$t('Installing dependency successful'), + message: this.$t('You have successfully installed a dependency: ') + name + }) + this.$set(this.depsDataDict, key, 'installed') + } + this.$st.sendEv('节点列表', '安装', '安装依赖') + }, + async uninstallDep (node, dep) { + const key = node._id + '|' + dep.name + this.$set(this.depsDataDict, key, 'installing') + const data = await this.$request.post(`/nodes/${node._id}/deps/uninstall`, { + lang: this.activeLang, + dep_name: dep.name + }) + if (!data || data.error) { + this.$notify.error({ + title: this.$t('Uninstalling dependency failed'), + message: this.$t('The dependency uninstallation is unsuccessful: ') + dep.name + }) + this.$set(this.depsDataDict, key, 'installed') + } else { + this.$notify.success({ + title: this.$t('Uninstalling dependency successful'), + message: this.$t('You have successfully uninstalled a dependency: ') + dep.name + }) + this.$set(this.depsDataDict, key, 'uninstalled') + } + this.$st.sendEv('节点列表', '安装', '卸载依赖') + }, + onSearch () { + this.isShowInstalled = false + this.getDepList() + this.$st.sendEv('节点列表', '安装', '搜索依赖') + }, + async getDepList () { + const masterNode = this.nodeList.filter(n => n.is_master)[0] + this.depsSet = [] + this.isDepsLoading = true + const res = await this.$request.get(`/nodes/${masterNode._id}/deps`, { + lang: this.activeLang, + dep_name: this.depName + }) + this.isDepsLoading = false + this.depsSet = new Set(res.data.data.map(d => d.name)) + }, + onIsShowInstalledChange (val) { + if (val) { + this.getDepsData() + } else { + this.depsSet = [] + } + this.$st.sendEv('节点列表', '安装', '点击查看已安装') } }, async created () { setTimeout(() => { - this.getData() + this.getLangsData() + this.getDepsData() }, 1000) this.handle = setInterval(() => { - this.getData() + this.getLangsData() }, 10000) }, destroyed () { @@ -210,15 +440,12 @@ export default { border-radius: 5px; } - .lang-table { - } - - .lang-table >>> .el-table tr { + .el-table tr { cursor: pointer; } - .lang-table >>> .el-table .header-with-action, - .lang-table >>> .el-table .cell-with-action { + .el-table .header-with-action, + .el-table .cell-with-action { display: flex; justify-content: space-between; align-items: center; diff --git a/frontend/src/i18n/zh.js b/frontend/src/i18n/zh.js index ae300f71..899d9d44 100644 --- a/frontend/src/i18n/zh.js +++ b/frontend/src/i18n/zh.js @@ -354,6 +354,7 @@ export default { 'This language is not installed yet.': '语言还未安装', 'Languages': '语言', 'Dependencies': '依赖', + 'Install on All Nodes': '安装在所有节点', // 弹出框 'Notification': '提示',