mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
refactor: 初步提交全局授权凭证-资源多账号改造
This commit is contained in:
@@ -56,7 +56,7 @@
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.69.0",
|
||||
"typescript": "^5.3.2",
|
||||
"vite": "^5.2.6",
|
||||
"vite": "^5.2.8",
|
||||
"vue-eslint-parser": "^9.4.2"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -7,4 +7,5 @@ export const TagResourceTypeEnum = {
|
||||
Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }),
|
||||
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }),
|
||||
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }),
|
||||
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket' }),
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ const clipboardRef = ref({} as any);
|
||||
|
||||
const props = defineProps({
|
||||
machineId: {
|
||||
type: Number,
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
clipboardList: {
|
||||
|
||||
28
mayfly_go_web/src/views/ops/component/ResourceAuthCert.vue
Normal file
28
mayfly_go_web/src/views/ops/component/ResourceAuthCert.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div v-if="props.authCerts">
|
||||
<el-select default-first-option value-key="name" style="width: 100%" v-model="selectAuthCert" size="small">
|
||||
<el-option v-for="item in props.authCerts" :key="item.name" :label="item.username" :value="item">
|
||||
{{ item.username }}
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<EnumTag :value="item.type" :enums="AuthCertTypeEnum" />
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<EnumTag :value="item.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||
import { AuthCertTypeEnum, AuthCertCiphertextTypeEnum } from '../tag/enums';
|
||||
const props = defineProps({
|
||||
authCerts: {
|
||||
type: [Array<any>],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const selectAuthCert = defineModel('selectAuthCert');
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
172
mayfly_go_web/src/views/ops/component/ResourceAuthCertEdit.vue
Normal file
172
mayfly_go_web/src/views/ops/component/ResourceAuthCertEdit.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="auth-cert-manage">
|
||||
<el-table :data="authCerts" max-height="180" stripe style="width: 100%" size="small">
|
||||
<el-table-column min-wdith="120px">
|
||||
<template #header>
|
||||
<el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="edit(null)"> </el-button>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-link @click="edit(scope.row)" type="primary" icon="edit"></el-link>
|
||||
<el-link class="ml5" v-auth="'machine:file:del'" type="danger" @click="deleteRow(scope.$index)" icon="delete"></el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="name" label="名称" min-width="100px"> </el-table-column>
|
||||
<el-table-column prop="username" label="用户名" min-width="120px" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="ciphertextType" label="密文类型" width="100px">
|
||||
<template #default="scope">
|
||||
<EnumTag :value="scope.row.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="凭证类型" width="100px">
|
||||
<template #default="scope">
|
||||
<EnumTag :value="scope.row.type" :enums="AuthCertTypeEnum" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="凭证保存" v-model="state.dvisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-form ref="acForm" :model="state.form" label-width="auto">
|
||||
<el-form-item prop="type" label="凭证类型" required>
|
||||
<el-select style="width: 100%" v-model="form.type" placeholder="请选择凭证类型">
|
||||
<el-option v-for="item in AuthCertTypeEnum" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ciphertextType" label="密文类型" required>
|
||||
<el-select style="width: 100%" v-model="form.ciphertextType" placeholder="请选择密文类型">
|
||||
<el-option v-for="item in AuthCertCiphertextTypeEnum" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="name" label="名称" required>
|
||||
<el-input :disabled="form.id" v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username" label="用户名">
|
||||
<el-input v-model="form.username"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.Password.value" prop="ciphertext" label="密码">
|
||||
<el-input type="password" show-password clearable v-model.trim="form.ciphertext" placeholder="请输入密码" autocomplete="new-password">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="ciphertext" label="秘钥">
|
||||
<el-input type="textarea" :rows="5" v-model="form.ciphertext" placeholder="请将私钥文件内容拷贝至此"> </el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="passphrase" label="秘钥密码">
|
||||
<el-input type="password" v-model="form.extra.passphrase"> </el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelEdit">取 消</el-button>
|
||||
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import { AuthCertTypeEnum, AuthCertCiphertextTypeEnum } from '../tag/enums';
|
||||
import { resourceAuthCertApi } from '../tag/api';
|
||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
resourceType: { type: Number },
|
||||
resourceCode: { type: String },
|
||||
});
|
||||
|
||||
const authCerts = defineModel<any>('modelValue', { required: true, default: [] });
|
||||
|
||||
const acForm: any = ref(null);
|
||||
|
||||
const DefaultForm = {
|
||||
id: null,
|
||||
name: '',
|
||||
username: '',
|
||||
ciphertextType: AuthCertCiphertextTypeEnum.Password.value,
|
||||
type: AuthCertTypeEnum.Private.value,
|
||||
ciphertext: '',
|
||||
extra: {} as any,
|
||||
remark: '',
|
||||
};
|
||||
const state = reactive({
|
||||
dvisible: false,
|
||||
params: [] as any,
|
||||
form: { ...DefaultForm },
|
||||
btnLoading: false,
|
||||
edit: false,
|
||||
});
|
||||
|
||||
const { form, btnLoading } = toRefs(state);
|
||||
|
||||
onMounted(() => {
|
||||
getAuthCerts();
|
||||
});
|
||||
|
||||
const getAuthCerts = async () => {
|
||||
if (!props.resourceCode || !props.resourceType) {
|
||||
return;
|
||||
}
|
||||
const res = await resourceAuthCertApi.listByQuery.request({
|
||||
resourceCode: props.resourceCode,
|
||||
resourceType: props.resourceType,
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
});
|
||||
authCerts.value = res.list?.reverse() || [];
|
||||
};
|
||||
|
||||
const edit = (form: any) => {
|
||||
if (form) {
|
||||
state.form = form;
|
||||
state.edit = true;
|
||||
}
|
||||
state.dvisible = true;
|
||||
};
|
||||
|
||||
const deleteRow = (idx: any) => {
|
||||
authCerts.value.splice(idx, 1);
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
state.dvisible = false;
|
||||
setTimeout(() => {
|
||||
state.form = { ...DefaultForm };
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const btnOk = async () => {
|
||||
acForm.value.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
const isEdit = state.form.id;
|
||||
if (isEdit || state.edit) {
|
||||
cancelEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (authCerts.value?.filter((x: any) => x.username == state.form.username || x.name == state.form.name).length > 0) {
|
||||
ElMessage.error('该名称或用户名已存在于该账号列表中');
|
||||
return;
|
||||
}
|
||||
const res = await resourceAuthCertApi.listByQuery.request({
|
||||
name: state.form.name,
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
});
|
||||
if (res.total) {
|
||||
ElMessage.error('该授权凭证名称已存在');
|
||||
return;
|
||||
}
|
||||
|
||||
authCerts.value.push(state.form);
|
||||
cancelEdit();
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
@@ -48,12 +48,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="code" label="编号" required>
|
||||
<el-input
|
||||
:disabled="form.id"
|
||||
v-model.trim="form.code"
|
||||
placeholder="请输入机器编号 (数字字母下划线), 不可修改"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
<el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="别名" required>
|
||||
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
|
||||
|
||||
@@ -1,87 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="cancel" />
|
||||
</template>
|
||||
|
||||
<el-form :model="form" ref="machineForm" :rules="rules" label-width="auto">
|
||||
<el-tabs v-model="tabActiveName">
|
||||
<el-tab-pane label="基础信息" name="basic">
|
||||
<el-form-item ref="tagSelectRef" prop="tagId" label="标签">
|
||||
<tag-tree-select
|
||||
multiple
|
||||
@change-tag="
|
||||
(tagIds) => {
|
||||
form.tagId = tagIds;
|
||||
tagSelectRef.validate();
|
||||
}
|
||||
"
|
||||
:tag-path="form.tagPath"
|
||||
:select-tags="form.tagId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" label="编号" required>
|
||||
<el-input
|
||||
:disabled="form.id"
|
||||
v-model.trim="form.code"
|
||||
placeholder="请输入机器编号 (数字字母下划线), 不可修改"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="名称" required>
|
||||
<el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="protocol" label="协议" required>
|
||||
<el-radio-group v-model="form.protocol" @change="handleChangeProtocol">
|
||||
<el-radio v-for="item in MachineProtocolEnum" :key="item.value" :label="item.label" :value="item.value"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ip" label="ip" required>
|
||||
<el-col :span="18">
|
||||
<el-input v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"> </el-input>
|
||||
</el-col>
|
||||
<el-col style="text-align: center" :span="1">:</el-col>
|
||||
<el-col :span="5">
|
||||
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">基本</el-divider>
|
||||
<el-form-item ref="tagSelectRef" prop="tagId" label="标签">
|
||||
<tag-tree-select
|
||||
multiple
|
||||
@change-tag="
|
||||
(tagIds) => {
|
||||
form.tagId = tagIds;
|
||||
tagSelectRef.validate();
|
||||
}
|
||||
"
|
||||
:tag-path="form.tagPath"
|
||||
:select-tags="form.tagId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" label="编号" required>
|
||||
<el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="名称" required>
|
||||
<el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="protocol" label="协议" required>
|
||||
<el-radio-group v-model="form.protocol" @change="handleChangeProtocol">
|
||||
<el-radio v-for="item in MachineProtocolEnum" :key="item.value" :label="item.label" :value="item.value"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ip" label="ip" required>
|
||||
<el-col :span="18">
|
||||
<el-input v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"> </el-input>
|
||||
</el-col>
|
||||
<el-col style="text-align: center" :span="1">:</el-col>
|
||||
<el-col :span="5">
|
||||
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="username" label="用户名">
|
||||
<el-input v-model.trim="form.username" placeholder="请输授权用户名" autocomplete="new-password"> </el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="remark" label="备注">
|
||||
<el-input type="textarea" v-model="form.remark"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="认证方式" required>
|
||||
<el-select @change="changeAuthMethod" style="width: 100%" v-model="state.authType" placeholder="请选认证方式">
|
||||
<el-option key="1" label="密码" :value="1"> </el-option>
|
||||
<el-option key="2" label="授权凭证" :value="2"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="state.authType == 1" prop="password" label="密码">
|
||||
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">账号</el-divider>
|
||||
<div>
|
||||
<ResourceAuthCertEdit v-model="form.authCerts" :resource-code="form.code" :resource-type="TagResourceTypeEnum.Machine.value" />
|
||||
</div>
|
||||
|
||||
<el-form-item v-if="state.authType == 2" prop="authCertId" label="授权凭证" required>
|
||||
<auth-cert-select ref="authCertSelectRef" v-model="form.authCertId" />
|
||||
</el-form-item>
|
||||
<!-- <el-tab-pane label="其他配置" name="other"> -->
|
||||
<el-divider content-position="left">其他</el-divider>
|
||||
<el-form-item prop="enableRecorder" label="终端回放">
|
||||
<el-checkbox v-model="form.enableRecorder" :true-value="1" :false-value="-1"></el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="remark" label="备注">
|
||||
<el-input type="textarea" v-model="form.remark"></el-input>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="其他配置" name="other">
|
||||
<el-form-item prop="enableRecorder" label="终端回放">
|
||||
<el-checkbox v-model="form.enableRecorder" :true-value="1" :false-value="-1"></el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="sshTunnelMachineId" label="SSH隧道">
|
||||
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form-item prop="sshTunnelMachineId" label="SSH隧道">
|
||||
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -100,11 +78,12 @@ import { reactive, ref, toRefs, watch } from 'vue';
|
||||
import { machineApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import ResourceAuthCertEdit from '../component/ResourceAuthCertEdit.vue';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
import AuthCertSelect from './authcert/AuthCertSelect.vue';
|
||||
import { MachineProtocolEnum } from './enums';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import { ResourceCodePattern } from '@/common/pattern';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -162,24 +141,9 @@ const rules = {
|
||||
trigger: ['blur'],
|
||||
},
|
||||
],
|
||||
authCertId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择授权凭证',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入授权用户名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const machineForm: any = ref(null);
|
||||
const authCertSelectRef: any = ref(null);
|
||||
const tagSelectRef: any = ref(null);
|
||||
|
||||
const defaultForm = {
|
||||
@@ -188,11 +152,9 @@ const defaultForm = {
|
||||
tagPath: '',
|
||||
ip: null,
|
||||
port: 22,
|
||||
protocol: 1, // 1.ssh 2.rdp
|
||||
protocol: MachineProtocolEnum.Ssh.value,
|
||||
name: null,
|
||||
authCertId: null as any,
|
||||
username: '',
|
||||
password: '',
|
||||
authCerts: [],
|
||||
tagId: [],
|
||||
remark: '',
|
||||
sshTunnelMachineId: null as any,
|
||||
@@ -201,16 +163,13 @@ const defaultForm = {
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
tabActiveName: 'basic',
|
||||
sshTunnelMachineList: [] as any,
|
||||
authCerts: [] as any,
|
||||
authType: 1,
|
||||
form: defaultForm,
|
||||
submitForm: {},
|
||||
pwd: '',
|
||||
});
|
||||
|
||||
const { dialogVisible, tabActiveName, form, submitForm } = toRefs(state);
|
||||
const { dialogVisible, form, submitForm } = toRefs(state);
|
||||
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = machineApi.testConn.useApi(submitForm);
|
||||
const { isFetching: saveBtnLoading, execute: saveMachineExec } = machineApi.saveMachine.useApi(submitForm);
|
||||
@@ -221,32 +180,13 @@ watch(props, async (newValue: any) => {
|
||||
state.form = defaultForm;
|
||||
return;
|
||||
}
|
||||
state.tabActiveName = 'basic';
|
||||
if (newValue.machine) {
|
||||
state.form = { ...newValue.machine };
|
||||
state.form.tagId = newValue.machine.tags.map((t: any) => t.tagId);
|
||||
// 如果凭证类型为公共的,则表示使用授权凭证认证
|
||||
const authCertId = (state.form as any).authCertId;
|
||||
if (authCertId > 0) {
|
||||
state.authType = 2;
|
||||
} else {
|
||||
state.authType = 1;
|
||||
}
|
||||
} else {
|
||||
state.authType = 1;
|
||||
state.form.authCerts = newValue.machine.authCerts || [];
|
||||
}
|
||||
});
|
||||
|
||||
const changeAuthMethod = (val: any) => {
|
||||
if (state.form.id) {
|
||||
if (val == 2) {
|
||||
state.form.authCertId = null;
|
||||
} else {
|
||||
state.form.password = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const testConn = async () => {
|
||||
machineForm.value.validate(async (valid: boolean) => {
|
||||
if (!valid) {
|
||||
@@ -267,6 +207,11 @@ const btnOk = async () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state.form.authCerts.length == 0) {
|
||||
ElMessage.error('请完善授权凭证账号信息');
|
||||
return false;
|
||||
}
|
||||
|
||||
state.submitForm = getReqForm();
|
||||
await saveMachineExec();
|
||||
ElMessage.success('保存成功');
|
||||
@@ -277,10 +222,6 @@ const btnOk = async () => {
|
||||
|
||||
const getReqForm = () => {
|
||||
const reqForm: any = { ...state.form };
|
||||
// 如果为密码认证,则置空授权凭证id
|
||||
if (state.authType == 1) {
|
||||
reqForm.authCertId = -1;
|
||||
}
|
||||
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
||||
reqForm.sshTunnelMachineId = -1;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
ref="pageTableRef"
|
||||
:page-api="machineApi.list"
|
||||
:before-query-fn="checkRouteTagPath"
|
||||
:data-handler-fn="handleData"
|
||||
:search-items="searchItems"
|
||||
v-model:query-form="params"
|
||||
:show-selection="true"
|
||||
@@ -84,13 +85,17 @@
|
||||
<ResourceTags :tags="data.tags" />
|
||||
</template>
|
||||
|
||||
<template #authCert="{ data }">
|
||||
<ResourceAuthCert v-model:select-auth-cert="data.selectAuthCert" :auth-certs="data.authCerts" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
<span v-auth="'machine:terminal'">
|
||||
<el-tooltip v-if="data.protocol == 1" :show-after="500" content="按住ctrl则为新标签打开" placement="top">
|
||||
<el-tooltip v-if="data.protocol == MachineProtocolEnum.Ssh.value" :show-after="500" content="按住ctrl则为新标签打开" placement="top">
|
||||
<el-button :disabled="data.status == -1" type="primary" @click="showTerminal(data, $event)" link>SSH</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-button v-if="data.protocol == 2" type="primary" @click="showRDP(data)" link>RDP</el-button>
|
||||
<el-button v-if="data.protocol == MachineProtocolEnum.Rdp.value" type="primary" @click="showRDP(data)" link>RDP</el-button>
|
||||
<el-button v-if="data.protocol == 3" type="primary" @click="showRDP(data)" link>VNC</el-button>
|
||||
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
@@ -140,11 +145,6 @@
|
||||
<el-descriptions-item :span="2" label="IP">{{ infoDialog.data.ip }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="认证方式">
|
||||
{{ infoDialog.data.authCertId > 1 ? '授权凭证' : '密码' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
||||
@@ -162,7 +162,7 @@
|
||||
<template #headerTitle="{ terminalInfo }">
|
||||
{{ `${(terminalInfo.terminalId + '').slice(-2)}` }}
|
||||
<el-divider direction="vertical" />
|
||||
{{ `${terminalInfo.meta.username}@${terminalInfo.meta.ip}:${terminalInfo.meta.port}` }}
|
||||
{{ `${terminalInfo.meta.selectAuthCert.username}@${terminalInfo.meta.ip}:${terminalInfo.meta.port}` }}
|
||||
<el-divider direction="vertical" />
|
||||
{{ terminalInfo.meta.name }}
|
||||
</template>
|
||||
@@ -179,7 +179,12 @@
|
||||
|
||||
<script-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" />
|
||||
|
||||
<file-conf-list :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" />
|
||||
<file-conf-list
|
||||
:title="fileDialog.title"
|
||||
v-model:visible="fileDialog.visible"
|
||||
v-model:machineId="fileDialog.machineId"
|
||||
:auth-cert-name="fileDialog.authCertName"
|
||||
/>
|
||||
|
||||
<machine-stats v-model:visible="machineStatsDialog.visible" :machineId="machineStatsDialog.machineId" :title="machineStatsDialog.title"></machine-stats>
|
||||
|
||||
@@ -221,6 +226,8 @@ import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
import { MachineProtocolEnum } from './enums';
|
||||
|
||||
// 组件
|
||||
const TerminalDialog = defineAsyncComponent(() => import('@/components/terminal/TerminalDialog.vue'));
|
||||
@@ -265,7 +272,7 @@ const columns = [
|
||||
TableColumn.new('ipPort', 'ip:port').isSlot().setAddWidth(50),
|
||||
TableColumn.new('stat', '运行状态').isSlot().setAddWidth(55),
|
||||
TableColumn.new('fs', '磁盘(挂载点=>可用/总)').isSlot().setAddWidth(25),
|
||||
TableColumn.new('username', '用户名'),
|
||||
TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(20),
|
||||
TableColumn.new('status', '状态').isSlot().setMinWidth(85),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('action', '操作').isSlot().setMinWidth(238).fixedRight().alignCenter(),
|
||||
@@ -300,6 +307,7 @@ const state = reactive({
|
||||
fileDialog: {
|
||||
visible: false,
|
||||
machineId: 0,
|
||||
authCertName: '',
|
||||
title: '',
|
||||
},
|
||||
filesystemDialog: {
|
||||
@@ -308,6 +316,7 @@ const state = reactive({
|
||||
protocol: 1,
|
||||
title: '',
|
||||
fileId: 0,
|
||||
authCertName: '',
|
||||
path: '',
|
||||
},
|
||||
machineStatsDialog: {
|
||||
@@ -360,6 +369,15 @@ const checkRouteTagPath = (query: any) => {
|
||||
return query;
|
||||
};
|
||||
|
||||
const handleData = (res: any) => {
|
||||
const dataList = res.list;
|
||||
// 赋值授权凭证
|
||||
for (let x of dataList) {
|
||||
x.selectAuthCert = x.authCerts[0];
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
const handleCommand = (commond: any) => {
|
||||
const data = commond.data;
|
||||
const type = commond.type;
|
||||
@@ -392,12 +410,13 @@ const handleCommand = (commond: any) => {
|
||||
};
|
||||
|
||||
const showTerminal = (row: any, event: PointerEvent) => {
|
||||
const ac = row.selectAuthCert.name;
|
||||
// 按住ctrl点击,则新建标签页打开, metaKey对应mac command键
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
const { href } = router.resolve({
|
||||
path: `/machine/terminal`,
|
||||
query: {
|
||||
id: row.id,
|
||||
ac,
|
||||
name: row.name,
|
||||
},
|
||||
});
|
||||
@@ -408,9 +427,9 @@ const showTerminal = (row: any, event: PointerEvent) => {
|
||||
const terminalId = Date.now();
|
||||
terminalDialogRef.value.open({
|
||||
terminalId,
|
||||
socketUrl: getMachineTerminalSocketUrl(row.id),
|
||||
socketUrl: getMachineTerminalSocketUrl(ac),
|
||||
minTitle: `${row.name} [${(terminalId + '').slice(-2)}]`, // 截取terminalId最后两位区分多个terminal
|
||||
minDesc: `${row.username}@${row.ip}:${row.port} (${row.name})`,
|
||||
minDesc: `${row.selectAuthCert.username}@${row.ip}:${row.port} (${row.name})`,
|
||||
meta: row,
|
||||
});
|
||||
};
|
||||
@@ -485,18 +504,20 @@ const submitSuccess = () => {
|
||||
};
|
||||
|
||||
const showFileManage = (data: any) => {
|
||||
if (data.protocol === 1) {
|
||||
if (data.protocol === MachineProtocolEnum.Ssh.value) {
|
||||
// ssh
|
||||
state.fileDialog.visible = true;
|
||||
state.fileDialog.machineId = data.id;
|
||||
state.fileDialog.title = `${data.name} => ${data.ip}`;
|
||||
} else if (data.protocol === 2) {
|
||||
state.fileDialog.authCertName = data.selectAuthCert.name;
|
||||
state.fileDialog.title = `${data.name} => ${data.selectAuthCert.username}@${data.ip}`;
|
||||
} else if (data.protocol === MachineProtocolEnum.Rdp.value) {
|
||||
// rdp
|
||||
state.filesystemDialog.protocol = 2;
|
||||
state.filesystemDialog.machineId = data.id;
|
||||
state.filesystemDialog.fileId = data.id;
|
||||
state.filesystemDialog.authCertName = data.selectAuthCert.name;
|
||||
state.filesystemDialog.path = '/';
|
||||
state.filesystemDialog.title = `${data.name} => 远程桌面文件`;
|
||||
state.filesystemDialog.title = `${data.name} => ${data.selectAuthCert.username}@远程桌面文件`;
|
||||
state.filesystemDialog.visible = true;
|
||||
}
|
||||
};
|
||||
@@ -534,7 +555,7 @@ const showRDP = (row: any, blank = false) => {
|
||||
const { href } = router.resolve({
|
||||
path: `/machine/terminal-rdp`,
|
||||
query: {
|
||||
id: row.id,
|
||||
ac: row.selectAuthCert.name,
|
||||
name: row.name,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,22 +6,26 @@
|
||||
<tag-tree
|
||||
class="machine-terminal-tree"
|
||||
ref="tagTreeRef"
|
||||
:resource-type="TagResourceTypeEnum.Machine.value"
|
||||
:resource-type="TagResourceTypeEnum.MachineAuthCert.value"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
>
|
||||
<template #prefix="{ data }">
|
||||
<SvgIcon v-if="data.icon && data.params.status == 1 && data.params.protocol == 1" :name="data.icon.name" :color="data.icon.color" />
|
||||
<SvgIcon
|
||||
v-if="data.icon && data.params.status == -1 && data.params.protocol == 1"
|
||||
v-if="data.icon && data.params.status == 1 && data.params.protocol == MachineProtocolEnum.Ssh.value"
|
||||
:name="data.icon.name"
|
||||
:color="data.icon.color"
|
||||
/>
|
||||
<SvgIcon
|
||||
v-if="data.icon && data.params.status == -1 && data.params.protocol == MachineProtocolEnum.Ssh.value"
|
||||
:name="data.icon.name"
|
||||
color="var(--el-color-danger)"
|
||||
/>
|
||||
<SvgIcon v-if="data.icon && data.params.protocol != 1" :name="data.icon.name" :color="data.icon.color" />
|
||||
<SvgIcon v-if="data.icon && data.params.protocol != MachineProtocolEnum.Ssh.value" :name="data.icon.name" :color="data.icon.color" />
|
||||
</template>
|
||||
|
||||
<template #suffix="{ data }">
|
||||
<span style="color: #c4c9c4; font-size: 9px" v-if="data.type.value == MachineNodeType.Machine">{{
|
||||
` ${data.params.username}@${data.params.ip}:${data.params.port}`
|
||||
<span style="color: #c4c9c4; font-size: 9px" v-if="data.type.value == MachineNodeType.AuthCert">{{
|
||||
` ${data.params.selectAuthCert.username}@${data.params.ip}:${data.params.port}`
|
||||
}}</span>
|
||||
</template>
|
||||
</tag-tree>
|
||||
@@ -66,14 +70,14 @@
|
||||
|
||||
<div :ref="(el: any) => setTerminalWrapperRef(el, dt.key)" class="terminal-wrapper" style="height: calc(100vh - 155px)">
|
||||
<TerminalBody
|
||||
v-if="dt.params.protocol == 1"
|
||||
v-if="dt.params.protocol == MachineProtocolEnum.Ssh.value"
|
||||
:mount-init="false"
|
||||
@status-change="terminalStatusChange(dt.key, $event)"
|
||||
:ref="(el: any) => setTerminalRef(el, dt.key)"
|
||||
:socket-url="dt.socketUrl"
|
||||
/>
|
||||
<machine-rdp
|
||||
v-if="dt.params.protocol != 1"
|
||||
v-if="dt.params.protocol != MachineProtocolEnum.Ssh.value"
|
||||
:machine-id="dt.params.id"
|
||||
:ref="(el: any) => setTerminalRef(el, dt.key)"
|
||||
@status-change="terminalStatusChange(dt.key, $event)"
|
||||
@@ -87,16 +91,11 @@
|
||||
<el-descriptions-item :span="1.5" label="机器id">{{ infoDialog.data.id }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1.5" label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data.tagPath }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="infoDialog.data.tags" /></el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="IP">{{ infoDialog.data.ip }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="认证方式">
|
||||
{{ infoDialog.data.authCertId > 1 ? '授权凭证' : '密码' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
||||
@@ -160,6 +159,9 @@ import TerminalBody from '@/components/terminal/TerminalBody.vue';
|
||||
import { TerminalStatus } from '@/components/terminal/common';
|
||||
import MachineRdp from '@/components/terminal-rdp/MachineRdp.vue';
|
||||
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import { MachineProtocolEnum } from './enums';
|
||||
|
||||
// 组件
|
||||
const ScriptManage = defineAsyncComponent(() => import('./ScriptManage.vue'));
|
||||
const FileConfList = defineAsyncComponent(() => import('./file/FileConfList.vue'));
|
||||
@@ -182,6 +184,7 @@ const actionBtns = hasPerms([perms.updateMachine, perms.closeCli]);
|
||||
|
||||
class MachineNodeType {
|
||||
static Machine = 1;
|
||||
static AuthCert = 2;
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
@@ -238,7 +241,9 @@ const { infoDialog, serviceDialog, processDialog, fileDialog, machineStatsDialog
|
||||
|
||||
const tagTreeRef: any = ref(null);
|
||||
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (node: any) => {
|
||||
let openIds = {};
|
||||
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (node: TagTreeNode) => {
|
||||
// 加载标签树下的机器列表
|
||||
state.params.tagPath = node.key;
|
||||
state.params.pageNum = 1;
|
||||
@@ -247,60 +252,71 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
|
||||
// 把list 根据name字段排序
|
||||
res.list = res.list.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
return res.list.map((x: any) =>
|
||||
new TagTreeNode(x.id, x.name, NodeTypeMachine(x))
|
||||
new TagTreeNode(x.id, x.name, NodeTypeMachine)
|
||||
.withParams(x)
|
||||
.withDisabled(x.status == -1 && x.protocol == 1)
|
||||
.withDisabled(x.status == -1 && x.protocol == MachineProtocolEnum.Ssh.value)
|
||||
.withIcon({
|
||||
name: 'Monitor',
|
||||
color: '#409eff',
|
||||
})
|
||||
.withIsLeaf(true)
|
||||
);
|
||||
});
|
||||
|
||||
let openIds = {};
|
||||
const NodeTypeMachine = new NodeType(MachineNodeType.Machine)
|
||||
.withLoadNodesFunc((node: TagTreeNode) => {
|
||||
const machine = node.params;
|
||||
// 获取授权凭证列表
|
||||
const authCerts = machine.authCerts;
|
||||
return authCerts.map((x: any) =>
|
||||
new TagTreeNode(x.id, x.username, NodeTypeAuthCert)
|
||||
.withParams({ ...machine, selectAuthCert: x })
|
||||
.withDisabled(machine.status == -1 && machine.protocol == MachineProtocolEnum.Ssh.value)
|
||||
.withIcon({
|
||||
name: 'Ticket',
|
||||
color: '#409eff',
|
||||
})
|
||||
.withIsLeaf(true)
|
||||
);
|
||||
})
|
||||
.withContextMenuItems([
|
||||
new ContextmenuItem('detail', '详情').withIcon('More').withOnClick((node: any) => showInfo(node.params)),
|
||||
new ContextmenuItem('status', '状态').withIcon('Compass').withOnClick((node: any) => showMachineStats(node.params)),
|
||||
new ContextmenuItem('process', '进程').withIcon('DataLine').withOnClick((node: any) => showProcess(node.params)),
|
||||
new ContextmenuItem('edit', '终端回放')
|
||||
.withIcon('Compass')
|
||||
.withOnClick((node: any) => showRec(node.params))
|
||||
.withHideFunc((node: any) => actionBtns[perms.updateMachine] && node.params.enableRecorder == 1),
|
||||
]);
|
||||
|
||||
const NodeTypeMachine = (machine: any) => {
|
||||
let contextMenuItems = [];
|
||||
contextMenuItems.push(new ContextmenuItem('term', '打开终端').withIcon('Monitor').withOnClick(() => openTerminal(machine)));
|
||||
contextMenuItems.push(new ContextmenuItem('term-ex', '打开终端(新窗口)').withIcon('Monitor').withOnClick(() => openTerminal(machine, true)));
|
||||
contextMenuItems.push(new ContextmenuItem('files', '文件管理').withIcon('FolderOpened').withOnClick(() => showFileManage(machine)));
|
||||
contextMenuItems.push(new ContextmenuItem('scripts', '脚本管理').withIcon('Files').withOnClick(() => serviceManager(machine)));
|
||||
contextMenuItems.push(new ContextmenuItem('detail', '详情').withIcon('More').withOnClick(() => showInfo(machine)));
|
||||
contextMenuItems.push(new ContextmenuItem('status', '状态').withIcon('Compass').withOnClick(() => showMachineStats(machine)));
|
||||
contextMenuItems.push(new ContextmenuItem('process', '进程').withIcon('DataLine').withOnClick(() => showProcess(machine)));
|
||||
if (actionBtns[perms.updateMachine] && machine.enableRecorder == 1) {
|
||||
contextMenuItems.push(new ContextmenuItem('edit', '终端回放').withIcon('Compass').withOnClick(() => showRec(machine)));
|
||||
}
|
||||
|
||||
return new NodeType(MachineNodeType.Machine).withContextMenuItems(contextMenuItems).withNodeDblclickFunc(() => {
|
||||
// for (let k of state.tabs.keys()) {
|
||||
// // 存在该机器相关的终端tab,则直接激活该tab
|
||||
// if (k.startsWith(`${machine.id}_${machine.username}_`)) {
|
||||
// state.activeTermName = k;
|
||||
// onTabChange();
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
openTerminal(machine);
|
||||
});
|
||||
};
|
||||
const NodeTypeAuthCert = new NodeType(MachineNodeType.AuthCert)
|
||||
.withNodeDblclickFunc((node: TagTreeNode) => {
|
||||
openTerminal(node.params);
|
||||
})
|
||||
.withContextMenuItems([
|
||||
new ContextmenuItem('term', '打开终端').withIcon('Monitor').withOnClick((node: any) => openTerminal(node.params)),
|
||||
new ContextmenuItem('term-ex', '打开终端(新窗口)').withIcon('Monitor').withOnClick((node: any) => openTerminal(node.params, true)),
|
||||
new ContextmenuItem('files', '文件管理').withIcon('FolderOpened').withOnClick((node: any) => showFileManage(node.params)),
|
||||
new ContextmenuItem('scripts', '脚本管理').withIcon('Files').withOnClick((node: any) => serviceManager(node.params)),
|
||||
]);
|
||||
|
||||
const openTerminal = (machine: any, ex?: boolean) => {
|
||||
// 授权凭证名
|
||||
const ac = machine.selectAuthCert.name;
|
||||
|
||||
// 新窗口打开
|
||||
if (ex) {
|
||||
if (machine.protocol == 1) {
|
||||
if (machine.protocol == MachineProtocolEnum.Ssh.value) {
|
||||
const { href } = router.resolve({
|
||||
path: `/machine/terminal`,
|
||||
query: {
|
||||
id: machine.id,
|
||||
ac,
|
||||
name: machine.name,
|
||||
},
|
||||
});
|
||||
window.open(href, '_blank');
|
||||
return;
|
||||
} else if (machine.protocol == 2) {
|
||||
}
|
||||
if (machine.protocol == MachineProtocolEnum.Rdp.value) {
|
||||
const { href } = router.resolve({
|
||||
path: `/machine/terminal-rdp`,
|
||||
query: {
|
||||
@@ -313,21 +329,22 @@ const openTerminal = (machine: any, ex?: boolean) => {
|
||||
}
|
||||
}
|
||||
|
||||
let { name, id, username } = machine;
|
||||
let { name, username } = machine;
|
||||
const labelName = `${machine.selectAuthCert.username}@${name}`;
|
||||
|
||||
// 同一个机器的终端打开多次,key后添加下划线和数字区分
|
||||
openIds[id] = openIds[id] ? ++openIds[id] : 1;
|
||||
let sameIndex = openIds[id];
|
||||
openIds[ac] = openIds[ac] ? ++openIds[ac] : 1;
|
||||
let sameIndex = openIds[ac];
|
||||
|
||||
let key = `${id}_${username}_${sameIndex}`;
|
||||
// 只保留name的10个字,超出部分只保留前后4个字符,中间用省略号代替
|
||||
let label = name.length > 10 ? name.slice(0, 4) + '...' + name.slice(-4) : name;
|
||||
let key = `${ac}_${username}_${sameIndex}`;
|
||||
// 只保留name的15个字,超出部分只保留前后10个字符,中间用省略号代替
|
||||
const label = labelName.length > 15 ? labelName.slice(0, 10) + '...' + labelName.slice(-10) : labelName;
|
||||
|
||||
let tab = {
|
||||
key,
|
||||
label: `${label}${sameIndex === 1 ? '' : ':' + sameIndex}`, // label组成为:总打开term次数+name+同一个机器打开的次数
|
||||
params: machine,
|
||||
socketUrl: getMachineTerminalSocketUrl(id),
|
||||
socketUrl: getMachineTerminalSocketUrl(ac),
|
||||
};
|
||||
|
||||
state.tabs.set(key, tab);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="terminal-wrapper" ref="terminalWrapperRef">
|
||||
<machine-rdp ref="rdpRef" :machine-id="route.query.id" />
|
||||
<machine-rdp ref="rdpRef" :machine-id="route.query.ac" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,7 +17,6 @@ const terminalWrapperRef = ref({} as any);
|
||||
onMounted(() => {
|
||||
let width = terminalWrapperRef.value.clientWidth;
|
||||
let height = terminalWrapperRef.value.clientHeight;
|
||||
console.log(width, height);
|
||||
rdpRef.value?.init(width, height, false);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="terminal-wrapper">
|
||||
<TerminalBody :socket-url="getMachineTerminalSocketUrl(route.query.id)" />
|
||||
<TerminalBody :socket-url="getMachineTerminalSocketUrl(route.query.ac)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ export const cronJobApi = {
|
||||
execList: Api.newGet('/machine-cronjobs/execs'),
|
||||
};
|
||||
|
||||
export function getMachineTerminalSocketUrl(machineId: any) {
|
||||
return `${config.baseWsUrl}/machines/${machineId}/terminal?${joinClientParams()}`;
|
||||
export function getMachineTerminalSocketUrl(authCertName: any) {
|
||||
return `${config.baseWsUrl}/machines/terminal/${authCertName}?${joinClientParams()}`;
|
||||
}
|
||||
|
||||
export function getMachineRdpSocketUrl(machineId: any) {
|
||||
return `${config.baseWsUrl}/machines/${machineId}/rdp`;
|
||||
export function getMachineRdpSocketUrl(authCertName: any) {
|
||||
return `${config.baseWsUrl}/machines/rdp/${authCertName}`;
|
||||
}
|
||||
|
||||
@@ -44,13 +44,21 @@
|
||||
</el-row>
|
||||
|
||||
<el-dialog destroy-on-close :title="fileDialog.title" v-model="fileDialog.visible" :close-on-click-modal="false" width="70%">
|
||||
<machine-file :title="fileDialog.title" :machine-id="machineId" :file-id="fileDialog.fileId" :path="fileDialog.path" :protocol="protocol" />
|
||||
<machine-file
|
||||
:title="fileDialog.title"
|
||||
:machine-id="machineId"
|
||||
:auth-cert-name="props.authCertName"
|
||||
:file-id="fileDialog.fileId"
|
||||
:path="fileDialog.path"
|
||||
:protocol="protocol"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<machine-file-content
|
||||
:title="fileContent.title"
|
||||
v-model:visible="fileContent.contentVisible"
|
||||
:machine-id="machineId"
|
||||
:auth-cert-name="props.authCertName"
|
||||
:file-id="fileContent.fileId"
|
||||
:path="fileContent.path"
|
||||
/>
|
||||
@@ -70,6 +78,7 @@ const props = defineProps({
|
||||
visible: { type: Boolean },
|
||||
protocol: { type: Number, default: 1 },
|
||||
machineId: { type: Number },
|
||||
authCertName: { type: String },
|
||||
title: { type: String },
|
||||
});
|
||||
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
<machine-file-content
|
||||
v-model:visible="fileContent.contentVisible"
|
||||
:machine-id="machineId"
|
||||
:auth-cert-name="props.authCertName"
|
||||
:file-id="fileId"
|
||||
:path="fileContent.path"
|
||||
:protocol="protocol"
|
||||
@@ -290,6 +291,7 @@ import { getMachineConfig } from '@/common/sysconfig';
|
||||
|
||||
const props = defineProps({
|
||||
machineId: { type: Number },
|
||||
authCertName: { type: String },
|
||||
protocol: { type: Number, default: 1 },
|
||||
fileId: { type: Number, default: 0 },
|
||||
path: { type: String, default: '' },
|
||||
@@ -419,7 +421,8 @@ const pasteFile = async () => {
|
||||
await api.request({
|
||||
machineId: props.machineId,
|
||||
fileId: props.fileId,
|
||||
path: cmFile.paths,
|
||||
authCertName: props.authCertName,
|
||||
paths: cmFile.paths,
|
||||
toPath: state.nowPath,
|
||||
protocol: props.protocol,
|
||||
});
|
||||
@@ -462,8 +465,9 @@ const fileRename = async (row: any) => {
|
||||
try {
|
||||
await machineApi.renameFile.request({
|
||||
machineId: parseInt(props.machineId + ''),
|
||||
authCertName: props.authCertName,
|
||||
fileId: parseInt(props.fileId + ''),
|
||||
oldname: state.nowPath + pathSep + state.renameFile.oldname,
|
||||
path: state.nowPath + pathSep + state.renameFile.oldname,
|
||||
newname: state.nowPath + pathSep + row.name,
|
||||
protocol: props.protocol,
|
||||
});
|
||||
@@ -508,6 +512,7 @@ const lsFile = async (path: string) => {
|
||||
const res = await machineApi.lsFile.request({
|
||||
fileId: props.fileId,
|
||||
machineId: props.machineId,
|
||||
authCertName: props.authCertName,
|
||||
protocol: props.protocol,
|
||||
path,
|
||||
});
|
||||
@@ -574,6 +579,7 @@ const createFile = async () => {
|
||||
const path = state.nowPath + pathSep + name;
|
||||
await machineApi.createFile.request({
|
||||
machineId: props.machineId,
|
||||
authCertName: props.authCertName,
|
||||
id: props.fileId,
|
||||
protocol: props.protocol,
|
||||
path,
|
||||
@@ -607,8 +613,9 @@ const deleteFile = async (files: any) => {
|
||||
state.loading = true;
|
||||
await machineApi.rmFile.request({
|
||||
fileId: props.fileId,
|
||||
path: files.map((x: any) => x.path),
|
||||
paths: files.map((x: any) => x.path),
|
||||
machineId: props.machineId,
|
||||
authCertName: props.authCertName,
|
||||
protocol: props.protocol,
|
||||
});
|
||||
ElMessage.success('删除成功');
|
||||
@@ -624,7 +631,7 @@ const downloadFile = (data: any) => {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute(
|
||||
'href',
|
||||
`${config.baseApiUrl}/machines/${props.machineId}/files/${props.fileId}/download?path=${data.path}&machineId=${props.machineId}&protocol=${props.protocol}&${joinClientParams()}`
|
||||
`${config.baseApiUrl}/machines/${props.machineId}/files/${props.fileId}/download?path=${data.path}&machineId=${props.machineId}&authCertName=${props.authCertName}&protocol=${props.protocol}&${joinClientParams()}`
|
||||
);
|
||||
a.click();
|
||||
};
|
||||
@@ -638,6 +645,7 @@ function uploadFolder(e: any) {
|
||||
// 把文件夹数据放到formData里面,下面的files和paths字段根据接口来定
|
||||
var form = new FormData();
|
||||
form.append('basePath', state.nowPath);
|
||||
form.append('authCertName', props.authCertName as any);
|
||||
form.append('machineId', props.machineId as any);
|
||||
form.append('protocol', props.protocol as any);
|
||||
form.append('fileId', props.fileId as any);
|
||||
@@ -693,6 +701,7 @@ const uploadFile = (content: any) => {
|
||||
const path = state.nowPath;
|
||||
params.append('file', content.file);
|
||||
params.append('path', path);
|
||||
params.append('authCertName', props.authCertName as any);
|
||||
params.append('machineId', props.machineId as any);
|
||||
params.append('protocol', props.protocol as any);
|
||||
params.append('fileId', props.fileId as any);
|
||||
|
||||
@@ -35,6 +35,7 @@ const props = defineProps({
|
||||
protocol: { type: Number, default: 1 },
|
||||
title: { type: String, default: '' },
|
||||
machineId: { type: Number },
|
||||
authCertName: { type: String },
|
||||
fileId: { type: Number, default: 0 },
|
||||
path: { type: String, default: '' },
|
||||
});
|
||||
@@ -64,6 +65,7 @@ const getFileContent = async () => {
|
||||
fileId: props.fileId,
|
||||
path,
|
||||
machineId: props.machineId,
|
||||
authCertName: props.authCertName,
|
||||
protocol: props.protocol,
|
||||
});
|
||||
state.fileType = getFileType(path);
|
||||
@@ -81,6 +83,7 @@ const updateContent = async () => {
|
||||
id: props.fileId,
|
||||
path: props.path,
|
||||
machineId: props.machineId,
|
||||
authCertName: props.authCertName,
|
||||
protocol: props.protocol,
|
||||
});
|
||||
ElMessage.success('修改成功');
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<el-input
|
||||
:disabled="form.id"
|
||||
v-model.trim="form.code"
|
||||
placeholder="请输入机器编号 (数字字母下划线), 不可修改"
|
||||
placeholder="请输入编号 (数字字母下划线), 不可修改"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<el-input
|
||||
:disabled="form.id"
|
||||
v-model.trim="form.code"
|
||||
placeholder="请输入机器编号 (数字字母下划线), 不可修改"
|
||||
placeholder="请输入编号 (数字字母下划线), 不可修改"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="tag-tree-list card">
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="25" min-size="20" max-size="30">
|
||||
<Pane size="30" min-size="25" max-size="35">
|
||||
<div class="card pd5 mr5">
|
||||
<el-input v-model="filterTag" clearable placeholder="关键字过滤(右击操作)" style="width: 200px; margin-right: 10px" />
|
||||
<el-button
|
||||
@@ -28,7 +28,6 @@
|
||||
<el-scrollbar class="tag-tree-data">
|
||||
<el-tree
|
||||
ref="tagTreeRef"
|
||||
class="none-select"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
:props="props"
|
||||
@@ -166,7 +165,7 @@ const contextmenuAdd = new ContextmenuItem('addTag', '添加子标签')
|
||||
.withPermission('tag:save')
|
||||
.withHideFunc((data: any) => {
|
||||
// 非标签类型不可添加子标签
|
||||
return data.type != -1;
|
||||
return data.type != TagResourceTypeEnum.Tag.value || (data.children && data.children?.[0].type != TagResourceTypeEnum.Tag.value);
|
||||
})
|
||||
.withOnClick((data: any) => showSaveTagDialog(data));
|
||||
|
||||
@@ -180,7 +179,7 @@ const contextmenuDel = new ContextmenuItem('delete', '删除')
|
||||
.withPermission('tag:del')
|
||||
.withHideFunc((data: any) => {
|
||||
// 存在子标签,则不允许删除
|
||||
return data.children || data.type != -1;
|
||||
return data.children || data.type != TagResourceTypeEnum.Tag.value;
|
||||
})
|
||||
.withOnClick((data: any) => deleteTag(data));
|
||||
|
||||
@@ -339,15 +338,6 @@ const deleteTag = (data: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
// const changeStatus = async (data: any, status: any) => {
|
||||
// await resourceApi.changeStatus.request({
|
||||
// id: data.id,
|
||||
// status: status,
|
||||
// });
|
||||
// data.status = status;
|
||||
// ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
|
||||
// };
|
||||
|
||||
// 节点被展开时触发的事件
|
||||
const handleNodeExpand = (data: any, node: any) => {
|
||||
const id: any = node.data.id;
|
||||
@@ -393,14 +383,4 @@ const removeDeafultExpandId = (id: any) => {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.none-select {
|
||||
moz-user-select: -moz-none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,3 +20,7 @@ export const tagApi = {
|
||||
getTeamTagIds: Api.newGet('/teams/{teamId}/tags'),
|
||||
saveTeamTags: Api.newPost('/teams/{teamId}/tags'),
|
||||
};
|
||||
|
||||
export const resourceAuthCertApi = {
|
||||
listByQuery: Api.newGet('/auth-certs'),
|
||||
};
|
||||
|
||||
15
mayfly_go_web/src/views/ops/tag/enums.ts
Normal file
15
mayfly_go_web/src/views/ops/tag/enums.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { EnumValue } from '@/common/Enum';
|
||||
|
||||
// 授权凭证类型
|
||||
export const AuthCertTypeEnum = {
|
||||
Private: EnumValue.of(1, '普通账号').tagTypeSuccess(),
|
||||
Privileged: EnumValue.of(11, '特权账号').tagTypeSuccess(),
|
||||
PrivateDefault: EnumValue.of(12, '默认账号').tagTypeSuccess(),
|
||||
};
|
||||
|
||||
// 授权凭证密文类型
|
||||
export const AuthCertCiphertextTypeEnum = {
|
||||
Password: EnumValue.of(1, '密码').tagTypeSuccess(),
|
||||
PrivateKey: EnumValue.of(2, '秘钥').tagTypeSuccess(),
|
||||
Public: EnumValue.of(-1, '公共凭证').tagTypeSuccess(),
|
||||
};
|
||||
@@ -16,10 +16,10 @@ const (
|
||||
// RedisConnExpireTime = 2 * time.Minute
|
||||
// MongoConnExpireTime = 2 * time.Minute
|
||||
|
||||
TagResourceTypeMachine = 1
|
||||
TagResourceTypeDb = 2
|
||||
TagResourceTypeRedis = 3
|
||||
TagResourceTypeMongo = 4
|
||||
TagResourceTypeMachine int8 = 1
|
||||
TagResourceTypeDb int8 = 2
|
||||
TagResourceTypeRedis int8 = 3
|
||||
TagResourceTypeMongo int8 = 4
|
||||
|
||||
// 删除机器的事件主题名
|
||||
DeleteMachineEventTopic = "machine:delete"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/errorx"
|
||||
@@ -87,7 +88,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
|
||||
}, func(ctx context.Context) error {
|
||||
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceCode: dbEntity.Code,
|
||||
ResourceType: consts.TagResourceTypeDb,
|
||||
ResourceType: tagentity.TagTypeDb,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
})
|
||||
@@ -127,7 +128,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
|
||||
}, func(ctx context.Context) error {
|
||||
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceCode: old.Code,
|
||||
ResourceType: consts.TagResourceTypeDb,
|
||||
ResourceType: tagentity.TagTypeDb,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
})
|
||||
@@ -154,7 +155,7 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
}, func(ctx context.Context) error {
|
||||
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceCode: db.Code,
|
||||
ResourceType: consts.TagResourceTypeDb,
|
||||
ResourceType: tagentity.TagTypeDb,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/machine/application"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
)
|
||||
|
||||
type Dashbord struct {
|
||||
TagTreeApp tagapp.TagTree `inject:""`
|
||||
MachineApp application.Machine `inject:""`
|
||||
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
|
||||
MachineApp application.Machine `inject:""`
|
||||
}
|
||||
|
||||
func (m *Dashbord) Dashbord(rc *req.Ctx) {
|
||||
accountId := rc.GetLoginAccount().Id
|
||||
machienNum := len(m.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMachine, ""))
|
||||
|
||||
machienAuthCerts := m.ResourceAuthCertApp.GetAccountAuthCert(accountId, tagentity.TagTypeMachineAuthCert)
|
||||
machineCodes := collx.ArrayMap(machienAuthCerts, func(ac *tagentity.ResourceAuthCert) string {
|
||||
return ac.ResourceCode
|
||||
})
|
||||
|
||||
machienNum := len(collx.ArrayDeduplicate(machineCodes))
|
||||
|
||||
rc.ResData = collx.M{
|
||||
"machineNum": machienNum,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package form
|
||||
|
||||
import tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
|
||||
type MachineForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
Protocol int `json:"protocol" binding:"required"`
|
||||
@@ -8,11 +10,8 @@ type MachineForm struct {
|
||||
Ip string `json:"ip" binding:"required"` // IP地址
|
||||
Port int `json:"port" binding:"required"` // 端口号
|
||||
|
||||
// 资产授权凭证信息列表
|
||||
AuthCertId int `json:"authCertId"`
|
||||
TagId []uint64 `json:"tagId" binding:"required"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
TagId []uint64 `json:"tagId" binding:"required"`
|
||||
AuthCerts []*tagentity.ResourceAuthCert // 资产授权凭证信息列表
|
||||
|
||||
Remark string `json:"remark"`
|
||||
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
@@ -24,14 +23,6 @@ type MachineRunForm struct {
|
||||
Cmd string `json:"cmd" binding:"required"`
|
||||
}
|
||||
|
||||
type MachineFileForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
MachineId uint64 `json:"machineId" binding:"required"`
|
||||
Type int `json:"type" binding:"required"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
}
|
||||
|
||||
type MachineScriptForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
@@ -42,39 +33,6 @@ type MachineScriptForm struct {
|
||||
Script string `json:"script" binding:"required"`
|
||||
}
|
||||
|
||||
type ServerFileOptionForm struct {
|
||||
MachineId uint64 `form:"machineId"`
|
||||
Protocol int `form:"protocol"`
|
||||
Path string `form:"path"`
|
||||
Type string `form:"type"`
|
||||
Content string `form:"content"`
|
||||
Id uint64 `form:"id"`
|
||||
FileId uint64 `form:"fileId"`
|
||||
}
|
||||
|
||||
type MachineFileUpdateForm struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
Id uint64 `json:"id" binding:"required"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
}
|
||||
|
||||
type MachineFileOpForm struct {
|
||||
Path []string `json:"path" binding:"required"`
|
||||
ToPath string `json:"toPath"`
|
||||
MachineId uint64 `json:"machineId" binding:"required"`
|
||||
Protocol int `json:"protocol" binding:"required"`
|
||||
FileId uint64 `json:"fileId" binding:"required"`
|
||||
}
|
||||
|
||||
type MachineFileRename struct {
|
||||
MachineId uint64 `json:"machineId" binding:"required"`
|
||||
Protocol int `json:"protocol" binding:"required"`
|
||||
FileId uint64 `json:"fileId" binding:"required"`
|
||||
|
||||
Oldname string `json:"oldname" binding:"required"`
|
||||
Newname string `json:"newname" binding:"required"`
|
||||
}
|
||||
|
||||
// 授权凭证
|
||||
type AuthCertForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
|
||||
48
server/internal/machine/api/form/machine_file.go
Normal file
48
server/internal/machine/api/form/machine_file.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package form
|
||||
|
||||
import "mayfly-go/internal/machine/application"
|
||||
|
||||
type MachineFileForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
MachineId uint64 `json:"machineId" binding:"required"`
|
||||
Type int `json:"type" binding:"required"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
}
|
||||
|
||||
type MachineFileUpdateForm struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
Id uint64 `json:"id" binding:"required"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
}
|
||||
|
||||
type CreateFileForm struct {
|
||||
*application.MachineFileOpParam
|
||||
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type WriteFileContentForm struct {
|
||||
*application.MachineFileOpParam
|
||||
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
|
||||
type RemoveFileForm struct {
|
||||
*application.MachineFileOpParam
|
||||
|
||||
Paths []string `json:"paths" binding:"required"`
|
||||
}
|
||||
|
||||
type CopyFileForm struct {
|
||||
*application.MachineFileOpParam
|
||||
|
||||
Paths []string `json:"paths" binding:"required"`
|
||||
ToPath string `json:"toPath" binding:"required"`
|
||||
}
|
||||
|
||||
type RenameForm struct {
|
||||
*application.MachineFileOpParam
|
||||
|
||||
Newname string `json:"newname" binding:"required"`
|
||||
}
|
||||
@@ -3,10 +3,6 @@ package api
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/may-fly/cast"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/machine/api/form"
|
||||
"mayfly-go/internal/machine/api/vo"
|
||||
"mayfly-go/internal/machine/application"
|
||||
@@ -29,24 +25,33 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type Machine struct {
|
||||
MachineApp application.Machine `inject:""`
|
||||
MachineTermOpApp application.MachineTermOp `inject:""`
|
||||
TagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
MachineApp application.Machine `inject:""`
|
||||
MachineTermOpApp application.MachineTermOp `inject:""`
|
||||
TagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
|
||||
}
|
||||
|
||||
func (m *Machine) Machines(rc *req.Ctx) {
|
||||
condition, pageParam := req.BindQueryAndPage(rc, new(entity.MachineQuery))
|
||||
|
||||
// 不存在可访问标签id,即没有可操作数据
|
||||
codes := m.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeMachine, condition.TagPath)
|
||||
if len(codes) == 0 {
|
||||
authCerts := m.ResourceAuthCertApp.GetAccountAuthCert(rc.GetLoginAccount().Id, tagentity.TagTypeMachineAuthCert, condition.TagPath)
|
||||
// 不存在可操作的授权凭证,即没有可操作数据
|
||||
if len(authCerts) == 0 {
|
||||
rc.ResData = model.EmptyPageResult[any]()
|
||||
return
|
||||
}
|
||||
condition.Codes = codes
|
||||
|
||||
machineCodes := collx.ArrayMap(authCerts, func(ac *tagentity.ResourceAuthCert) string {
|
||||
return ac.ResourceCode
|
||||
})
|
||||
condition.Codes = collx.ArrayDeduplicate(machineCodes)
|
||||
|
||||
var machinevos []*vo.MachineVO
|
||||
res, err := m.MachineApp.GetMachineList(condition, pageParam, &machinevos)
|
||||
@@ -61,6 +66,11 @@ func (m *Machine) Machines(rc *req.Ctx) {
|
||||
return mvo
|
||||
})...)
|
||||
|
||||
// 填充授权凭证信息
|
||||
m.ResourceAuthCertApp.FillAuthCert(authCerts, collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
|
||||
return mvo
|
||||
})...)
|
||||
|
||||
for _, mv := range machinevos {
|
||||
if machineStats, err := m.MachineApp.GetMachineStats(mv.Id); err == nil {
|
||||
mv.Stat = collx.M{
|
||||
@@ -85,16 +95,20 @@ func (m *Machine) SaveMachine(rc *req.Ctx) {
|
||||
machineForm := new(form.MachineForm)
|
||||
me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine))
|
||||
|
||||
machineForm.Password = "******"
|
||||
rc.ReqParam = machineForm
|
||||
|
||||
biz.ErrIsNil(m.MachineApp.SaveMachine(rc.MetaCtx, me, machineForm.TagId...))
|
||||
biz.ErrIsNil(m.MachineApp.SaveMachine(rc.MetaCtx, &application.SaveMachineParam{
|
||||
Machine: me,
|
||||
TagIds: machineForm.TagId,
|
||||
AuthCerts: machineForm.AuthCerts,
|
||||
}))
|
||||
}
|
||||
|
||||
func (m *Machine) TestConn(rc *req.Ctx) {
|
||||
me := req.BindJsonAndCopyTo(rc, new(form.MachineForm), new(entity.Machine))
|
||||
machineForm := new(form.MachineForm)
|
||||
me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine))
|
||||
// 测试连接
|
||||
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me), "该机器无法连接: %s")
|
||||
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me, machineForm.AuthCerts[0]), "该机器无法连接: %s")
|
||||
}
|
||||
|
||||
func (m *Machine) ChangeStatus(rc *req.Ctx) {
|
||||
@@ -175,7 +189,7 @@ func (m *Machine) WsSSH(g *gin.Context) {
|
||||
panic(errorx.NewBiz("\033[1;31m您没有权限操作该机器终端,请重新登录后再试~\033[0m"))
|
||||
}
|
||||
|
||||
cli, err := m.MachineApp.NewCli(GetMachineId(rc))
|
||||
cli, err := m.MachineApp.NewCli(GetMachineAc(rc))
|
||||
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
|
||||
defer cli.Close()
|
||||
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
|
||||
@@ -231,9 +245,9 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
rc := req.NewCtxWithGin(g).WithRequiredPermission(req.NewPermission("machine:terminal"))
|
||||
machineId := GetMachineId(rc)
|
||||
ac := GetMachineAc(rc)
|
||||
|
||||
mi, err := m.MachineApp.ToMachineInfoById(machineId)
|
||||
mi, err := m.MachineApp.ToMachineInfoByAc(ac)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -258,7 +272,7 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
|
||||
|
||||
if mi.EnableRecorder == 1 {
|
||||
// 操作记录 查看文档:https://guacamole.apache.org/doc/gug/configuring-guacamole.html#graphical-recording
|
||||
params["recording-path"] = fmt.Sprintf("/rdp-rec/%d", machineId)
|
||||
params["recording-path"] = fmt.Sprintf("/rdp-rec/%s", ac)
|
||||
params["create-recording-path"] = "true"
|
||||
params["recording-include-keys"] = "true"
|
||||
}
|
||||
@@ -273,14 +287,14 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
|
||||
if query.Get("force") != "" {
|
||||
// 判断是否强制连接,是的话,查询是否有正在连接的会话,有的话强制关闭
|
||||
if cast.ToBool(query.Get("force")) {
|
||||
tn := sessions.Get(machineId)
|
||||
tn := sessions.Get(ac)
|
||||
if tn != nil {
|
||||
_ = tn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tunnel, err := guac.DoConnect(query, params, machineId)
|
||||
tunnel, err := guac.DoConnect(query, params, ac)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -290,9 +304,9 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
|
||||
}
|
||||
}()
|
||||
|
||||
sessions.Add(machineId, wsConn, g.Request, tunnel)
|
||||
sessions.Add(ac, wsConn, g.Request, tunnel)
|
||||
|
||||
defer sessions.Delete(machineId, wsConn, g.Request, tunnel)
|
||||
defer sessions.Delete(ac, wsConn, g.Request, tunnel)
|
||||
|
||||
writer := tunnel.AcquireWriter()
|
||||
reader := tunnel.AcquireReader()
|
||||
@@ -312,3 +326,9 @@ func GetMachineId(rc *req.Ctx) uint64 {
|
||||
biz.IsTrue(machineId != 0, "machineId错误")
|
||||
return uint64(machineId)
|
||||
}
|
||||
|
||||
func GetMachineAc(rc *req.Ctx) string {
|
||||
ac := rc.PathParam("ac")
|
||||
biz.IsTrue(ac != "", "authCertName错误")
|
||||
return ac
|
||||
}
|
||||
|
||||
@@ -23,9 +23,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type MachineFile struct {
|
||||
@@ -62,8 +63,7 @@ func (m *MachineFile) DeleteFile(rc *req.Ctx) {
|
||||
/*** sftp相关操作 */
|
||||
|
||||
func (m *MachineFile) CreateFile(rc *req.Ctx) {
|
||||
|
||||
opForm := req.BindJsonAndValid(rc, new(form.ServerFileOptionForm))
|
||||
opForm := req.BindJsonAndValid(rc, new(form.CreateFileForm))
|
||||
path := opForm.Path
|
||||
|
||||
attrs := collx.Kvs("path", path)
|
||||
@@ -71,10 +71,10 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
|
||||
var err error
|
||||
if opForm.Type == dir {
|
||||
attrs["type"] = "目录"
|
||||
mi, err = m.MachineFileApp.MkDir(opForm.FileId, opForm.Path, opForm)
|
||||
mi, err = m.MachineFileApp.MkDir(opForm.MachineFileOpParam)
|
||||
} else {
|
||||
attrs["type"] = "文件"
|
||||
mi, err = m.MachineFileApp.CreateFile(opForm.FileId, opForm.Path, opForm)
|
||||
mi, err = m.MachineFileApp.CreateFile(opForm.MachineFileOpParam)
|
||||
}
|
||||
attrs["machine"] = mi
|
||||
rc.ReqParam = attrs
|
||||
@@ -82,7 +82,7 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
|
||||
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
|
||||
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
|
||||
readPath := opForm.Path
|
||||
// 特殊处理rdp文件
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
@@ -96,7 +96,7 @@ func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm.FileId, readPath)
|
||||
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
|
||||
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
|
||||
defer sftpFile.Close()
|
||||
@@ -112,7 +112,7 @@ func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *MachineFile) DownloadFile(rc *req.Ctx) {
|
||||
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
|
||||
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
|
||||
|
||||
readPath := opForm.Path
|
||||
|
||||
@@ -131,7 +131,7 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm.FileId, readPath)
|
||||
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
|
||||
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
|
||||
defer sftpFile.Close()
|
||||
@@ -140,11 +140,11 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
|
||||
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
|
||||
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
|
||||
readPath := opForm.Path
|
||||
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
|
||||
|
||||
fis, err := m.MachineFileApp.ReadDir(opForm.FileId, opForm)
|
||||
fis, err := m.MachineFileApp.ReadDir(opForm)
|
||||
biz.ErrIsNilAppendErr(err, "读取目录失败: %s")
|
||||
|
||||
fisVO := make([]vo.MachineFileInfo, 0)
|
||||
@@ -173,34 +173,34 @@ func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *MachineFile) GetDirSize(rc *req.Ctx) {
|
||||
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
|
||||
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
|
||||
|
||||
size, err := m.MachineFileApp.GetDirSize(opForm.FileId, opForm)
|
||||
size, err := m.MachineFileApp.GetDirSize(opForm)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = size
|
||||
}
|
||||
|
||||
func (m *MachineFile) GetFileStat(rc *req.Ctx) {
|
||||
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
|
||||
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
|
||||
res, err := m.MachineFileApp.FileStat(opForm)
|
||||
biz.ErrIsNil(err, res)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
|
||||
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
|
||||
opForm := req.BindJsonAndValid(rc, new(form.WriteFileContentForm))
|
||||
path := opForm.Path
|
||||
|
||||
mi, err := m.MachineFileApp.WriteFileContent(opForm.FileId, path, []byte(opForm.Content), opForm)
|
||||
mi, err := m.MachineFileApp.WriteFileContent(opForm.MachineFileOpParam, []byte(opForm.Content))
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "path", path)
|
||||
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
|
||||
}
|
||||
|
||||
func (m *MachineFile) UploadFile(rc *req.Ctx) {
|
||||
fid := GetMachineFileId(rc)
|
||||
path := rc.PostForm("path")
|
||||
protocol, err := strconv.Atoi(rc.PostForm("protocol"))
|
||||
machineId, err := strconv.Atoi(rc.PostForm("machineId"))
|
||||
protocol := cast.ToInt(rc.PostForm("protocol"))
|
||||
machineId := cast.ToUint64(rc.PostForm("machineId"))
|
||||
authCertName := rc.PostForm("authCertName")
|
||||
|
||||
fileheader, err := rc.FormFile("file")
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
|
||||
@@ -219,14 +219,14 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
|
||||
}
|
||||
}()
|
||||
|
||||
opForm := &form.ServerFileOptionForm{
|
||||
FileId: fid,
|
||||
MachineId: uint64(machineId),
|
||||
Protocol: protocol,
|
||||
Path: path,
|
||||
opForm := &application.MachineFileOpParam{
|
||||
MachineId: machineId,
|
||||
AuthCertName: authCertName,
|
||||
Protocol: protocol,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
mi, err := m.MachineFileApp.UploadFile(fid, path, fileheader.Filename, file, opForm)
|
||||
mi, err := m.MachineFileApp.UploadFile(opForm, fileheader.Filename, file)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
|
||||
biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
|
||||
// 保存消息并发送文件上传成功通知
|
||||
@@ -239,8 +239,6 @@ type FolderFile struct {
|
||||
}
|
||||
|
||||
func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
||||
fid := GetMachineFileId(rc)
|
||||
|
||||
mf, err := rc.MultipartForm()
|
||||
biz.ErrIsNilAppendErr(err, "获取表单信息失败: %s")
|
||||
basePath := mf.Value["basePath"][0]
|
||||
@@ -256,21 +254,24 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
||||
biz.IsTrue(allFileSize <= maxUploadFileSize, "文件夹总大小不能超过%d字节", maxUploadFileSize)
|
||||
|
||||
paths := mf.Value["paths"]
|
||||
|
||||
authCertName := mf.Value["authCertName"][0]
|
||||
machineId := cast.ToUint64(mf.Value["machineId"][0])
|
||||
// protocol
|
||||
protocol, err := strconv.Atoi(mf.Value["protocol"][0])
|
||||
protocol := cast.ToInt(mf.Value["protocol"][0])
|
||||
|
||||
opForm := &application.MachineFileOpParam{
|
||||
MachineId: machineId,
|
||||
Protocol: protocol,
|
||||
AuthCertName: authCertName,
|
||||
}
|
||||
|
||||
if protocol == entity.MachineProtocolRdp {
|
||||
machineId, _ := strconv.Atoi(mf.Value["machineId"][0])
|
||||
opForm := &form.ServerFileOptionForm{
|
||||
MachineId: uint64(machineId),
|
||||
Protocol: protocol,
|
||||
}
|
||||
m.MachineFileApp.UploadFiles(basePath, fileheaders, paths, opForm)
|
||||
m.MachineFileApp.UploadFiles(opForm, basePath, fileheaders, paths)
|
||||
return
|
||||
}
|
||||
|
||||
folderName := filepath.Dir(paths[0])
|
||||
mcli, err := m.MachineFileApp.GetMachineCli(fid, basePath+"/"+folderName)
|
||||
mcli, err := m.MachineFileApp.GetMachineCli(authCertName)
|
||||
biz.ErrIsNil(err)
|
||||
mi := mcli.Info
|
||||
|
||||
@@ -344,30 +345,30 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (m *MachineFile) RemoveFile(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
|
||||
opForm := req.BindJsonAndValid(rc, new(form.RemoveFileForm))
|
||||
|
||||
mi, err := m.MachineFileApp.RemoveFile(opForm)
|
||||
mi, err := m.MachineFileApp.RemoveFile(opForm.MachineFileOpParam, opForm.Paths...)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "path", opForm)
|
||||
biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
|
||||
}
|
||||
|
||||
func (m *MachineFile) CopyFile(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
|
||||
mi, err := m.MachineFileApp.Copy(opForm)
|
||||
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
|
||||
mi, err := m.MachineFileApp.Copy(opForm.MachineFileOpParam, opForm.ToPath, opForm.Paths...)
|
||||
biz.ErrIsNilAppendErr(err, "文件拷贝失败: %s")
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm)
|
||||
}
|
||||
|
||||
func (m *MachineFile) MvFile(rc *req.Ctx) {
|
||||
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
|
||||
mi, err := m.MachineFileApp.Mv(opForm)
|
||||
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
|
||||
mi, err := m.MachineFileApp.Mv(opForm.MachineFileOpParam, opForm.ToPath, opForm.Paths...)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm)
|
||||
biz.ErrIsNilAppendErr(err, "文件移动失败: %s")
|
||||
}
|
||||
|
||||
func (m *MachineFile) Rename(rc *req.Ctx) {
|
||||
renameForm := req.BindJsonAndValid(rc, new(form.MachineFileRename))
|
||||
mi, err := m.MachineFileApp.Rename(renameForm)
|
||||
renameForm := req.BindJsonAndValid(rc, new(form.RenameForm))
|
||||
mi, err := m.MachineFileApp.Rename(renameForm.MachineFileOpParam, renameForm.Newname)
|
||||
rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm)
|
||||
biz.ErrIsNilAppendErr(err, "文件重命名失败: %s")
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ type AuthCertBaseVO struct {
|
||||
}
|
||||
|
||||
type MachineVO struct {
|
||||
tagentity.ResourceTags
|
||||
tagentity.ResourceTags // 标签信息
|
||||
tagentity.AuthCerts // 授权凭证信息
|
||||
|
||||
Id uint64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
@@ -21,8 +22,6 @@ type MachineVO struct {
|
||||
Protocol int `json:"protocol"`
|
||||
Ip string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
AuthCertId int `json:"authCertId"`
|
||||
Status *int8 `json:"status"`
|
||||
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
@@ -33,8 +32,6 @@ type MachineVO struct {
|
||||
ModifierId *int64 `json:"modifierId"`
|
||||
Remark *string `json:"remark"`
|
||||
EnableRecorder int8 `json:"enableRecorder"`
|
||||
// TagId uint64 `json:"tagId"`
|
||||
// TagPath string `json:"tagPath"`
|
||||
|
||||
Stat map[string]any `json:"stat" gorm:"-"`
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"mayfly-go/internal/machine/infrastructure/cache"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/global"
|
||||
@@ -17,15 +18,23 @@ import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/scheduler"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type SaveMachineParam struct {
|
||||
Machine *entity.Machine
|
||||
TagIds []uint64
|
||||
AuthCerts []*tagentity.ResourceAuthCert
|
||||
}
|
||||
|
||||
type Machine interface {
|
||||
base.App[*entity.Machine]
|
||||
|
||||
SaveMachine(ctx context.Context, m *entity.Machine, tagIds ...uint64) error
|
||||
SaveMachine(ctx context.Context, param *SaveMachineParam) error
|
||||
|
||||
// 测试机器连接
|
||||
TestConn(me *entity.Machine) error
|
||||
TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error
|
||||
|
||||
// 调整机器状态
|
||||
ChangeStatus(ctx context.Context, id uint64, status int8) error
|
||||
@@ -36,11 +45,14 @@ type Machine interface {
|
||||
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity *[]*vo.MachineVO, orderBy ...string) (*model.PageResult[*[]*vo.MachineVO], error)
|
||||
|
||||
// 新建机器客户端连接(需手动调用Close)
|
||||
NewCli(id uint64) (*mcm.Cli, error)
|
||||
NewCli(authCertName string) (*mcm.Cli, error)
|
||||
|
||||
// 获取已缓存的机器连接,若不存在则新建客户端连接并缓存,主要用于定时获取状态等(避免频繁创建连接)
|
||||
GetCli(id uint64) (*mcm.Cli, error)
|
||||
|
||||
// 根据授权凭证获取客户端连接
|
||||
GetCliByAc(authCertName string) (*mcm.Cli, error)
|
||||
|
||||
// 获取ssh隧道机器连接
|
||||
GetSshTunnelMachine(id int) (*mcm.SshTunnelMachine, error)
|
||||
|
||||
@@ -50,14 +62,15 @@ type Machine interface {
|
||||
// 获取机器运行时状态信息
|
||||
GetMachineStats(machineId uint64) (*mcm.Stats, error)
|
||||
|
||||
ToMachineInfoById(machineId uint64) (*mcm.MachineInfo, error)
|
||||
ToMachineInfoByAc(ac string) (*mcm.MachineInfo, error)
|
||||
}
|
||||
|
||||
type machineAppImpl struct {
|
||||
base.AppImpl[*entity.Machine, repository.Machine]
|
||||
|
||||
authCertApp AuthCert `inject:"AuthCertApp"`
|
||||
tagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
// authCertApp AuthCert `inject:"AuthCertApp"`
|
||||
tagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
|
||||
}
|
||||
|
||||
// 注入MachineRepo
|
||||
@@ -70,19 +83,21 @@ func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pagePara
|
||||
return m.GetRepo().GetMachineList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, tagIds ...uint64) error {
|
||||
func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachineParam) error {
|
||||
me := param.Machine
|
||||
tagIds := param.TagIds
|
||||
authCerts := param.AuthCerts
|
||||
resourceType := tagentity.TagTypeMachine
|
||||
authCertTagType := tagentity.TagTypeMachineAuthCert
|
||||
|
||||
oldMachine := &entity.Machine{
|
||||
Ip: me.Ip,
|
||||
Port: me.Port,
|
||||
Username: me.Username,
|
||||
SshTunnelMachineId: me.SshTunnelMachineId,
|
||||
}
|
||||
|
||||
err := m.GetBy(oldMachine)
|
||||
|
||||
if errEnc := me.PwdEncrypt(); errEnc != nil {
|
||||
return errorx.NewBiz(errEnc.Error())
|
||||
}
|
||||
if me.Id == 0 {
|
||||
if err == nil {
|
||||
return errorx.NewBiz("该机器信息已存在")
|
||||
@@ -94,14 +109,23 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, ta
|
||||
// 新增机器,默认启用状态
|
||||
me.Status = entity.MachineStatusEnable
|
||||
|
||||
return m.Tx(ctx, func(ctx context.Context) error {
|
||||
if err := m.Tx(ctx, func(ctx context.Context) error {
|
||||
return m.Insert(ctx, me)
|
||||
}, func(ctx context.Context) error {
|
||||
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceCode: me.Code,
|
||||
ResourceType: consts.TagResourceTypeMachine,
|
||||
ResourceType: resourceType,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
|
||||
ResourceCode: me.Code,
|
||||
ResourceType: resourceType,
|
||||
AuthCertTagType: authCertTagType,
|
||||
AuthCerts: authCerts,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -118,20 +142,29 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, ta
|
||||
mcm.DeleteCli(me.Id)
|
||||
// 防止误传修改
|
||||
me.Code = ""
|
||||
return m.Tx(ctx, func(ctx context.Context) error {
|
||||
if err := m.Tx(ctx, func(ctx context.Context) error {
|
||||
return m.UpdateById(ctx, me)
|
||||
}, func(ctx context.Context) error {
|
||||
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceCode: oldMachine.Code,
|
||||
ResourceType: consts.TagResourceTypeMachine,
|
||||
ResourceType: resourceType,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
|
||||
ResourceCode: oldMachine.Code,
|
||||
ResourceType: resourceType,
|
||||
AuthCertTagType: authCertTagType,
|
||||
AuthCerts: authCerts,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) TestConn(me *entity.Machine) error {
|
||||
func (m *machineAppImpl) TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error {
|
||||
me.Id = 0
|
||||
mi, err := m.toMachineInfo(me)
|
||||
mi, err := m.toMi(me, authCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,31 +198,51 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
|
||||
// 发布机器删除事件
|
||||
global.EventBus.Publish(ctx, consts.DeleteMachineEventTopic, machine)
|
||||
|
||||
resourceType := tagentity.TagTypeMachine
|
||||
return m.Tx(ctx,
|
||||
func(ctx context.Context) error {
|
||||
return m.DeleteById(ctx, id)
|
||||
}, func(ctx context.Context) error {
|
||||
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceCode: machine.Code,
|
||||
ResourceType: consts.TagResourceTypeMachine,
|
||||
ResourceType: resourceType,
|
||||
})
|
||||
}, func(ctx context.Context) error {
|
||||
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
|
||||
ResourceCode: machine.Code,
|
||||
ResourceType: resourceType,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) NewCli(machineId uint64) (*mcm.Cli, error) {
|
||||
if mi, err := m.ToMachineInfoById(machineId); err != nil {
|
||||
func (m *machineAppImpl) NewCli(authCertName string) (*mcm.Cli, error) {
|
||||
if mi, err := m.ToMachineInfoByAc(authCertName); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return mi.Conn()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) GetCli(machineId uint64) (*mcm.Cli, error) {
|
||||
return mcm.GetMachineCli(machineId, func(mid uint64) (*mcm.MachineInfo, error) {
|
||||
return m.ToMachineInfoById(mid)
|
||||
func (m *machineAppImpl) GetCliByAc(authCertName string) (*mcm.Cli, error) {
|
||||
return mcm.GetMachineCli(authCertName, func(ac string) (*mcm.MachineInfo, error) {
|
||||
return m.ToMachineInfoByAc(ac)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) GetCli(machineId uint64) (*mcm.Cli, error) {
|
||||
cli, err := mcm.GetMachineCliById(machineId)
|
||||
if err == nil {
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
_, authCert, err := m.getMachineAndAuthCert(machineId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.GetCliByAc(authCert.Name)
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) GetSshTunnelMachine(machineId int) (*mcm.SshTunnelMachine, error) {
|
||||
return mcm.GetSshTunnelMachine(machineId, func(mid uint64) (*mcm.MachineInfo, error) {
|
||||
return m.ToMachineInfoById(mid)
|
||||
@@ -229,62 +282,68 @@ func (m *machineAppImpl) GetMachineStats(machineId uint64) (*mcm.Stats, error) {
|
||||
return cache.GetMachineStats(machineId)
|
||||
}
|
||||
|
||||
// 生成机器信息,根据授权凭证id填充用户密码等
|
||||
func (m *machineAppImpl) ToMachineInfoById(machineId uint64) (*mcm.MachineInfo, error) {
|
||||
me, err := m.GetById(new(entity.Machine), machineId)
|
||||
// 根据授权凭证,生成机器信息
|
||||
func (m *machineAppImpl) ToMachineInfoByAc(authCertName string) (*mcm.MachineInfo, error) {
|
||||
authCert, err := m.resourceAuthCertApp.GetAuthCert(authCertName)
|
||||
if err != nil {
|
||||
return nil, errorx.NewBiz("机器信息不存在")
|
||||
}
|
||||
if me.Status != entity.MachineStatusEnable && me.Protocol == 1 {
|
||||
return nil, errorx.NewBiz("该机器已被停用")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if mi, err := m.toMachineInfo(me); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return mi, nil
|
||||
machine := &entity.Machine{
|
||||
Code: authCert.ResourceCode,
|
||||
}
|
||||
if err := m.GetBy(machine); err != nil {
|
||||
return nil, errorx.NewBiz("该授权凭证关联的机器信息不存在")
|
||||
}
|
||||
|
||||
return m.toMi(machine, authCert)
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) toMachineInfo(me *entity.Machine) (*mcm.MachineInfo, error) {
|
||||
// 生成机器信息,根据授权凭证id填充用户密码等
|
||||
func (m *machineAppImpl) ToMachineInfoById(machineId uint64) (*mcm.MachineInfo, error) {
|
||||
me, authCert, err := m.getMachineAndAuthCert(machineId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.toMi(me, authCert)
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) getMachineAndAuthCert(machineId uint64) (*entity.Machine, *tagentity.ResourceAuthCert, error) {
|
||||
me, err := m.GetById(new(entity.Machine), machineId)
|
||||
if err != nil {
|
||||
return nil, nil, errorx.NewBiz("[%d]机器信息不存在", machineId)
|
||||
}
|
||||
if me.Status != entity.MachineStatusEnable && me.Protocol == 1 {
|
||||
return nil, nil, errorx.NewBiz("[%s]该机器已被停用", me.Code)
|
||||
}
|
||||
|
||||
authCert, err := m.resourceAuthCertApp.GetResourceAuthCert(tagentity.TagTypeMachine, me.Code)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return me, authCert, nil
|
||||
}
|
||||
|
||||
func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAuthCert) (*mcm.MachineInfo, error) {
|
||||
mi := new(mcm.MachineInfo)
|
||||
mi.Id = me.Id
|
||||
mi.Name = me.Name
|
||||
mi.Ip = me.Ip
|
||||
mi.Port = me.Port
|
||||
mi.Username = me.Username
|
||||
mi.TagPath = m.tagApp.ListTagPathByResource(consts.TagResourceTypeMachine, me.Code)
|
||||
mi.TagPath = m.tagApp.ListTagPathByResource(int8(tagentity.TagTypeMachineAuthCert), authCert.Name)
|
||||
mi.EnableRecorder = me.EnableRecorder
|
||||
mi.Protocol = me.Protocol
|
||||
|
||||
if me.UseAuthCert() {
|
||||
ac, err := m.authCertApp.GetById(new(entity.AuthCert), uint64(me.AuthCertId))
|
||||
if err != nil {
|
||||
return nil, errorx.NewBiz("授权凭证信息已不存在,请重新关联")
|
||||
}
|
||||
mi.AuthMethod = ac.AuthMethod
|
||||
if err := ac.PwdDecrypt(); err != nil {
|
||||
return nil, errorx.NewBiz(err.Error())
|
||||
}
|
||||
mi.Password = ac.Password
|
||||
mi.Passphrase = ac.Passphrase
|
||||
} else {
|
||||
mi.AuthMethod = entity.AuthCertAuthMethodPassword
|
||||
if me.Id != 0 {
|
||||
if err := me.PwdDecrypt(); err != nil {
|
||||
return nil, errorx.NewBiz(err.Error())
|
||||
}
|
||||
}
|
||||
mi.Password = me.Password
|
||||
}
|
||||
mi.Username = authCert.Username
|
||||
mi.Password = authCert.Ciphertext
|
||||
mi.Passphrase = cast.ToString(authCert.Extra["passphrase"])
|
||||
mi.AuthMethod = int8(authCert.CiphertextType)
|
||||
|
||||
// 使用了ssh隧道,则将隧道机器信息也附上
|
||||
if me.SshTunnelMachineId > 0 {
|
||||
sshTunnelMe, err := m.GetById(new(entity.Machine), uint64(me.SshTunnelMachineId))
|
||||
if err != nil {
|
||||
return nil, errorx.NewBiz("隧道机器信息不存在")
|
||||
}
|
||||
sshTunnelMi, err := m.toMachineInfo(sshTunnelMe)
|
||||
sshTunnelMi, err := m.ToMachineInfoById(uint64(me.SshTunnelMachineId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"mayfly-go/internal/machine/api/form"
|
||||
"mayfly-go/internal/machine/config"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/domain/repository"
|
||||
@@ -17,6 +15,7 @@ import (
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/bytex"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -25,6 +24,13 @@ import (
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
type MachineFileOpParam struct {
|
||||
MachineId uint64 `json:"machineId" binding:"required" form:"machineId"`
|
||||
Protocol int `json:"protocol" binding:"required" form:"protocol"`
|
||||
AuthCertName string `json:"authCertName" binding:"required" form:"authCertName"` // 授权凭证
|
||||
Path string `json:"path" form:"path"` // 文件路径
|
||||
}
|
||||
|
||||
type MachineFile interface {
|
||||
base.App[*entity.MachineFile]
|
||||
|
||||
@@ -36,50 +42,47 @@ type MachineFile interface {
|
||||
|
||||
Save(ctx context.Context, entity *entity.MachineFile) error
|
||||
|
||||
// 获取文件关联的机器信息,主要用于记录日志使用
|
||||
// GetMachine(fileId uint64) *mcm.Info
|
||||
|
||||
// 检查文件路径,并返回机器id
|
||||
GetMachineCli(fileId uint64, path ...string) (*mcm.Cli, error)
|
||||
// 获取机器cli
|
||||
GetMachineCli(authCertName string) (*mcm.Cli, error)
|
||||
|
||||
GetRdpFilePath(MachineId uint64, path string) string
|
||||
|
||||
/** sftp 相关操作 **/
|
||||
|
||||
// 创建目录
|
||||
MkDir(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
|
||||
MkDir(opParam *MachineFileOpParam) (*mcm.MachineInfo, error)
|
||||
|
||||
// 创建文件
|
||||
CreateFile(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
|
||||
CreateFile(opParam *MachineFileOpParam) (*mcm.MachineInfo, error)
|
||||
|
||||
// 读取目录
|
||||
ReadDir(fid uint64, opForm *form.ServerFileOptionForm) ([]fs.FileInfo, error)
|
||||
ReadDir(opParam *MachineFileOpParam) ([]fs.FileInfo, error)
|
||||
|
||||
// 获取指定目录内容大小
|
||||
GetDirSize(fid uint64, opForm *form.ServerFileOptionForm) (string, error)
|
||||
GetDirSize(opParam *MachineFileOpParam) (string, error)
|
||||
|
||||
// 获取文件stat
|
||||
FileStat(opForm *form.ServerFileOptionForm) (string, error)
|
||||
FileStat(opParam *MachineFileOpParam) (string, error)
|
||||
|
||||
// 读取文件内容
|
||||
ReadFile(fileId uint64, path string) (*sftp.File, *mcm.MachineInfo, error)
|
||||
ReadFile(opParam *MachineFileOpParam) (*sftp.File, *mcm.MachineInfo, error)
|
||||
|
||||
// 写文件
|
||||
WriteFileContent(fileId uint64, path string, content []byte, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
|
||||
WriteFileContent(opParam *MachineFileOpParam, content []byte) (*mcm.MachineInfo, error)
|
||||
|
||||
// 文件上传
|
||||
UploadFile(fileId uint64, path, filename string, reader io.Reader, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
|
||||
UploadFile(opParam *MachineFileOpParam, filename string, reader io.Reader) (*mcm.MachineInfo, error)
|
||||
|
||||
UploadFiles(basePath string, fileHeaders []*multipart.FileHeader, paths []string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
|
||||
UploadFiles(opParam *MachineFileOpParam, basePath string, fileHeaders []*multipart.FileHeader, paths []string) (*mcm.MachineInfo, error)
|
||||
|
||||
// 移除文件
|
||||
RemoveFile(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error)
|
||||
RemoveFile(opParam *MachineFileOpParam, path ...string) (*mcm.MachineInfo, error)
|
||||
|
||||
Copy(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error)
|
||||
Copy(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error)
|
||||
|
||||
Mv(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error)
|
||||
Mv(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error)
|
||||
|
||||
Rename(renameForm *form.MachineFileRename) (*mcm.MachineInfo, error)
|
||||
Rename(opParam *MachineFileOpParam, newname string) (*mcm.MachineInfo, error)
|
||||
}
|
||||
|
||||
type machineFileAppImpl struct {
|
||||
@@ -117,29 +120,37 @@ func (m *machineFileAppImpl) Save(ctx context.Context, mf *entity.MachineFile) e
|
||||
return m.Insert(ctx, mf)
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) ReadDir(fid uint64, opForm *form.ServerFileOptionForm) ([]fs.FileInfo, error) {
|
||||
if !strings.HasSuffix(opForm.Path, "/") {
|
||||
opForm.Path = opForm.Path + "/"
|
||||
func (m *machineFileAppImpl) ReadDir(opParam *MachineFileOpParam) ([]fs.FileInfo, error) {
|
||||
path := opParam.Path
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path = path + "/"
|
||||
}
|
||||
|
||||
// 如果是rdp,则直接读取本地文件
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
opForm.Path = m.GetRdpFilePath(opForm.MachineId, opForm.Path)
|
||||
return ioutil.ReadDir(opForm.Path)
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opParam.MachineId, path)
|
||||
dirs, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return collx.ArrayMap[fs.DirEntry, fs.FileInfo](dirs, func(val fs.DirEntry) fs.FileInfo {
|
||||
fi, _ := val.Info()
|
||||
return fi
|
||||
}), nil
|
||||
}
|
||||
|
||||
_, sftpCli, err := m.GetMachineSftpCli(fid, opForm.Path)
|
||||
_, sftpCli, err := m.GetMachineSftpCli(opParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sftpCli.ReadDir(opForm.Path)
|
||||
return sftpCli.ReadDir(path)
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) GetDirSize(fid uint64, opForm *form.ServerFileOptionForm) (string, error) {
|
||||
path := opForm.Path
|
||||
func (m *machineFileAppImpl) GetDirSize(opParam *MachineFileOpParam) (string, error) {
|
||||
path := opParam.Path
|
||||
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
dirPath := m.GetRdpFilePath(opForm.MachineId, path)
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
dirPath := m.GetRdpFilePath(opParam.MachineId, path)
|
||||
|
||||
// 递归计算目录下文件大小
|
||||
var totalSize int64
|
||||
@@ -160,7 +171,7 @@ func (m *machineFileAppImpl) GetDirSize(fid uint64, opForm *form.ServerFileOptio
|
||||
return bytex.FormatSize(totalSize), nil
|
||||
}
|
||||
|
||||
mcli, err := m.GetMachineCli(fid, path)
|
||||
mcli, err := m.GetMachineCli(opParam.AuthCertName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -184,32 +195,34 @@ func (m *machineFileAppImpl) GetDirSize(fid uint64, opForm *form.ServerFileOptio
|
||||
return strings.Split(res, "\t")[0], nil
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) FileStat(opForm *form.ServerFileOptionForm) (string, error) {
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
path := m.GetRdpFilePath(opForm.MachineId, opForm.Path)
|
||||
func (m *machineFileAppImpl) FileStat(opParam *MachineFileOpParam) (string, error) {
|
||||
path := opParam.Path
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opParam.MachineId, path)
|
||||
stat, err := os.Stat(path)
|
||||
return fmt.Sprintf("%v", stat), err
|
||||
}
|
||||
|
||||
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path)
|
||||
mcli, err := m.GetMachineCli(opParam.AuthCertName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mcli.Run(fmt.Sprintf("stat -L %s", opForm.Path))
|
||||
return mcli.Run(fmt.Sprintf("stat -L %s", path))
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) MkDir(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
|
||||
func (m *machineFileAppImpl) MkDir(opParam *MachineFileOpParam) (*mcm.MachineInfo, error) {
|
||||
path := opParam.Path
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path = path + "/"
|
||||
}
|
||||
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opForm.MachineId, path)
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opParam.MachineId, path)
|
||||
os.MkdirAll(path, os.ModePerm)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(fid, path)
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -218,14 +231,15 @@ func (m *machineFileAppImpl) MkDir(fid uint64, path string, opForm *form.ServerF
|
||||
return mi, err
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) CreateFile(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(fid, path)
|
||||
func (m *machineFileAppImpl) CreateFile(opParam *MachineFileOpParam) (*mcm.MachineInfo, error) {
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opForm.MachineId, path)
|
||||
path := opParam.Path
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opParam.MachineId, path)
|
||||
file, err := os.Create(path)
|
||||
defer file.Close()
|
||||
return nil, err
|
||||
@@ -239,22 +253,22 @@ func (m *machineFileAppImpl) CreateFile(fid uint64, path string, opForm *form.Se
|
||||
return mi, err
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) ReadFile(fileId uint64, path string) (*sftp.File, *mcm.MachineInfo, error) {
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(fileId, path)
|
||||
func (m *machineFileAppImpl) ReadFile(opParam *MachineFileOpParam) (*sftp.File, *mcm.MachineInfo, error) {
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
fc, err := sftpCli.Open(path)
|
||||
fc, err := sftpCli.Open(opParam.Path)
|
||||
return fc, mi, err
|
||||
}
|
||||
|
||||
// 写文件内容
|
||||
func (m *machineFileAppImpl) WriteFileContent(fileId uint64, path string, content []byte, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
|
||||
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opForm.MachineId, path)
|
||||
func (m *machineFileAppImpl) WriteFileContent(opParam *MachineFileOpParam, content []byte) (*mcm.MachineInfo, error) {
|
||||
path := opParam.Path
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opParam.MachineId, path)
|
||||
file, err := os.Create(path)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
@@ -264,7 +278,7 @@ func (m *machineFileAppImpl) WriteFileContent(fileId uint64, path string, conten
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(fileId, path)
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -279,13 +293,14 @@ func (m *machineFileAppImpl) WriteFileContent(fileId uint64, path string, conten
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
func (m *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, reader io.Reader, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
|
||||
func (m *machineFileAppImpl) UploadFile(opParam *MachineFileOpParam, filename string, reader io.Reader) (*mcm.MachineInfo, error) {
|
||||
path := opParam.Path
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path = path + "/"
|
||||
}
|
||||
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opForm.MachineId, path)
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
path = m.GetRdpFilePath(opParam.MachineId, path)
|
||||
file, err := os.Create(path + filename)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
@@ -295,7 +310,7 @@ func (m *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, re
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(fileId, path)
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -309,9 +324,9 @@ func (m *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, re
|
||||
return mi, err
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) UploadFiles(basePath string, fileHeaders []*multipart.FileHeader, paths []string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
baseFolder := m.GetRdpFilePath(opForm.MachineId, basePath)
|
||||
func (m *machineFileAppImpl) UploadFiles(opParam *MachineFileOpParam, basePath string, fileHeaders []*multipart.FileHeader, paths []string) (*mcm.MachineInfo, error) {
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
baseFolder := m.GetRdpFilePath(opParam.MachineId, basePath)
|
||||
|
||||
for i, fileHeader := range fileHeaders {
|
||||
file, err := fileHeader.Open()
|
||||
@@ -326,7 +341,11 @@ func (m *machineFileAppImpl) UploadFiles(basePath string, fileHeaders []*multipa
|
||||
rdpBaseDir = rdpBaseDir + "/"
|
||||
}
|
||||
rdpDir := filepath.Dir(rdpBaseDir + paths[i])
|
||||
m.MkDir(0, rdpDir, opForm)
|
||||
m.MkDir(&MachineFileOpParam{
|
||||
MachineId: opParam.MachineId,
|
||||
Protocol: opParam.Protocol,
|
||||
Path: rdpDir,
|
||||
})
|
||||
|
||||
// 创建文件
|
||||
if !strings.HasSuffix(baseFolder, "/") {
|
||||
@@ -348,24 +367,23 @@ func (m *machineFileAppImpl) UploadFiles(basePath string, fileHeaders []*multipa
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
func (m *machineFileAppImpl) RemoveFile(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error) {
|
||||
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
for _, pt := range opForm.Path {
|
||||
pt = m.GetRdpFilePath(opForm.MachineId, pt)
|
||||
func (m *machineFileAppImpl) RemoveFile(opParam *MachineFileOpParam, path ...string) (*mcm.MachineInfo, error) {
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
for _, pt := range path {
|
||||
pt = m.GetRdpFilePath(opParam.MachineId, pt)
|
||||
os.RemoveAll(pt)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path...)
|
||||
mcli, err := m.GetMachineCli(opParam.AuthCertName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minfo := mcli.Info
|
||||
|
||||
// 优先使用命令删除(速度快),sftp需要递归遍历删除子文件等
|
||||
res, err := mcli.Run(fmt.Sprintf("rm -rf %s", strings.Join(opForm.Path, " ")))
|
||||
res, err := mcli.Run(fmt.Sprintf("rm -rf %s", strings.Join(path, " ")))
|
||||
if err == nil {
|
||||
return minfo, nil
|
||||
}
|
||||
@@ -376,7 +394,7 @@ func (m *machineFileAppImpl) RemoveFile(opForm *form.MachineFileOpForm) (*mcm.Ma
|
||||
return minfo, err
|
||||
}
|
||||
|
||||
for _, p := range opForm.Path {
|
||||
for _, p := range path {
|
||||
err = sftpCli.RemoveAll(p)
|
||||
if err != nil {
|
||||
break
|
||||
@@ -385,11 +403,11 @@ func (m *machineFileAppImpl) RemoveFile(opForm *form.MachineFileOpForm) (*mcm.Ma
|
||||
return minfo, err
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) Copy(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error) {
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
for _, pt := range opForm.Path {
|
||||
srcPath := m.GetRdpFilePath(opForm.MachineId, pt)
|
||||
targetPath := m.GetRdpFilePath(opForm.MachineId, opForm.ToPath+pt)
|
||||
func (m *machineFileAppImpl) Copy(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error) {
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
for _, pt := range path {
|
||||
srcPath := m.GetRdpFilePath(opParam.MachineId, pt)
|
||||
targetPath := m.GetRdpFilePath(opParam.MachineId, toPath+pt)
|
||||
|
||||
// 打开源文件
|
||||
srcFile, err := os.Open(srcPath)
|
||||
@@ -408,59 +426,57 @@ func (m *machineFileAppImpl) Copy(opForm *form.MachineFileOpForm) (*mcm.MachineI
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path...)
|
||||
mcli, err := m.GetMachineCli(opParam.AuthCertName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mi := mcli.Info
|
||||
res, err := mcli.Run(fmt.Sprintf("cp -r %s %s", strings.Join(opForm.Path, " "), opForm.ToPath))
|
||||
res, err := mcli.Run(fmt.Sprintf("cp -r %s %s", strings.Join(path, " "), toPath))
|
||||
if err != nil {
|
||||
return mi, errors.New(res)
|
||||
}
|
||||
return mi, err
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) Mv(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error) {
|
||||
if opForm.Protocol == entity.MachineProtocolRdp {
|
||||
for _, pt := range opForm.Path {
|
||||
func (m *machineFileAppImpl) Mv(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error) {
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
for _, pt := range path {
|
||||
// 获取文件名
|
||||
filename := filepath.Base(pt)
|
||||
topath := opForm.ToPath
|
||||
if !strings.HasSuffix(topath, "/") {
|
||||
topath += "/"
|
||||
if !strings.HasSuffix(toPath, "/") {
|
||||
toPath += "/"
|
||||
}
|
||||
|
||||
srcPath := m.GetRdpFilePath(opForm.MachineId, pt)
|
||||
targetPath := m.GetRdpFilePath(opForm.MachineId, topath+filename)
|
||||
srcPath := m.GetRdpFilePath(opParam.MachineId, pt)
|
||||
targetPath := m.GetRdpFilePath(opParam.MachineId, toPath+filename)
|
||||
os.Rename(srcPath, targetPath)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path...)
|
||||
mcli, err := m.GetMachineCli(opParam.AuthCertName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mi := mcli.Info
|
||||
res, err := mcli.Run(fmt.Sprintf("mv %s %s", strings.Join(opForm.Path, " "), opForm.ToPath))
|
||||
res, err := mcli.Run(fmt.Sprintf("mv %s %s", strings.Join(path, " "), toPath))
|
||||
if err != nil {
|
||||
return mi, errorx.NewBiz(res)
|
||||
}
|
||||
return mi, err
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) Rename(renameForm *form.MachineFileRename) (*mcm.MachineInfo, error) {
|
||||
oldname := renameForm.Oldname
|
||||
newname := renameForm.Newname
|
||||
if renameForm.Protocol == entity.MachineProtocolRdp {
|
||||
oldname = m.GetRdpFilePath(renameForm.MachineId, renameForm.Oldname)
|
||||
newname = m.GetRdpFilePath(renameForm.MachineId, renameForm.Newname)
|
||||
func (m *machineFileAppImpl) Rename(opParam *MachineFileOpParam, newname string) (*mcm.MachineInfo, error) {
|
||||
oldname := opParam.Path
|
||||
if opParam.Protocol == entity.MachineProtocolRdp {
|
||||
oldname = m.GetRdpFilePath(opParam.MachineId, oldname)
|
||||
newname = m.GetRdpFilePath(opParam.MachineId, newname)
|
||||
return nil, os.Rename(oldname, newname)
|
||||
}
|
||||
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(renameForm.FileId, newname)
|
||||
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -468,24 +484,13 @@ func (m *machineFileAppImpl) Rename(renameForm *form.MachineFileRename) (*mcm.Ma
|
||||
}
|
||||
|
||||
// 获取文件机器cli
|
||||
func (m *machineFileAppImpl) GetMachineCli(fid uint64, inputPath ...string) (*mcm.Cli, error) {
|
||||
mf, err := m.GetById(new(entity.MachineFile), fid)
|
||||
if err != nil {
|
||||
return nil, errorx.NewBiz("文件不存在")
|
||||
}
|
||||
|
||||
for _, path := range inputPath {
|
||||
// 接口传入的地址需为配置路径的子路径
|
||||
if !strings.HasPrefix(path, mf.Path) {
|
||||
return nil, errorx.NewBiz("无权访问该目录或文件: %s", path)
|
||||
}
|
||||
}
|
||||
return m.machineApp.GetCli(mf.MachineId)
|
||||
func (m *machineFileAppImpl) GetMachineCli(authCertName string) (*mcm.Cli, error) {
|
||||
return m.machineApp.GetCliByAc(authCertName)
|
||||
}
|
||||
|
||||
// 获取文件机器 sftp cli
|
||||
func (m *machineFileAppImpl) GetMachineSftpCli(fid uint64, inputPath ...string) (*mcm.MachineInfo, *sftp.Client, error) {
|
||||
mcli, err := m.GetMachineCli(fid, inputPath...)
|
||||
func (m *machineFileAppImpl) GetMachineSftpCli(opParam *MachineFileOpParam) (*mcm.MachineInfo, *sftp.Client, error) {
|
||||
mcli, err := m.GetMachineCli(opParam.AuthCertName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -498,6 +503,6 @@ func (m *machineFileAppImpl) GetMachineSftpCli(fid uint64, inputPath ...string)
|
||||
return mcli.Info, sftpCli, nil
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) GetRdpFilePath(MachineId uint64, path string) string {
|
||||
return fmt.Sprintf("%s/%d%s", config.GetMachine().GuacdFilePath, MachineId, path)
|
||||
func (m *machineFileAppImpl) GetRdpFilePath(machineId uint64, path string) string {
|
||||
return fmt.Sprintf("%s/%d%s", config.GetMachine().GuacdFilePath, machineId, path)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mayfly-go/internal/common/utils"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
@@ -14,9 +12,6 @@ type Machine struct {
|
||||
Protocol int `json:"protocol"` // 连接协议 1.ssh 2.rdp
|
||||
Ip string `json:"ip"` // IP地址
|
||||
Port int `json:"port"` // 端口号
|
||||
Username string `json:"username"` // 用户名
|
||||
Password string `json:"password"` // 密码
|
||||
AuthCertId int `json:"authCertId"` // 授权凭证id
|
||||
Status int8 `json:"status"` // 状态 1:启用;2:停用
|
||||
Remark string `json:"remark"` // 备注
|
||||
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
@@ -30,27 +25,3 @@ const (
|
||||
MachineProtocolSsh = 1
|
||||
MachineProtocolRdp = 2
|
||||
)
|
||||
|
||||
func (m *Machine) PwdEncrypt() error {
|
||||
// 密码替换为加密后的密码
|
||||
password, err := utils.PwdAesEncrypt(m.Password)
|
||||
if err != nil {
|
||||
return errors.New("加密主机密码失败")
|
||||
}
|
||||
m.Password = password
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Machine) PwdDecrypt() error {
|
||||
// 密码替换为解密后的密码
|
||||
password, err := utils.PwdAesDecrypt(m.Password)
|
||||
if err != nil {
|
||||
return errors.New("解密主机密码失败")
|
||||
}
|
||||
m.Password = password
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Machine) UseAuthCert() bool {
|
||||
return m.AuthCertId > 0
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// creates the tunnel to the remote machine (via guacd)
|
||||
func DoConnect(query url.Values, parameters map[string]string, machineId uint64) (Tunnel, error) {
|
||||
func DoConnect(query url.Values, parameters map[string]string, ac string) (Tunnel, error) {
|
||||
conf := NewGuacamoleConfiguration()
|
||||
|
||||
parameters["enable-wallpaper"] = "true" // 允许显示墙纸
|
||||
@@ -33,7 +33,7 @@ func DoConnect(query url.Values, parameters map[string]string, machineId uint64)
|
||||
parameters["enable-drive"] = "true"
|
||||
parameters["drive-name"] = "Filesystem"
|
||||
parameters["create-drive-path"] = "true"
|
||||
parameters["drive-path"] = fmt.Sprintf("/rdp-file/%d", machineId)
|
||||
parameters["drive-path"] = fmt.Sprintf("/rdp-file/%s", ac)
|
||||
|
||||
conf.Protocol = parameters["scheme"]
|
||||
conf.Parameters = parameters
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
package guac
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// MemorySessionStore is a simple in-memory store of connected sessions that is used by
|
||||
// the WebsocketServer to store active sessions.
|
||||
type MemorySessionStore struct {
|
||||
sync.RWMutex
|
||||
ConnIds map[uint64]Tunnel
|
||||
ConnIds map[string]Tunnel
|
||||
}
|
||||
|
||||
// NewMemorySessionStore creates a new store
|
||||
func NewMemorySessionStore() *MemorySessionStore {
|
||||
return &MemorySessionStore{
|
||||
ConnIds: map[uint64]Tunnel{},
|
||||
ConnIds: map[string]Tunnel{},
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a connection by uuid
|
||||
func (s *MemorySessionStore) Get(id uint64) Tunnel {
|
||||
func (s *MemorySessionStore) Get(id string) Tunnel {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return s.ConnIds[id]
|
||||
}
|
||||
|
||||
// Add inserts a new connection by uuid
|
||||
func (s *MemorySessionStore) Add(id uint64, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
|
||||
func (s *MemorySessionStore) Add(id string, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
n, ok := s.ConnIds[id]
|
||||
@@ -41,7 +42,7 @@ func (s *MemorySessionStore) Add(id uint64, conn *websocket.Conn, req *http.Requ
|
||||
}
|
||||
|
||||
// Delete removes a connection by uuid
|
||||
func (s *MemorySessionStore) Delete(id uint64, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
|
||||
func (s *MemorySessionStore) Delete(id string, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
n, ok := s.ConnIds[id]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package mcm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mayfly-go/internal/common/consts"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/cache"
|
||||
"mayfly-go/pkg/logx"
|
||||
"time"
|
||||
@@ -29,29 +31,63 @@ func init() {
|
||||
go checkClientAvailability(3 * time.Minute)
|
||||
}
|
||||
|
||||
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
|
||||
func GetMachineCli(machineId uint64, getMachine func(uint64) (*MachineInfo, error)) (*Cli, error) {
|
||||
if load, ok := cliCache.Get(machineId); ok {
|
||||
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建。
|
||||
// @param 机器的授权凭证名
|
||||
func GetMachineCli(authCertName string, getMachine func(string) (*MachineInfo, error)) (*Cli, error) {
|
||||
if load, ok := cliCache.Get(authCertName); ok {
|
||||
return load.(*Cli), nil
|
||||
}
|
||||
|
||||
me, err := getMachine(machineId)
|
||||
mi, err := getMachine(authCertName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mi.Key = authCertName
|
||||
c, err := mi.Conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := me.Conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cliCache.Put(machineId, c)
|
||||
cliCache.Put(authCertName, c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// 根据机器id从已连接的机器客户端中获取特权账号连接, 若不存在特权账号,则随机返回一个
|
||||
func GetMachineCliById(machineId uint64) (*Cli, error) {
|
||||
// 遍历所有机器连接实例,删除指定机器id关联的连接...
|
||||
items := cliCache.Items()
|
||||
|
||||
var machineCli *Cli
|
||||
for _, v := range items {
|
||||
cli := v.Value.(*Cli)
|
||||
mi := cli.Info
|
||||
if mi.Id != machineId {
|
||||
continue
|
||||
}
|
||||
machineCli = cli
|
||||
|
||||
// 如果是特权账号,则跳出
|
||||
if mi.AuthCertType == tagentity.AuthCertTypePrivileged {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if machineCli != nil {
|
||||
return machineCli, nil
|
||||
}
|
||||
return nil, errors.New("不存在该机器id的连接")
|
||||
}
|
||||
|
||||
// 删除指定机器缓存客户端,并关闭客户端连接
|
||||
func DeleteCli(id uint64) {
|
||||
cliCache.Delete(id)
|
||||
// 遍历所有机器连接实例,删除指定机器id关联的连接...
|
||||
items := cliCache.Items()
|
||||
for _, v := range items {
|
||||
mi := v.Value.(*Cli).Info
|
||||
if mi.Id == id {
|
||||
cliCache.Delete(mi.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查缓存中的客户端是否可用,不可用则关闭客户端连接
|
||||
|
||||
@@ -2,7 +2,7 @@ package mcm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"net"
|
||||
@@ -13,16 +13,20 @@ import (
|
||||
|
||||
// 机器信息
|
||||
type MachineInfo struct {
|
||||
Key string `json:"key"` // 缓存key
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Protocol int `json:"protocol"`
|
||||
|
||||
Ip string `json:"ip"` // IP地址
|
||||
Port int `json:"-"` // 端口号
|
||||
AuthMethod int8 `json:"-"` // 授权认证方式
|
||||
Username string `json:"-"` // 用户名
|
||||
Password string `json:"-"`
|
||||
Passphrase string `json:"-"` // 私钥口令
|
||||
Ip string `json:"ip"` // IP地址
|
||||
Port int `json:"-"` // 端口号
|
||||
|
||||
AuthCertName string `json:"authCertName"`
|
||||
AuthCertType tagentity.AuthCertType `json:"-"`
|
||||
AuthMethod int8 `json:"-"` // 授权认证方式
|
||||
Username string `json:"-"` // 用户名
|
||||
Password string `json:"-"`
|
||||
Passphrase string `json:"-"` // 私钥口令
|
||||
|
||||
SshTunnelMachine *MachineInfo `json:"-"` // ssh隧道机器
|
||||
TempSshMachineId uint64 `json:"-"` // ssh隧道机器id,用于记录隧道机器id,连接出错后关闭隧道
|
||||
@@ -118,9 +122,9 @@ func GetSshClient(m *MachineInfo, jumpClient *ssh.Client) (*ssh.Client, error) {
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
if m.AuthMethod == entity.AuthCertAuthMethodPassword {
|
||||
if m.AuthMethod == int8(tagentity.AuthCertCiphertextTypePassword) {
|
||||
config.Auth = []ssh.AuthMethod{ssh.Password(m.Password)}
|
||||
} else if m.AuthMethod == entity.MachineAuthMethodPublicKey {
|
||||
} else if m.AuthMethod == int8(tagentity.AuthCertCiphertextTypePrivateKey) {
|
||||
var key ssh.Signer
|
||||
var err error
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ func InitMachineRouter(router *gin.RouterGroup) {
|
||||
req.BatchSetGroup(machines, reqs[:])
|
||||
|
||||
// 终端连接
|
||||
machines.GET(":machineId/terminal", m.WsSSH)
|
||||
machines.GET("terminal/:ac", m.WsSSH)
|
||||
|
||||
// 终端连接
|
||||
machines.GET(":machineId/rdp", m.WsGuacamole)
|
||||
machines.GET("rdp/:ac", m.WsGuacamole)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"mayfly-go/internal/mongo/domain/repository"
|
||||
"mayfly-go/internal/mongo/mgm"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
@@ -59,7 +60,7 @@ func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
},
|
||||
func(ctx context.Context) error {
|
||||
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceType: consts.TagResourceTypeMongo,
|
||||
ResourceType: tagentity.TagTypeMongo,
|
||||
ResourceCode: mongoEntity.Code,
|
||||
})
|
||||
})
|
||||
@@ -90,7 +91,7 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
|
||||
return d.Insert(ctx, m)
|
||||
}, func(ctx context.Context) error {
|
||||
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceType: consts.TagResourceTypeMongo,
|
||||
ResourceType: tagentity.TagTypeMongo,
|
||||
ResourceCode: m.Code,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
@@ -113,7 +114,7 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
|
||||
return d.UpdateById(ctx, m)
|
||||
}, func(ctx context.Context) error {
|
||||
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceType: consts.TagResourceTypeMongo,
|
||||
ResourceType: tagentity.TagTypeMongo,
|
||||
ResourceCode: oldMongo.Code,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"mayfly-go/internal/redis/domain/repository"
|
||||
"mayfly-go/internal/redis/rdm"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagenttiy "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
@@ -107,7 +108,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
|
||||
return r.Insert(ctx, re)
|
||||
}, func(ctx context.Context) error {
|
||||
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceType: consts.TagResourceTypeRedis,
|
||||
ResourceType: tagenttiy.TagTypeRedis,
|
||||
ResourceCode: re.Code,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
@@ -138,7 +139,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
|
||||
return r.UpdateById(ctx, re)
|
||||
}, func(ctx context.Context) error {
|
||||
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceType: consts.TagResourceTypeRedis,
|
||||
ResourceType: tagenttiy.TagTypeRedis,
|
||||
ResourceCode: oldRedis.Code,
|
||||
TagIds: tagIds,
|
||||
})
|
||||
@@ -161,7 +162,7 @@ func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
return r.DeleteById(ctx, id)
|
||||
}, func(ctx context.Context) error {
|
||||
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
|
||||
ResourceType: consts.TagResourceTypeRedis,
|
||||
ResourceType: tagenttiy.TagTypeRedis,
|
||||
ResourceCode: re.Code,
|
||||
})
|
||||
})
|
||||
|
||||
29
server/internal/tag/api/resource_auth_cert.go
Normal file
29
server/internal/tag/api/resource_auth_cert.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/application"
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
)
|
||||
|
||||
type ResourceAuthCert struct {
|
||||
ResourceAuthCertApp application.ResourceAuthCert `inject:""`
|
||||
}
|
||||
|
||||
func (r *ResourceAuthCert) ListByQuery(rc *req.Ctx) {
|
||||
cond := new(entity.ResourceAuthCert)
|
||||
cond.ResourceCode = rc.Query("resourceCode")
|
||||
cond.ResourceType = int8(rc.QueryInt("resourceType"))
|
||||
cond.Type = entity.AuthCertType(rc.QueryInt("type"))
|
||||
cond.CiphertextType = entity.AuthCertCiphertextType(rc.QueryInt("ciphertextType"))
|
||||
cond.Name = rc.Query("name")
|
||||
|
||||
var racs []*entity.ResourceAuthCert
|
||||
res, err := r.ResourceAuthCertApp.PageQuery(cond, rc.GetPageParam(), &racs)
|
||||
biz.ErrIsNil(err)
|
||||
for _, rac := range racs {
|
||||
rac.CiphertextDecrypt()
|
||||
}
|
||||
rc.ResData = res
|
||||
}
|
||||
@@ -15,14 +15,16 @@ import (
|
||||
|
||||
type TagTree struct {
|
||||
TagTreeApp application.TagTree `inject:""`
|
||||
|
||||
ResourceAuthCertApp application.ResourceAuthCert `inject:""`
|
||||
}
|
||||
|
||||
func (p *TagTree) GetTagTree(rc *req.Ctx) {
|
||||
tagType := rc.QueryInt("type")
|
||||
tagType := entity.TagType(rc.QueryInt("type"))
|
||||
// 超管返回所有标签树
|
||||
if rc.GetLoginAccount().Id == consts.AdminId {
|
||||
var tagTrees vo.TagTreeVOS
|
||||
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{Type: int8(tagType)}, &tagTrees)
|
||||
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{Type: tagType}, &tagTrees)
|
||||
rc.ResData = tagTrees.ToTrees(0)
|
||||
return
|
||||
}
|
||||
@@ -40,7 +42,7 @@ func (p *TagTree) GetTagTree(rc *req.Ctx) {
|
||||
|
||||
// 获取所有以root标签开头的子标签
|
||||
var tags []*entity.TagTree
|
||||
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{CodePathLikes: collx.MapKeys(rootTag), Type: int8(tagType)}, &tags)
|
||||
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{CodePathLikes: collx.MapKeys(rootTag), Type: tagType}, &tags)
|
||||
|
||||
tagTrees := make(vo.TagTreeVOS, 0)
|
||||
for _, tag := range tags {
|
||||
@@ -81,12 +83,14 @@ func (p *TagTree) DelTagTree(rc *req.Ctx) {
|
||||
biz.ErrIsNil(p.TagTreeApp.Delete(rc.MetaCtx, uint64(rc.PathParamInt("id"))))
|
||||
}
|
||||
|
||||
// 获取用户可操作的资源标签路径
|
||||
// 获取用户可操作的标签路径
|
||||
func (p *TagTree) TagResources(rc *req.Ctx) {
|
||||
resourceType := int8(rc.PathParamInt("rtype"))
|
||||
tagResources := p.TagTreeApp.GetAccountTagResources(rc.GetLoginAccount().Id, resourceType, "")
|
||||
tagPath2Resource := collx.ArrayToMap[entity.TagTree, string](tagResources, func(tagResource entity.TagTree) string {
|
||||
return tagResource.GetParentPath()
|
||||
accountId := rc.GetLoginAccount().Id
|
||||
tagResources := p.TagTreeApp.GetAccountTagResources(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType)})
|
||||
|
||||
tagPath2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(tagResource *entity.TagTree) string {
|
||||
return tagResource.GetParentPath(1)
|
||||
})
|
||||
|
||||
tagPaths := collx.MapKeys(tagPath2Resource)
|
||||
|
||||
22
server/internal/tag/api/vo/resource_auth_cert.go
Normal file
22
server/internal/tag/api/vo/resource_auth_cert.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ResourceAuthCert struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"` // 名称
|
||||
ResourceCode string `json:"resourceCode"` // 资源编号
|
||||
ResourceType int8 `json:"resourceType"` // 资源类型
|
||||
Username string `json:"username"` // 用户名
|
||||
Ciphertext string `json:"ciphertext"` // 密文
|
||||
CiphertextType entity.AuthCertCiphertextType `json:"ciphertextType"` // 密文类型
|
||||
Extra model.Map[string, any] `json:"extra"` // 账号需要的其他额外信息(如秘钥口令等)
|
||||
Type entity.AuthCertType `json:"type"` // 凭证类型
|
||||
Remark string `json:"remark"` // 备注
|
||||
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
}
|
||||
@@ -7,4 +7,5 @@ import (
|
||||
func InitIoc() {
|
||||
ioc.Register(new(tagTreeAppImpl), ioc.WithComponentName("TagTreeApp"))
|
||||
ioc.Register(new(teamAppImpl), ioc.WithComponentName("TeamApp"))
|
||||
ioc.Register(new(resourceAuthCertAppImpl), ioc.WithComponentName("ResourceAuthCertApp"))
|
||||
}
|
||||
|
||||
268
server/internal/tag/application/resouce_auth_cert.go
Normal file
268
server/internal/tag/application/resouce_auth_cert.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
)
|
||||
|
||||
type SaveAuthCertParam struct {
|
||||
ResourceCode string
|
||||
// 资源标签类型
|
||||
ResourceType entity.TagType
|
||||
|
||||
// 授权凭证类型
|
||||
AuthCertTagType entity.TagType
|
||||
|
||||
// 空数组则为删除该资源绑定的授权凭证
|
||||
AuthCerts []*entity.ResourceAuthCert
|
||||
}
|
||||
|
||||
type ResourceAuthCert interface {
|
||||
base.App[*entity.ResourceAuthCert]
|
||||
|
||||
// SaveAuthCert 保存资源授权凭证信息,不可放于事务中
|
||||
SaveAuthCert(ctx context.Context, param *SaveAuthCertParam) error
|
||||
|
||||
// GetAuthCert 根据授权凭证名称获取授权凭证
|
||||
GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error)
|
||||
|
||||
// GetResourceAuthCert 获取资源授权凭证,默认获取特权账号,若没有则返回第一个
|
||||
GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error)
|
||||
|
||||
// GetAccountAuthCert 获取账号有权限操作的授权凭证信息
|
||||
GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert
|
||||
|
||||
// FillAuthCert 填充资源的授权凭证信息
|
||||
// @param resources 实现了entity.IAuthCert接口的资源信息
|
||||
FillAuthCert(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert)
|
||||
}
|
||||
|
||||
type resourceAuthCertAppImpl struct {
|
||||
base.AppImpl[*entity.ResourceAuthCert, repository.ResourceAuthCert]
|
||||
|
||||
tagTreeApp TagTree `inject:"TagTreeApp"`
|
||||
}
|
||||
|
||||
// 注入Repo
|
||||
func (r *resourceAuthCertAppImpl) InjectResourceAuthCertRepo(resourceAuthCertRepo repository.ResourceAuthCert) {
|
||||
r.Repo = resourceAuthCertRepo
|
||||
}
|
||||
|
||||
func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *SaveAuthCertParam) error {
|
||||
resourceCode := params.ResourceCode
|
||||
resourceType := int8(params.ResourceType)
|
||||
resourceAuthCerts := params.AuthCerts
|
||||
authCertTagType := params.AuthCertTagType
|
||||
|
||||
if authCertTagType == 0 {
|
||||
return errorx.NewBiz("资源授权凭证所属标签类型不能为空")
|
||||
}
|
||||
|
||||
if resourceCode == "" {
|
||||
return errorx.NewBiz("资源授权凭证的资源编号不能为空")
|
||||
}
|
||||
|
||||
// 删除授权信息
|
||||
if len(resourceAuthCerts) == 0 {
|
||||
if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除该资源下的所有授权凭证资源标签
|
||||
if err := r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{
|
||||
ResourceCode: resourceCode,
|
||||
ResourceType: params.ResourceType,
|
||||
ChildType: authCertTagType,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
name2AuthCert := make(map[string]*entity.ResourceAuthCert, 0)
|
||||
for _, resourceAuthCert := range resourceAuthCerts {
|
||||
resourceAuthCert.ResourceCode = resourceCode
|
||||
resourceAuthCert.ResourceType = int8(resourceType)
|
||||
name2AuthCert[resourceAuthCert.Name] = resourceAuthCert
|
||||
|
||||
existNameAc := &entity.ResourceAuthCert{Name: resourceAuthCert.Name}
|
||||
if r.GetBy(existNameAc) == nil && existNameAc.ResourceCode != resourceCode {
|
||||
return errorx.NewBiz("授权凭证的名称不能重复[%s]", resourceAuthCert.Name)
|
||||
}
|
||||
|
||||
// 公共授权凭证,则无需进行密文加密,密文即为公共授权凭证名
|
||||
if resourceAuthCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
|
||||
continue
|
||||
}
|
||||
|
||||
// 密文加密
|
||||
if err := resourceAuthCert.CiphertextEncrypt(); err != nil {
|
||||
return errorx.NewBiz(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var oldAuthCert []*entity.ResourceAuthCert
|
||||
r.ListByCond(&entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}, &oldAuthCert)
|
||||
|
||||
var adds, dels, unmodifys []string
|
||||
if len(oldAuthCert) == 0 {
|
||||
adds = collx.MapKeys(name2AuthCert)
|
||||
} else {
|
||||
oldNames := collx.ArrayMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string {
|
||||
return ac.Name
|
||||
})
|
||||
adds, dels, unmodifys = collx.ArrayCompare[string](collx.MapKeys(name2AuthCert), oldNames)
|
||||
}
|
||||
|
||||
addAuthCerts := make([]*entity.ResourceAuthCert, 0)
|
||||
for _, add := range adds {
|
||||
addAc := name2AuthCert[add]
|
||||
addAc.Id = 0
|
||||
addAuthCerts = append(addAuthCerts, addAc)
|
||||
}
|
||||
|
||||
// 处理新增的授权凭证
|
||||
if len(addAuthCerts) > 0 {
|
||||
if err := r.BatchInsert(ctx, addAuthCerts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取资源编号对应的资源标签信息
|
||||
var resourceTags []*entity.TagTree
|
||||
r.tagTreeApp.ListByCond(&entity.TagTree{Type: params.ResourceType, Code: resourceCode}, &resourceTags)
|
||||
// 资源标签id(相当于父tag id)
|
||||
resourceTagIds := collx.ArrayMap(resourceTags, func(tag *entity.TagTree) uint64 {
|
||||
return tag.Id
|
||||
})
|
||||
|
||||
// 保存授权凭证类型的资源标签
|
||||
for _, authCert := range addAuthCerts {
|
||||
if err := r.tagTreeApp.SaveResource(ctx, &SaveResourceTagParam{
|
||||
ResourceCode: authCert.Name,
|
||||
ResourceType: authCertTagType,
|
||||
ResourceName: authCert.Username,
|
||||
TagIds: resourceTagIds,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, del := range dels {
|
||||
if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType, Name: del}); err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除对应授权凭证资源标签
|
||||
if err := r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{
|
||||
ResourceCode: del,
|
||||
ResourceType: authCertTagType,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, unmodify := range unmodifys {
|
||||
unmodifyAc := name2AuthCert[unmodify]
|
||||
if unmodifyAc.Id == 0 {
|
||||
continue
|
||||
}
|
||||
if err := r.UpdateById(ctx, unmodifyAc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resourceAuthCertAppImpl) GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error) {
|
||||
authCert := &entity.ResourceAuthCert{Name: authCertName}
|
||||
if err := r.GetBy(authCert); err != nil {
|
||||
return nil, errorx.NewBiz("该授权凭证不存在")
|
||||
}
|
||||
|
||||
return r.decryptAuthCert(authCert)
|
||||
}
|
||||
|
||||
func (r *resourceAuthCertAppImpl) GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error) {
|
||||
var resourceAuthCerts []*entity.ResourceAuthCert
|
||||
if err := r.ListByCond(&entity.ResourceAuthCert{
|
||||
ResourceType: int8(resourceType),
|
||||
ResourceCode: resourceCode,
|
||||
}, &resourceAuthCerts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resourceAuthCerts) == 0 {
|
||||
return nil, errorx.NewBiz("该资源不存在授权凭证账号")
|
||||
}
|
||||
|
||||
for _, resourceAuthCert := range resourceAuthCerts {
|
||||
if resourceAuthCert.Type == entity.AuthCertTypePrivileged {
|
||||
return r.decryptAuthCert(resourceAuthCert)
|
||||
}
|
||||
}
|
||||
|
||||
return r.decryptAuthCert(resourceAuthCerts[0])
|
||||
}
|
||||
|
||||
func (r *resourceAuthCertAppImpl) GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert {
|
||||
// 获取用户有权限操作的授权凭证资源标签
|
||||
tagQuery := &entity.TagTreeQuery{
|
||||
Type: authCertTagType,
|
||||
CodePathLikes: tagPath,
|
||||
}
|
||||
authCertTags := r.tagTreeApp.GetAccountTagResources(accountId, tagQuery)
|
||||
|
||||
// 获取所有授权凭证名称
|
||||
authCertNames := collx.ArrayMap(authCertTags, func(tag *entity.TagTree) string {
|
||||
return tag.Code
|
||||
})
|
||||
|
||||
var authCerts []*entity.ResourceAuthCert
|
||||
r.GetRepo().ListByWheres(collx.M{
|
||||
"name in ?": collx.ArrayDeduplicate(authCertNames),
|
||||
}, &authCerts)
|
||||
|
||||
return authCerts
|
||||
}
|
||||
|
||||
func (r *resourceAuthCertAppImpl) FillAuthCert(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert) {
|
||||
if len(resources) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 资源编号 -> 资源
|
||||
resourceCode2Resouce := collx.ArrayToMap(resources, func(ac entity.IAuthCert) string {
|
||||
return ac.GetCode()
|
||||
})
|
||||
|
||||
for _, authCert := range authCerts {
|
||||
resourceCode2Resouce[authCert.ResourceCode].SetAuthCert(entity.AuthCert{
|
||||
Name: authCert.Name,
|
||||
Username: authCert.Username,
|
||||
Type: authCert.Type,
|
||||
CiphertextType: authCert.CiphertextType,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 解密授权凭证信息
|
||||
func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) {
|
||||
if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
|
||||
// 如果是公共授权凭证,则密文为公共授权凭证名称,需要使用该名称再去获取对应的授权凭证
|
||||
authCert = &entity.ResourceAuthCert{Name: authCert.Ciphertext}
|
||||
if err := r.GetBy(authCert); err != nil {
|
||||
return nil, errorx.NewBiz("该公共授权凭证[%s]不存在", authCert.Ciphertext)
|
||||
}
|
||||
}
|
||||
|
||||
if err := authCert.CiphertextDecrypt(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authCert, nil
|
||||
}
|
||||
@@ -16,9 +16,19 @@ import (
|
||||
type SaveResourceTagParam struct {
|
||||
ResourceCode string
|
||||
ResourceName string
|
||||
ResourceType int8
|
||||
ResourceType entity.TagType
|
||||
|
||||
TagIds []uint64 // 关联标签,相当于父标签 pid
|
||||
TagIds []uint64 // 关联标签,相当于父标签 pid,空数组则为删除该资源绑定的标签
|
||||
}
|
||||
|
||||
type DelResourceTagParam struct {
|
||||
ResourceCode string
|
||||
ResourceType entity.TagType
|
||||
|
||||
Pid uint64 //父标签 pid
|
||||
|
||||
// 要删除的子节点类型,若存在值,则为删除资源标签下的指定类型的子标签
|
||||
ChildType entity.TagType
|
||||
}
|
||||
|
||||
type TagTree interface {
|
||||
@@ -34,7 +44,7 @@ type TagTree interface {
|
||||
// @param accountId 账号id
|
||||
// @param resourceType 资源类型
|
||||
// @param tagPath 访问指定的标签路径下关联的资源
|
||||
GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagTree
|
||||
GetAccountTagResources(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree
|
||||
|
||||
// 获取指定账号有权限操作的资源codes
|
||||
GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string
|
||||
@@ -42,6 +52,9 @@ type TagTree interface {
|
||||
// SaveResource 保存资源标签
|
||||
SaveResource(ctx context.Context, req *SaveResourceTagParam) error
|
||||
|
||||
// DeleteResource 删除资源标签,会删除该资源下所有子节点信息
|
||||
DeleteResource(ctx context.Context, param *DelResourceTagParam) error
|
||||
|
||||
// 根据资源信息获取对应的标签路径列表
|
||||
ListTagPathByResource(resourceType int8, resourceCode string) []string
|
||||
|
||||
@@ -115,12 +128,12 @@ func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity an
|
||||
p.GetRepo().SelectByCondition(condition, toEntity)
|
||||
}
|
||||
|
||||
func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagTree {
|
||||
func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree {
|
||||
tagResourceQuery := &entity.TagTreeQuery{
|
||||
Type: resourceType,
|
||||
Type: query.Type,
|
||||
}
|
||||
|
||||
var tagResources []entity.TagTree
|
||||
var tagResources []*entity.TagTree
|
||||
var accountTagPaths []string
|
||||
|
||||
if accountId != consts.AdminId {
|
||||
@@ -131,16 +144,37 @@ func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType i
|
||||
}
|
||||
}
|
||||
|
||||
tagResourceQuery.CodePathLike = tagPath
|
||||
// 去除空字符串标签
|
||||
tagPaths := collx.ArrayRemoveBlank(query.CodePathLikes)
|
||||
// 如果需要查询指定标签下的资源标签,则需要与用户拥有的权限进行过滤,避免越权
|
||||
if len(tagPaths) > 0 {
|
||||
// admin 则直接赋值需要获取的标签
|
||||
if len(accountTagPaths) == 0 {
|
||||
accountTagPaths = tagPaths
|
||||
} else {
|
||||
accountTagPaths = collx.ArrayFilter[string](tagPaths, func(s string) bool {
|
||||
for _, v := range accountTagPaths {
|
||||
// 要过滤的权限需要在用户拥有的子标签下, accountTagPath: test/ tagPath: test/test1/ -> true
|
||||
if strings.HasPrefix(v, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// tagResourceQuery.CodePathLike = tagPath
|
||||
tagResourceQuery.Codes = query.Codes
|
||||
tagResourceQuery.CodePathLikes = accountTagPaths
|
||||
p.ListByQuery(tagResourceQuery, &tagResources)
|
||||
return tagResources
|
||||
}
|
||||
|
||||
func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string {
|
||||
tagResources := p.GetAccountTagResources(accountId, resourceType, tagPath)
|
||||
tagResources := p.GetAccountTagResources(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType), CodePathLikes: []string{tagPath}})
|
||||
// resouce code去重
|
||||
code2Resource := collx.ArrayToMap[entity.TagTree, string](tagResources, func(val entity.TagTree) string {
|
||||
code2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(val *entity.TagTree) string {
|
||||
return val.Code
|
||||
})
|
||||
|
||||
@@ -149,7 +183,7 @@ func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType
|
||||
|
||||
func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagParam) error {
|
||||
resourceCode := req.ResourceCode
|
||||
resourceType := req.ResourceType
|
||||
resourceType := entity.TagType(req.ResourceType)
|
||||
resourceName := req.ResourceName
|
||||
tagIds := req.TagIds
|
||||
|
||||
@@ -162,7 +196,10 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
|
||||
|
||||
// 如果tagIds为空数组,则为删除该资源标签
|
||||
if len(tagIds) == 0 {
|
||||
return p.DeleteByCond(ctx, &entity.TagTree{Code: resourceCode, Type: resourceType})
|
||||
return p.DeleteResource(ctx, &DelResourceTagParam{
|
||||
ResourceType: resourceType,
|
||||
ResourceCode: resourceCode,
|
||||
})
|
||||
}
|
||||
|
||||
if resourceName == "" {
|
||||
@@ -205,10 +242,36 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
|
||||
|
||||
if len(delTagIds) > 0 {
|
||||
for _, tagId := range delTagIds {
|
||||
cond := &entity.TagTree{Code: resourceCode, Type: resourceType, Pid: tagId}
|
||||
if err := p.DeleteByCond(ctx, cond); err != nil {
|
||||
if err := p.DeleteResource(ctx, &DelResourceTagParam{
|
||||
ResourceType: resourceType,
|
||||
ResourceCode: resourceCode,
|
||||
Pid: tagId,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *tagTreeAppImpl) DeleteResource(ctx context.Context, param *DelResourceTagParam) error {
|
||||
// 获取资源编号对应的资源标签信息
|
||||
var resourceTags []*entity.TagTree
|
||||
p.ListByCond(&entity.TagTree{Type: param.ResourceType, Code: param.ResourceCode, Pid: param.Pid}, &resourceTags)
|
||||
if len(resourceTags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
delTagType := param.ChildType
|
||||
for _, resourceTag := range resourceTags {
|
||||
// 删除所有code_path下的子标签
|
||||
if err := p.DeleteByWheres(ctx, collx.M{
|
||||
"code_path LIKE ?": resourceTag.CodePath + "%",
|
||||
"type = ?": delTagType,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +280,7 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
|
||||
|
||||
func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string {
|
||||
var trs []*entity.TagTree
|
||||
p.ListByCond(&entity.TagTree{Type: resourceType, Code: resourceCode}, &trs)
|
||||
p.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &trs)
|
||||
return collx.ArrayMap(trs, func(tr *entity.TagTree) string {
|
||||
return tr.CodePath
|
||||
})
|
||||
@@ -260,7 +323,7 @@ func (p *tagTreeAppImpl) FillTagInfo(resources ...entity.ITagResource) {
|
||||
|
||||
for _, tr := range tagResources {
|
||||
// 赋值标签信息
|
||||
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetParentPath()})
|
||||
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetParentPath(0)})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ type TagTreeQuery struct {
|
||||
model.Model
|
||||
|
||||
Pid uint64
|
||||
Type int8 `json:"type"`
|
||||
Code string `json:"code"` // 标识
|
||||
Type TagType `json:"type"`
|
||||
Code string `json:"code"` // 标识
|
||||
Codes []string
|
||||
CodePath string `json:"codePath"` // 标识路径
|
||||
CodePaths []string
|
||||
|
||||
123
server/internal/tag/domain/entity/resource_auth_cert.go
Normal file
123
server/internal/tag/domain/entity/resource_auth_cert.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mayfly-go/internal/common/utils"
|
||||
"mayfly-go/pkg/model"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
// 资源授权凭证
|
||||
type ResourceAuthCert struct {
|
||||
model.Model
|
||||
|
||||
Name string `json:"name"` // 名称(全局唯一)
|
||||
|
||||
ResourceCode string `json:"resourceCode"` // 资源编号
|
||||
ResourceType int8 `json:"resourceType"` // 资源类型
|
||||
Username string `json:"username"` // 用户名
|
||||
Ciphertext string `json:"ciphertext"` // 密文
|
||||
CiphertextType AuthCertCiphertextType `json:"ciphertextType"` // 密文类型
|
||||
Extra model.Map[string, any] `json:"extra"` // 账号需要的其他额外信息(如秘钥口令等)
|
||||
Type AuthCertType `json:"type"` // 凭证类型
|
||||
Remark string `json:"remark"` // 备注
|
||||
}
|
||||
|
||||
func (m *ResourceAuthCert) CiphertextEncrypt() error {
|
||||
// 密码替换为加密后的密码
|
||||
password, err := utils.PwdAesEncrypt(m.Ciphertext)
|
||||
if err != nil {
|
||||
return errors.New("加密密文失败")
|
||||
}
|
||||
m.Ciphertext = password
|
||||
|
||||
// 加密秘钥口令
|
||||
if m.CiphertextType == AuthCertCiphertextTypePrivateKey {
|
||||
passphrase := cast.ToString(m.Extra["passphrase"])
|
||||
if passphrase != "" {
|
||||
passphrase, err := utils.PwdAesEncrypt(passphrase)
|
||||
if err != nil {
|
||||
return errors.New("加密秘钥口令失败")
|
||||
}
|
||||
m.Extra["passphrase"] = passphrase
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ResourceAuthCert) CiphertextDecrypt() error {
|
||||
// 密码替换为解密后的密码
|
||||
password, err := utils.PwdAesDecrypt(m.Ciphertext)
|
||||
if err != nil {
|
||||
return errors.New("解密密文失败")
|
||||
}
|
||||
m.Ciphertext = password
|
||||
|
||||
// 加密秘钥口令
|
||||
if m.CiphertextType == AuthCertCiphertextTypePrivateKey {
|
||||
passphrase := cast.ToString(m.Extra["passphrase"])
|
||||
if passphrase != "" {
|
||||
passphrase, err := utils.PwdAesDecrypt(passphrase)
|
||||
if err != nil {
|
||||
return errors.New("解密秘钥口令失败")
|
||||
}
|
||||
m.Extra["passphrase"] = passphrase
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 密文类型
|
||||
type AuthCertCiphertextType int8
|
||||
|
||||
// 凭证类型
|
||||
type AuthCertType int8
|
||||
|
||||
const (
|
||||
AuthCertCiphertextTypePublic AuthCertCiphertextType = -1 // 公共授权凭证
|
||||
AuthCertCiphertextTypePassword AuthCertCiphertextType = 1 // 密码
|
||||
AuthCertCiphertextTypePrivateKey AuthCertCiphertextType = 2 // 私钥
|
||||
|
||||
AuthCertTypePublic AuthCertType = 2 // 公共凭证(可多个资源共享该授权凭证)
|
||||
AuthCertTypePrivate AuthCertType = 1 // 普通私有凭证
|
||||
AuthCertTypePrivileged AuthCertType = 11 // 特权私有凭证
|
||||
AuthCertTypePrivateDefault AuthCertType = 12 // 默认私有凭证
|
||||
)
|
||||
|
||||
// 授权凭证接口,填充资源授权凭证信息
|
||||
type IAuthCert interface {
|
||||
// 获取资源code
|
||||
GetCode() string
|
||||
|
||||
// 设置授权信息
|
||||
SetAuthCert(ac AuthCert)
|
||||
}
|
||||
|
||||
// 资源关联的标签信息
|
||||
type AuthCert struct {
|
||||
Name string `json:"name" gorm:"-"` // 名称
|
||||
Username string `json:"username" gorm:"-"` // 用户名
|
||||
CiphertextType AuthCertCiphertextType `json:"ciphertextType" gorm:"-"` // 密文类型
|
||||
Type AuthCertType `json:"type" gorm:"-"` // 凭证类型
|
||||
}
|
||||
|
||||
func (r *AuthCert) SetAuthCert(ac AuthCert) {
|
||||
r.Name = ac.Name
|
||||
r.Username = ac.Username
|
||||
r.Type = ac.Type
|
||||
r.CiphertextType = ac.CiphertextType
|
||||
}
|
||||
|
||||
// 资源标签列表
|
||||
type AuthCerts struct {
|
||||
AuthCerts []AuthCert `json:"authCerts" gorm:"-"`
|
||||
}
|
||||
|
||||
func (r *AuthCerts) SetAuthCert(rt AuthCert) {
|
||||
if r.AuthCerts == nil {
|
||||
r.AuthCerts = make([]AuthCert, 0)
|
||||
}
|
||||
r.AuthCerts = append(r.AuthCerts, rt)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/pkg/model"
|
||||
"strings"
|
||||
)
|
||||
@@ -9,17 +10,28 @@ import (
|
||||
type TagTree struct {
|
||||
model.Model
|
||||
|
||||
Pid uint64 `json:"pid"`
|
||||
Type int8 `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
|
||||
Code string `json:"code"` // 标识编码, 若类型不为-1,则为对应资源编码
|
||||
CodePath string `json:"codePath"` // 标识路径
|
||||
Name string `json:"name"` // 名称
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
Pid uint64 `json:"pid"`
|
||||
Type TagType `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
|
||||
Code string `json:"code"` // 标识编码, 若类型不为-1,则为对应资源编码
|
||||
CodePath string `json:"codePath"` // 标识路径
|
||||
Name string `json:"name"` // 名称
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
}
|
||||
|
||||
type TagType int8
|
||||
|
||||
const (
|
||||
// 标识路径分隔符
|
||||
CodePathSeparator = "/"
|
||||
|
||||
TagTypeTag TagType = -1
|
||||
TagTypeMachine TagType = TagType(consts.TagResourceTypeMachine)
|
||||
TagTypeDb TagType = TagType(consts.TagResourceTypeDb)
|
||||
TagTypeRedis TagType = TagType(consts.TagResourceTypeRedis)
|
||||
TagTypeMongo TagType = TagType(consts.TagResourceTypeMongo)
|
||||
|
||||
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
|
||||
TagTypeDbAuthCert TagType = 21 // DB-授权凭证
|
||||
)
|
||||
|
||||
// GetRootCode 获取根路径信息
|
||||
@@ -27,19 +39,25 @@ func (pt *TagTree) GetRootCode() string {
|
||||
return strings.Split(pt.CodePath, CodePathSeparator)[0]
|
||||
}
|
||||
|
||||
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> test/test1/
|
||||
func (pt *TagTree) GetParentPath() string {
|
||||
// 去掉末尾的分隔符
|
||||
input := strings.TrimRight(pt.CodePath, CodePathSeparator)
|
||||
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> index = 0 => test/test1/ index = 1 => test/
|
||||
func (pt *TagTree) GetParentPath(index int) string {
|
||||
// 去除末尾的斜杠
|
||||
codePath := strings.TrimSuffix(pt.CodePath, "/")
|
||||
|
||||
// 查找倒数第二个连字符位置
|
||||
lastHyphenIndex := strings.LastIndex(input, CodePathSeparator)
|
||||
if lastHyphenIndex == -1 {
|
||||
return ""
|
||||
// 使用 Split 方法将路径按斜杠分割成切片
|
||||
paths := strings.Split(codePath, "/")
|
||||
|
||||
// 确保索引在有效范围内
|
||||
if index < 0 {
|
||||
index = 0
|
||||
} else if index > len(paths)-2 {
|
||||
index = len(paths) - 2
|
||||
}
|
||||
|
||||
// 截取字符串
|
||||
return input[:lastHyphenIndex+1]
|
||||
// 按索引拼接父标签路径
|
||||
parentPath := strings.Join(paths[:len(paths)-index-1], "/")
|
||||
|
||||
return parentPath + "/"
|
||||
}
|
||||
|
||||
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
|
||||
|
||||
10
server/internal/tag/domain/repository/resource_auth_cert.go
Normal file
10
server/internal/tag/domain/repository/resource_auth_cert.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type ResourceAuthCert interface {
|
||||
base.Repo[*entity.ResourceAuthCert]
|
||||
}
|
||||
@@ -9,4 +9,5 @@ func InitIoc() {
|
||||
ioc.Register(newTagTreeTeamRepo(), ioc.WithComponentName("TagTreeTeamRepo"))
|
||||
ioc.Register(newTeamRepo(), ioc.WithComponentName("TeamRepo"))
|
||||
ioc.Register(newTeamMemberRepo(), ioc.WithComponentName("TeamMemberRepo"))
|
||||
ioc.Register(newResourceAuthCertRepoImpl(), ioc.WithComponentName("ResourceAuthCertRepo"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type resourceAuthCertRepoImpl struct {
|
||||
base.RepoImpl[*entity.ResourceAuthCert]
|
||||
}
|
||||
|
||||
func newResourceAuthCertRepoImpl() repository.ResourceAuthCert {
|
||||
return &resourceAuthCertRepoImpl{base.RepoImpl[*entity.ResourceAuthCert]{M: new(entity.ResourceAuthCert)}}
|
||||
}
|
||||
24
server/internal/tag/router/resource_auth_cert.go
Normal file
24
server/internal/tag/router/resource_auth_cert.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/api"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitResourceAuthCertRouter(router *gin.RouterGroup) {
|
||||
m := new(api.ResourceAuthCert)
|
||||
biz.ErrIsNil(ioc.Inject(m))
|
||||
|
||||
resourceAuthCert := router.Group("/auth-certs")
|
||||
{
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("", m.ListByQuery),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(resourceAuthCert, reqs[:])
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,5 @@ import "github.com/gin-gonic/gin"
|
||||
func Init(router *gin.RouterGroup) {
|
||||
InitTagTreeRouter(router)
|
||||
InitTeamRouter(router)
|
||||
InitResourceAuthCertRouter(router)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -31,12 +32,28 @@ type App[T model.ModelI] interface {
|
||||
// 使用指定gorm db执行,主要用于事务执行
|
||||
UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T) error
|
||||
|
||||
// UpdateByWheres 更新满足wheres条件的数据
|
||||
// @param wheres key => "age > ?" value => 10等
|
||||
UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error
|
||||
|
||||
// UpdateByWheresWithDb 使用指定gorm.Db更新满足wheres条件的数据
|
||||
// @param wheres key => "age > ?" value => 10等
|
||||
UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error
|
||||
|
||||
// 根据实体主键删除实体
|
||||
DeleteById(ctx context.Context, id uint64) error
|
||||
|
||||
// 使用指定gorm db执行,主要用于事务执行
|
||||
DeleteByIdWithDb(ctx context.Context, db *gorm.DB, id uint64) error
|
||||
|
||||
// DeleteByWheres 根据wheres条件进行删除
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
DeleteByWheres(ctx context.Context, wheres collx.M) error
|
||||
|
||||
// DeleteByWheresWithDb 使用指定gorm.Db根据wheres条件进行删除
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error
|
||||
|
||||
// 根据实体条件,更新参数udpateFields指定字段
|
||||
Updates(ctx context.Context, cond any, udpateFields map[string]any) error
|
||||
|
||||
@@ -64,6 +81,9 @@ type App[T model.ModelI] interface {
|
||||
// 根据条件查询数据映射至listModels
|
||||
ListByCond(cond any, listModels any, cols ...string) error
|
||||
|
||||
// PageQuery 分页查询
|
||||
PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error)
|
||||
|
||||
// 获取满足model中不为空的字段值条件的所有数据.
|
||||
//
|
||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体
|
||||
@@ -117,6 +137,14 @@ func (ai *AppImpl[T, R]) UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T)
|
||||
return ai.GetRepo().UpdateByIdWithDb(ctx, db, e)
|
||||
}
|
||||
|
||||
func (ai *AppImpl[T, R]) UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error {
|
||||
return ai.GetRepo().UpdateByWheres(ctx, e, wheres, columns...)
|
||||
}
|
||||
|
||||
func (ai *AppImpl[T, R]) UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error {
|
||||
return ai.GetRepo().UpdateByWheresWithDb(ctx, db, e, wheres, columns...)
|
||||
}
|
||||
|
||||
// 根据实体条件,更新参数udpateFields指定字段 (单纯更新,不做其他业务逻辑处理)
|
||||
func (ai *AppImpl[T, R]) Updates(ctx context.Context, cond any, udpateFields map[string]any) error {
|
||||
return ai.GetRepo().Updates(cond, udpateFields)
|
||||
@@ -152,6 +180,14 @@ func (ai *AppImpl[T, R]) DeleteByCondWithDb(ctx context.Context, db *gorm.DB, co
|
||||
return ai.GetRepo().DeleteByCondWithDb(ctx, db, cond)
|
||||
}
|
||||
|
||||
func (ai *AppImpl[T, R]) DeleteByWheres(ctx context.Context, wheres collx.M) error {
|
||||
return ai.GetRepo().DeleteByWheres(ctx, wheres)
|
||||
}
|
||||
|
||||
func (ai *AppImpl[T, R]) DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error {
|
||||
return ai.GetRepo().DeleteByWheresWithDb(ctx, db, wheres)
|
||||
}
|
||||
|
||||
// 根据实体id查询
|
||||
func (ai *AppImpl[T, R]) GetById(e T, id uint64, cols ...string) (T, error) {
|
||||
if err := ai.GetRepo().GetById(e, id, cols...); err != nil {
|
||||
@@ -174,6 +210,11 @@ func (ai *AppImpl[T, R]) ListByCond(cond any, listModels any, cols ...string) er
|
||||
return ai.GetRepo().ListByCond(cond, listModels, cols...)
|
||||
}
|
||||
|
||||
// PageQuery 分页查询
|
||||
func (ai *AppImpl[T, R]) PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error) {
|
||||
return ai.GetRepo().PageQuery(cond, pageParam, toModels)
|
||||
}
|
||||
|
||||
// 获取满足model中不为空的字段值条件的所有数据.
|
||||
//
|
||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -33,6 +34,14 @@ type Repo[T model.ModelI] interface {
|
||||
// 使用指定gorm db执行,主要用于事务执行
|
||||
UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T, columns ...string) error
|
||||
|
||||
// UpdateByWheres 更新满足wheres条件的数据
|
||||
// @param wheres key => "age > ?" value => 10等
|
||||
UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error
|
||||
|
||||
// UpdateByWheresWithDb 使用指定gorm.Db更新满足wheres条件的数据
|
||||
// @param wheres key => "age > ?" value => 10等
|
||||
UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error
|
||||
|
||||
// 保存实体,实体IsCreate返回true则新增,否则更新
|
||||
Save(ctx context.Context, e T) error
|
||||
|
||||
@@ -55,6 +64,14 @@ type Repo[T model.ModelI] interface {
|
||||
// 使用指定gorm db执行,主要用于事务执行
|
||||
DeleteByCondWithDb(ctx context.Context, db *gorm.DB, cond any) error
|
||||
|
||||
// DeleteByWheres 根据wheres条件进行删除
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
DeleteByWheres(ctx context.Context, wheres collx.M) error
|
||||
|
||||
// DeleteByWheresWithDb 使用指定gorm.Db根据wheres条件进行删除
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error
|
||||
|
||||
// 根据实体id查询
|
||||
GetById(e T, id uint64, cols ...string) error
|
||||
|
||||
@@ -67,6 +84,13 @@ type Repo[T model.ModelI] interface {
|
||||
// 根据实体条件查询数据映射至listModels
|
||||
ListByCond(cond any, listModels any, cols ...string) error
|
||||
|
||||
// 根据wheres条件进行过滤
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
ListByWheres(wheres collx.M, listModels any, cols ...string) error
|
||||
|
||||
// PageQuery 分页查询
|
||||
PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error)
|
||||
|
||||
// 获取满足model中不为空的字段值条件的所有数据.
|
||||
//
|
||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体
|
||||
@@ -123,6 +147,24 @@ func (br *RepoImpl[T]) UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T, c
|
||||
return gormx.UpdateByIdWithDb(db, br.fillBaseInfo(ctx, e), columns...)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error {
|
||||
if db := contextx.GetDb(ctx); db != nil {
|
||||
return br.UpdateByWheresWithDb(ctx, db, e, wheres, columns...)
|
||||
}
|
||||
|
||||
e = br.fillBaseInfo(ctx, e)
|
||||
// model的主键值需为空,否则会带上主键条件
|
||||
e.SetId(0)
|
||||
return gormx.UpdateByWheres(e, wheres)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error {
|
||||
e = br.fillBaseInfo(ctx, e)
|
||||
// model的主键值需为空,否则会带上主键条件
|
||||
e.SetId(0)
|
||||
return gormx.UpdateByWheresWithDb(db, br.fillBaseInfo(ctx, e), wheres, columns...)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) Updates(cond any, udpateFields map[string]any) error {
|
||||
return gormx.Updates(br.GetModel(), cond, udpateFields)
|
||||
}
|
||||
@@ -163,6 +205,23 @@ func (br *RepoImpl[T]) DeleteByCondWithDb(ctx context.Context, db *gorm.DB, cond
|
||||
return gormx.DeleteByCondWithDb(db, br.GetModel(), cond)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) DeleteByWheres(ctx context.Context, wheres collx.M) error {
|
||||
if db := contextx.GetDb(ctx); db != nil {
|
||||
return br.DeleteByWheresWithDb(ctx, db, wheres)
|
||||
}
|
||||
// model的主键值需为空,否则会带上主键条件
|
||||
e := br.GetModel()
|
||||
e.SetId(0)
|
||||
return gormx.DeleteByWheres(e, wheres)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error {
|
||||
// model的主键值需为空,否则会带上主键条件
|
||||
e := br.GetModel()
|
||||
e.SetId(0)
|
||||
return gormx.DeleteByWheresWithDb(db, e, wheres)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) GetById(e T, id uint64, cols ...string) error {
|
||||
if err := gormx.GetById(e, id, cols...); err != nil {
|
||||
return err
|
||||
@@ -182,6 +241,15 @@ func (br *RepoImpl[T]) ListByCond(cond any, listModels any, cols ...string) erro
|
||||
return gormx.ListByCond(br.GetModel(), cond, listModels, cols...)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) ListByWheres(wheres collx.M, listModels any, cols ...string) error {
|
||||
return gormx.ListByWheres(br.GetModel(), wheres, listModels, cols...)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error) {
|
||||
qd := gormx.NewQuery(br.GetModel()).WithCondModel(cond)
|
||||
return gormx.PageQuery(qd, pageParam, toModels)
|
||||
}
|
||||
|
||||
func (br *RepoImpl[T]) ListByCondOrder(cond any, list any, order ...string) error {
|
||||
return gormx.ListByCondOrder(br.GetModel(), cond, list, order...)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -93,6 +95,19 @@ func ListByCond(model any, cond any, list any, cols ...string) error {
|
||||
return global.Db.Model(model).Select(cols).Where(cond).Scopes(UndeleteScope).Order("id desc").Find(list).Error
|
||||
}
|
||||
|
||||
// 获取满足cond中不为空的字段值条件的所有model表数据.
|
||||
//
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
func ListByWheres(model any, wheres collx.M, list any, cols ...string) error {
|
||||
gdb := global.Db.Model(model).Select(cols)
|
||||
for k, v := range wheres {
|
||||
if !anyx.IsBlank(v) {
|
||||
gdb.Where(k, v)
|
||||
}
|
||||
}
|
||||
return gdb.Scopes(UndeleteScope).Order("id desc").Find(list).Error
|
||||
}
|
||||
|
||||
// 获取满足model中不为空的字段值条件的所有数据.
|
||||
//
|
||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体
|
||||
@@ -154,6 +169,24 @@ func UpdateByIdWithDb(db *gorm.DB, model any, columns ...string) error {
|
||||
return db.Model(model).Select(columns).Updates(model).Error
|
||||
}
|
||||
|
||||
// UpdateByWheres 更新满足wheres条件的数据(model的主键值需为空,否则会带上主键条件)
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
func UpdateByWheres(model_ any, wheres collx.M) error {
|
||||
return UpdateByWheresWithDb(global.Db, model_, wheres)
|
||||
}
|
||||
|
||||
// UpdateByWheresWithDb 使用指定gorm.DB更新满足wheres条件的数据(model的主键值需为空,否则会带上主键条件)
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
func UpdateByWheresWithDb(db *gorm.DB, model any, wheres collx.M, columns ...string) error {
|
||||
gormDb := db.Model(model).Select(columns)
|
||||
for k, v := range wheres {
|
||||
if !anyx.IsBlank(v) {
|
||||
gormDb.Where(k, v)
|
||||
}
|
||||
}
|
||||
return gormDb.Updates(model).Error
|
||||
}
|
||||
|
||||
// 根据实体条件,更新参数udpateFields指定字段
|
||||
func Updates(model any, condition any, updateFields map[string]any) error {
|
||||
return global.Db.Model(model).Where(condition).Updates(updateFields).Error
|
||||
@@ -190,6 +223,24 @@ func DeleteByWithDb(db *gorm.DB, model_ any) error {
|
||||
return DeleteByCondWithDb(db, model_, model_)
|
||||
}
|
||||
|
||||
// DeleteByWheres 使用指定wheres删除(model的主键值需为空,否则会带上主键条件)
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
func DeleteByWheres(model_ any, wheres collx.M) error {
|
||||
return DeleteByWheresWithDb(global.Db, model_, wheres)
|
||||
}
|
||||
|
||||
// DeleteByWheresWithDb 使用指定gorm.Db根据wheres条件进行删除(model的主键值需为空,否则会带上主键条件)
|
||||
// @param wheres key -> "age > ?" value -> 10等
|
||||
func DeleteByWheresWithDb(db *gorm.DB, model_ any, wheres collx.M) error {
|
||||
gormDb := db.Model(model_)
|
||||
for k, v := range wheres {
|
||||
if !anyx.IsBlank(v) {
|
||||
gormDb.Where(k, v)
|
||||
}
|
||||
}
|
||||
return gormDb.Updates(getDeleteColumnValue()).Error
|
||||
}
|
||||
|
||||
// 根据cond条件删除指定model表数据
|
||||
//
|
||||
// @param model 数据库映射实体模型
|
||||
|
||||
@@ -22,11 +22,13 @@ const (
|
||||
|
||||
// 实体接口
|
||||
type ModelI interface {
|
||||
// SetId 设置id
|
||||
SetId(id uint64)
|
||||
|
||||
// 是否为新建该实体模型, 默认 id == 0 为新建
|
||||
// IsCreate 是否为新建该实体模型, 默认 id == 0 为新建
|
||||
IsCreate() bool
|
||||
|
||||
// 使用当前登录账号信息赋值实体结构体的基础信息
|
||||
// FillBaseInfo 使用当前登录账号信息赋值实体结构体的基础信息
|
||||
//
|
||||
// 如创建时间,修改时间,创建者,修改者信息等
|
||||
FillBaseInfo(idGenType IdGenType, account *LoginAccount)
|
||||
@@ -36,6 +38,10 @@ type IdModel struct {
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
func (m *IdModel) SetId(id uint64) {
|
||||
m.Id = id
|
||||
}
|
||||
|
||||
func (m *IdModel) IsCreate() bool {
|
||||
return m.Id == 0
|
||||
}
|
||||
@@ -45,7 +51,7 @@ func (m *IdModel) FillBaseInfo(idGenType IdGenType, account *LoginAccount) {
|
||||
if !m.IsCreate() {
|
||||
return
|
||||
}
|
||||
m.Id = GetIdByGenType(idGenType)
|
||||
m.SetId(GetIdByGenType(idGenType))
|
||||
}
|
||||
|
||||
// 含有删除字段模型
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package collx
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 数组比较
|
||||
// 依次返回,新增值,删除值,以及不变值
|
||||
@@ -131,6 +134,13 @@ func ArrayRemoveFunc[T any](arr []T, isDeleteFunc func(T) bool) []T {
|
||||
return newArr
|
||||
}
|
||||
|
||||
// ArrayRemoveBlank 移除元素中的空元素
|
||||
func ArrayRemoveBlank[T any](arr []T) []T {
|
||||
return ArrayRemoveFunc(arr, func(val T) bool {
|
||||
return anyx.IsBlank(val)
|
||||
})
|
||||
}
|
||||
|
||||
// 数组元素去重
|
||||
func ArrayDeduplicate[T comparable](arr []T) []T {
|
||||
encountered := map[T]bool{}
|
||||
@@ -155,3 +165,14 @@ func ArrayAnyMatches(arr []string, subStr string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ArrayFilter 过滤函数,根据提供的条件函数将切片中的元素进行过滤
|
||||
func ArrayFilter[T any](array []T, fn func(T) bool) []T {
|
||||
var filtered []T
|
||||
for _, val := range array {
|
||||
if fn(val) {
|
||||
filtered = append(filtered, val)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
@@ -335,10 +335,6 @@ CREATE TABLE `t_machine` (
|
||||
`ip` varchar(50) NOT NULL,
|
||||
`port` int(12) NOT NULL,
|
||||
`protocol` tinyint(2) NULL COMMENT '协议 1、SSH 2、RDP',
|
||||
`username` varchar(12) NOT NULL,
|
||||
`auth_method` tinyint(2) DEFAULT NULL COMMENT '1.密码登录2.publickey登录',
|
||||
`password` varchar(100) DEFAULT NULL,
|
||||
`auth_cert_id` bigint(20) DEFAULT NULL COMMENT '授权凭证id',
|
||||
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
|
||||
`enable_recorder` tinyint(2) DEFAULT NULL COMMENT '是否启用终端回放记录',
|
||||
`status` tinyint(2) NOT NULL COMMENT '状态: 1:启用; -1:禁用',
|
||||
@@ -893,6 +889,7 @@ DROP TABLE IF EXISTS `t_tag_tree`;
|
||||
CREATE TABLE `t_tag_tree` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`pid` bigint(20) NOT NULL DEFAULT '0',
|
||||
`type` tinyint NOT NULL DEFAULT '-1' COMMENT '类型: -1.普通标签; 其他值则为对应的资源类型',
|
||||
`code` varchar(36) NOT NULL COMMENT '标识符',
|
||||
`code_path` varchar(255) NOT NULL COMMENT '标识符路径',
|
||||
`name` varchar(36) DEFAULT NULL COMMENT '名称',
|
||||
@@ -997,6 +994,31 @@ BEGIN;
|
||||
INSERT INTO `t_team_member` VALUES (7, 3, 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
DROP TABLE IF EXISTS `t_resource_auth_cert`;
|
||||
-- 资源授权凭证
|
||||
CREATE TABLE `t_resource_auth_cert` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号名称',
|
||||
`resource_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '资源编码',
|
||||
`resource_type` tinyint NOT NULL COMMENT '资源类型',
|
||||
`type` tinyint DEFAULT NULL COMMENT '凭证类型',
|
||||
`username` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
|
||||
`ciphertext` varchar(5000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密文内容',
|
||||
`ciphertext_type` tinyint NOT NULL COMMENT '密文类型(-1.公共授权凭证 1.密码 2.秘钥)',
|
||||
`extra` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号需要的其他额外信息(如秘钥口令等)',
|
||||
`remark` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint NOT NULL,
|
||||
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`update_time` datetime NOT NULL,
|
||||
`modifier_id` bigint NOT NULL,
|
||||
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`is_deleted` tinyint DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_resource_code` (`resource_code`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='资源授权凭证表';
|
||||
|
||||
DROP TABLE IF EXISTS `t_flow_procdef`;
|
||||
-- 工单流程相关表
|
||||
CREATE TABLE `t_flow_procdef` (
|
||||
|
||||
@@ -55,6 +55,9 @@ update `t_machine` set `protocol` = 1 where `protocol` is NULL;
|
||||
delete from `t_sys_config` where `key` = 'MachineConfig';
|
||||
INSERT INTO t_sys_config ( name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"},{"name":"guacd服务记录存储位置","model":"guacdRecPath","placeholder":"guacd服务记录存储位置,用于记录rdp操作记录"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file","guacdRecPath":"./guacd/rdp-rec"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-04-06 12:25:03', 1, 'admin', 0, NULL);
|
||||
|
||||
|
||||
ALTER TABLE t_tag_tree ADD `type` tinyint NOT NULL DEFAULT '-1' COMMENT '类型: -1.普通标签; 其他值则为对应的资源类型';
|
||||
|
||||
BEGIN;
|
||||
INSERT
|
||||
INTO
|
||||
@@ -88,5 +91,34 @@ from
|
||||
WHERE
|
||||
is_deleted = 0;
|
||||
|
||||
DROP TABLE t_tag_tree;
|
||||
COMMIT;
|
||||
DROP TABLE t_tag_resource;
|
||||
COMMIT;
|
||||
|
||||
-- 资源授权凭证
|
||||
CREATE TABLE `t_resource_auth_cert` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号名称',
|
||||
`resource_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '资源编码',
|
||||
`resource_type` tinyint NOT NULL COMMENT '资源类型',
|
||||
`type` tinyint DEFAULT NULL COMMENT '凭证类型',
|
||||
`username` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
|
||||
`ciphertext` varchar(5000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密文内容',
|
||||
`ciphertext_type` tinyint NOT NULL COMMENT '密文类型(-1.公共授权凭证 1.密码 2.秘钥)',
|
||||
`extra` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号需要的其他额外信息(如秘钥口令等)',
|
||||
`remark` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint NOT NULL,
|
||||
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`update_time` datetime NOT NULL,
|
||||
`modifier_id` bigint NOT NULL,
|
||||
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`is_deleted` tinyint DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_resource_code` (`resource_code`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='资源授权凭证表';
|
||||
|
||||
-- 删除机器表 账号相关字段
|
||||
ALTER TABLE t_machine DROP COLUMN username;
|
||||
ALTER TABLE t_machine DROP COLUMN password;
|
||||
ALTER TABLE t_machine DROP COLUMN auth_cert_id;
|
||||
|
||||
Reference in New Issue
Block a user