1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-16 18:54:43 +08:00

feat: 增加单独修改文件权限页面

This commit is contained in:
zhengkunwang223 2022-08-29 15:26:36 +08:00
parent 9db23ed649
commit 12527de2ba
11 changed files with 169 additions and 60 deletions

View File

@ -62,3 +62,17 @@ func (b *BaseApi) DeleteFile(c *gin.Context) {
} }
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
func (b *BaseApi) ChangeFileMode(c *gin.Context) {
var req dto.FileCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := fileService.ChangeMode(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/app/dto" "github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/utils/files" "github.com/1Panel-dev/1Panel/utils/files"
"github.com/pkg/errors"
"io" "io"
"io/fs" "io/fs"
) )
@ -48,6 +49,11 @@ func (f FileService) GetFileTree(op dto.FileOption) ([]dto.FileTree, error) {
func (f FileService) Create(op dto.FileCreate) error { func (f FileService) Create(op dto.FileCreate) error {
fo := files.NewFileOp() fo := files.NewFileOp()
if fo.Stat(op.Path) {
return errors.New("file is exist")
}
if op.IsDir { if op.IsDir {
return fo.CreateDir(op.Path, fs.FileMode(op.Mode)) return fo.CreateDir(op.Path, fs.FileMode(op.Mode))
} }
@ -64,6 +70,11 @@ func (f FileService) Delete(op dto.FileDelete) error {
} }
} }
func (f FileService) ChangeMode(op dto.FileCreate) error {
fo := files.NewFileOp()
return fo.Chmod(op.Path, fs.FileMode(op.Mode))
}
func getUuid() string { func getUuid() string {
b := make([]byte, 16) b := make([]byte, 16)
io.ReadFull(rand.Reader, b) io.ReadFull(rand.Reader, b)

View File

@ -19,6 +19,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
fileRouter.POST("/tree", baseApi.GetFileTree) fileRouter.POST("/tree", baseApi.GetFileTree)
fileRouter.POST("", baseApi.CreateFile) fileRouter.POST("", baseApi.CreateFile)
fileRouter.POST("/del", baseApi.DeleteFile) fileRouter.POST("/del", baseApi.DeleteFile)
fileRouter.POST("/mode", baseApi.ChangeFileMode)
} }
} }

View File

@ -26,6 +26,14 @@ func (f FileOp) DeleteDir(dst string) error {
return f.Fs.RemoveAll(dst) return f.Fs.RemoveAll(dst)
} }
func (f FileOp) Stat(dst string) bool {
info, _ := f.Fs.Stat(dst)
if info != nil {
return true
}
return false
}
func (f FileOp) DeleteFile(dst string) error { func (f FileOp) DeleteFile(dst string) error {
return f.Fs.Remove(dst) return f.Fs.Remove(dst)
} }
@ -50,3 +58,7 @@ func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error {
} }
return nil return nil
} }
func (f FileOp) Chmod(dst string, mode fs.FileMode) error {
return f.Fs.Chmod(dst, mode)
}

View File

@ -1,26 +0,0 @@
{
"code": 200,
"data": {
"items": [
{
"name": "var",
"isDir": true,
"mode": 775,
"user": "root",
"group": "root",
"size": 2048,
"updateTime": "2022-08-11T11:05:22.001+08:00"
},
{
"name": "test.txt",
"isDir": false,
"mode": 775,
"user": "root",
"group": "root",
"size": 4096,
"updateTime": "2022-08-11T11:05:22.001+08:00"
}
],
"total": 2
}
}

View File

