├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 1bug.yaml │ ├── 2feature.yaml │ ├── 3consultation.yaml │ ├── bug.yaml │ ├── config.yml │ ├── consultation.yaml │ └── feature.yaml └── workflows │ ├── codeql-analysis.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTING_cn.md ├── LICENSE ├── README.md ├── README_cn.md ├── SECURITY.md ├── backend ├── .gitignore ├── cmd │ └── server │ │ ├── main.go │ │ └── server ├── configs │ └── config.example.yaml ├── dockerfile ├── go.mod ├── go.sum ├── internal │ ├── acl │ │ ├── acl.go │ │ ├── auth.go │ │ ├── login.go │ │ ├── perm.go │ │ ├── resource.go │ │ └── role.go │ ├── api │ │ ├── api.go │ │ ├── controller │ │ │ ├── account.go │ │ │ ├── asset.go │ │ │ ├── authorization.go │ │ │ ├── base.go │ │ │ ├── command.go │ │ │ ├── config.go │ │ │ ├── connect.go │ │ │ ├── file.go │ │ │ ├── gateway.go │ │ │ ├── history.go │ │ │ ├── node.go │ │ │ ├── preference.go │ │ │ ├── public_key.go │ │ │ ├── quick_command.go │ │ │ ├── session.go │ │ │ ├── share.go │ │ │ └── stat.go │ │ ├── docs │ │ │ ├── docs.go │ │ │ ├── swagger.json │ │ │ └── swagger.yaml │ │ ├── middleware │ │ │ ├── auth.go │ │ │ ├── error.go │ │ │ └── logger.go │ │ └── router │ │ │ └── router.go │ ├── connector │ │ ├── handlers.go │ │ └── protocols │ │ │ ├── db │ │ │ ├── connect.go │ │ │ ├── interface.go │ │ │ ├── mongodb.go │ │ │ ├── mysql.go │ │ │ ├── postgresql.go │ │ │ └── redis.go │ │ │ ├── guacd.go │ │ │ ├── ssh.go │ │ │ ├── telnet.go │ │ │ └── utils.go │ ├── guacd │ │ ├── conn.go │ │ ├── file_instruction.go │ │ ├── filetransfer.go │ │ └── instruction.go │ ├── i18n │ │ ├── i18n.go │ │ └── locales │ │ │ ├── active.en.toml │ │ │ ├── active.zh.toml │ │ │ └── translate.zh.toml │ ├── model │ │ ├── account.go │ │ ├── asset.go │ │ ├── authorization.go │ │ ├── command.go │ │ ├── config.go │ │ ├── default.go │ │ ├── file_history.go │ │ ├── gateway.go │ │ ├── history.go │ │ ├── model.go │ │ ├── node.go │ │ ├── preference.go │ │ ├── public_key.go │ │ ├── quick_command.go │ │ ├── session.go │ │ ├── share.go │ │ └── stat.go │ ├── repository │ │ ├── account.go │ │ ├── asset.go │ │ ├── authorization.go │ │ ├── base.go │ │ ├── cache.go │ │ ├── command.go │ │ ├── config.go │ │ ├── file.go │ │ ├── gateway.go │ │ ├── history.go │ │ ├── node.go │ │ ├── preference.go │ │ ├── public_key.go │ │ ├── quick_command.go │ │ ├── session.go │ │ ├── share.go │ │ └── stat.go │ ├── schedule │ │ ├── config.go │ │ ├── connectable.go │ │ └── schedule.go │ ├── service │ │ ├── account.go │ │ ├── asset.go │ │ ├── authorization.go │ │ ├── base.go │ │ ├── command.go │ │ ├── config.go │ │ ├── file.go │ │ ├── gateway.go │ │ ├── history.go │ │ ├── node.go │ │ ├── preference.go │ │ ├── public_key.go │ │ ├── quick_command.go │ │ ├── session.go │ │ ├── share.go │ │ ├── ssh.go │ │ └── stat.go │ ├── session │ │ ├── parser.go │ │ ├── record.go │ │ └── session.go │ ├── sshsrv │ │ ├── handler.go │ │ ├── sshsrv.go │ │ ├── textinput │ │ │ └── input.go │ │ └── view.go │ └── tunneling │ │ ├── manager.go │ │ └── tunnel.go └── pkg │ ├── cache │ └── redis.go │ ├── config │ └── config.go │ ├── db │ ├── database.go │ └── query_helper.go │ ├── errors │ └── errors.go │ ├── logger │ └── logger.go │ ├── remote │ └── http.go │ └── utils │ ├── aes.go │ ├── aes_test.go │ └── net.go ├── deploy ├── .env ├── acl.sql ├── config.yaml ├── create-users.sql ├── docker-compose.yaml ├── mysqld.cnf └── nginx.oneterm.conf.example ├── docs └── images │ └── wechat.png └── oneterm-ui ├── .editorconfig ├── .env ├── .env.preview ├── .eslintignore ├── .eslintrc.js ├── .prettierrc ├── .travis.yml ├── Dockerfile ├── babel.config.js ├── config └── plugin.config.js ├── jest.config.js ├── jsconfig.json ├── lang ├── en.js └── zh.js ├── package.json ├── postcss.config.js ├── public ├── color.less ├── dag.png ├── iconfont │ ├── demo.css │ ├── demo_index.html │ ├── iconfont.css │ ├── iconfont.js │ ├── iconfont.json │ ├── iconfont.ttf │ ├── iconfont.woff │ └── iconfont.woff2 ├── income_template.docx ├── incumbency_template.docx ├── index.html ├── loading │ ├── loading.css │ ├── loading.html │ └── option2 │ │ ├── html_code_segment.html │ │ ├── loading.css │ │ └── loading.svg └── logo.png ├── src ├── App.vue ├── api │ ├── auth.js │ ├── cmdb.js │ ├── company.js │ ├── employee.js │ ├── file.js │ ├── index.js │ ├── login.js │ ├── message.js │ └── noticeSetting.js ├── assets │ ├── background.svg │ ├── data_empty.png │ ├── icon-bg-selected.png │ ├── icon-bg.png │ ├── icons │ │ ├── CUSTOM.svg │ │ ├── DUTY.svg │ │ ├── MEETING.svg │ │ ├── OPEN_DAY.svg │ │ ├── OUTING.svg │ │ ├── TEAM.svg │ │ ├── bx-analyse.svg │ │ ├── create-rule.svg │ │ ├── dag_aliyun_ehpc.svg │ │ ├── dag_apollo.svg │ │ ├── dag_applet_add.svg │ │ ├── dag_aws_batch.svg │ │ ├── dag_aws_s3.svg │ │ ├── dag_branch_node.svg │ │ ├── dag_dag.svg │ │ ├── dag_email.svg │ │ ├── dag_option_k2.svg │ │ ├── dag_task.svg │ │ ├── dag_task_0.svg │ │ ├── dag_task_1.svg │ │ ├── dag_task_2.svg │ │ ├── dag_task_3.svg │ │ ├── dag_transfer.svg │ │ ├── edit.svg │ │ ├── ellipsis.svg │ │ ├── gray-circle.svg │ │ ├── green-circle.svg │ │ ├── grid.svg │ │ ├── light.svg │ │ ├── mailOpen.svg │ │ ├── notification.svg │ │ ├── ops-default_show.svg │ │ ├── ops-is_choice.svg │ │ ├── ops-is_index.svg │ │ ├── ops-is_link.svg │ │ ├── ops-is_password.svg │ │ ├── ops-is_sortable.svg │ │ ├── ops-is_unique.svg │ │ ├── ops-move-icon.svg │ │ ├── orange-circle.svg │ │ ├── python-logo.svg │ │ ├── red-circle.svg │ │ ├── top_acl.svg │ │ └── top_agent.svg │ ├── login_bg.png │ ├── login_img.png │ ├── logo.png │ ├── logo_oneterm.png │ ├── ops_logout.png │ ├── sidebar_background.png │ └── sidebar_selected.png ├── bus │ └── index.js ├── components │ ├── CMDBFilterComp │ │ ├── constants.js │ │ ├── expression.vue │ │ └── index.vue │ ├── CMDBValueTypeMapIcon │ │ └── index.vue │ ├── CardTitle │ │ ├── CardTitle.vue │ │ └── index.js │ ├── CollapseTransition │ │ └── index.vue │ ├── Crontab │ │ ├── Crontab-Day.vue │ │ ├── Crontab-Hour.vue │ │ ├── Crontab-Min.vue │ │ ├── Crontab-Mouth.vue │ │ ├── Crontab-Result.vue │ │ ├── Crontab-Second.vue │ │ ├── Crontab-Week.vue │ │ ├── Crontab-Year.vue │ │ ├── Crontab.vue │ │ ├── index.js │ │ └── utils │ │ │ └── index.js │ ├── CustomDrawer │ │ ├── CustomDrawer.vue │ │ └── index.js │ ├── CustomRadio │ │ ├── CustomRadio.vue │ │ └── index.js │ ├── CustomTransfer │ │ ├── CustomTransfer.vue │ │ └── index.js │ ├── EmployeeTransfer │ │ ├── index.js │ │ └── index.vue │ ├── Exception │ │ ├── ExceptionPage.vue │ │ ├── index.js │ │ └── type.js │ ├── FooterToolbar │ │ ├── FooterToolBar.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── GlobalFooter │ │ ├── GlobalFooter.vue │ │ └── index.js │ ├── GlobalHeader │ │ ├── GlobalHeader.vue │ │ └── index.js │ ├── Menu │ │ ├── SideMenu.vue │ │ ├── index.js │ │ └── menu.js │ ├── MultiTab │ │ ├── MultiTab.vue │ │ ├── index.js │ │ └── index.less │ ├── OpsTable │ │ ├── index.js │ │ └── index.vue │ ├── PageHeader │ │ ├── PageHeader.vue │ │ └── index.js │ ├── PageLoading │ │ └── index.jsx │ ├── Pager │ │ ├── index.js │ │ └── index.vue │ ├── Result │ │ ├── Result.vue │ │ └── index.js │ ├── RoleTransfer │ │ ├── index.js │ │ └── index.vue │ ├── SettingDrawer │ │ ├── SettingDrawer.vue │ │ ├── SettingItem.vue │ │ ├── index.js │ │ ├── settingConfig.js │ │ └── themeColor.js │ ├── SplitPane │ │ ├── SplitPane.vue │ │ ├── index.js │ │ └── index.less │ ├── TagSelect │ │ ├── TagSelectOption.jsx │ │ └── index.jsx │ ├── TwoColumnLayout │ │ ├── TwoColumnLayout.vue │ │ └── index.js │ ├── _util │ │ └── util.js │ ├── chartTime │ │ ├── constants.js │ │ └── index.vue │ ├── ciReferenceAttr │ │ └── index.vue │ ├── index.js │ ├── index.less │ └── tools │ │ ├── Breadcrumb.vue │ │ ├── DetailList.vue │ │ ├── DocumentLink.vue │ │ ├── HeadInfo.vue │ │ ├── Logo.vue │ │ ├── TopMenu.vue │ │ ├── TwoStepCaptcha.vue │ │ ├── UserMenu.vue │ │ ├── index.js │ │ └── userPanel.vue ├── config │ ├── app.js │ └── setting.js ├── core │ ├── EventBus.js │ ├── bootstrap.js │ ├── directives │ │ └── action.js │ ├── icons.js │ ├── lazy_lib │ │ └── components_use.js │ ├── lazy_use.js │ └── use.js ├── directive │ ├── highlight │ │ ├── highlight.js │ │ ├── highlight.less │ │ └── index.js │ └── waves │ │ ├── index.js │ │ ├── waves.css │ │ └── waves.js ├── guard.js ├── lang │ ├── en.js │ ├── index.js │ └── zh.js ├── layouts │ ├── BasicLayout.vue │ ├── BlankLayout.vue │ ├── PageView.vue │ ├── RouteView.vue │ ├── UserLayout.vue │ └── index.js ├── main.js ├── modules │ ├── acl │ │ ├── api │ │ │ ├── app.js │ │ │ ├── history.js │ │ │ ├── permission.js │ │ │ ├── resource.js │ │ │ ├── role.js │ │ │ ├── secretKey.js │ │ │ ├── trigger.js │ │ │ └── user.js │ │ ├── constants │ │ │ └── constants.js │ │ ├── index.js │ │ ├── lang │ │ │ ├── en.js │ │ │ └── zh.js │ │ ├── router │ │ │ └── index.js │ │ ├── store │ │ │ └── index.js │ │ ├── style │ │ │ ├── index.css │ │ │ ├── index.css.map │ │ │ └── index.less │ │ └── views │ │ │ ├── apps.vue │ │ │ ├── history.vue │ │ │ ├── module │ │ │ ├── appForm.vue │ │ │ ├── permCollectForm.vue │ │ │ ├── permissionForm.vue │ │ │ ├── permissionHistoryTable.vue │ │ │ ├── resourceBatchPerm.vue │ │ │ ├── resourceForm.vue │ │ │ ├── resourceGroupMember.vue │ │ │ ├── resourceGroupModal.vue │ │ │ ├── resourceHistoryTable.vue │ │ │ ├── resourcePermForm.vue │ │ │ ├── resourcePermManageForm.vue │ │ │ ├── resourceTypeForm.vue │ │ │ ├── resourceTypeHistoryTable.vue │ │ │ ├── resourceUserForm.vue │ │ │ ├── roleForm.vue │ │ │ ├── roleHistoryTable.vue │ │ │ ├── searchForm.vue │ │ │ ├── triggerForm.vue │ │ │ ├── triggerHistoryTable.vue │ │ │ ├── triggerPattern.vue │ │ │ ├── userForm.vue │ │ │ └── usersUnderRoleForm.vue │ │ │ ├── operation_history │ │ │ ├── index.vue │ │ │ └── modules │ │ │ │ ├── permissionTable.vue │ │ │ │ ├── resourceHistoryTable.vue │ │ │ │ ├── resourceTypeHistoryTable.vue │ │ │ │ ├── roleHistoryTable.vue │ │ │ │ └── triggerHistoryTable.vue │ │ │ ├── resource_types.vue │ │ │ ├── resources.vue │ │ │ ├── roles.vue │ │ │ ├── secretKey.vue │ │ │ ├── trigger.vue │ │ │ └── users.vue │ └── oneterm │ │ ├── api │ │ ├── account.js │ │ ├── asset.js │ │ ├── authorization.js │ │ ├── command.js │ │ ├── config.js │ │ ├── connect.js │ │ ├── gateway.js │ │ ├── loginLog.js │ │ ├── node.js │ │ ├── operationLog.js │ │ ├── otherModules.js │ │ ├── preference.js │ │ ├── publicKey.js │ │ ├── quickCommand.js │ │ ├── replay.js │ │ ├── session.js │ │ └── stat.js │ │ ├── assets │ │ ├── dashboard-1.png │ │ ├── dashboard-2.png │ │ ├── dashboard-3.png │ │ ├── dashboard-4.png │ │ └── dashboard-5.png │ │ ├── components │ │ ├── cmdbTypeSelect │ │ │ ├── cmdbTypeSelect.vue │ │ │ └── index.js │ │ ├── dragWeektime │ │ │ ├── index.js │ │ │ ├── index.vue │ │ │ └── weektimeData.js │ │ └── grant │ │ │ ├── aclTable.vue │ │ │ ├── grantModal.vue │ │ │ └── grantUserModal.vue │ │ ├── index.js │ │ ├── lang │ │ ├── en.js │ │ └── zh.js │ │ ├── mixins │ │ └── fullScreenMixin.js │ │ ├── router │ │ └── index.js │ │ ├── store │ │ └── index.js │ │ ├── style │ │ └── index.less │ │ ├── utils │ │ └── index.js │ │ └── views │ │ ├── assets │ │ ├── account │ │ │ ├── accountModal.vue │ │ │ └── index.vue │ │ ├── assets │ │ │ ├── accessAuth.vue │ │ │ ├── account.vue │ │ │ ├── assetList.vue │ │ │ ├── batchUpdateModal.vue │ │ │ ├── createAsset.vue │ │ │ ├── createNode.vue │ │ │ ├── index.vue │ │ │ ├── loginModal.vue │ │ │ ├── protocol.vue │ │ │ └── tempLink │ │ │ │ ├── createTempLink.vue │ │ │ │ ├── tempLinkModal.vue │ │ │ │ └── tempLinkTable.vue │ │ └── gateway │ │ │ ├── gatewayModal.vue │ │ │ └── index.vue │ │ ├── connect │ │ ├── guacamoleClient │ │ │ ├── clipboardModal.vue │ │ │ ├── index.vue │ │ │ └── operationMenu.vue │ │ └── terminal │ │ │ ├── index.vue │ │ │ └── operationMenu.vue │ │ ├── dashboard │ │ ├── account.vue │ │ ├── assetActive.vue │ │ ├── assetType.vue │ │ ├── index.vue │ │ ├── timeRadio.vue │ │ └── userRank.vue │ │ ├── log │ │ ├── constants.js │ │ ├── login │ │ │ └── index.vue │ │ └── operation │ │ │ └── index.vue │ │ ├── replay │ │ ├── guacamoleReplay.vue │ │ └── index.vue │ │ ├── session │ │ ├── history.vue │ │ ├── online.vue │ │ ├── sessionDetailTable.vue │ │ └── sesstionTable.vue │ │ ├── share │ │ └── index.vue │ │ ├── systemSettings │ │ ├── commandIntercept │ │ │ ├── commandModal.vue │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── publicKey │ │ │ ├── editModal.vue │ │ │ └── index.vue │ │ ├── quickCommand │ │ │ ├── commandDrawer.vue │ │ │ ├── commandModal.vue │ │ │ └── index.vue │ │ ├── terminalControl │ │ │ └── index.vue │ │ └── terminalDisplay │ │ │ ├── constants.js │ │ │ ├── displaySetting.vue │ │ │ ├── index.vue │ │ │ └── themeSetting.vue │ │ └── workStation │ │ ├── index.vue │ │ └── recentSession.vue ├── router │ ├── config.js │ ├── index.js │ └── utils.js ├── store │ ├── global │ │ ├── app.js │ │ ├── company.js │ │ ├── getters.js │ │ ├── mutation-types.js │ │ ├── notice.js │ │ ├── routes.js │ │ └── user.js │ └── index.js ├── style │ ├── global.less │ ├── index.css │ ├── index.less │ └── static.less ├── utils │ ├── axios.js │ ├── device.js │ ├── domUtil.js │ ├── download.js │ ├── filter.js │ ├── functions │ │ └── set.js │ ├── helper │ │ └── permission.js │ ├── mixin.js │ ├── request.js │ ├── util.js │ ├── utils.css │ └── utils.less └── views │ ├── exception │ ├── 403.vue │ ├── 404.vue │ └── 500.vue │ ├── noticeCenter │ └── index.vue │ ├── setting │ ├── auth │ │ ├── cas.vue │ │ ├── common.vue │ │ ├── index.vue │ │ ├── ldap.vue │ │ ├── loginModal.vue │ │ └── oauth2.vue │ ├── companyInfo │ │ └── index.vue │ ├── companyStructure │ │ ├── BatchModal.vue │ │ ├── CategoryTree.vue │ │ ├── DepartmentModal.vue │ │ ├── EmployeeModal.vue │ │ ├── eventBus │ │ │ └── bus.js │ │ └── index.vue │ ├── components │ │ ├── BatchUpload.vue │ │ ├── EditImage.vue │ │ ├── SearchForm.vue │ │ ├── departmentTreeSelect.vue │ │ ├── employeeTable.vue │ │ ├── employeeTreeSelect.vue │ │ ├── relateEmployee.vue │ │ ├── settingFilterComp │ │ │ ├── constants.js │ │ │ └── index.vue │ │ └── spanTitle.vue │ ├── lang │ │ ├── en.js │ │ └── zh.js │ ├── notice │ │ ├── basic.vue │ │ └── email │ │ │ ├── index.less │ │ │ ├── index.vue │ │ │ ├── receive.vue │ │ │ └── send.vue │ └── person │ │ └── index.vue │ └── user │ ├── Login.vue │ ├── Logout.vue │ ├── Register.vue │ └── RegisterResult.vue ├── vue.config.js └── webstorm.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vue linguist-language=golang 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2feature.yaml: -------------------------------------------------------------------------------- 1 | name: Feature wanted 2 | description: A new feature would be good 3 | title: "[Feature]: " 4 | labels: ["✏️ feature"] 5 | assignees: 6 | - pycook 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thank you for your feature suggestion; we will evaluate it carefully! 12 | - type: markdown 13 | attributes: 14 | value: | 15 | **Note**: Issue 的所有信息务必使用英文, 否则会被关闭. 谢谢! (All messages in Issue must be in English. If not, it will be closed. Thanks!) 16 | - type: input 17 | id: contact 18 | attributes: 19 | label: Contact Details 20 | description: How can we get in touch with you if we need more info? 21 | placeholder: ex. email@example.com 22 | validations: 23 | required: false 24 | - type: dropdown 25 | id: aspects 26 | attributes: 27 | label: feature is related to UI or API aspects? 28 | multiple: true 29 | options: 30 | - UI 31 | - API 32 | - type: textarea 33 | id: feature 34 | attributes: 35 | label: What is your advice? 36 | description: Also tell us, what did you expect to happen? 37 | placeholder: Tell us what you want! 38 | value: "everyone wants this feature!" 39 | validations: 40 | required: true 41 | - type: input 42 | id: version 43 | attributes: 44 | label: Version 45 | description: What version of our software are you running? 46 | value: "newest" 47 | validations: 48 | required: true 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3consultation.yaml: -------------------------------------------------------------------------------- 1 | name: Help wanted 2 | description: I have a question 3 | title: "[help wanted]: " 4 | labels: ["help wanted"] 5 | assignees: 6 | - ivonGwy 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Please tell us what's you need! 12 | - type: markdown 13 | attributes: 14 | value: | 15 | **Note**: Issue 的所有信息务必使用英文, 否则会被关闭. 谢谢! (All messages in Issue must be in English. If not, it will be closed. Thanks!) 16 | - type: input 17 | id: contact 18 | attributes: 19 | label: Contact Details 20 | description: How can we get in touch with you if we need more info? 21 | placeholder: ex. email@example.com 22 | validations: 23 | required: false 24 | - type: textarea 25 | id: question 26 | attributes: 27 | label: What is your question? 28 | description: Also tell us, how can we help? 29 | placeholder: Tell us what you need! 30 | value: "i have a question!" 31 | validations: 32 | required: true 33 | - type: input 34 | id: version 35 | attributes: 36 | label: Version 37 | description: What version of our software are you running? 38 | value: "newest" 39 | validations: 40 | required: true 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: veops official website 4 | url: https://veops.cn/#hero 5 | about: you can contact us here. 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/consultation.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/.github/ISSUE_TEMPLATE/consultation.yaml -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yaml: -------------------------------------------------------------------------------- 1 | name: Feature wanted 2 | description: A new feature would be good 3 | title: "[Feature]: " 4 | labels: ["feature"] 5 | assignees: 6 | - pycook 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thank you for your feature suggestion; we will evaluate it carefully! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: dropdown 21 | id: type 22 | attributes: 23 | label: feature is related to UI or API aspects? 24 | multiple: true 25 | options: 26 | - UI 27 | - API 28 | - type: textarea 29 | id: describe the feature 30 | attributes: 31 | label: What is your advice? 32 | description: Also tell us, what did you expect to happen? 33 | placeholder: Tell us what you want! 34 | value: "everyone wants this feature!" 35 | validations: 36 | required: true 37 | - type: textarea 38 | id: version 39 | attributes: 40 | label: Version 41 | description: What version of our software are you running? 42 | default: 2.3.5 43 | validations: 44 | required: true 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | build-api: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Build API 15 | run: | 16 | docker login --username=${{ secrets.ALI_ACCOUNT }} --password=${{ secrets.ALI_PASSWORD }} registry.cn-hangzhou.aliyuncs.com 17 | cd backend 18 | docker build . --file Dockerfile --tag registry.cn-hangzhou.aliyuncs.com/veops/oneterm-api:latest 19 | docker push registry.cn-hangzhou.aliyuncs.com/veops/oneterm-api:latest 20 | docker build . --file Dockerfile --tag registry.cn-hangzhou.aliyuncs.com/veops/oneterm-api:${{github.ref_name}} 21 | docker push registry.cn-hangzhou.aliyuncs.com/veops/oneterm-api:${{github.ref_name}} 22 | build-ui: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Build UI 27 | run: | 28 | docker login --username=${{ secrets.ALI_ACCOUNT }} --password=${{ secrets.ALI_PASSWORD }} registry.cn-hangzhou.aliyuncs.com 29 | cd oneterm-ui 30 | docker build . --file Dockerfile --tag registry.cn-hangzhou.aliyuncs.com/veops/oneterm-ui:latest 31 | docker push registry.cn-hangzhou.aliyuncs.com/veops/oneterm-ui:latest 32 | docker build . --file Dockerfile --tag registry.cn-hangzhou.aliyuncs.com/veops/oneterm-ui:${{github.ref_name}} 33 | docker push registry.cn-hangzhou.aliyuncs.com/veops/oneterm-ui:${{github.ref_name}} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .idea 3 | .vscode 4 | migrates 5 | config.cfg 6 | *.log 7 | *_packed.js 8 | *_packed.css 9 | *.orig 10 | *.zip 11 | nohup.out 12 | .DS_Store 13 | *.py[cod] 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Packages 19 | *.egg 20 | *.egg-info 21 | build 22 | eggs 23 | parts 24 | bin 25 | var 26 | sdist 27 | develop-eggs 28 | .installed.cfg 29 | #lib 30 | #lib64 31 | Pipfile.lock 32 | 33 | # Installer logs 34 | pip-log.txt 35 | 36 | # Unit test / coverage reports 37 | .coverage 38 | .tox 39 | nosetests.xml 40 | .pytest_cache 41 | oneterm-api/test-output 42 | oneterm-api/api/uploaded_files 43 | oneterm-api/migrations/versions 44 | 45 | # Translations 46 | #*.mo 47 | messages.pot 48 | 49 | # Mr Developer 50 | .mr.developer.cfg 51 | .project 52 | .pydevproject 53 | 54 | # Complexity 55 | output/*.html 56 | output/*/index.html 57 | 58 | # Sphinx 59 | docs/_build 60 | 61 | # Virtualenvs 62 | env/ 63 | 64 | 65 | # Configuration 66 | settings.py 67 | 68 | # Development database 69 | *.db 70 | 71 | # UI 72 | oneterm-ui/node_modules 73 | oneterm-ui/dist 74 | oneterm-ui/yarn.lock 75 | 76 | # Log files 77 | oneterm-ui/npm-debug.log* 78 | oneterm-ui/yarn-debug.log* 79 | oneterm-ui/yarn-error.log* 80 | oneterm-ui/package-lock.json 81 | 82 | 83 | ## backend 84 | 85 | *.log 86 | *.cast 87 | vendor/ 88 | volume 89 | 90 | backend/cmd/ssh/ssh 91 | backend/cmd/ssh/config.yaml 92 | backend/cmd/ssh/app.log 93 | backend/cmd/api/api 94 | backend/cmd/api/config.yaml 95 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /CONTRIBUTING_cn.md: -------------------------------------------------------------------------------- 1 | # 🎉 Contributing to OneTerm 🥳 2 | 3 | 首先,非常感谢您考虑为我们的项目做出贡献!我们欢迎任何形式的贡献,无论是提出新功能、改进代码、修复 bug 还是改善文档。 4 | 5 | 本指南将为您提供所有相关信息,帮助您快速入门并开始参与本项目。请花几分钟阅读它,它将帮助我们更好地协作,共同创造一个更好的项目。 6 | 7 | ## ❖ 提交问题 (Issue) 8 | 9 | 在提交 PR 之前,请先搜索 现有的 [PR](https://github.com/veops/oneterm/pulls) 或 [Issue](https://github.com/veops/oneterm/issues),查看是否已经有相关的开放或关闭的提交。 10 | 11 | 如果是修复 bug,请首先提交一个 Issue。 12 | 13 | 对于新增功能,请先通过我们提供的联系方式与我们直接联系,以便更好的合作。 14 | 15 | ## ❖ 提交 PR 的步骤 16 | 17 | 1. 在 Github 上 fork 该项目的仓库。 18 | 2. 在本地复制仓库后创建一个新分支,用于开发新功能、修复 bug 或进行其他贡献,命令:`git checkout -b feat/xxxx`。 19 | 3. 提交您的更改:`git commit -am 'feat: add xxxxx'`。 20 | 4. 推送您的分支:`git push origin feat/xxxx`。 21 | 5. 提交 Pull Request 时,请确保您的源分支是刚刚推送的分支,目标分支是 OneTerm 项目的 main 分支。 22 | 6. 提交后,请留意与 Pull Request 相关的邮件和通知。待通过审核后,我们会按计划将其合并到 main 分支,并进行新一轮的版本发布。 23 | 24 | ## ❖ 开发环境 25 | - Go >= 1.21.3 26 | - Node.js >= 14.17.6 27 | - Docker 28 | 29 | ## ❖ 代码风格 30 | 31 | **API**: 请遵循 [Go Style](https://google.github.io/styleguide/go/) 32 | 33 | **UI**: 请遵循 [node-style-guide](https://github.com/felixge/node-style-guide) 34 | 35 | ## ❖ 提交信息 36 | 37 | + 请遵循 [Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular) 38 | 39 | + 提交时使用不同的范围 40 | - API: `feat(api): xxx` 41 | - UI: `feat(ui): xxx` 42 | 43 | + 为了确保所有开发者都能更好地理解,提交信息请使用英文。 44 | 45 | - `feat` 添加新功能 46 | - `fix` 修复问题/BUG 47 | - `style` 代码风格相关,不影响运行结果 48 | - `perf` 优化/性能提升 49 | - `refactor` 代码重构 50 | - `revert` 撤销编辑 51 | - `test` 测试相关 52 | - `docs` 文档/注释 53 | - `chore` 依赖更新/脚手架配置修改等 54 | - `workflow` 工作流优化 55 | - `ci` 持续集成 56 | - `types` 类型定义文件变更 57 | - `wip` 开发中 58 | 59 | ## ❖ 代码内容 60 | 61 | 为了便于所有开发者理解,请确保代码注释和代码内容使用英文。 -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # 安全说明 2 | 3 | 如果你发现安全问题,请直接联系我们, 感谢您的支持! 4 | 5 | - email: bd@veops.cn 6 | 7 | # Security Policy 8 | 9 | If you find a security issue, please contact us directly, thank you for your support! 10 | 11 | - email: bd@veops.cn 12 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.log 3 | *.cast 4 | vendor/ 5 | volume 6 | 7 | cmd/ssh/ssh 8 | cmd/ssh/config.yaml 9 | cmd/ssh/app.log 10 | 11 | cmd/api/api 12 | cmd/api/config.yaml 13 | 14 | -------------------------------------------------------------------------------- /backend/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/oklog/run" 10 | "go.uber.org/zap" 11 | 12 | "github.com/veops/oneterm/internal/api" 13 | "github.com/veops/oneterm/internal/schedule" 14 | "github.com/veops/oneterm/internal/sshsrv" 15 | "github.com/veops/oneterm/pkg/logger" 16 | ) 17 | 18 | func main() { 19 | rg := run.Group{} 20 | { 21 | term := make(chan os.Signal, 1) 22 | signal.Notify(term, os.Interrupt, syscall.SIGTERM) 23 | rg.Add(func() error { 24 | <-term 25 | return errors.New("terminated") 26 | }, func(err error) {}) 27 | } 28 | { 29 | rg.Add(func() error { 30 | return api.RunApi() 31 | }, func(err error) { 32 | api.StopApi() 33 | }) 34 | } 35 | { 36 | rg.Add(func() error { 37 | return sshsrv.RunSsh() 38 | }, func(err error) { 39 | sshsrv.StopSsh() 40 | }) 41 | } 42 | { 43 | rg.Add(func() error { 44 | return schedule.RunSchedule() 45 | }, func(err error) { 46 | schedule.StopSchedule() 47 | }) 48 | } 49 | 50 | if err := rg.Run(); err != nil { 51 | logger.L().Fatal("", zap.Error(err)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /backend/cmd/server/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/backend/cmd/server/server -------------------------------------------------------------------------------- /backend/configs/config.example.yaml: -------------------------------------------------------------------------------- 1 | mode: debug 2 | 3 | http: 4 | host: 0.0.0.0 5 | port: 8888 6 | 7 | ssh: 8 | host: 0.0.0.0 9 | port: 2222 10 | privateKey: --BEGIN PRIVATE KEY-----END PRIVATE KEY----- 11 | 12 | guacd: 13 | host: oneterm-guacd 14 | port: 4822 15 | 16 | mysql: 17 | host: oneterm-mysql 18 | port: 3306 19 | user: root 20 | password: root 21 | 22 | database: 23 | type: mysql # alternative: postgres, tidb, tdsql, dm 24 | host: oneterm-mysql 25 | port: 3306 26 | user: root 27 | password: root 28 | database: oneterm 29 | charset: utf8mb4 30 | max_idle_conns: 10 31 | max_open_conns: 100 32 | conn_max_lifetime: 3600 # seconds 33 | conn_max_idle_time: 600 # seconds 34 | ssl_mode: disable 35 | 36 | redis: 37 | addr: oneterm-redis:6379 38 | password: root 39 | 40 | log: 41 | level: debug 42 | format: json 43 | maxSize: 1 44 | consoleEnable: true 45 | 46 | auth: 47 | acl: 48 | appId: acl app id 49 | secretKey: acl app secret key 50 | url: http://host/api/v1 51 | resourceNames: 52 | - key: account 53 | value: account 54 | - key: asset 55 | value: asset 56 | - key: command 57 | value: command 58 | - key: gateway 59 | value: gateway 60 | - key: authorization 61 | value: authorization 62 | 63 | secretKey: acl secret key 64 | -------------------------------------------------------------------------------- /backend/dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | WORKDIR /oneterm 3 | COPY . . 4 | RUN go env -w GOPROXY=https://goproxy.cn,direct \ 5 | && go build --ldflags "-s -w" -o ./build/oneterm ./cmd/server/main.go 6 | 7 | FROM alpine:latest 8 | RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories 9 | RUN apk add tzdata 10 | ENV TZ=Asia/Shanghai 11 | ENV TERM=xterm-256color 12 | WORKDIR /oneterm 13 | COPY --from=0 /oneterm/configs/config.example.yaml ./config.yaml 14 | COPY --from=0 /oneterm/internal/i18n/locales ./locales 15 | COPY --from=0 /oneterm/build/oneterm . 16 | CMD [ "./oneterm","run","-c","./config.yaml"] 17 | 18 | -------------------------------------------------------------------------------- /backend/internal/acl/perm.go: -------------------------------------------------------------------------------- 1 | // Package acl 2 | package acl 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | ) 8 | 9 | func GetSessionFromCtx(ctx context.Context) (res *Session, err error) { 10 | res, ok := ctx.Value("session").(*Session) 11 | if !ok || res == nil { 12 | err = fmt.Errorf("empty session") 13 | } 14 | return 15 | } 16 | 17 | func IsAdmin(session *Session) bool { 18 | for _, pr := range session.Acl.ParentRoles { 19 | if pr == "admin" || pr == "acl_admin" || pr == "oneterm_admin" { 20 | return true 21 | } 22 | } 23 | return false 24 | } 25 | 26 | func CreateGrantAcl(ctx context.Context, session *Session, resourceType string, resourceName string) (resourceId int, err error) { 27 | resource, err := AddResource(ctx, session.GetUid(), resourceType, resourceName) 28 | if err != nil { 29 | return 30 | } 31 | 32 | if err = GrantRoleResource(ctx, session.GetUid(), session.Acl.Rid, resource.ResourceId, AllPermissions); err != nil { 33 | return 34 | } 35 | 36 | resourceId = resource.ResourceId 37 | 38 | return 39 | } 40 | 41 | func CreateAcl(ctx context.Context, session *Session, resourceType string, resourceName string) (resourceId int, err error) { 42 | resource, err := AddResource(ctx, session.GetUid(), resourceType, resourceName) 43 | if err != nil { 44 | return 45 | } 46 | 47 | resourceId = resource.ResourceId 48 | 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /backend/internal/api/controller/connect.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | 6 | "github.com/veops/oneterm/internal/connector" 7 | ) 8 | 9 | // Connect handles WebSocket connections for terminal sessions 10 | // @Tags connect 11 | // @Success 200 {object} HttpResponse 12 | // @Param w query int false "width" 13 | // @Param h query int false "height" 14 | // @Param dpi query int false "dpi" 15 | // @Success 200 {object} HttpResponse{} 16 | // @Router /connect/:asset_id/:account_id/:protocol [get] 17 | func (c *Controller) Connect(ctx *gin.Context) { 18 | connector.Connect(ctx) 19 | } 20 | 21 | // ConnectMonitor handles WebSocket connections for monitoring sessions 22 | // @Tags connect 23 | // @Success 200 {object} HttpResponse 24 | // @Router /connect/monitor/:session_id [get] 25 | func (c *Controller) ConnectMonitor(ctx *gin.Context) { 26 | connector.ConnectMonitor(ctx) 27 | } 28 | 29 | // ConnectClose handles closing a session 30 | // @Tags connect 31 | // @Success 200 {object} HttpResponse 32 | // @Router /connect/close/:session_id [post] 33 | func (c *Controller) ConnectClose(ctx *gin.Context) { 34 | connector.ConnectClose(ctx) 35 | } 36 | -------------------------------------------------------------------------------- /backend/internal/api/middleware/error.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/nicksnyder/go-i18n/v2/i18n" 10 | 11 | myi18n "github.com/veops/oneterm/internal/i18n" 12 | "github.com/veops/oneterm/pkg/errors" 13 | ) 14 | 15 | type bodyWriter struct { 16 | gin.ResponseWriter 17 | body *bytes.Buffer 18 | } 19 | 20 | func (w bodyWriter) Write(b []byte) (int, error) { 21 | return w.body.Write(b) 22 | } 23 | 24 | func Error2RespMiddleware() gin.HandlerFunc { 25 | return func(ctx *gin.Context) { 26 | // Skip middleware for session replay and file download endpoints 27 | urlPath := ctx.Request.URL.String() 28 | if strings.Contains(urlPath, "session/replay") || 29 | strings.Contains(urlPath, "/file/download/") { 30 | ctx.Next() 31 | return 32 | } 33 | 34 | wb := &bodyWriter{ 35 | body: &bytes.Buffer{}, 36 | ResponseWriter: ctx.Writer, 37 | } 38 | ctx.Writer = wb 39 | 40 | ctx.Next() 41 | 42 | obj := make(map[string]any) 43 | json.Unmarshal(wb.body.Bytes(), &obj) 44 | if len(ctx.Errors) > 0 { 45 | if v, ok := obj["code"]; !ok || v == 0 { 46 | obj["code"] = ctx.Writer.Status() 47 | } 48 | 49 | if v, ok := obj["message"]; !ok || v == "" { 50 | e := ctx.Errors.Last().Err 51 | obj["message"] = e.Error() 52 | 53 | ae, ok := e.(*errors.ApiError) 54 | if ok { 55 | lang := ctx.PostForm("lang") 56 | accept := ctx.GetHeader("Accept-Language") 57 | localizer := i18n.NewLocalizer(myi18n.Bundle, lang, accept) 58 | obj["message"] = ae.Message(localizer) 59 | 60 | } 61 | } 62 | } 63 | bs, _ := json.Marshal(obj) 64 | wb.ResponseWriter.Write(bs) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /backend/internal/api/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | "go.uber.org/zap" 8 | 9 | "github.com/veops/oneterm/pkg/logger" 10 | ) 11 | 12 | func LoggerMiddleware() gin.HandlerFunc { 13 | return func(ctx *gin.Context) { 14 | start := time.Now() 15 | 16 | ctx.Next() 17 | 18 | cost := time.Since(start) 19 | logger.L().Info(ctx.Request.URL.String(), 20 | zap.String("method", ctx.Request.Method), 21 | zap.Int("status", ctx.Writer.Status()), 22 | zap.String("ip", ctx.ClientIP()), 23 | zap.Duration("cost", cost), 24 | ) 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/internal/connector/protocols/db/interface.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/veops/oneterm/internal/model" 5 | gsession "github.com/veops/oneterm/internal/session" 6 | ) 7 | 8 | // DBClientConfig holds the configuration for a database client 9 | type DBClientConfig struct { 10 | Command string 11 | Args []string 12 | ExitAliases []string 13 | } 14 | 15 | // ConnectDB connects to a database with the given session, asset, account, and gateway 16 | func ConnectDB(sess *gsession.Session, asset *model.Asset, account *model.Account, gateway *model.Gateway) error { 17 | return connectDB(sess, asset, account, gateway) 18 | } 19 | -------------------------------------------------------------------------------- /backend/internal/connector/protocols/db/mongodb.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | ) 8 | 9 | // getMongoDBConfig returns MongoDB client configuration 10 | func getMongoDBConfig(ip string, port int, account *model.Account) DBClientConfig { 11 | // Build connection string 12 | connectionString := fmt.Sprintf("mongodb://%s:%d", ip, port) 13 | 14 | // Add authentication if provided 15 | args := []string{} 16 | if account.Account != "" && account.Password != "" { 17 | // Use --username and --password parameters for MongoDB 18 | args = append(args, "--username", account.Account, "--password", account.Password) 19 | } 20 | 21 | // Add the connection string as the last argument 22 | args = append(args, connectionString) 23 | 24 | return DBClientConfig{ 25 | Command: "mongosh", 26 | Args: args, 27 | ExitAliases: []string{"exit", "quit"}, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/internal/connector/protocols/db/mysql.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | ) 8 | 9 | // getMySQLConfig returns MySQL client configuration 10 | func getMySQLConfig(ip string, port int, account *model.Account) DBClientConfig { 11 | args := []string{"-h", ip, "-P", fmt.Sprintf("%d", port), "-u", account.Account} 12 | if account.Password != "" { 13 | args = append(args, fmt.Sprintf("-p%s", account.Password)) 14 | } 15 | 16 | return DBClientConfig{ 17 | Command: "mysql", 18 | Args: args, 19 | ExitAliases: []string{"exit", "quit", "\\q"}, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/internal/connector/protocols/db/postgresql.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/veops/oneterm/internal/model" 9 | ) 10 | 11 | // getPostgreSQLConfig returns PostgreSQL client configuration 12 | func getPostgreSQLConfig(ip string, port int, account *model.Account) DBClientConfig { 13 | // Set PGPASSWORD environment variable instead of using -W 14 | os.Setenv("PGPASSWORD", account.Password) 15 | 16 | args := []string{ 17 | "-h", ip, 18 | "-p", fmt.Sprintf("%d", port), 19 | "-U", account.Account, 20 | "postgres", // Default database name 21 | } 22 | 23 | // Add database name if specified in the account.Account field (username/database format) 24 | parts := strings.Split(account.Account, "/") 25 | if len(parts) > 1 { 26 | args = append(args, "-d", parts[1]) 27 | } 28 | 29 | return DBClientConfig{ 30 | Command: "psql", 31 | Args: args, 32 | ExitAliases: []string{"\\q", "exit", "quit"}, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/internal/connector/protocols/db/redis.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | ) 8 | 9 | // getRedisConfig returns Redis client configuration 10 | func getRedisConfig(ip string, port int, account *model.Account) DBClientConfig { 11 | args := []string{"-h", ip, "-p", fmt.Sprintf("%d", port)} 12 | if account.Password != "" { 13 | args = append(args, "-a", account.Password) 14 | } 15 | 16 | return DBClientConfig{ 17 | Command: "redis-cli", 18 | Args: args, 19 | ExitAliases: []string{"exit", "quit"}, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/internal/guacd/instruction.go: -------------------------------------------------------------------------------- 1 | package guacd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | internalDataOpcode = "" 10 | delimiter = ';' 11 | ) 12 | 13 | var ( 14 | InternalOpcodeIns = []byte(fmt.Sprint(len(internalDataOpcode), ".", internalDataOpcode)) 15 | ) 16 | 17 | type Instruction struct { 18 | Opcode string 19 | Args []string 20 | cache string 21 | } 22 | 23 | func NewInstruction(opcode string, args ...string) *Instruction { 24 | return &Instruction{ 25 | Opcode: opcode, 26 | Args: args, 27 | } 28 | } 29 | 30 | func (i *Instruction) String() string { 31 | if len(i.cache) > 0 { 32 | return i.cache 33 | } 34 | 35 | i.cache = fmt.Sprintf("%d.%s", len(i.Opcode), i.Opcode) 36 | for _, value := range i.Args { 37 | i.cache += fmt.Sprintf(",%d.%s", len(value), value) 38 | } 39 | i.cache += string(delimiter) 40 | return i.cache 41 | } 42 | 43 | func (i *Instruction) Bytes() []byte { 44 | return []byte(i.String()) 45 | } 46 | 47 | func (i *Instruction) Parse(content string) *Instruction { 48 | if strings.LastIndex(content, ";") > 0 { 49 | content = strings.TrimRight(content, ";") 50 | } 51 | elements := strings.Split(content, ",") 52 | 53 | var args = make([]string, len(elements)) 54 | for i, e := range elements { 55 | ss := strings.Split(e, ".") 56 | if len(ss) < 2 { 57 | continue 58 | } 59 | args[i] = ss[1] 60 | } 61 | return NewInstruction(args[0], args[1:]...) 62 | } 63 | 64 | func IsActive(p []byte) bool { 65 | i := (&Instruction{}).Parse(string(p)) 66 | return i.Opcode == "mouse" || i.Opcode == "key" 67 | } 68 | -------------------------------------------------------------------------------- /backend/internal/i18n/locales/translate.zh.toml: -------------------------------------------------------------------------------- 1 | [MsgUnauthorized] 2 | hash = "sha1-740b83150add8d2de17b3ab10d33605bb00e9589" 3 | other = "未认证" 4 | -------------------------------------------------------------------------------- /backend/internal/model/command.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "regexp" 5 | "time" 6 | 7 | "gorm.io/plugin/soft_delete" 8 | ) 9 | 10 | type Command struct { 11 | Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"` 12 | Name string `json:"name" gorm:"column:name;uniqueIndex:name_del;size:128"` 13 | Cmd string `json:"cmd" gorm:"column:cmd"` 14 | IsRe bool `json:"is_re" gorm:"column:is_re"` 15 | Enable bool `json:"enable" gorm:"column:enable"` 16 | Re *regexp.Regexp `json:"-" gorm:"-"` 17 | 18 | Permissions []string `json:"permissions" gorm:"-"` 19 | ResourceId int `json:"resource_id" gorm:"column:resource_id"` 20 | CreatorId int `json:"creator_id" gorm:"column:creator_id"` 21 | UpdaterId int `json:"updater_id" gorm:"column:updater_id"` 22 | CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` 23 | UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` 24 | DeletedAt soft_delete.DeletedAt `json:"-" gorm:"column:deleted_at;uniqueIndex:name_del"` 25 | } 26 | 27 | func (m *Command) TableName() string { 28 | return "command" 29 | } 30 | func (m *Command) SetId(id int) { 31 | m.Id = id 32 | } 33 | func (m *Command) SetCreatorId(creatorId int) { 34 | m.CreatorId = creatorId 35 | } 36 | func (m *Command) SetUpdaterId(updaterId int) { 37 | m.UpdaterId = updaterId 38 | } 39 | func (m *Command) SetResourceId(resourceId int) { 40 | m.ResourceId = resourceId 41 | } 42 | func (m *Command) GetResourceId() int { 43 | return m.ResourceId 44 | } 45 | func (m *Command) GetName() string { 46 | return m.Name 47 | } 48 | func (m *Command) GetId() int { 49 | return m.Id 50 | } 51 | 52 | func (m *Command) SetPerms(perms []string) { 53 | m.Permissions = perms 54 | } 55 | -------------------------------------------------------------------------------- /backend/internal/model/default.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | var ( 4 | DefaultAccount = &Account{} 5 | DefaultAsset = &Asset{} 6 | DefaultAuthorization = &Authorization{} 7 | DefaultCommand = &Command{} 8 | DefaultConfig = &Config{} 9 | DefaultFileHistory = &FileHistory{} 10 | DefaultGateway = &Gateway{} 11 | DefaultHistory = &History{} 12 | DefaultNode = &Node{} 13 | DefaultPublicKey = &PublicKey{} 14 | DefaultSession = &Session{} 15 | DefaultSessionCmd = &SessionCmd{} 16 | DefaultShare = &Share{} 17 | DefaultQuickCommand = &QuickCommand{} 18 | DefaultUserPreference = &UserPreference{} 19 | ) 20 | -------------------------------------------------------------------------------- /backend/internal/model/file_history.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | FILE_ACTION_LS = iota + 1 9 | FILE_ACTION_MKDIR 10 | FILE_ACTION_UPLOAD 11 | FILE_ACTION_DOWNLOAD 12 | ) 13 | 14 | type FileHistory struct { 15 | Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"` 16 | Uid int `json:"uid" gorm:"column:uid"` 17 | UserName string `json:"user_name" gorm:"column:user_name"` 18 | AssetId int `json:"asset_id" gorm:"column:asset_id"` 19 | AccountId int `json:"account_id" gorm:"column:account_id"` 20 | ClientIp string `json:"client_ip" gorm:"column:client_ip"` 21 | Action int `json:"action" gorm:"column:action"` 22 | Dir string `json:"dir" gorm:"column:dir"` 23 | Filename string `json:"filename" gorm:"column:filename"` 24 | 25 | CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` 26 | UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` 27 | } 28 | 29 | func (m *FileHistory) TableName() string { 30 | return "file_history" 31 | } 32 | -------------------------------------------------------------------------------- /backend/internal/model/history.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type History struct { 8 | Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"` 9 | RemoteIp string `json:"remote_ip" gorm:"column:remote_ip"` 10 | Type string `json:"type" gorm:"column:type"` 11 | TargetId int `json:"target_id" gorm:"column:target_id"` 12 | ActionType int `json:"action_type" gorm:"column:action_type"` 13 | Old Map[string, any] `json:"old" gorm:"column:old"` 14 | New Map[string, any] `json:"new" gorm:"column:new"` 15 | 16 | CreatorId int `json:"creator_id" gorm:"column:creator_id"` 17 | CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` 18 | } 19 | 20 | func (m *History) TableName() string { 21 | return "history" 22 | } 23 | -------------------------------------------------------------------------------- /backend/internal/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | ) 7 | 8 | const ( 9 | ACTION_CREATE = iota + 1 10 | ACTION_DELETE 11 | ACTION_UPDATE 12 | ) 13 | 14 | type Slice[T int | string | Range] []T 15 | 16 | func (s *Slice[T]) Scan(value any) error { 17 | return json.Unmarshal(value.([]byte), s) 18 | } 19 | 20 | func (s Slice[T]) Value() (driver.Value, error) { 21 | return json.Marshal(s) 22 | } 23 | 24 | type Map[K comparable, V any] map[K]V 25 | 26 | func (m *Map[K, V]) Scan(value any) error { 27 | return json.Unmarshal(value.([]byte), m) 28 | 29 | } 30 | 31 | func (m Map[K, V]) Value() (driver.Value, error) { 32 | return json.Marshal(m) 33 | } 34 | 35 | type Model interface { 36 | TableName() string 37 | SetId(int) 38 | SetCreatorId(int) 39 | SetUpdaterId(int) 40 | SetResourceId(int) 41 | GetResourceId() int 42 | GetId() int 43 | GetName() string 44 | SetPerms([]string) 45 | } 46 | 47 | type Pair[T1, T2 any] struct { 48 | First T1 49 | Second T2 50 | } 51 | -------------------------------------------------------------------------------- /backend/internal/model/quick_command.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/plugin/soft_delete" 7 | ) 8 | 9 | // QuickCommand represents a quick command model 10 | type QuickCommand struct { 11 | Id int `json:"id" gorm:"primaryKey"` 12 | Name string `json:"name" gorm:"size:50;not null"` // Command name 13 | Command string `json:"command" gorm:"size:500;not null"` // Actual command to execute 14 | Description string `json:"description" gorm:"size:200"` // Command description 15 | IsGlobal bool `json:"is_global" gorm:"default:false"` // Whether it's a global command 16 | CreatorId int `json:"creator_id" gorm:"not null"` // Creator ID 17 | CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` 18 | UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` 19 | DeletedAt soft_delete.DeletedAt `json:"-" gorm:"column:deleted_at"` 20 | } 21 | 22 | func (m *QuickCommand) TableName() string { 23 | return "quick_commands" 24 | } 25 | func (m *QuickCommand) SetId(id int) { 26 | m.Id = id 27 | } 28 | func (m *QuickCommand) SetCreatorId(creatorId int) { 29 | m.CreatorId = creatorId 30 | } 31 | func (m *QuickCommand) SetUpdaterId(updaterId int) { 32 | } 33 | 34 | func (m *QuickCommand) SetResourceId(resourceId int) { 35 | 36 | } 37 | func (m *QuickCommand) GetResourceId() int { 38 | return 0 39 | } 40 | func (m *QuickCommand) GetName() string { 41 | return m.Name 42 | } 43 | func (m *QuickCommand) GetId() int { 44 | return m.Id 45 | } 46 | 47 | func (m *QuickCommand) SetPerms(perms []string) {} 48 | -------------------------------------------------------------------------------- /backend/internal/model/share.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/plugin/soft_delete" 7 | ) 8 | 9 | const ( 10 | TABLE_NAME_SHARE = "share" 11 | ) 12 | 13 | type Share struct { 14 | Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"` 15 | Uuid string `json:"uuid" gorm:"column:uuid;uniqueIndex:uuid;size:128"` 16 | AssetId int `json:"asset_id" gorm:"column:asset_id"` 17 | AccountId int `json:"account_id" gorm:"column:account_id"` 18 | Protocol string `json:"protocol" gorm:"column:protocol"` 19 | NoLimit bool `json:"no_limit" gorm:"column:no_limit"` 20 | Times int `json:"times" gorm:"column:times"` 21 | Start time.Time `json:"start" gorm:"column:start"` 22 | End time.Time `json:"end" gorm:"column:end"` 23 | 24 | CreatorId int `json:"creator_id" gorm:"column:creator_id"` 25 | UpdaterId int `json:"updater_id" gorm:"column:updater_id"` 26 | CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` 27 | UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` 28 | DeletedAt soft_delete.DeletedAt `json:"-" gorm:"column:deleted_at"` 29 | } 30 | 31 | func (m *Share) TableName() string { 32 | return TABLE_NAME_SHARE 33 | } 34 | func (m *Share) SetId(id int) { 35 | m.Id = id 36 | } 37 | func (m *Share) SetCreatorId(creatorId int) { 38 | m.CreatorId = creatorId 39 | } 40 | func (m *Share) SetUpdaterId(updaterId int) { 41 | m.UpdaterId = updaterId 42 | } 43 | func (m *Share) SetResourceId(resourceId int) { 44 | 45 | } 46 | func (m *Share) GetResourceId() int { 47 | return 0 48 | } 49 | func (m *Share) GetName() string { 50 | return "" 51 | } 52 | func (m *Share) GetId() int { 53 | return m.Id 54 | } 55 | 56 | func (m *Share) SetPerms(perms []string){} -------------------------------------------------------------------------------- /backend/internal/repository/cache.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/veops/oneterm/internal/model" 9 | redis "github.com/veops/oneterm/pkg/cache" 10 | "github.com/veops/oneterm/pkg/db" 11 | ) 12 | 13 | func GetAllFromCacheDb[T model.Model](ctx context.Context, m T) (res []T, err error) { 14 | k := fmt.Sprintf("all-%s", m.TableName()) 15 | if err = redis.Get(ctx, k, &res); err == nil { 16 | return 17 | } 18 | if err = db.DB.Model(m).Find(&res).Error; err != nil { 19 | return 20 | } 21 | redis.SetEx(ctx, k, res, time.Hour) 22 | return 23 | } 24 | 25 | func DeleteAllFromCacheDb(ctx context.Context, m model.Model) (err error) { 26 | k := fmt.Sprintf("all-%s", m.TableName()) 27 | err = redis.RC.Del(ctx, k).Err() 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /backend/internal/repository/command.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | dbpkg "github.com/veops/oneterm/pkg/db" 8 | ) 9 | 10 | // CommandRepository defines the interface for command repository 11 | type CommandRepository interface { 12 | // Add any repository-specific methods here 13 | GetCommand(ctx context.Context, id int) (*model.Command, error) 14 | ListCommands(ctx context.Context, filters map[string]interface{}) ([]*model.Command, error) 15 | } 16 | 17 | type commandRepository struct{} 18 | 19 | // NewCommandRepository creates a new command repository 20 | func NewCommandRepository() CommandRepository { 21 | return &commandRepository{} 22 | } 23 | 24 | // GetCommand retrieves a command by ID 25 | func (r *commandRepository) GetCommand(ctx context.Context, id int) (*model.Command, error) { 26 | command := &model.Command{} 27 | if err := dbpkg.DB.Where("id = ?", id).First(command).Error; err != nil { 28 | return nil, err 29 | } 30 | return command, nil 31 | } 32 | 33 | // ListCommands lists commands based on filters 34 | func (r *commandRepository) ListCommands(ctx context.Context, filters map[string]interface{}) ([]*model.Command, error) { 35 | var commands []*model.Command 36 | db := dbpkg.DB.Model(&model.Command{}) 37 | 38 | for key, value := range filters { 39 | db = db.Where(key, value) 40 | } 41 | 42 | if err := db.Find(&commands).Error; err != nil { 43 | return nil, err 44 | } 45 | 46 | return commands, nil 47 | } 48 | -------------------------------------------------------------------------------- /backend/internal/repository/config.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | dbpkg "github.com/veops/oneterm/pkg/db" 8 | ) 9 | 10 | // ConfigRepository defines the interface for config repository 11 | type ConfigRepository interface { 12 | GetConfig(ctx context.Context) (*model.Config, error) 13 | SaveConfig(ctx context.Context, cfg *model.Config) error 14 | } 15 | 16 | type configRepository struct{} 17 | 18 | // NewConfigRepository creates a new config repository 19 | func NewConfigRepository() ConfigRepository { 20 | return &configRepository{} 21 | } 22 | 23 | // GetConfig retrieves the current configuration 24 | func (r *configRepository) GetConfig(ctx context.Context) (*model.Config, error) { 25 | cfg := &model.Config{} 26 | if err := dbpkg.DB.Model(cfg).First(cfg).Error; err != nil { 27 | return nil, err 28 | } 29 | return cfg, nil 30 | } 31 | 32 | // SaveConfig saves a configuration 33 | func (r *configRepository) SaveConfig(ctx context.Context, cfg *model.Config) error { 34 | return dbpkg.DB.Create(cfg).Error 35 | } 36 | -------------------------------------------------------------------------------- /backend/internal/repository/file.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "gorm.io/gorm" 7 | 8 | "github.com/veops/oneterm/internal/model" 9 | ) 10 | 11 | // IFileRepository file history repository interface 12 | type IFileRepository interface { 13 | AddFileHistory(ctx context.Context, history *model.FileHistory) error 14 | GetFileHistory(ctx context.Context, filters map[string]interface{}) ([]*model.FileHistory, int64, error) 15 | } 16 | 17 | // FileRepository file history repository implementation 18 | type FileRepository struct { 19 | db *gorm.DB 20 | } 21 | 22 | // NewFileRepository creates a file history repository 23 | func NewFileRepository(db *gorm.DB) IFileRepository { 24 | return &FileRepository{ 25 | db: db, 26 | } 27 | } 28 | 29 | // AddFileHistory adds a file history record 30 | func (r *FileRepository) AddFileHistory(ctx context.Context, history *model.FileHistory) error { 31 | return r.db.Create(history).Error 32 | } 33 | 34 | // GetFileHistory gets file history records 35 | func (r *FileRepository) GetFileHistory(ctx context.Context, filters map[string]interface{}) ([]*model.FileHistory, int64, error) { 36 | db := r.db.Model(&model.FileHistory{}) 37 | 38 | // Apply filter conditions 39 | for key, value := range filters { 40 | if value != nil && value != "" { 41 | db = db.Where(key, value) 42 | } 43 | } 44 | 45 | // Count total records 46 | var count int64 47 | if err := db.Count(&count).Error; err != nil { 48 | return nil, 0, err 49 | } 50 | 51 | // Query records 52 | var histories []*model.FileHistory 53 | if err := db.Find(&histories).Error; err != nil { 54 | return nil, 0, err 55 | } 56 | 57 | return histories, count, nil 58 | } 59 | -------------------------------------------------------------------------------- /backend/internal/repository/history.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | dbpkg "github.com/veops/oneterm/pkg/db" 8 | ) 9 | 10 | // HistoryRepository defines the interface for history repository 11 | type HistoryRepository interface { 12 | GetHistories(ctx context.Context, filters map[string]any) ([]*model.History, error) 13 | GetHistory(ctx context.Context, id int) (*model.History, error) 14 | CreateHistory(ctx context.Context, history *model.History) error 15 | } 16 | 17 | type historyRepository struct{} 18 | 19 | // NewHistoryRepository creates a new history repository 20 | func NewHistoryRepository() HistoryRepository { 21 | return &historyRepository{} 22 | } 23 | 24 | // GetHistory retrieves a history record by ID 25 | func (r *historyRepository) GetHistory(ctx context.Context, id int) (*model.History, error) { 26 | history := &model.History{} 27 | if err := dbpkg.DB.Where("id = ?", id).First(history).Error; err != nil { 28 | return nil, err 29 | } 30 | return history, nil 31 | } 32 | 33 | // GetHistories retrieves history records based on filters 34 | func (r *historyRepository) GetHistories(ctx context.Context, filters map[string]any) ([]*model.History, error) { 35 | var histories []*model.History 36 | db := dbpkg.DB.Model(&model.History{}) 37 | 38 | for key, value := range filters { 39 | db = db.Where(key, value) 40 | } 41 | 42 | if err := db.Find(&histories).Error; err != nil { 43 | return nil, err 44 | } 45 | 46 | return histories, nil 47 | } 48 | 49 | // CreateHistory creates a new history record 50 | func (r *historyRepository) CreateHistory(ctx context.Context, history *model.History) error { 51 | return dbpkg.DB.Create(history).Error 52 | } 53 | -------------------------------------------------------------------------------- /backend/internal/repository/public_key.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/veops/oneterm/internal/model" 8 | dbpkg "github.com/veops/oneterm/pkg/db" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | // PublicKeyRepository defines the interface for public key repository 13 | type PublicKeyRepository interface { 14 | GetPublicKey(ctx context.Context, id int) (*model.PublicKey, error) 15 | BuildQuery(ctx *gin.Context, uid int) *gorm.DB 16 | } 17 | 18 | type publicKeyRepository struct{} 19 | 20 | // NewPublicKeyRepository creates a new public key repository 21 | func NewPublicKeyRepository() PublicKeyRepository { 22 | return &publicKeyRepository{} 23 | } 24 | 25 | // GetPublicKey retrieves a public key by ID 26 | func (r *publicKeyRepository) GetPublicKey(ctx context.Context, id int) (*model.PublicKey, error) { 27 | publicKey := &model.PublicKey{} 28 | if err := dbpkg.DB.Where("id = ?", id).First(publicKey).Error; err != nil { 29 | return nil, err 30 | } 31 | return publicKey, nil 32 | } 33 | 34 | // BuildQuery constructs a query for public keys with filters 35 | func (r *publicKeyRepository) BuildQuery(ctx *gin.Context, uid int) *gorm.DB { 36 | db := dbpkg.DB.Model(&model.PublicKey{}) 37 | 38 | // Filter by search terms 39 | if q, ok := ctx.GetQuery("search"); ok && q != "" { 40 | db = db.Where("name LIKE ? OR mac LIKE ?", "%"+q+"%", "%"+q+"%") 41 | } 42 | 43 | // Filter by ID 44 | if q, ok := ctx.GetQuery("id"); ok && q != "" { 45 | db = db.Where("id = ?", q) 46 | } 47 | 48 | // Filter by name 49 | if q, ok := ctx.GetQuery("name"); ok && q != "" { 50 | db = db.Where("name LIKE ?", "%"+q+"%") 51 | } 52 | 53 | // Filter by user ID 54 | db = db.Where("uid = ?", uid) 55 | 56 | return db 57 | } 58 | -------------------------------------------------------------------------------- /backend/internal/repository/quick_command.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | dbpkg "github.com/veops/oneterm/pkg/db" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type QuickCommand struct{} 12 | 13 | var DefaultQuickCommand = NewQuickCommand() 14 | 15 | func NewQuickCommand() *QuickCommand { 16 | return &QuickCommand{} 17 | } 18 | 19 | // BuildQuery builds the base query for quick commands 20 | func (r *QuickCommand) BuildQuery(ctx context.Context) *gorm.DB { 21 | return dbpkg.DB.Model(&model.QuickCommand{}) 22 | } 23 | 24 | // Create creates a new quick command 25 | func (r *QuickCommand) Create(ctx context.Context, cmd *model.QuickCommand) error { 26 | return dbpkg.DB.Create(cmd).Error 27 | } 28 | 29 | // GetUserCommands retrieves all quick commands visible to the user 30 | func (r *QuickCommand) GetUserCommands(ctx context.Context, userId int) ([]*model.QuickCommand, error) { 31 | var cmds []*model.QuickCommand 32 | err := dbpkg.DB.Where("creator_id = ? OR is_global = ?", userId, true).Find(&cmds).Error 33 | return cmds, err 34 | } 35 | 36 | // GetById retrieves a quick command by its ID 37 | func (r *QuickCommand) GetById(ctx context.Context, id int) (*model.QuickCommand, error) { 38 | var cmd model.QuickCommand 39 | err := dbpkg.DB.First(&cmd, id).Error 40 | return &cmd, err 41 | } 42 | 43 | // Delete deletes a quick command by its ID 44 | func (r *QuickCommand) Delete(ctx context.Context, id int) error { 45 | return dbpkg.DB.Delete(&model.QuickCommand{}, id).Error 46 | } 47 | 48 | // Update updates an existing quick command 49 | func (r *QuickCommand) Update(ctx context.Context, cmd *model.QuickCommand) error { 50 | return dbpkg.DB.Save(cmd).Error 51 | } 52 | -------------------------------------------------------------------------------- /backend/internal/schedule/config.go: -------------------------------------------------------------------------------- 1 | package schedule 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/veops/oneterm/internal/model" 7 | "github.com/veops/oneterm/pkg/cache" 8 | dbpkg "github.com/veops/oneterm/pkg/db" 9 | ) 10 | 11 | func UpdateConfig() { 12 | cfg := &model.Config{} 13 | defer func() { 14 | cache.SetEx(ctx, "config", cfg, time.Hour) 15 | model.GlobalConfig.Store(cfg) 16 | }() 17 | err := cache.Get(ctx, "config", cfg) 18 | if err == nil { 19 | return 20 | } 21 | err = dbpkg.DB.Model(cfg).First(cfg).Error 22 | if err != nil { 23 | return 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/internal/schedule/schedule.go: -------------------------------------------------------------------------------- 1 | package schedule 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | var ( 9 | ctx, cancel = context.WithCancel(context.Background()) 10 | ) 11 | 12 | func init() { 13 | UpdateConfig() 14 | } 15 | 16 | func RunSchedule() (err error) { 17 | tk2h := time.NewTicker(time.Hour * 2) 18 | tk1m := time.NewTicker(time.Minute) 19 | for { 20 | select { 21 | case <-ctx.Done(): 22 | return 23 | case <-tk2h.C: 24 | UpdateConnectables() 25 | case <-tk1m.C: 26 | UpdateConfig() 27 | } 28 | } 29 | } 30 | 31 | func StopSchedule() { 32 | defer cancel() 33 | } 34 | -------------------------------------------------------------------------------- /backend/internal/service/config.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/veops/oneterm/internal/model" 8 | "github.com/veops/oneterm/internal/repository" 9 | "github.com/veops/oneterm/pkg/cache" 10 | dbpkg "github.com/veops/oneterm/pkg/db" 11 | "gorm.io/gorm" 12 | ) 13 | 14 | // ConfigService handles configuration business logic 15 | type ConfigService struct { 16 | repo repository.ConfigRepository 17 | } 18 | 19 | // NewConfigService creates a new config service 20 | func NewConfigService() *ConfigService { 21 | return &ConfigService{ 22 | repo: repository.NewConfigRepository(), 23 | } 24 | } 25 | 26 | // SaveConfig saves a configuration 27 | func (s *ConfigService) SaveConfig(ctx context.Context, cfg *model.Config) error { 28 | cfg.Id = 0 // Ensure we're creating a new config 29 | 30 | if err := dbpkg.DB.Model(cfg).Transaction(func(tx *gorm.DB) error { 31 | if err := tx.Where("deleted_at = 0").Delete(&model.Config{}).Error; err != nil { 32 | return err 33 | } 34 | return tx.Create(cfg).Error 35 | }); err != nil { 36 | return err 37 | } 38 | 39 | // Update global config and cache 40 | model.GlobalConfig.Store(cfg) 41 | cache.SetEx(ctx, "config", cfg, time.Hour) 42 | 43 | return nil 44 | } 45 | 46 | // GetConfig retrieves the current configuration 47 | func (s *ConfigService) GetConfig(ctx context.Context) (*model.Config, error) { 48 | return s.repo.GetConfig(ctx) 49 | } 50 | -------------------------------------------------------------------------------- /backend/internal/service/quick_command.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/veops/oneterm/internal/model" 8 | "github.com/veops/oneterm/internal/repository" 9 | dbpkg "github.com/veops/oneterm/pkg/db" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | type QuickCommand struct { 14 | repo *repository.QuickCommand 15 | } 16 | 17 | var DefaultQuickCommand = NewQuickCommand() 18 | 19 | func NewQuickCommand() *QuickCommand { 20 | return &QuickCommand{ 21 | repo: repository.DefaultQuickCommand, 22 | } 23 | } 24 | 25 | // BuildQuery builds the base query for quick commands 26 | func (s *QuickCommand) BuildQuery(ctx *gin.Context) *gorm.DB { 27 | db := dbpkg.DB.Model(&model.QuickCommand{}) 28 | 29 | db = dbpkg.FilterLike(ctx, db, "name") 30 | db = dbpkg.FilterSearch(ctx, db, "name", "command") 31 | 32 | return db 33 | } 34 | 35 | // Create creates a new quick command 36 | func (s *QuickCommand) Create(ctx context.Context, cmd *model.QuickCommand) error { 37 | return s.repo.Create(ctx, cmd) 38 | } 39 | 40 | // GetUserCommands retrieves all quick commands visible to the user 41 | func (s *QuickCommand) GetUserCommands(ctx context.Context, userId int) ([]*model.QuickCommand, error) { 42 | return s.repo.GetUserCommands(ctx, userId) 43 | } 44 | 45 | // GetById retrieves a quick command by its ID 46 | func (s *QuickCommand) GetById(ctx context.Context, id int) (*model.QuickCommand, error) { 47 | return s.repo.GetById(ctx, id) 48 | } 49 | 50 | // Delete deletes a quick command by its ID 51 | func (s *QuickCommand) Delete(ctx context.Context, id int) error { 52 | return s.repo.Delete(ctx, id) 53 | } 54 | 55 | // Update updates an existing quick command 56 | func (s *QuickCommand) Update(ctx context.Context, cmd *model.QuickCommand) error { 57 | return s.repo.Update(ctx, cmd) 58 | } 59 | -------------------------------------------------------------------------------- /backend/internal/session/record.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | 10 | "go.uber.org/zap" 11 | 12 | "github.com/veops/oneterm/pkg/config" 13 | "github.com/veops/oneterm/pkg/logger" 14 | ) 15 | 16 | type Asciinema struct { 17 | file *os.File 18 | ts time.Time 19 | } 20 | 21 | func NewAsciinema(id string, w, h int) (ret *Asciinema, err error) { 22 | replayDir := config.Cfg.Session.ReplayDir 23 | if err = os.MkdirAll(replayDir, 0755); err != nil { 24 | logger.L().Error("create replay directory failed", zap.String("dir", replayDir), zap.Error(err)) 25 | return 26 | } 27 | 28 | f, err := os.Create(filepath.Join(replayDir, fmt.Sprintf("%s.cast", id))) 29 | if err != nil { 30 | logger.L().Error("open cast failed", zap.String("id", id), zap.Error(err)) 31 | return 32 | } 33 | ret = &Asciinema{file: f, ts: time.Now()} 34 | bs, _ := json.Marshal(map[string]any{ 35 | "version": 2, 36 | "width": w, 37 | "height": h, 38 | "timestamp": ret.ts.Unix(), 39 | "title": id, 40 | "env": map[string]any{ 41 | "SHELL": "/bin/bash", 42 | "TERM": "xterm-256color", 43 | }, 44 | }) 45 | ret.file.Write(append(bs, '\r', '\n')) 46 | return 47 | } 48 | 49 | func (a *Asciinema) Write(p []byte) { 50 | o := [3]any{} 51 | o[0] = float64(time.Now().UnixMicro()-a.ts.UnixMicro()) / 1_000_000 52 | o[1] = "o" 53 | o[2] = string(p) 54 | bs, _ := json.Marshal(o) 55 | a.file.Write(append(bs, '\r', '\n')) 56 | } 57 | 58 | func (a *Asciinema) Resize(w, h int) { 59 | r := [3]any{} 60 | r[0] = float64(time.Now().UnixMicro()-a.ts.UnixMicro()) / 1_000_000 61 | r[1] = "r" 62 | r[2] = fmt.Sprintf("%dx%d", w, h) 63 | bs, _ := json.Marshal(r) 64 | a.file.Write(append(bs, '\r', '\n')) 65 | } 66 | -------------------------------------------------------------------------------- /backend/internal/sshsrv/sshsrv.go: -------------------------------------------------------------------------------- 1 | package sshsrv 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gliderlabs/ssh" 8 | gossh "golang.org/x/crypto/ssh" 9 | 10 | "github.com/veops/oneterm/internal/acl" 11 | "github.com/veops/oneterm/pkg/config" 12 | "github.com/veops/oneterm/pkg/utils" 13 | ) 14 | 15 | var ( 16 | ctx, cancel = context.WithCancel(context.Background()) 17 | server *ssh.Server 18 | ) 19 | 20 | func init() { 21 | server = &ssh.Server{ 22 | Addr: fmt.Sprintf("%s:%d", config.Cfg.Ssh.Host, config.Cfg.Ssh.Port), 23 | Handler: handler, 24 | PasswordHandler: func(ctx ssh.Context, password string) bool { 25 | sess, err := acl.LoginByPassword(ctx, ctx.User(), password, utils.IpFromNetAddr(ctx.RemoteAddr())) 26 | ctx.SetValue("session", sess) 27 | return err == nil 28 | }, 29 | PublicKeyHandler: func(ctx ssh.Context, key ssh.PublicKey) bool { 30 | sess, err := acl.LoginByPublicKey(ctx, ctx.User(), string(gossh.MarshalAuthorizedKey(key)), utils.IpFromNetAddr(ctx.RemoteAddr())) 31 | ctx.SetValue("session", sess) 32 | return err == nil 33 | }, 34 | HostSigners: []ssh.Signer{signer()}, 35 | } 36 | } 37 | 38 | func RunSsh() error { 39 | return server.ListenAndServe() 40 | } 41 | 42 | func StopSsh() { 43 | defer cancel() 44 | } 45 | -------------------------------------------------------------------------------- /backend/internal/tunneling/manager.go: -------------------------------------------------------------------------------- 1 | package tunneling 2 | 3 | import "github.com/veops/oneterm/internal/model" 4 | 5 | // Global tunnel manager instance 6 | var DefaultManager = NewTunnelManager() 7 | 8 | // GetTunnelBySessionId gets a gateway tunnel by session ID using the default manager 9 | func GetTunnelBySessionId(sessionId string) *GatewayTunnel { 10 | return DefaultManager.GetTunnelBySessionId(sessionId) 11 | } 12 | 13 | // OpenTunnel opens a new gateway tunnel using the default manager 14 | func OpenTunnel(isConnectable bool, sessionId, remoteIp string, remotePort int, gateway *model.Gateway) (*GatewayTunnel, error) { 15 | return DefaultManager.OpenTunnel(isConnectable, sessionId, remoteIp, remotePort, gateway) 16 | } 17 | 18 | // CloseTunnels closes gateway tunnels by session IDs using the default manager 19 | func CloseTunnels(sessionIds ...string) { 20 | DefaultManager.CloseTunnels(sessionIds...) 21 | } 22 | -------------------------------------------------------------------------------- /backend/pkg/cache/redis.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/redis/go-redis/v9" 10 | "go.uber.org/zap" 11 | 12 | "github.com/veops/oneterm/pkg/config" 13 | "github.com/veops/oneterm/pkg/logger" 14 | ) 15 | 16 | var ( 17 | // RC redis cache client 18 | RC *redis.Client 19 | ) 20 | 21 | func init() { 22 | ctx := context.Background() 23 | addr := fmt.Sprintf("%s:%d", config.Cfg.Redis.Host, config.Cfg.Redis.Port) 24 | RC = redis.NewClient(&redis.Options{ 25 | Addr: addr, 26 | Password: config.Cfg.Redis.Password, 27 | }) 28 | 29 | if _, err := RC.Ping(ctx).Result(); err != nil { 30 | logger.L().Fatal("ping redis failed", zap.String("addr", addr), zap.Error(err)) 31 | } 32 | } 33 | 34 | func Get(ctx context.Context, key string, dst any) (err error) { 35 | bs, err := RC.Get(ctx, key).Bytes() 36 | if err != nil { 37 | return 38 | } 39 | return json.Unmarshal(bs, dst) 40 | } 41 | 42 | func SetEx(ctx context.Context, key string, src any, exp time.Duration) (err error) { 43 | bs, err := json.Marshal(src) 44 | if err != nil { 45 | return 46 | } 47 | return RC.SetEx(ctx, key, bs, exp).Err() 48 | } 49 | -------------------------------------------------------------------------------- /backend/pkg/db/query_helper.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | // FilterEqual filters records by exact match of field values 9 | // It adds WHERE conditions for each specified field if the query parameter exists 10 | func FilterEqual(ctx *gin.Context, db *gorm.DB, fields ...string) *gorm.DB { 11 | for _, f := range fields { 12 | if q, ok := ctx.GetQuery(f); ok { 13 | db = db.Where(f+" = ?", q) 14 | } 15 | } 16 | return db 17 | } 18 | 19 | // FilterLike filters records by partial match (LIKE) of field values 20 | // It adds WHERE conditions with OR for each specified field if the query parameter exists 21 | func FilterLike(ctx *gin.Context, db *gorm.DB, fields ...string) *gorm.DB { 22 | likes := false 23 | d := DB 24 | for _, f := range fields { 25 | if q, ok := ctx.GetQuery(f); ok && q != "" { 26 | d = d.Or(f+" LIKE ?", "%"+q+"%") 27 | likes = true 28 | } 29 | } 30 | if !likes { 31 | return db 32 | } 33 | return db.Where(d) 34 | } 35 | 36 | // FilterSearch performs a search across multiple fields using a single search parameter 37 | // It looks for the "search" query parameter and searches all specified fields 38 | func FilterSearch(ctx *gin.Context, db *gorm.DB, fields ...string) *gorm.DB { 39 | q, ok := ctx.GetQuery("search") 40 | if !ok || len(fields) <= 0 { 41 | return db 42 | } 43 | 44 | d := DB 45 | for _, f := range fields { 46 | d = d.Or(f+" LIKE ?", "%"+q+"%") 47 | } 48 | 49 | return db.Where(d) 50 | } 51 | -------------------------------------------------------------------------------- /backend/pkg/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "os" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | "gopkg.in/natefinch/lumberjack.v2" 9 | 10 | "github.com/veops/oneterm/pkg/config" 11 | ) 12 | 13 | func init() { 14 | level := zapcore.DebugLevel 15 | switch config.Cfg.Log.Level { 16 | case "error": 17 | level = zapcore.ErrorLevel 18 | case "warn": 19 | level = zapcore.WarnLevel 20 | case "info": 21 | level = zapcore.InfoLevel 22 | case "debug": 23 | level = zapcore.DebugLevel 24 | } 25 | fw := &lumberjack.Logger{ 26 | Filename: "logs/oneterm.log", 27 | MaxSize: 0, 28 | MaxBackups: 0, 29 | MaxAge: 15, 30 | LocalTime: false, 31 | Compress: false, 32 | } 33 | cfg := zap.NewProductionEncoderConfig() 34 | cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000") 35 | encoder := zapcore.NewConsoleEncoder(cfg) 36 | cores := []zapcore.Core{zapcore.NewCore( 37 | encoder, 38 | zapcore.AddSync(fw), 39 | level, 40 | )} 41 | if config.Cfg.Log.ConsoleEnable { 42 | cores = append(cores, zapcore.NewCore( 43 | encoder, 44 | zapcore.AddSync(zapcore.Lock(os.Stderr)), 45 | level, 46 | )) 47 | } 48 | zap.ReplaceGlobals(zap.New(zapcore.NewTee(cores...))) 49 | } 50 | 51 | func L() *zap.Logger { 52 | return zap.L() 53 | } 54 | -------------------------------------------------------------------------------- /backend/pkg/utils/aes.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "encoding/base64" 8 | 9 | "github.com/veops/oneterm/pkg/config" 10 | ) 11 | 12 | var ( 13 | key, iv []byte 14 | ) 15 | 16 | func init() { 17 | key = []byte(config.Cfg.Auth.Aes.Key) 18 | iv = []byte(config.Cfg.Auth.Aes.Iv) 19 | } 20 | 21 | func EncryptAES(plainText string) string { 22 | block, _ := aes.NewCipher(key) 23 | bs := []byte(plainText) 24 | bs = paddingPKCS7(bs, aes.BlockSize) 25 | 26 | mode := cipher.NewCBCEncrypter(block, iv) 27 | mode.CryptBlocks(bs, bs) 28 | 29 | return base64.StdEncoding.EncodeToString(bs) 30 | } 31 | 32 | func DecryptAES(cipherText string) string { 33 | bs, _ := base64.StdEncoding.DecodeString(cipherText) 34 | block, err := aes.NewCipher(key) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | mode := cipher.NewCBCDecrypter(block, iv) 40 | mode.CryptBlocks(bs, bs) 41 | 42 | return string(unPaddingPKCS7(bs)) 43 | } 44 | 45 | func paddingPKCS7(plaintext []byte, blockSize int) []byte { 46 | paddingSize := blockSize - len(plaintext)%blockSize 47 | paddingText := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize) 48 | return append(plaintext, paddingText...) 49 | } 50 | 51 | func unPaddingPKCS7(s []byte) []byte { 52 | length := len(s) 53 | if length == 0 { 54 | return s 55 | } 56 | unPadding := int(s[length-1]) 57 | return s[:(length - unPadding)] 58 | } 59 | -------------------------------------------------------------------------------- /backend/pkg/utils/aes_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEncryptAES(t *testing.T) { 8 | type args struct { 9 | plaintext string 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "Test 1", 18 | args: args{ 19 | plaintext: "123456789abcdefghijklmnopqrstuvwxyz", 20 | }, 21 | want: "hrr23HSXrZEOw5haacoj32QJLrHdpj42jaQcPVRf9AI8SzeSdWJhzTrYgsOgmNoN", 22 | }, 23 | } 24 | for _, tt := range tests { 25 | t.Run(tt.name, func(t *testing.T) { 26 | if got := EncryptAES(tt.args.plaintext); got != tt.want { 27 | t.Errorf("EncryptAES() = %v, want %v", got, tt.want) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | func TestDecryptAES(t *testing.T) { 34 | type args struct { 35 | cipherText string 36 | } 37 | tests := []struct { 38 | name string 39 | args args 40 | want string 41 | }{ 42 | { 43 | name: "Test 1", 44 | args: args{cipherText: "hrr23HSXrZEOw5haacoj32QJLrHdpj42jaQcPVRf9AI8SzeSdWJhzTrYgsOgmNoN"}, 45 | want: "123456789abcdefghijklmnopqrstuvwxyz", 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if got := DecryptAES(tt.args.cipherText); got != tt.want { 51 | t.Errorf("DecryptAES() = %v, want %v", got, tt.want) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /backend/pkg/utils/net.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "net" 4 | 5 | func IpFromNetAddr(addr net.Addr) string { 6 | switch t := addr.(type) { 7 | case *net.UDPAddr: 8 | return t.IP.String() 9 | case *net.TCPAddr: 10 | return t.IP.String() 11 | } 12 | return "" 13 | } 14 | -------------------------------------------------------------------------------- /deploy/.env: -------------------------------------------------------------------------------- 1 | # Environment variable overrides for local development 2 | FLASK_APP=autoapp.py 3 | FLASK_DEBUG=1 4 | FLASK_ENV=development 5 | GUNICORN_WORKERS=2 6 | LOG_LEVEL=debug 7 | SECRET_KEY='xW2FAUfgffjmerTEBXADmURDOQ43ojLN' 8 | -------------------------------------------------------------------------------- /deploy/config.yaml: -------------------------------------------------------------------------------- 1 | mode: debug 2 | 3 | http: 4 | host: 0.0.0.0 5 | port: 8888 6 | 7 | ssh: 8 | host: 0.0.0.0 9 | port: 2222 10 | privateKey: | 11 | -----BEGIN OPENSSH PRIVATE KEY----- 12 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 13 | QyNTUxOQAAACBg490b4zqumtizCyM4RWtzJnPEsPIInBFugk8+UCb8XgAAAKCc1yKrnNci 14 | qwAAAAtzc2gtZWQyNTUxOQAAACBg490b4zqumtizCyM4RWtzJnPEsPIInBFugk8+UCb8Xg 15 | AAAECvd1Yj+bQxyxJtU3PirLK68CD3MWqBv0/shlFKS6wmbWDj3RvjOq6a2LMLIzhFa3Mm 16 | c8Sw8gicEW6CTz5QJvxeAAAAGnJvb3RAbG9jYWxob3N0LmxvY2FsZG9tYWluAQID 17 | -----END OPENSSH PRIVATE KEY----- 18 | 19 | 20 | guacd: 21 | host: oneterm-guacd 22 | port: 4822 23 | 24 | mysql: 25 | host: mysql 26 | port: 3306 27 | user: root 28 | password: "123456" 29 | 30 | redis: 31 | host: redis 32 | port: 6379 33 | password: "" 34 | 35 | log: 36 | level: debug 37 | format: json 38 | maxSize: 1 39 | consoleEnable: true 40 | 41 | auth: 42 | acl: 43 | appId: 5867e079dfd1437e9ae07576ab24b391 44 | secretKey: 2qlTA4z@#KyigJLYHGrev?0WD6hjX*8E 45 | url: http://acl-api:5000/api/v1 46 | aes: 47 | key: thisis32bitlongpassphraseimusing 48 | iv: 0123456789abcdef 49 | 50 | secretKey: xW2FAUfgffjmerTEBXADmURDOQ43ojLN 51 | 52 | session: 53 | replayDir: /replay/ 54 | -------------------------------------------------------------------------------- /deploy/create-users.sql: -------------------------------------------------------------------------------- 1 | -- create database 2 | CREATE DATABASE IF NOT EXISTS acl; 3 | CREATE DATABASE IF NOT EXISTS oneterm; 4 | 5 | -- create user 6 | CREATE USER 'oneterm'@'%' IDENTIFIED BY '123456'; 7 | CREATE USER 'acl'@'%' IDENTIFIED BY '123456'; 8 | 9 | -- grant privileges 10 | GRANT ALL PRIVILEGES ON `oneterm`.* TO 'oneterm'@'%' WITH GRANT OPTION; 11 | GRANT ALL PRIVILEGES ON `acl`.* TO 'acl'@'%'; 12 | -------------------------------------------------------------------------------- /docs/images/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/docs/images/wechat.png -------------------------------------------------------------------------------- /oneterm-ui/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=false 5 | indent_style=space 6 | indent_size=2 7 | 8 | [{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}] 13 | indent_style=space 14 | indent_size=2 15 | 16 | [{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] 17 | indent_style=space 18 | indent_size=2 19 | 20 | [*.svg] 21 | indent_style=space 22 | indent_size=2 23 | 24 | [*.js.map] 25 | indent_style=space 26 | indent_size=2 27 | 28 | [*.less] 29 | indent_style=space 30 | indent_size=2 31 | 32 | [*.vue] 33 | indent_style=space 34 | indent_size=2 35 | 36 | [{.analysis_options,*.yml,*.yaml}] 37 | indent_style=space 38 | indent_size=2 39 | 40 | -------------------------------------------------------------------------------- /oneterm-ui/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=false 3 | VUE_APP_API_BASE_URL=/api 4 | VUE_APP_BUILD_PACKAGES="ticket,calendar,acl" 5 | VUE_APP_IS_OUTER=true 6 | VUE_APP_IS_OPEN_SOURCE=true 7 | -------------------------------------------------------------------------------- /oneterm-ui/.env.preview: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=true 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /oneterm-ui/.eslintignore: -------------------------------------------------------------------------------- 1 | /public/iconfont -------------------------------------------------------------------------------- /oneterm-ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": false, 4 | "singleQuote": true, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /oneterm-ui/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.15.0 4 | cache: yarn 5 | script: 6 | - yarn 7 | - yarn run lint --no-fix && yarn run build 8 | -------------------------------------------------------------------------------- /oneterm-ui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.20.0-alpine AS builder 2 | WORKDIR /oneterm-ui 3 | COPY . . 4 | RUN yarn config set registry https://registry.npmmirror.com/ 5 | RUN yarn install 6 | RUN yarn build 7 | 8 | FROM nginx:alpine 9 | COPY --from=0 /oneterm-ui/dist /etc/nginx/html 10 | RUN mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak -------------------------------------------------------------------------------- /oneterm-ui/babel.config.js: -------------------------------------------------------------------------------- 1 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) 2 | 3 | const plugins = ['@babel/plugin-syntax-import-meta', '@babel/plugin-proposal-optional-chaining', '@babel/plugin-proposal-nullish-coalescing-operator'] 4 | if (IS_PROD) { 5 | plugins.push('transform-remove-console') 6 | } 7 | 8 | // lazy load ant-design-vue 9 | // if your use import on Demand, Use this code 10 | // plugins.push(['import', { 11 | // 'libraryName': 'ant-design-vue', 12 | // 'libraryDirectory': 'es', 13 | // 'style': true // `style: true` 会加载 less 文件 14 | // }]) 15 | 16 | module.exports = { 17 | presets: [ 18 | '@vue/cli-plugin-babel/preset', 19 | [ 20 | '@babel/preset-env', 21 | { 22 | 'useBuiltIns': 'entry', 23 | 'corejs': 3 24 | } 25 | ] 26 | ], 27 | plugins 28 | } 29 | -------------------------------------------------------------------------------- /oneterm-ui/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/' 23 | } 24 | -------------------------------------------------------------------------------- /oneterm-ui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "dist"], 10 | "include": ["src/*"] 11 | } 12 | -------------------------------------------------------------------------------- /oneterm-ui/lang/en.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/lang/en.js -------------------------------------------------------------------------------- /oneterm-ui/lang/zh.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/lang/zh.js -------------------------------------------------------------------------------- /oneterm-ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /oneterm-ui/public/dag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/public/dag.png -------------------------------------------------------------------------------- /oneterm-ui/public/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/public/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /oneterm-ui/public/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/public/iconfont/iconfont.woff -------------------------------------------------------------------------------- /oneterm-ui/public/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/public/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /oneterm-ui/public/income_template.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/public/income_template.docx -------------------------------------------------------------------------------- /oneterm-ui/public/incumbency_template.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/public/incumbency_template.docx -------------------------------------------------------------------------------- /oneterm-ui/public/loading/loading.css: -------------------------------------------------------------------------------- 1 | #preloadingAnimation{position:fixed;left:0;top:0;height:100%;width:100%;background:#ffffff;user-select:none;z-index: 9999;overflow: hidden}.lds-roller{display:inline-block;position:relative;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(0.5,0,0.5,1) infinite;transform-origin:32px 32px;}.lds-roller div:after{content:" ";display:block;position:absolute;width:6px;height:6px;border-radius:50%;background:#13c2c2;margin:-3px 0 0 -3px;}.lds-roller div:nth-child(1){animation-delay:-0.036s;}.lds-roller div:nth-child(1):after{top:50px;left:50px;}.lds-roller div:nth-child(2){animation-delay:-0.072s;}.lds-roller div:nth-child(2):after{top:54px;left:45px;}.lds-roller div:nth-child(3){animation-delay:-0.108s;}.lds-roller div:nth-child(3):after{top:57px;left:39px;}.lds-roller div:nth-child(4){animation-delay:-0.144s;}.lds-roller div:nth-child(4):after{top:58px;left:32px;}.lds-roller div:nth-child(5){animation-delay:-0.18s;}.lds-roller div:nth-child(5):after{top:57px;left:25px;}.lds-roller div:nth-child(6){animation-delay:-0.216s;}.lds-roller div:nth-child(6):after{top:54px;left:19px;}.lds-roller div:nth-child(7){animation-delay:-0.252s;}.lds-roller div:nth-child(7):after{top:50px;left:14px;}.lds-roller div:nth-child(8){animation-delay:-0.288s;}.lds-roller div:nth-child(8):after{top:45px;left:10px;}#preloadingAnimation .load-tips{color: #13c2c2;font-size:2rem;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin-top:80px;text-align:center;width:400px;height:64px;} @keyframes lds-roller{0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);}} -------------------------------------------------------------------------------- /oneterm-ui/public/loading/loading.html: -------------------------------------------------------------------------------- 1 |
Loading
-------------------------------------------------------------------------------- /oneterm-ui/public/loading/option2/loading.css: -------------------------------------------------------------------------------- 1 | .preloading-animate{background:#ffffff;width:100%;height:100%;position:fixed;left:0;top:0;z-index:299;}.preloading-animate .preloading-wrapper{position:absolute;width:5rem;height:5rem;left:50%;top:50%;transform:translate(-50%,-50%);}.preloading-animate .preloading-wrapper .preloading-balls{font-size:5rem;} -------------------------------------------------------------------------------- /oneterm-ui/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/public/logo.png -------------------------------------------------------------------------------- /oneterm-ui/src/api/auth.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getAuthData(data_type) { 4 | return axios({ 5 | url: `/common-setting/v1/auth_config/${data_type}`, 6 | method: 'get', 7 | }) 8 | } 9 | 10 | export function postAuthData(data_type, data) { 11 | return axios({ 12 | url: `/common-setting/v1/auth_config/${data_type}`, 13 | method: 'post', 14 | data, 15 | }) 16 | } 17 | 18 | export function putAuthData(data_type, id, data) { 19 | return axios({ 20 | url: `/common-setting/v1/auth_config/${data_type}/${id}`, 21 | method: 'put', 22 | data, 23 | }) 24 | } 25 | 26 | export function getAuthDataEnable() { 27 | return axios({ 28 | url: `/common-setting/v1/auth_config/enable_list`, 29 | method: 'get', 30 | }) 31 | } 32 | 33 | export function testLDAP(data) { 34 | return axios({ 35 | url: `/common-setting/v1/auth_config/LDAP/test`, 36 | method: 'post', 37 | data, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /oneterm-ui/src/api/cmdb.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function searchCI(params, isShowMessage = true) { 4 | return axios({ 5 | url: `/v0.1/ci/s`, 6 | method: 'GET', 7 | params: params, 8 | isShowMessage 9 | }) 10 | } 11 | 12 | export function getCIType(CITypeName, parameter) { 13 | return axios({ 14 | url: `/v0.1/ci_types/${CITypeName}`, 15 | method: 'GET', 16 | params: parameter 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /oneterm-ui/src/api/file.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function postImageFile(parameter) { 4 | return axios({ 5 | url: '/common-setting/v1/file', 6 | method: 'post', 7 | data: parameter, 8 | }) 9 | } 10 | 11 | export function getFileData(data_type) { 12 | return axios({ 13 | url: `/common-setting/v1/data/${data_type}`, 14 | method: 'get', 15 | }) 16 | } 17 | 18 | export function addFileData(data_type, data) { 19 | return axios({ 20 | url: `/common-setting/v1/data/${data_type}`, 21 | method: 'post', 22 | data, 23 | }) 24 | } 25 | 26 | export function deleteFileData(data_type, id) { 27 | return axios({ 28 | url: `/common-setting/v1/data/${data_type}/${id}`, 29 | method: 'delete', 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /oneterm-ui/src/api/index.js: -------------------------------------------------------------------------------- 1 | const api = { 2 | Login: '/v1/acl/login', 3 | Logout: '/v1/acl/logout', 4 | ForgePassword: '/auth/forge-password', 5 | Register: '/auth/register', 6 | twoStepCode: '/auth/2step-code', 7 | SendSms: '/account/sms', 8 | SendSmsErr: '/account/sms_err', 9 | // get my info 10 | // UserInfo: '/v1/perms/user/info' 11 | UserInfo: process.env.VUE_APP_IS_OUTER === 'false' ? '/v1/perms/user/info' : '/v1/acl/users/info', 12 | } 13 | export default api 14 | -------------------------------------------------------------------------------- /oneterm-ui/src/api/login.js: -------------------------------------------------------------------------------- 1 | import api from './index' 2 | import { axios } from '@/utils/request' 3 | /** 4 | * login func 5 | * parameter: { 6 | * username: '', 7 | * password: '', 8 | * remember_me: true, 9 | * captcha: '12345' 10 | * } 11 | * @param parameter 12 | * @returns {*} 13 | */ 14 | export function login(data, auth_type) { 15 | if (auth_type) { 16 | localStorage.setItem('ops_auth_type', auth_type) 17 | window.location.href = `/api/${auth_type.toLowerCase()}/login` 18 | } else { 19 | return axios({ 20 | url: api.Login, 21 | method: 'POST', 22 | data: data 23 | }) 24 | } 25 | } 26 | 27 | export function getSmsCaptcha(parameter) { 28 | return axios({ 29 | url: api.SendSms, 30 | method: 'post', 31 | data: parameter 32 | }) 33 | } 34 | 35 | export function getInfo() { 36 | return axios({ 37 | url: api.UserInfo, 38 | method: 'get', 39 | headers: { 40 | 'Content-Type': 'application/json;charset=UTF-8' 41 | } 42 | }) 43 | } 44 | 45 | export function logout() { 46 | const auth_type = localStorage.getItem('ops_auth_type') 47 | localStorage.clear() 48 | return axios({ 49 | url: auth_type ? `/${auth_type.toLowerCase()}/logout` : api.Logout, 50 | method: auth_type ? 'get' : 'post', 51 | headers: { 52 | 'Content-Type': 'application/json;charset=UTF-8' 53 | } 54 | }) 55 | } 56 | 57 | /** 58 | * get user 2step code open? 59 | * @param parameter {*} 60 | */ 61 | export function get2step(parameter) { 62 | return axios({ 63 | url: api.twoStepCode, 64 | method: 'post', 65 | data: parameter 66 | }) 67 | } 68 | 69 | export function getAllUsers(params) { 70 | return axios({ 71 | url: '/v1/acl/users', 72 | method: 'GET', 73 | params 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /oneterm-ui/src/api/message.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export const getNoticeApps = () => { 4 | return axios({ 5 | url: `/common-setting/v1/message/apps`, 6 | method: 'get', 7 | }) 8 | } 9 | 10 | export const getNoticeCategoriesByApp = (app_name) => { 11 | return axios({ 12 | url: `/common-setting/v1/message/${app_name}/categories`, 13 | method: 'get', 14 | }) 15 | } 16 | 17 | export const getMessage = (params) => { 18 | return axios({ 19 | url: `/common-setting/v1/message`, 20 | method: 'get', 21 | params 22 | }) 23 | } 24 | 25 | export const postMessage = (data) => { 26 | return axios({ 27 | url: `/common-setting/v1/message`, 28 | method: 'post', 29 | data 30 | }) 31 | } 32 | 33 | export const updateMessage = (id, data) => { 34 | return axios({ 35 | url: `/common-setting/v1/message/${id}`, 36 | method: 'put', 37 | data 38 | }) 39 | } 40 | 41 | export const getUnreadMessageCount = (params) => { 42 | return axios({ 43 | url: `/common-setting/v1/message/unread`, 44 | method: 'get', 45 | params 46 | }) 47 | } 48 | 49 | export const batchUpdateMessage = (data) => { 50 | return axios({ 51 | url: `/common-setting/v1/message/batch`, 52 | method: 'post', 53 | data 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /oneterm-ui/src/api/noticeSetting.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function sendTestEmail(receive_address, data) { 4 | return axios({ 5 | url: `/common-setting/v1/notice_config/send_test_email?receive_address=${receive_address}`, 6 | method: 'post', 7 | data 8 | }) 9 | } 10 | 11 | export const getNoticeConfigByPlatform = (platform) => { 12 | return axios({ 13 | url: '/common-setting/v1/notice_config', 14 | method: 'get', 15 | params: { ...platform }, 16 | }) 17 | } 18 | 19 | export const postNoticeConfigByPlatform = (data) => { 20 | return axios({ 21 | url: '/common-setting/v1/notice_config', 22 | method: 'post', 23 | data 24 | }) 25 | } 26 | 27 | export const putNoticeConfigByPlatform = (id, info) => { 28 | return axios({ 29 | url: `/common-setting/v1/notice_config/${id}`, 30 | method: 'put', 31 | data: info 32 | }) 33 | } 34 | 35 | export const getNoticeConfigAppBot = () => { 36 | return axios({ 37 | url: `/common-setting/v1/notice_config/app_bot`, 38 | method: 'get', 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/data_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/data_empty.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icon-bg-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/icon-bg-selected.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icon-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/icon-bg.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/CUSTOM.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/OPEN_DAY.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/TEAM.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/bx-analyse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/dag_dag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/dag_email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/dag_option_k2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/dag_task.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/dag_task_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/dag_task_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/dag_transfer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/edit.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/ellipsis.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/gray-circle.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/green-circle.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/notification.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/ops-default_show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/ops-is_choice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/ops-is_password.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/ops-is_sortable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/ops-is_unique.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/ops-move-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/orange-circle.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/python-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/red-circle.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/top_acl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/icons/top_agent.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oneterm-ui/src/assets/login_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/login_bg.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/login_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/login_img.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/logo.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/logo_oneterm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/logo_oneterm.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/ops_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/ops_logout.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/sidebar_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/sidebar_background.png -------------------------------------------------------------------------------- /oneterm-ui/src/assets/sidebar_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/assets/sidebar_selected.png -------------------------------------------------------------------------------- /oneterm-ui/src/bus/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | export default new Vue() 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CMDBFilterComp/constants.js: -------------------------------------------------------------------------------- 1 | import i18n from '@/lang' 2 | 3 | export const ruleTypeList = () => { 4 | return [ 5 | { value: 'and', label: i18n.t('cmdbFilterComp.and') }, 6 | { value: 'or', label: i18n.t('cmdbFilterComp.or') }, 7 | // { value: 'not', label: '非' }, 8 | ] 9 | } 10 | 11 | export const expList = () => { 12 | return [ 13 | { value: 'is', label: i18n.t('cmdbFilterComp.is') }, 14 | { value: '~is', label: i18n.t('cmdbFilterComp.~is') }, 15 | { value: 'contain', label: i18n.t('cmdbFilterComp.contain') }, 16 | { value: '~contain', label: i18n.t('cmdbFilterComp.~contain') }, 17 | { value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') }, 18 | { value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') }, 19 | { value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') }, 20 | { value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') }, 21 | { value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕 22 | { value: 'value', label: i18n.t('cmdbFilterComp.value') }, 23 | ] 24 | } 25 | 26 | export const advancedExpList = () => { 27 | return [ 28 | { value: 'in', label: i18n.t('cmdbFilterComp.in') }, 29 | { value: '~in', label: i18n.t('cmdbFilterComp.~in') }, 30 | { value: 'range', label: i18n.t('cmdbFilterComp.range') }, 31 | { value: '~range', label: i18n.t('cmdbFilterComp.~range') }, 32 | { value: 'compare', label: i18n.t('cmdbFilterComp.compare') }, 33 | ] 34 | } 35 | 36 | export const compareTypeList = [ 37 | { value: '1', label: '>' }, 38 | { value: '2', label: '>=' }, 39 | { value: '3', label: '<' }, 40 | { value: '4', label: '<=' }, 41 | ] 42 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CMDBValueTypeMapIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CardTitle/CardTitle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 25 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CardTitle/index.js: -------------------------------------------------------------------------------- 1 | import CardTitle from './CardTitle' 2 | export default CardTitle 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/Crontab/index.js: -------------------------------------------------------------------------------- 1 | import Vcrontab from './Crontab.vue' 2 | export default Vcrontab 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CustomDrawer/index.js: -------------------------------------------------------------------------------- 1 | import CustomDrawer from './CustomDrawer' 2 | export default CustomDrawer 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CustomRadio/CustomRadio.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 44 | 45 | 62 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CustomRadio/index.js: -------------------------------------------------------------------------------- 1 | import CustomRadio from './CustomRadio' 2 | export default CustomRadio 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/CustomTransfer/index.js: -------------------------------------------------------------------------------- 1 | import CustomTransfer from './CustomTransfer' 2 | export default CustomTransfer 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/EmployeeTransfer/index.js: -------------------------------------------------------------------------------- 1 | import EmployeeTransfer from './index.vue' 2 | export default EmployeeTransfer 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/Exception/index.js: -------------------------------------------------------------------------------- 1 | import ExceptionPage from './ExceptionPage.vue' 2 | export default ExceptionPage 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/Exception/type.js: -------------------------------------------------------------------------------- 1 | const types = { 2 | 403: { 3 | img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg', 4 | title: '403', 5 | desc: '抱歉,你无权访问该页面' 6 | }, 7 | 404: { 8 | img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg', 9 | title: '404', 10 | desc: '抱歉,你访问的页面不存在或仍在开发中' 11 | }, 12 | 500: { 13 | img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg', 14 | title: '500', 15 | desc: '抱歉,服务器出错了' 16 | } 17 | } 18 | 19 | export default types 20 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/FooterToolbar/FooterToolBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/FooterToolbar/index.js: -------------------------------------------------------------------------------- 1 | import FooterToolBar from './FooterToolBar' 2 | import './index.less' 3 | 4 | export default FooterToolBar 5 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import "../index"; 2 | 3 | @footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar"; 4 | 5 | .@{footer-toolbar-prefix-cls} { 6 | position: fixed; 7 | width: 100%; 8 | bottom: 0; 9 | right: 0; 10 | height: 56px; 11 | line-height: 56px; 12 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); 13 | background: #fff; 14 | border-top: 1px solid #e8e8e8; 15 | padding: 0 24px; 16 | z-index: 9; 17 | 18 | &:after { 19 | content: ""; 20 | display: block; 21 | clear: both; 22 | } 23 | } -------------------------------------------------------------------------------- /oneterm-ui/src/components/FooterToolbar/index.md: -------------------------------------------------------------------------------- 1 | # FooterToolbar 底部工具栏 2 | 3 | 固定在底部的工具栏。 4 | 5 | 6 | 7 | ## 何时使用 8 | 9 | 固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 10 | 11 | 12 | 13 | 引用方式: 14 | 15 | ```javascript 16 | import FooterToolBar from '@/components/FooterToolbar' 17 | 18 | export default { 19 | components: { 20 | FooterToolBar 21 | } 22 | } 23 | ``` 24 | 25 | 26 | 27 | ## 代码演示 28 | 29 | ```html 30 | 31 | 提交 32 | 33 | ``` 34 | 或 35 | ```html 36 | 37 | 提交 38 | 39 | ``` 40 | 41 | 42 | ## API 43 | 44 | 参数 | 说明 | 类型 | 默认值 45 | ----|------|-----|------ 46 | children (slot) | 工具栏内容,向右对齐 | - | - 47 | extra | 额外信息,向左对齐 | String, Object | - 48 | 49 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/GlobalFooter/GlobalFooter.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 31 | 32 | 59 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import GlobalFooter from './GlobalFooter' 2 | export default GlobalFooter 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/GlobalHeader/index.js: -------------------------------------------------------------------------------- 1 | import GlobalHeader from './GlobalHeader' 2 | export default GlobalHeader 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/Menu/SideMenu.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 68 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/Menu/index.js: -------------------------------------------------------------------------------- 1 | import SMenu from './menu' 2 | export default SMenu 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/MultiTab/index.js: -------------------------------------------------------------------------------- 1 | import MultiTab from './MultiTab' 2 | import './index.less' 3 | 4 | export default MultiTab 5 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/MultiTab/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab"; 4 | @multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper"; 5 | 6 | /* 7 | .topmenu .@{multi-tab-prefix-cls} { 8 | max-width: 1200px; 9 | margin: -23px auto 24px auto; 10 | } 11 | */ 12 | .@{multi-tab-prefix-cls} { 13 | margin: -23px -24px 24px -24px; 14 | background: #fff; 15 | } 16 | 17 | .topmenu .@{multi-tab-wrapper-prefix-cls} { 18 | max-width: 1200px; 19 | margin: 0 auto; 20 | } 21 | 22 | .topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} { 23 | max-width: 100%; 24 | margin: 0 auto; 25 | } 26 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/OpsTable/index.js: -------------------------------------------------------------------------------- 1 | import OpsTable from './index.vue' 2 | export default OpsTable 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/PageHeader/index.js: -------------------------------------------------------------------------------- 1 | import PageHeader from './PageHeader' 2 | export default PageHeader 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/PageLoading/index.jsx: -------------------------------------------------------------------------------- 1 | import { Spin } from 'ant-design-vue' 2 | 3 | export default { 4 | name: 'PageLoading', 5 | render () { 6 | return (
7 | 8 |
) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/Pager/index.js: -------------------------------------------------------------------------------- 1 | import Pager from './index.vue' 2 | export default Pager 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/Result/index.js: -------------------------------------------------------------------------------- 1 | import Result from './Result.vue' 2 | export default Result 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/RoleTransfer/index.js: -------------------------------------------------------------------------------- 1 | import RolesTransfer from './index.vue' 2 | export default RolesTransfer 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/SettingDrawer/SettingItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 39 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/SettingDrawer/index.js: -------------------------------------------------------------------------------- 1 | import SettingDrawer from './SettingDrawer' 2 | export default SettingDrawer 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/SettingDrawer/themeColor.js: -------------------------------------------------------------------------------- 1 | import client from 'webpack-theme-color-replacer/client' 2 | import generate from '@ant-design/colors/lib/generate' 3 | 4 | export default { 5 | getAntdSerials (color) { 6 | // 淡化(即less的tint) 7 | const lightens = new Array(9).fill().map((t, i) => { 8 | return client.varyColor.lighten(color, i / 10) 9 | }) 10 | // colorPalette变换得到颜色值 11 | const colorPalettes = generate(color) 12 | return lightens.concat(colorPalettes) 13 | }, 14 | changeColor (newColor) { 15 | var options = { 16 | newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors` 17 | changeUrl (cssUrl) { 18 | return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path 19 | } 20 | } 21 | return client.changer.changeColor(options, Promise) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/SplitPane/index.js: -------------------------------------------------------------------------------- 1 | import SplitPane from './SplitPane' 2 | export default SplitPane 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/SplitPane/index.less: -------------------------------------------------------------------------------- 1 | .split-pane { 2 | height: 100%; 3 | display: flex; 4 | } 5 | 6 | .split-pane .pane-two { 7 | flex: 1; 8 | } 9 | 10 | .split-pane .pane-trigger { 11 | user-select: none; 12 | } 13 | 14 | .split-pane.row .pane-one { 15 | width: 20%; 16 | height: 100%; 17 | // overflow-y: auto; 18 | } 19 | 20 | .split-pane.column .pane { 21 | width: 100%; 22 | } 23 | 24 | .split-pane.row .pane-trigger { 25 | width: 8px; 26 | height: 100%; 27 | cursor: e-resize; 28 | background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=') 29 | 1px 50% no-repeat #f0f2f5; 30 | } 31 | 32 | .split-pane .collapse-btn { 33 | width: 25px; 34 | height: 70px; 35 | position: absolute; 36 | right: 8px; 37 | top: calc(50% - 35px); 38 | background-color: #f0f2f5; 39 | border-color: transparent; 40 | border-radius: 8px 0px 0px 8px; 41 | .anticon { 42 | color: #7cb0fe; 43 | } 44 | } 45 | 46 | .split-pane .spliter-wrap { 47 | position: relative; 48 | } 49 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/TagSelect/TagSelectOption.jsx: -------------------------------------------------------------------------------- 1 | import { Tag } from 'ant-design-vue' 2 | const { CheckableTag } = Tag 3 | 4 | export default { 5 | name: 'TagSelectOption', 6 | props: { 7 | prefixCls: { 8 | type: String, 9 | default: 'ant-pro-tag-select-option' 10 | }, 11 | value: { 12 | type: [String, Number, Object], 13 | default: '' 14 | }, 15 | checked: { 16 | type: Boolean, 17 | default: false 18 | } 19 | }, 20 | data () { 21 | return { 22 | localChecked: this.checked || false 23 | } 24 | }, 25 | watch: { 26 | 'checked' (val) { 27 | this.localChecked = val 28 | }, 29 | '$parent.items': { 30 | handler: function (val) { 31 | this.value && val.hasOwnProperty(this.value) && (this.localChecked = val[this.value]) 32 | }, 33 | deep: true 34 | } 35 | }, 36 | render () { 37 | const { $slots, value } = this 38 | const onChange = (checked) => { 39 | this.$emit('change', { value, checked }) 40 | } 41 | return ( 42 | {$slots.default} 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/TwoColumnLayout/index.js: -------------------------------------------------------------------------------- 1 | import TwoColumnLayout from './TwoColumnLayout' 2 | export default TwoColumnLayout 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/_util/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * components util 3 | */ 4 | 5 | /** 6 | * 清理空值,对象 7 | * @param children 8 | * @returns {*[]} 9 | */ 10 | export function filterEmpty (children = []) { 11 | return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) 12 | } 13 | 14 | /** 15 | * 获取字符串长度,英文字符 长度1,中文字符长度2 16 | * @param {*} str 17 | */ 18 | export const getStrFullLength = (str = '') => 19 | str.split('').reduce((pre, cur) => { 20 | const charCode = cur.charCodeAt(0) 21 | if (charCode >= 0 && charCode <= 128) { 22 | return pre + 1 23 | } 24 | return pre + 2 25 | }, 0) 26 | 27 | /** 28 | * 截取字符串,根据 maxLength 截取后返回 29 | * @param {*} str 30 | * @param {*} maxLength 31 | */ 32 | export const cutStrByFullLength = (str = '', maxLength) => { 33 | let showLength = 0 34 | return str.split('').reduce((pre, cur) => { 35 | const charCode = cur.charCodeAt(0) 36 | if (charCode >= 0 && charCode <= 128) { 37 | showLength += 1 38 | } else { 39 | showLength += 2 40 | } 41 | if (showLength <= maxLength) { 42 | return pre + cur 43 | } 44 | return pre 45 | }, '') 46 | } 47 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/chartTime/constants.js: -------------------------------------------------------------------------------- 1 | export const intervalTimeList = [{ 2 | name: 'off', 3 | value: 'off' 4 | }, { 5 | name: '5s', 6 | value: 5 * 1000 7 | }, { 8 | name: '10s', 9 | value: 10 * 1000 10 | }, { 11 | name: '30s', 12 | value: 30 * 1000 13 | }, { 14 | name: '1m', 15 | value: 1 * 60 * 1000 16 | }, { 17 | name: '5m', 18 | value: 5 * 60 * 1000 19 | }, { 20 | name: '15m', 21 | value: 15 * 60 * 1000 22 | }, { 23 | name: '30m', 24 | value: 30 * 60 * 1000 25 | }, { 26 | name: '1h', 27 | value: 1 * 60 * 60 * 1000 28 | }, { 29 | name: '2h', 30 | value: 2 * 60 * 60 * 1000 31 | }, { 32 | name: '1d', 33 | value: 1 * 24 * 60 * 60 * 1000 34 | }] 35 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/index.js: -------------------------------------------------------------------------------- 1 | import MultiTab from '@/components/MultiTab' 2 | import Result from '@/components/Result' 3 | import TagSelect from '@/components/TagSelect' 4 | import ExceptionPage from '@/components/Exception' 5 | 6 | export { 7 | MultiTab, 8 | Result, 9 | ExceptionPage, 10 | TagSelect 11 | } 12 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/index.less: -------------------------------------------------------------------------------- 1 | @import "~ant-design-vue/lib/style/index"; 2 | 3 | // The prefix to use on all css classes from ant-pro. 4 | @ant-pro-prefix : ant-pro; 5 | @ant-global-header-zindex : 105; -------------------------------------------------------------------------------- /oneterm-ui/src/components/tools/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/tools/DetailList.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/tools/DocumentLink.vue: -------------------------------------------------------------------------------- 1 | 9 | 22 | 39 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/tools/HeadInfo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | 33 | 68 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/tools/Logo.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 49 | -------------------------------------------------------------------------------- /oneterm-ui/src/components/tools/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/components/tools/index.js -------------------------------------------------------------------------------- /oneterm-ui/src/config/app.js: -------------------------------------------------------------------------------- 1 | const appConfig = { 2 | buildModules: ['oneterm', 'acl'], // 需要编译的模块 3 | redirectTo: '/oneterm', // 首页的重定向路径 4 | buildAclToModules: true, // 是否在各个应用下 内联权限管理 5 | showDocs: false, 6 | useEncryption: false, 7 | } 8 | 9 | export default appConfig 10 | -------------------------------------------------------------------------------- /oneterm-ui/src/config/setting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目默认配置项 3 | * primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage 4 | * navTheme - sidebar theme ['dark', 'light'] 两种主题 5 | * colorWeak - 色盲模式 6 | * layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 7 | * fixedHeader - 固定 Header : boolean 8 | * fixSiderbar - 固定左侧菜单栏 : boolean 9 | * autoHideHeader - 向下滚动时,隐藏 Header : boolean 10 | * contentWidth - 内容区布局: 流式 | 固定 11 | * 12 | * storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) 13 | * 14 | */ 15 | 16 | export default { 17 | primaryColor: '#2f54eb', // primary color of ant design 18 | navTheme: 'dark', // theme for nav menu 19 | layout: 'sidemenu', // nav menu position: sidemenu or topmenu 20 | contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu 21 | fixedHeader: true, // sticky header 22 | fixSiderbar: true, // sticky siderbar 23 | autoHideHeader: true, // auto hide header 24 | colorWeak: false, 25 | multiTab: false, 26 | production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true', 27 | // vue-ls options 28 | storageOptions: { 29 | namespace: 'pro__', // key prefix 30 | name: 'ls', // name variable Vue.[ls] or this.[$ls], 31 | storage: 'local' // storage name session, local, memory 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oneterm-ui/src/core/EventBus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default new Vue() 4 | -------------------------------------------------------------------------------- /oneterm-ui/src/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store/' 3 | import { 4 | ACCESS_TOKEN, 5 | DEFAULT_COLOR, 6 | DEFAULT_THEME, 7 | DEFAULT_LAYOUT_MODE, 8 | DEFAULT_COLOR_WEAK, 9 | SIDEBAR_TYPE, 10 | DEFAULT_FIXED_HEADER, 11 | DEFAULT_FIXED_HEADER_HIDDEN, 12 | DEFAULT_FIXED_SIDEMENU, 13 | DEFAULT_CONTENT_WIDTH_TYPE, 14 | DEFAULT_MULTI_TAB 15 | } from '@/store/global/mutation-types' 16 | import config from '@/config/setting' 17 | 18 | export default function Initializer () { 19 | store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true)) 20 | store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme)) 21 | store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout)) 22 | store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader)) 23 | store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar)) 24 | store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth)) 25 | store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader)) 26 | store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak)) 27 | store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor)) 28 | store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab)) 29 | store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN)) 30 | // last step 31 | } 32 | -------------------------------------------------------------------------------- /oneterm-ui/src/core/directives/action.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | 4 | /** 5 | * Action 权限指令 6 | * 指令用法: 7 | * - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下: 8 | * 添加用户 9 | * 删除用户 10 | * 修改 11 | * 12 | * - 当前用户没有权限时,组件上使用了该指令则会被隐藏 13 | * - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可 14 | * 15 | * @see https://github.com/sendya/ant-design-pro-vue/pull/53 16 | */ 17 | const action = Vue.directive('action', { 18 | inserted: function (el, binding, vnode) { 19 | const actionName = binding.arg 20 | const roles = store.getters.roles 21 | const elVal = vnode.context.$route.meta.permission 22 | const permissionId = elVal instanceof String && [elVal] || elVal || [] 23 | roles.permissions.forEach(p => { 24 | if (!permissionId.includes(p.permissionId)) { 25 | return 26 | } 27 | if (p.actionList && !p.actionList.includes(actionName)) { 28 | el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none') 29 | } 30 | }) 31 | } 32 | }) 33 | 34 | export default action 35 | -------------------------------------------------------------------------------- /oneterm-ui/src/core/lazy_use.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueStorage from 'vue-ls' 3 | import config from '@/config/setting' 4 | 5 | // base library 6 | import '@/core/lazy_lib/components_use' 7 | import Viser from 'viser-vue' 8 | 9 | // ext library 10 | import VueClipboard from 'vue-clipboard2' 11 | import PermissionHelper from '@/utils/helper/permission' 12 | import './directives/action' 13 | 14 | VueClipboard.config.autoSetContainer = true 15 | 16 | Vue.use(Viser) 17 | 18 | Vue.use(VueStorage, config.storageOptions) 19 | Vue.use(VueClipboard) 20 | Vue.use(PermissionHelper) 21 | -------------------------------------------------------------------------------- /oneterm-ui/src/directive/highlight/highlight.js: -------------------------------------------------------------------------------- 1 | import './highlight.less' 2 | 3 | const highlight = (el, binding) => { 4 | if (binding.value.value) { 5 | let testValue = `${binding.value.value}` 6 | if (['(', ')', '$'].includes(testValue)) { 7 | testValue = `\\${testValue}` 8 | } 9 | const regex = new RegExp(`(${testValue})`, 'gi') 10 | el.innerHTML = el.innerText.replace(regex, `$1`) 11 | } 12 | } 13 | 14 | export default highlight 15 | -------------------------------------------------------------------------------- /oneterm-ui/src/directive/highlight/highlight.less: -------------------------------------------------------------------------------- 1 | @import '~@/style/static.less'; 2 | 3 | .ops-text-highlight { 4 | background-color: @primary-color_3; 5 | } 6 | -------------------------------------------------------------------------------- /oneterm-ui/src/directive/highlight/index.js: -------------------------------------------------------------------------------- 1 | import hightlight from './highlight' 2 | 3 | const install = function (Vue) { 4 | Vue.directive('hightlight', hightlight) 5 | } 6 | 7 | if (window.Vue) { 8 | window.hightlight = hightlight 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | hightlight.install = install 12 | export default hightlight 13 | -------------------------------------------------------------------------------- /oneterm-ui/src/directive/waves/index.js: -------------------------------------------------------------------------------- 1 | import waves from './waves' 2 | 3 | const install = function (Vue) { 4 | Vue.directive('waves', waves) 5 | } 6 | 7 | if (window.Vue) { 8 | window.waves = waves 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | waves.install = install 13 | export default waves 14 | -------------------------------------------------------------------------------- /oneterm-ui/src/directive/waves/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | background-color: rgba(0, 0, 0, 0.15); 5 | background-clip: padding-box; 6 | pointer-events: none; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | -webkit-transform: scale(0); 12 | -ms-transform: scale(0); 13 | transform: scale(0); 14 | opacity: 1; 15 | } 16 | 17 | .waves-ripple.z-active { 18 | opacity: 0; 19 | -webkit-transform: scale(2); 20 | -ms-transform: scale(2); 21 | transform: scale(2); 22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; 26 | } -------------------------------------------------------------------------------- /oneterm-ui/src/lang/index.js: -------------------------------------------------------------------------------- 1 | import VueI18n from 'vue-i18n' 2 | import zh from './zh' 3 | import en from './en' 4 | import Vue from 'vue' 5 | import zhCN from 'vxe-table/lib/locale/lang/zh-CN' 6 | import enUS from 'vxe-table/lib/locale/lang/en-US' 7 | 8 | Vue.use(VueI18n) 9 | const i18n = new VueI18n({ 10 | locale: 'zh', // 初始化中文 11 | messages: { 12 | 'zh': { ...zh, ...zhCN }, 13 | 'en': { ...en, ...enUS }, 14 | }, 15 | silentTranslationWarn: true 16 | }) 17 | 18 | export default i18n 19 | -------------------------------------------------------------------------------- /oneterm-ui/src/layouts/BlankLayout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /oneterm-ui/src/layouts/RouteView.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /oneterm-ui/src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import UserLayout from './UserLayout' 2 | import BlankLayout from './BlankLayout' 3 | import BasicLayout from './BasicLayout' 4 | import RouteView from './RouteView' 5 | import PageView from './PageView' 6 | 7 | export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView } 8 | -------------------------------------------------------------------------------- /oneterm-ui/src/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import '@babel/polyfill' 3 | import Vue from 'vue' 4 | import App from './App.vue' 5 | import router from './router' 6 | import store from './store/' 7 | import bootstrap from './core/bootstrap' 8 | import './core/use' 9 | import './guard' // guard permission control 10 | import './utils/filter' // global filter 11 | import Setting from './config/setting' 12 | import { Icon } from 'ant-design-vue' 13 | import i18n from './lang' 14 | 15 | import iconFont from '../public/iconfont/iconfont' 16 | 17 | // 存在直接crash的风险 还未到 18 | const customIcon = Icon.createFromIconfontCN(iconFont) 19 | Vue.component('ops-icon', customIcon) 20 | var vue; 21 | 22 | async function start() { 23 | const _vue = new Vue({ 24 | router, 25 | store, 26 | i18n, 27 | created: bootstrap, 28 | render: h => h(App) 29 | }).$mount('#app') 30 | vue = _vue 31 | 32 | if (process.env.NODE_ENV === 'development') { 33 | window.$app = vue 34 | window.$router = router 35 | window.$store = store 36 | window.$env = process.env 37 | } 38 | } 39 | 40 | start() 41 | window.$setting = Setting 42 | 43 | export default vue 44 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/api/app.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | const urlPrefix = '/v1/acl' 4 | 5 | export function searchRole(params) { 6 | return axios({ 7 | url: urlPrefix + `/roles`, 8 | method: 'GET', 9 | params: params 10 | }) 11 | } 12 | 13 | export function addRole(params) { 14 | return axios({ 15 | url: urlPrefix + '/roles', 16 | method: 'POST', 17 | data: params 18 | }) 19 | } 20 | 21 | export function updateRoleById(id, params) { 22 | return axios({ 23 | url: urlPrefix + `/roles/${id}`, 24 | method: 'PUT', 25 | data: params 26 | }) 27 | } 28 | 29 | export function deleteRoleById(id) { 30 | return axios({ 31 | url: urlPrefix + `/roles/${id}`, 32 | method: 'DELETE' 33 | }) 34 | } 35 | 36 | export function searchApp(params = {}) { 37 | return axios({ 38 | url: urlPrefix + '/apps', 39 | method: 'GET', 40 | params: { ...params, page_size: 9999 } 41 | }) 42 | } 43 | 44 | export function addApp(data) { 45 | return axios({ 46 | url: urlPrefix + '/apps', 47 | method: 'POST', 48 | data: data 49 | }) 50 | } 51 | 52 | export function updateApp(aid, data) { 53 | return axios({ 54 | url: urlPrefix + `/apps/${aid}`, 55 | method: 'PUT', 56 | data: data 57 | }) 58 | } 59 | 60 | export function getApp(aid) { 61 | return axios({ 62 | url: urlPrefix + `/apps/${aid}`, 63 | method: 'GET', 64 | }) 65 | } 66 | 67 | export function deleteApp(aid) { 68 | return axios({ 69 | url: urlPrefix + `/apps/${aid}`, 70 | method: 'DELETE' 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/api/history.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | const urlPrefix = '/v1/acl' 4 | 5 | export function searchPermissonHistory(params) { 6 | return axios({ 7 | url: urlPrefix + `/audit_log/permission`, 8 | method: 'GET', 9 | params: params 10 | }) 11 | } 12 | 13 | export function searchRoleHistory(params) { 14 | return axios({ 15 | url: urlPrefix + `/audit_log/role`, 16 | method: 'GET', 17 | params: params 18 | }) 19 | } 20 | 21 | export function searchResourceHistory(params) { 22 | return axios({ 23 | url: urlPrefix + `/audit_log/resource`, 24 | method: 'GET', 25 | params: params 26 | }) 27 | } 28 | 29 | export function searchTriggerHistory(params) { 30 | return axios({ 31 | url: urlPrefix + `/audit_log/trigger`, 32 | method: 'GET', 33 | params: params 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/api/secretKey.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getSecret() { 4 | return axios({ 5 | url: '/v1/acl/users/secret', 6 | method: 'GET' 7 | }) 8 | } 9 | 10 | export function updateSecret(data) { 11 | return axios({ 12 | url: '/v1/acl/users/reset_key_secret', 13 | method: 'POST', 14 | data, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/api/trigger.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | const urlPrefix = '/v1/acl' 4 | 5 | export function getTriggers(params) { 6 | return axios({ 7 | url: urlPrefix + '/triggers', 8 | method: 'GET', 9 | params: params 10 | }) 11 | } 12 | 13 | export function addTrigger(data) { 14 | return axios({ 15 | url: urlPrefix + '/triggers', 16 | method: 'POST', 17 | data: data 18 | }) 19 | } 20 | 21 | export function updateTrigger(tid, data) { 22 | return axios({ 23 | url: urlPrefix + `/triggers/${tid}`, 24 | method: 'PUT', 25 | data: data 26 | }) 27 | } 28 | 29 | export function deleteTrigger(tid) { 30 | return axios({ 31 | url: urlPrefix + `/triggers/${tid}`, 32 | method: 'DELETE' 33 | }) 34 | } 35 | 36 | export function applyTrigger(tid) { 37 | return axios({ 38 | url: urlPrefix + `/triggers/${tid}/apply`, 39 | method: 'POST' 40 | }) 41 | } 42 | 43 | export function cancelTrigger(tid) { 44 | return axios({ 45 | url: urlPrefix + `/triggers/${tid}/cancel`, 46 | method: 'POST' 47 | }) 48 | } 49 | 50 | export function patternResults(params) { 51 | return axios({ 52 | url: `${urlPrefix}/triggers/resources`, 53 | method: 'POST', 54 | data: params 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/api/user.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { axios } from '@/utils/request' 3 | 4 | const urlPrefix = '/v1/acl' 5 | 6 | export function currentUser() { 7 | return axios({ 8 | url: urlPrefix + `/users/info`, 9 | method: 'GET' 10 | }) 11 | } 12 | 13 | export function getOnDutyUser() { 14 | return axios({ 15 | url: urlPrefix + '/users/employee', 16 | method: 'GET', 17 | // data: { 'originUrl': 'http://hr.dfc.sh/api/all_users?work_status=在职' } 18 | }) 19 | } 20 | 21 | export function searchUser(params) { 22 | return axios({ 23 | url: urlPrefix + `/users`, 24 | method: 'GET', 25 | params: params 26 | }) 27 | } 28 | 29 | export function addUser(params) { 30 | return axios({ 31 | url: urlPrefix + '/users', 32 | method: 'POST', 33 | data: params 34 | }) 35 | } 36 | 37 | export function updateUserById(id, params) { 38 | return axios({ 39 | url: urlPrefix + `/users/${id}`, 40 | method: 'PUT', 41 | data: params 42 | }) 43 | } 44 | 45 | export function deleteUserById(id) { 46 | return axios({ 47 | url: urlPrefix + `/users/${id}`, 48 | method: 'DELETE' 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/constants/constants.js: -------------------------------------------------------------------------------- 1 | export const valueTypeMap = { 2 | '0': '整数', 3 | '1': '浮点数', 4 | '2': '文本', 5 | '3': 'datetime', 6 | '4': 'date', 7 | '5': 'time', 8 | '6': 'json' 9 | } 10 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/index.js: -------------------------------------------------------------------------------- 1 | import genAclRoutes from './router' 2 | 3 | export default { 4 | name: 'acl', 5 | route: genAclRoutes 6 | } 7 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/store/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/acl/store/index.js -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/style/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/acl/style/index.css -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/style/index.css.map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/acl/style/index.css.map -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/style/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/acl/style/index.less -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/views/module/resourceGroupMember.vue: -------------------------------------------------------------------------------- 1 | 10 | 36 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/acl/views/operation_history/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/account.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getAccountList(params) { 4 | return axios({ 5 | url: '/oneterm/v1/account', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function postAccount(data) { 12 | return axios({ 13 | url: '/oneterm/v1/account', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | 19 | export function putAccountById(id, data) { 20 | return axios({ 21 | url: `/oneterm/v1/account/${id}`, 22 | method: 'put', 23 | data 24 | }) 25 | } 26 | 27 | export function deleteAccountById(id) { 28 | return axios({ 29 | url: `/oneterm/v1/account/${id}`, 30 | method: 'delete', 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/asset.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getAssetList(params) { 4 | return axios({ 5 | url: '/oneterm/v1/asset', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function postAsset(data) { 12 | return axios({ 13 | url: '/oneterm/v1/asset', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | 19 | export function putAssetById(id, data) { 20 | return axios({ 21 | url: `/oneterm/v1/asset/${id}`, 22 | method: 'put', 23 | data 24 | }) 25 | } 26 | 27 | export function deleteAssetById(id) { 28 | return axios({ 29 | url: `/oneterm/v1/asset/${id}`, 30 | method: 'delete', 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/authorization.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getAuth(params) { 4 | return axios({ 5 | url: '/oneterm/v1/authorization', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function postAuth(data) { 12 | return axios({ 13 | url: '/oneterm/v1/authorization', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/command.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getCommandList(params) { 4 | return axios({ 5 | url: '/oneterm/v1/command', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function postCommand(data) { 12 | return axios({ 13 | url: '/oneterm/v1/command', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | 19 | export function putCommandById(id, data) { 20 | return axios({ 21 | url: `/oneterm/v1/command/${id}`, 22 | method: 'put', 23 | data 24 | }) 25 | } 26 | 27 | export function deleteCommandById(id) { 28 | return axios({ 29 | url: `/oneterm/v1/command/${id}`, 30 | method: 'delete', 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/config.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getConfig(params) { 4 | return axios({ 5 | url: '/oneterm/v1/config', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function postConfig(data) { 12 | return axios({ 13 | url: '/oneterm/v1/config', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/connect.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function closeConnect(session_id) { 4 | return axios({ 5 | url: `/oneterm/v1/connect/close/${session_id}`, 6 | method: 'post', 7 | }) 8 | } 9 | 10 | export function postConnectIsRight(asset_id, account_id, protocol, query = null) { 11 | let url = `/oneterm/v1/connect/${asset_id}/${account_id}/${protocol}` 12 | if (query) { 13 | url = `${url}?${query}` 14 | } 15 | return axios({ 16 | url, 17 | method: 'post', 18 | }) 19 | } 20 | 21 | export function postShareLink(data) { 22 | return axios({ 23 | url: `/oneterm/v1/share`, 24 | method: 'post', 25 | data 26 | }) 27 | } 28 | 29 | export function getShareLink(params) { 30 | return axios({ 31 | url: `/oneterm/v1/share`, 32 | method: 'get', 33 | params 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/gateway.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getGatewayList(params) { 4 | return axios({ 5 | url: '/oneterm/v1/gateway', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function postGateway(data) { 12 | return axios({ 13 | url: '/oneterm/v1/gateway', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | 19 | export function putGatewayById(id, data) { 20 | return axios({ 21 | url: `/oneterm/v1/gateway/${id}`, 22 | method: 'put', 23 | data 24 | }) 25 | } 26 | 27 | export function deleteGatewayById(id) { 28 | return axios({ 29 | url: `/oneterm/v1/gateway/${id}`, 30 | method: 'delete', 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/loginLog.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getLoginLogList(params) { 4 | return axios({ 5 | url: `/v1/acl/audit_log/login`, 6 | method: 'GET', 7 | params: params 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/node.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getNodeList(params) { 4 | return axios({ 5 | url: '/oneterm/v1/node', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function getNodeById(id) { 12 | return axios({ 13 | url: `/oneterm/v1/node?id=${id}`, 14 | method: 'get', 15 | }) 16 | } 17 | 18 | export function postNode(data) { 19 | return axios({ 20 | url: '/oneterm/v1/node', 21 | method: 'post', 22 | data 23 | }) 24 | } 25 | 26 | export function putNodeById(id, data) { 27 | return axios({ 28 | url: `/oneterm/v1/node/${id}`, 29 | method: 'put', 30 | data 31 | }) 32 | } 33 | 34 | export function deleteNodeById(id) { 35 | return axios({ 36 | url: `/oneterm/v1/node/${id}`, 37 | method: 'delete', 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/operationLog.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getOperationLogList(params) { 4 | return axios({ 5 | url: `/oneterm/v1/history`, 6 | method: 'GET', 7 | params: params 8 | }) 9 | } 10 | 11 | export function getResourceType(params) { 12 | return axios({ 13 | url: `/oneterm/v1/history/type/mapping`, 14 | method: 'get', 15 | params: params 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/otherModules.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getCITypeGroups(params) { 4 | return axios({ 5 | url: `/v0.1/ci_types/groups`, 6 | method: 'GET', 7 | params: params 8 | }) 9 | } 10 | 11 | export function getCITypeAttributesById(CITypeId, parameter) { 12 | return axios({ 13 | url: `/v0.1/ci_types/${CITypeId}/attributes`, 14 | method: 'get', 15 | params: parameter 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/preference.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getPreference() { 4 | return axios({ 5 | url: '/oneterm/v1/preference', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function putPreference(data) { 11 | return axios({ 12 | url: '/oneterm/v1/preference', 13 | method: 'put', 14 | data 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/publicKey.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getPublicKeyList(params) { 4 | return axios({ 5 | url: `/oneterm/v1/public_key`, 6 | method: 'GET', 7 | params: params 8 | }) 9 | } 10 | 11 | export function addPublicKey(data) { 12 | return axios({ 13 | url: `/oneterm/v1/public_key`, 14 | method: 'POST', 15 | data: data 16 | }) 17 | } 18 | 19 | export function putPublicKeyById(id, data) { 20 | return axios({ 21 | url: `/oneterm/v1/public_key/${id}`, 22 | method: 'put', 23 | data 24 | }) 25 | } 26 | 27 | export function deletePublicKeyById(id) { 28 | return axios({ 29 | url: `/oneterm/v1/public_key/${id}`, 30 | method: 'delete', 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/quickCommand.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getQuickCommand(params) { 4 | return axios({ 5 | url: '/oneterm/v1/quick_command', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function postQuickCommand(data) { 12 | return axios({ 13 | url: '/oneterm/v1/quick_command', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | 19 | export function putQuickCommandById(id, data) { 20 | return axios({ 21 | url: `/oneterm/v1/quick_command/${id}`, 22 | method: 'put', 23 | data 24 | }) 25 | } 26 | 27 | export function deleteQuickCommandById(id) { 28 | return axios({ 29 | url: `/oneterm/v1/quick_command/${id}`, 30 | method: 'delete', 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/replay.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getSessionReplayData(session_id) { 4 | return axios({ 5 | url: `/oneterm/v1/session/replay/${session_id}`, 6 | method: 'get' 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/session.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getSessionList(params) { 4 | return axios({ 5 | url: '/oneterm/v1/session', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function getSessionCmdList(session_id, params) { 12 | return axios({ 13 | url: `/oneterm/v1/session/${session_id}/cmd`, 14 | method: 'get', 15 | params 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/api/stat.js: -------------------------------------------------------------------------------- 1 | import { axios } from '@/utils/request' 2 | 3 | export function getCountStat(params) { 4 | return axios({ 5 | url: '/oneterm/v1/stat/count', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function getAssetTypeStat(params) { 12 | return axios({ 13 | url: '/oneterm/v1/stat/assettype', 14 | method: 'get', 15 | params 16 | }) 17 | } 18 | 19 | export function getAssetStat(params) { 20 | return axios({ 21 | url: '/oneterm/v1/stat/asset', 22 | method: 'get', 23 | params 24 | }) 25 | } 26 | 27 | export function getAccountStat(params) { 28 | return axios({ 29 | url: '/oneterm/v1/stat/account', 30 | method: 'get', 31 | params 32 | }) 33 | } 34 | 35 | export function getOfUserStat(params) { 36 | return axios({ 37 | url: '/oneterm/v1/stat/count/ofuser', 38 | method: 'get', 39 | params 40 | }) 41 | } 42 | 43 | export function getRankOfUserStat(params) { 44 | return axios({ 45 | url: '/oneterm/v1/stat/rank/ofuser', 46 | method: 'get', 47 | params 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/assets/dashboard-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/oneterm/assets/dashboard-1.png -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/assets/dashboard-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/oneterm/assets/dashboard-2.png -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/assets/dashboard-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/oneterm/assets/dashboard-3.png -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/assets/dashboard-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/oneterm/assets/dashboard-4.png -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/assets/dashboard-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/modules/oneterm/assets/dashboard-5.png -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/components/cmdbTypeSelect/index.js: -------------------------------------------------------------------------------- 1 | import CMDBTypeSelect from './cmdbTypeSelect.vue' 2 | export default CMDBTypeSelect 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/components/dragWeektime/index.js: -------------------------------------------------------------------------------- 1 | import DragWeektime from './index.vue' 2 | export default DragWeektime 3 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/index.js: -------------------------------------------------------------------------------- 1 | import genOnetermRoutes from './router/index' 2 | import onetermStore from './store' 3 | 4 | export default { 5 | name: 'oneterm', 6 | route: genOnetermRoutes, 7 | store: onetermStore 8 | } 9 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/store/index.js: -------------------------------------------------------------------------------- 1 | const onetermStore = { 2 | namespaced: true, 3 | name: 'onetermStore', 4 | state: {}, 5 | mutations: {}, 6 | actions: {} 7 | } 8 | export default onetermStore 9 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/style/index.less: -------------------------------------------------------------------------------- 1 | @import '~@/style/static.less'; 2 | 3 | .oneterm-layout { 4 | width: 100%; 5 | height: 100%; 6 | .oneterm-header { 7 | border-left: 4px solid @primary-color; 8 | padding-left: 10px; 9 | margin-bottom: 24px; 10 | font-size: 16px; 11 | font-weight: 400; 12 | > i { 13 | cursor: pointer; 14 | &:hover { 15 | color: @primary-color; 16 | } 17 | } 18 | } 19 | .oneterm-layout-container { 20 | width: 100%; 21 | height: calc(100% - 48px); 22 | background-color: #ffffff; 23 | border-radius: 15px; 24 | padding: 18px; 25 | .oneterm-layout-container-header { 26 | display: flex; 27 | justify-content: space-between; 28 | margin-bottom: 16px; 29 | } 30 | .oneterm-layout-pagination { 31 | text-align: right; 32 | margin-top: 8px; 33 | } 34 | } 35 | } 36 | 37 | .oneterm-charttime { 38 | .chart-time-display-time { 39 | background-color: @primary-color_5; 40 | } 41 | } 42 | 43 | .oneterm-table-right { 44 | border-radius: 22px; 45 | line-height: 24px; 46 | padding: 5px 15px 5px 25px; 47 | position: relative; 48 | 49 | &::after { 50 | content: ''; 51 | position: absolute; 52 | width: 9px; 53 | height: 9px; 54 | background: #4dcb73; 55 | border-radius: 50%; 56 | top: 50%; 57 | transform: translateY(-50%); 58 | left: 10px; 59 | } 60 | } 61 | .oneterm-table-right.oneterm-table-error::after { 62 | background: #f2637b; 63 | } 64 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/connect/guacamoleClient/clipboardModal.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/dashboard/timeRadio.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 63 | 64 | 75 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/log/constants.js: -------------------------------------------------------------------------------- 1 | export const chartTimeTagsList = [ 2 | { type: 'Today', label: '今天' }, 3 | { type: 'last', label: '过去5分钟', number: 5, valueFormat: 'minutes' }, 4 | { type: 'last', label: '过去30分钟', number: 30, valueFormat: 'minutes' }, 5 | { type: 'last', label: '过去1小时', number: 1, valueFormat: 'hour' }, 6 | { type: 'last', label: '过去6小时', number: 6, valueFormat: 'hour' }, 7 | { type: 'last', label: '过去1天', number: 1, valueFormat: 'day' }, 8 | { type: 'last', label: '过去2天', number: 2, valueFormat: 'day' }, 9 | { type: 'last', label: '过去1周', number: 1, valueFormat: 'week' }, 10 | { type: 'last', label: '过去1月', number: 1, valueFormat: 'month' }, 11 | { type: 'This Month', label: '本月' }, 12 | ] 13 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/session/history.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/session/online.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/share/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | 40 | 42 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/systemSettings/terminalDisplay/constants.js: -------------------------------------------------------------------------------- 1 | export const defaultPreferenceSetting = { 2 | font_family: 'default', 3 | font_size: 14, 4 | line_height: 1.1, 5 | letter_spacing: 0, 6 | cursor_style: 'block', 7 | theme: '' 8 | } 9 | -------------------------------------------------------------------------------- /oneterm-ui/src/modules/oneterm/views/systemSettings/terminalDisplay/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /oneterm-ui/src/router/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Vue from 'vue' 3 | import Router from 'vue-router' 4 | import { constantRouterMap } from '@/router/config' 5 | 6 | Vue.use(Router) 7 | 8 | const createRouter = () => new Router({ 9 | mode: 'history', 10 | base: process.env.BASE_URL, 11 | scrollBehavior: () => ({ y: 0, x: undefined }), 12 | routes: constantRouterMap 13 | }) 14 | 15 | const router = createRouter() 16 | 17 | export function resetRouter () { 18 | const newRouter = createRouter() 19 | router['matcher'] = newRouter['matcher'] // reset router 20 | } 21 | 22 | export default router 23 | -------------------------------------------------------------------------------- /oneterm-ui/src/store/global/company.js: -------------------------------------------------------------------------------- 1 | const company = { 2 | state: { 3 | name: undefined 4 | }, 5 | mutations: { 6 | SET_COMPANY_NAME: (state, name) => { 7 | state.name = name 8 | }, 9 | }, 10 | } 11 | export default company 12 | -------------------------------------------------------------------------------- /oneterm-ui/src/store/global/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | device: state => state.app.device, 3 | theme: state => state.app.theme, 4 | color: state => state.app.color, 5 | token: state => state.user.token, 6 | avatar: state => state.user.avatar, 7 | nickname: state => state.user.name, 8 | username: state => state.user.username, 9 | uid: state => state.user.uid, 10 | rid: state => state.user.rid, 11 | welcome: state => state.user.welcome, 12 | roles: state => state.user.roles, 13 | userInfo: state => state.user.info, 14 | appRoutes: state => state.routes.appRoutes, 15 | multiTab: state => state.app.multiTab, 16 | } 17 | 18 | export default getters 19 | -------------------------------------------------------------------------------- /oneterm-ui/src/store/global/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'Access-Token' 2 | export const SIDEBAR_TYPE = 'SIDEBAR_TYPE' 3 | export const DEFAULT_THEME = 'DEFAULT_THEME' 4 | export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE' 5 | export const DEFAULT_COLOR = 'DEFAULT_COLOR' 6 | export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK' 7 | export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER' 8 | export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU' 9 | export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN' 10 | export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE' 11 | export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB' 12 | 13 | export const CONTENT_WIDTH_TYPE = { 14 | Fluid: 'Fluid', 15 | Fixed: 'Fixed' 16 | } 17 | -------------------------------------------------------------------------------- /oneterm-ui/src/store/global/notice.js: -------------------------------------------------------------------------------- 1 | const notice = { 2 | state: { 3 | totalUnreadNum: 0, 4 | appUnreadNum: [] 5 | }, 6 | mutations: { 7 | SET_TOTAL_UNREAD_NUM: (state, totalUnreadNum) => { 8 | state.totalUnreadNum = totalUnreadNum 9 | }, 10 | SET_APP_UNREAD_NUM: (state, appUnreadNum) => { 11 | state.appUnreadNum = appUnreadNum 12 | }, 13 | }, 14 | actions: { 15 | } 16 | } 17 | export default notice 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/store/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import Vue from 'vue' 4 | import Vuex from 'vuex' 5 | import app from './global/app' 6 | import user from './global/user' 7 | import routes from './global/routes' 8 | import notice from './global/notice' 9 | import getters from './global/getters' 10 | import company from './global/company' 11 | import appConfig from '@/config/app' 12 | 13 | Vue.use(Vuex) 14 | 15 | const store = new Vuex.Store({ 16 | modules: { 17 | app, 18 | user, 19 | routes, 20 | notice, 21 | company 22 | }, 23 | state: { 24 | windowWidth: 800, 25 | windowHeight: 600, 26 | currentTime: 0, 27 | locale: 'zh' 28 | }, 29 | mutations: { 30 | SET_WINDOW_SIZE(state, { width, height }) { 31 | state.windowWidth = width 32 | state.windowHeight = height 33 | }, 34 | SET_TIME: (state, time) => { 35 | state.currentTime = time 36 | }, 37 | SET_LOCALE: (state, locale) => { 38 | state.locale = locale 39 | localStorage.setItem('ops_locale', locale) 40 | } 41 | }, 42 | actions: { 43 | setWindowSize({ commit }) { 44 | const width = window.innerWidth 45 | const height = window.innerHeight 46 | commit('SET_WINDOW_SIZE', { width, height }) 47 | }, 48 | setTime: ({ commit }, time) => { 49 | commit('SET_TIME', time) 50 | } 51 | }, 52 | getters 53 | }) 54 | 55 | appConfig.buildModules.forEach(appName => { 56 | import(`@/modules/${appName}/index.js`).then(m => { 57 | if (m.default.store) { 58 | store.registerModule(m.default.store.name || m.deault.name, m.default.store) 59 | } 60 | }) 61 | }) 62 | 63 | export default store 64 | -------------------------------------------------------------------------------- /oneterm-ui/src/style/index.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | width: 8px; 3 | height: 8px; 4 | } 5 | 6 | ::-webkit-scrollbar-track { 7 | width: 8px; 8 | background-color: rgba(217, 217, 217, 0.2); 9 | box-shadow: inset 0px 4px 4px rgba(0, 0, 0, 0.1); 10 | border-radius: 5px; 11 | } 12 | 13 | ::-webkit-scrollbar-thumb { 14 | background-color: rgba(47, 122, 235, 0.2); 15 | background-clip: padding-box; 16 | border-radius: 5px; 17 | } 18 | 19 | ::-webkit-scrollbar-thumb:hover { 20 | background-color: rgba(47, 122, 235, 0.2); 21 | } 22 | 23 | /*# sourceMappingURL=index.css.map */ -------------------------------------------------------------------------------- /oneterm-ui/src/style/index.less: -------------------------------------------------------------------------------- 1 | @import "./global.less"; 2 | -------------------------------------------------------------------------------- /oneterm-ui/src/utils/axios.js: -------------------------------------------------------------------------------- 1 | const VueAxios = { 2 | vm: {}, 3 | // eslint-disable-next-line no-unused-vars 4 | install(Vue, instance) { 5 | if (this.installed) { 6 | return 7 | } 8 | this.installed = true 9 | 10 | if (!instance) { 11 | // eslint-disable-next-line no-console 12 | console.error('You have to install axios') 13 | return 14 | } 15 | 16 | Vue.axios = instance 17 | Object.defineProperties(Vue.prototype, { 18 | axios: { 19 | get: function get() { 20 | return instance 21 | } 22 | }, 23 | $http: { 24 | get: function get() { 25 | return instance 26 | } 27 | } 28 | }) 29 | } 30 | } 31 | 32 | export { 33 | VueAxios 34 | } 35 | -------------------------------------------------------------------------------- /oneterm-ui/src/utils/device.js: -------------------------------------------------------------------------------- 1 | import enquireJs from 'enquire.js' 2 | 3 | export const DEVICE_TYPE = { 4 | DESKTOP: 'desktop', 5 | TABLET: 'tablet', 6 | MOBILE: 'mobile' 7 | } 8 | 9 | export const deviceEnquire = function (callback) { 10 | const matchDesktop = { 11 | match: () => { 12 | callback && callback(DEVICE_TYPE.DESKTOP) 13 | } 14 | } 15 | 16 | const matchLablet = { 17 | match: () => { 18 | callback && callback(DEVICE_TYPE.TABLET) 19 | } 20 | } 21 | 22 | const matchMobile = { 23 | match: () => { 24 | callback && callback(DEVICE_TYPE.MOBILE) 25 | } 26 | } 27 | 28 | // screen and (max-width: 1087.99px) 29 | enquireJs 30 | .register('screen and (max-width: 576px)', matchMobile) 31 | .register('screen and (min-width: 576px) and (max-width: 1199px)', matchLablet) 32 | .register('screen and (min-width: 1200px)', matchDesktop) 33 | } 34 | -------------------------------------------------------------------------------- /oneterm-ui/src/utils/domUtil.js: -------------------------------------------------------------------------------- 1 | export const setDocumentTitle = function (title) { 2 | document.title = title 3 | const ua = navigator.userAgent 4 | // eslint-disable-next-line 5 | const regex = /\bMicroMessenger\/([\d\.]+)/ 6 | if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) { 7 | const i = document.createElement('iframe') 8 | i.src = '/favicon.ico' 9 | i.style.display = 'none' 10 | i.onload = function () { 11 | setTimeout(function () { 12 | i.remove() 13 | }, 9) 14 | } 15 | document.body.appendChild(i) 16 | } 17 | } 18 | 19 | export const domTitle = 'OneOps' 20 | -------------------------------------------------------------------------------- /oneterm-ui/src/utils/filter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import moment from 'moment' 3 | import 'moment/locale/zh-cn' 4 | moment.locale('zh-cn') 5 | 6 | Vue.filter('NumberFormat', function (value) { 7 | return value.toLocaleString() 8 | }) 9 | 10 | Vue.filter('dayjs', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 11 | return moment(dataStr).format(pattern) 12 | }) 13 | 14 | Vue.filter('moment', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 15 | return moment(dataStr).format(pattern) 16 | }) 17 | -------------------------------------------------------------------------------- /oneterm-ui/src/utils/functions/set.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export function intersection(thisSet, otherSet) { 3 | //初始化一个新集合,用于表示交集。 4 | var interSectionSet = new Set() 5 | var values = Array.from(thisSet) 6 | for (var i = 0; i < values.length; i++) { 7 | 8 | if (otherSet.has(values[i])) { 9 | interSectionSet.add(values[i]) 10 | } 11 | } 12 | 13 | return interSectionSet 14 | } 15 | 16 | export function union(thisSet, otherSet) { 17 | var unionSet = new Set() 18 | var values = Array.from(thisSet) 19 | for (var i = 0; i < values.length; i++) { 20 | unionSet.add(values[i]) 21 | } 22 | values = Array.from(otherSet) 23 | for (var i = 0; i < values.length; i++) { 24 | unionSet.add(values[i]) 25 | } 26 | 27 | return unionSet 28 | } 29 | 30 | export function difference(thisSet, otherSet) { 31 | var differenceSet = new Set() 32 | var values = Array.from(thisSet) 33 | for (var i = 0; i < values.length; i++) { 34 | 35 | if (!otherSet.has(values[i])) { 36 | differenceSet.add(values[i]) 37 | } 38 | } 39 | 40 | return differenceSet 41 | } 42 | -------------------------------------------------------------------------------- /oneterm-ui/src/utils/helper/permission.js: -------------------------------------------------------------------------------- 1 | const PERMISSION_ENUM = { 2 | 'add': { key: 'add', label: '新增' }, 3 | 'delete': { key: 'delete', label: '删除' }, 4 | 'edit': { key: 'edit', label: '修改' }, 5 | 'query': { key: 'query', label: '查询' }, 6 | 'get': { key: 'get', label: '详情' }, 7 | 'enable': { key: 'enable', label: '启用' }, 8 | 'disable': { key: 'disable', label: '禁用' }, 9 | 'import': { key: 'import', label: '导入' }, 10 | 'export': { key: 'export', label: '导出' } 11 | } 12 | 13 | function plugin (Vue) { 14 | if (plugin.installed) { 15 | return 16 | } 17 | 18 | !Vue.prototype.$auth && Object.defineProperties(Vue.prototype, { 19 | $auth: { 20 | get () { 21 | const _this = this 22 | return (permissions) => { 23 | const [permission, action] = permissions.split('.') 24 | const permissionList = _this.$store.getters.roles.permissions 25 | return permissionList.find((val) => { 26 | return val.permissionId === permission 27 | }).actionList.findIndex((val) => { 28 | return val === action 29 | }) > -1 30 | } 31 | } 32 | } 33 | }) 34 | 35 | !Vue.prototype.$enum && Object.defineProperties(Vue.prototype, { 36 | $enum: { 37 | get () { 38 | // const _this = this; 39 | return (val) => { 40 | let result = PERMISSION_ENUM 41 | val && val.split('.').forEach(v => { 42 | result = result && result[v] || null 43 | }) 44 | return result 45 | } 46 | } 47 | } 48 | }) 49 | } 50 | 51 | export default plugin 52 | -------------------------------------------------------------------------------- /oneterm-ui/src/utils/utils.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veops/oneterm/913ef2101c09e0ad2ccf03efc9ce53fa6977ff56/oneterm-ui/src/utils/utils.css -------------------------------------------------------------------------------- /oneterm-ui/src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } -------------------------------------------------------------------------------- /oneterm-ui/src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/auth/common.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/auth/loginModal.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/companyStructure/eventBus/bus.js: -------------------------------------------------------------------------------- 1 | // 用于树状递归组件的通信 2 | import Vue from 'vue' 3 | export default new Vue() 4 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/components/relateEmployee.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/components/settingFilterComp/constants.js: -------------------------------------------------------------------------------- 1 | import i18n from '@/lang' 2 | 3 | export const ruleTypeList = [ 4 | { value: '&', label: i18n.t('cs.components.and') }, 5 | { value: '|', label: i18n.t('cs.components.or') }, 6 | // { value: 'not', label: '非' }, 7 | ] 8 | 9 | export const expList = [ 10 | { value: 1, label: i18n.t('cs.components.equal') }, 11 | { value: 2, label: i18n.t('cs.components.notEqual') }, 12 | // { value: 'contain', label: '包含' }, 13 | // { value: '~contain', label: '不包含' }, 14 | // { value: 'start_with', label: '以...开始' }, 15 | // { value: '~start_with', label: '不以...开始' }, 16 | // { value: 'end_with', label: '以...结束' }, 17 | // { value: '~end_with', label: '不以...结束' }, 18 | { value: 7, label: i18n.t('cs.components.isEmpty') }, // 为空的定义有点绕 19 | { value: 8, label: i18n.t('cs.components.isNotEmpty') }, 20 | ] 21 | 22 | export const advancedExpList = [ 23 | // { value: 'in', label: 'in查询' }, 24 | // { value: '~in', label: '非in查询' }, 25 | // { value: 'range', label: '范围' }, 26 | // { value: '~range', label: '范围外' }, 27 | { value: 'compare', label: i18n.t('cs.components.compare') }, 28 | ] 29 | 30 | export const compareTypeList = [ 31 | { value: 5, label: i18n.t('cs.components.moreThan') }, 32 | // { value: '2', label: '>=' }, 33 | { value: 6, label: i18n.t('cs.components.lessThan') }, 34 | // { value: '4', label: '<=' }, 35 | ] 36 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/components/spanTitle.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 32 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/notice/email/index.less: -------------------------------------------------------------------------------- 1 | .notice-email-wrapper { 2 | background-color: #fff; 3 | overflow: auto; 4 | .notice-email-error-tips { 5 | display: inline-block; 6 | background-color: #ffdfdf; 7 | border-radius: 4px; 8 | padding: 0 12px; 9 | width: 300px; 10 | color: #000000; 11 | margin-top: 8px; 12 | } 13 | .ant-form-item { 14 | margin-bottom: 10px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/setting/notice/email/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 32 | 33 | 44 | -------------------------------------------------------------------------------- /oneterm-ui/src/views/user/RegisterResult.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /oneterm-ui/webstorm.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | 4 | function resolve (dir) { 5 | return path.join(__dirname, '.', dir) 6 | } 7 | 8 | module.exports = { 9 | context: path.resolve(__dirname, './'), 10 | resolve: { 11 | extensions: ['.js', '.vue', '.json'], 12 | alias: { 13 | '@': resolve('src') 14 | } 15 | } 16 | } 17 | --------------------------------------------------------------------------------