@ -16,3 +16,7 @@ export const CreateFile = (form: File.FileCreate) => {
export const DeleteFile = (form: File.FileDelete) => { export const DeleteFile = (form: File.FileDelete) => {
return http.post<File.File>('files/del', form); return http.post<File.File>('files/del', form);
}; };
export const ChangeFileMode = (form: File.FileCreate) => {
return http.post<File.File>('files/mode', form);
};

View File

@ -44,7 +44,7 @@ interface Props {
const roles = ref<string[]>(['0', '1', '2', '3', '4', '5', '6', '7']); const roles = ref<string[]>(['0', '1', '2', '3', '4', '5', '6', '7']);
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
mode: '0775', mode: '0755',
}); });
const { mode } = toRefs(props); const { mode } = toRefs(props);
@ -53,7 +53,7 @@ let form = ref<RoleForm>({
owner: { r: true, w: true, x: true }, owner: { r: true, w: true, x: true },
group: { r: true, w: true, x: true }, group: { r: true, w: true, x: true },
public: { r: true, w: false, x: true }, public: { r: true, w: false, x: true },
mode: '0775', mode: '0755',
}); });
const em = defineEmits(['getMode']); const em = defineEmits(['getMode']);

View File

@ -7,6 +7,7 @@ import i18n from '@/lang';
* @param {Function} api 操作数据接口的api方法(必传) * @param {Function} api 操作数据接口的api方法(必传)
* @param {Object} params 携带的操作数据参数 {id,params}(必传) * @param {Object} params 携带的操作数据参数 {id,params}(必传)
* @param {String} message 提示信息(必传) * @param {String} message 提示信息(必传)
* @param {String} loading 页面loading
* @param {String} confirmType icon类型(不必传,默认为 warning) * @param {String} confirmType icon类型(不必传,默认为 warning)
* @return Promise * @return Promise
*/ */
@ -14,22 +15,28 @@ export const useDeleteData = <P = any, R = any>(
api: (params: P) => Promise<R>, api: (params: P) => Promise<R>,
params: Parameters<typeof api>[0], params: Parameters<typeof api>[0],
message: string, message: string,
loading: boolean,
confirmType: HandleData.MessageType = 'error', confirmType: HandleData.MessageType = 'error',
) => { ) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
loading = true;
ElMessageBox.confirm(i18n.global.t(`${message}`) + '?', i18n.global.t('commons.msg.deleteTitle'), { ElMessageBox.confirm(i18n.global.t(`${message}`) + '?', i18n.global.t('commons.msg.deleteTitle'), {
confirmButtonText: i18n.global.t('commons.button.confirm'), confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'), cancelButtonText: i18n.global.t('commons.button.cancel'),
type: confirmType, type: confirmType,
draggable: true, draggable: true,
}).then(async () => { })
const res = await api(params); .then(async () => {
if (!res) return reject(false); const res = await api(params);
ElMessage({ if (!res) return reject(false);
type: 'success', ElMessage({
message: i18n.global.t('commons.msg.deleteSuccess'), type: 'success',
message: i18n.global.t('commons.msg.deleteSuccess'),
});
resolve(true);
})
.finally(() => {
loading = false;
}); });
resolve(true);
});
}); });
}; };

View File

@ -0,0 +1,64 @@
<template>
<el-dialog
v-model="open"
:before-close="handleClose"
:title="$t('file.setRole')"
width="30%"
@open="onOpen"
v-loading="loading"
>
<FileRole :mode="mode" @get-mode="getMode"></FileRole>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit()">{{ $t('commons.button.confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { File } from '@/api/interface/file';
import { ChangeFileMode } from '@/api/modules/files';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import FileRole from '@/components/file-role/index.vue';
interface Props {
open: boolean;
file: Object;
}
const props = withDefaults(defineProps<Props>(), {});
let form = ref<File.FileCreate>({ path: '', isDir: false, mode: 0o755 });
let loading = ref<Boolean>(false);
let mode = ref('0755');
const em = defineEmits(['close']);
const handleClose = () => {
em('close', false);
};
const onOpen = () => {
const f = props.file as File.FileCreate;
form.value.isDir = f.isDir;
form.value.path = f.path;
mode.value = String(f.mode);
};
const getMode = (val: number) => {
form.value.mode = val;
};
const submit = async () => {
loading.value = true;
ChangeFileMode(form.value)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
};
</script>

View File

@ -7,12 +7,13 @@
@open="onOpen" @open="onOpen"
v-loading="loading" v-loading="loading"
> >
<el-form ref="fileForm" label-position="left" :model="form" label-width="100px"> <el-form ref="fileForm" label-position="left" :model="form" label-width="100px" :rules="rules">
<el-form-item :label="$t('file.path')"> <el-input v-model="form.path" /></el-form-item> <el-form-item :label="$t('file.path')"> <el-input v-model="getPath" disabled /></el-form-item>
<el-form-item :label="$t('file.name')"> <el-input v-model="name" /></el-form-item>
<el-checkbox v-model="isLink" :label="$t('file.link')"></el-checkbox> <el-checkbox v-model="isLink" :label="$t('file.link')"></el-checkbox>
</el-form> </el-form>
<el-checkbox v-model="setRole" :label="$t('file.setRole')"></el-checkbox> <el-checkbox v-model="setRole" :label="$t('file.setRole')"></el-checkbox>
<FileRole v-if="setRole" :mode="'0775'" @get-mode="getMode"></FileRole> <FileRole v-if="setRole" :mode="'0755'" @get-mode="getMode"></FileRole>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button> <el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
@ -23,17 +24,20 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { toRefs, ref } from 'vue'; import { toRefs, ref, reactive, computed } from 'vue';
import { File } from '@/api/interface/file'; import { File } from '@/api/interface/file';
import { ElMessage, FormInstance } from 'element-plus'; import { ElMessage, FormInstance, FormRules } from 'element-plus';
import { CreateFile } from '@/api/modules/files'; import { CreateFile } from '@/api/modules/files';
import i18n from '@/lang'; import i18n from '@/lang';
import FileRole from '@/components/file-role/index.vue'; import FileRole from '@/components/file-role/index.vue';
import { Rules } from '@/global/form-rues';
const fileForm = ref<FormInstance>(); const fileForm = ref<FormInstance>();
let loading = ref<Boolean>(false); let loading = ref(false);
let setRole = ref<Boolean>(false); let setRole = ref(false);
let isLink = ref<Boolean>(false); let isLink = ref(false);
let name = ref('');
let path = ref('');
const props = defineProps({ const props = defineProps({
open: Boolean, open: Boolean,
@ -46,10 +50,18 @@ const handleClose = () => {
em('close', open); em('close', open);
}; };
const rules = reactive<FormRules>({
name: [Rules.required],
});
const getMode = (val: number) => { const getMode = (val: number) => {
form.value.mode = val; form.value.mode = val;
}; };
let getPath = computed(() => {
return path.value + '/' + name.value;
});
const submit = async (formEl: FormInstance | undefined) => { const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
await formEl.validate((valid) => { await formEl.validate((valid) => {
@ -57,6 +69,7 @@ const submit = async (formEl: FormInstance | undefined) => {
return; return;
} }
loading.value = true; loading.value = true;
form.value.path = getPath.value;
CreateFile(form.value) CreateFile(form.value)
.then(() => { .then(() => {
ElMessage.success(i18n.global.t('commons.msg.createSuccess')); ElMessage.success(i18n.global.t('commons.msg.createSuccess'));
@ -72,5 +85,7 @@ const onOpen = () => {
const f = file?.value as File.FileCreate; const f = file?.value as File.FileCreate;
form.value.isDir = f.isDir; form.value.isDir = f.isDir;
form.value.path = f.path; form.value.path = f.path;
path.value = f.path;
name.value = '';
}; };
</script> </script>

View File

@ -54,9 +54,6 @@
<el-dropdown-item command="file"> <el-dropdown-item command="file">
<svg-icon iconName="p-file-normal"></svg-icon>{{ $t('file.file') }} <svg-icon iconName="p-file-normal"></svg-icon>{{ $t('file.file') }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item command="link">
<svg-icon iconName="p-file-normal"></svg-icon>{{ $t('file.linkFile') }}
</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -74,7 +71,11 @@
<el-link :underline="false" @click="open(row)">{{ row.name }}</el-link> <el-link :underline="false" @click="open(row)">{{ row.name }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('file.mode')" prop="mode"> </el-table-column> <el-table-column :label="$t('file.mode')" prop="mode">
<template #default="{ row }">
<el-link :underline="false" @click="openMode(row)">{{ row.mode }}</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('file.user')" prop="user"> </el-table-column> <el-table-column :label="$t('file.user')" prop="user"> </el-table-column>
<el-table-column :label="$t('file.group')" prop="group"> </el-table-column> <el-table-column :label="$t('file.group')" prop="group"> </el-table-column>
<el-table-column :label="$t('file.size')" prop="size"> </el-table-column> <el-table-column :label="$t('file.size')" prop="size"> </el-table-column>
@ -95,7 +96,8 @@
/> />
</ComplexTable> </ComplexTable>
</el-col> </el-col>
<CreateFile :open="openCreate" :file="fileCreate" @close="close"></CreateFile> <CreateFile :open="openCreate" :file="fileCreate" @close="closeCreate"></CreateFile>
<ChangeRole :open="openModePage" :file="modeForm" @close="closeMode"></ChangeRole>
</el-row> </el-row>
</LayoutContent> </LayoutContent>
</template> </template>
@ -111,6 +113,7 @@ import { File } from '@/api/interface/file';
import BreadCrumbs from '@/components/bread-crumbs/index.vue'; import BreadCrumbs from '@/components/bread-crumbs/index.vue';
import BreadCrumbItem from '@/components/bread-crumbs/bread-crumbs-item.vue'; import BreadCrumbItem from '@/components/bread-crumbs/bread-crumbs-item.vue';
import CreateFile from './create.vue'; import CreateFile from './create.vue';
import ChangeRole from './change-role.vue';
import { useDeleteData } from '@/hooks/use-delete-data'; import { useDeleteData } from '@/hooks/use-delete-data';
let data = ref(); let data = ref();
@ -123,6 +126,8 @@ let fileTree = ref<File.FileTree[]>([]);
let expandKeys = ref<string[]>([]); let expandKeys = ref<string[]>([]);
let openCreate = ref<boolean>(false); let openCreate = ref<boolean>(false);
let fileCreate = ref<File.FileCreate>({ path: '/', isDir: false, mode: 0o755 }); let fileCreate = ref<File.FileCreate>({ path: '/', isDir: false, mode: 0o755 });
let openModePage = ref<boolean>(false);
let modeForm = ref<File.FileCreate>({ path: '/', isDir: false, mode: 0o755 });
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
@ -228,24 +233,25 @@ const handleCreate = (commnad: string) => {
}; };
const delFile = async (row: File.File | null) => { const delFile = async (row: File.File | null) => {
// let ids: Array<number> = []; await useDeleteData(DeleteFile, row as File.FileDelete, 'commons.msg.delete', loading.value);
// if (row === null) {
// selects.value.forEach((item: File.File) => {
// ids.push(item.id);
// });
// } else {
// ids.push(row.id);
// }
await useDeleteData(DeleteFile, row as File.FileDelete, 'commons.msg.delete');
search(req); search(req);
}; };
const close = () => { const closeCreate = () => {
openCreate.value = false; openCreate.value = false;
search(req); search(req);
}; };
const openMode = (item: File.File) => {
modeForm.value = item;
openModePage.value = true;
};
const closeMode = () => {
openModePage.value = false;
search(req);
};
onMounted(() => { onMounted(() => {
search(req); search(req);
}); });
@ -257,6 +263,7 @@ const buttons = [
}, },
{ {
label: i18n.global.t('file.mode'), label: i18n.global.t('file.mode'),
click: openMode,
}, },
{ {
label: i18n.global.t('file.zip'), label: i18n.global.t('file.zip'),