🕹 Manage root users
🕹 Manage root users
This commit is contained in:
@@ -102,5 +102,8 @@
|
|||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"server": "PLUGINS_SERVER"
|
"server": "PLUGINS_SERVER"
|
||||||
|
},
|
||||||
|
"drive": {
|
||||||
|
"rootAdmins": "DRIVE_ROOT_ADMINS"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -40,7 +40,7 @@ export default class ElasticSearch extends SearchAdapter implements SearchAdapte
|
|||||||
|
|
||||||
public async connect() {
|
public async connect() {
|
||||||
try {
|
try {
|
||||||
let clientOptions: any = {
|
const clientOptions: any = {
|
||||||
node: this.configuration.endpoint,
|
node: this.configuration.endpoint,
|
||||||
ssl: {
|
ssl: {
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
@@ -48,7 +48,7 @@ export default class ElasticSearch extends SearchAdapter implements SearchAdapte
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.configuration.useAuth) {
|
if (this.configuration.useAuth) {
|
||||||
logger.info(`Using auth for ES client`);
|
logger.info("Using auth for ES client");
|
||||||
clientOptions.auth = {
|
clientOptions.auth = {
|
||||||
username: this.configuration.username,
|
username: this.configuration.username,
|
||||||
password: this.configuration.password,
|
password: this.configuration.password,
|
||||||
|
|||||||
@@ -61,6 +61,26 @@ export const isCompanyGuest = async (context: CompanyExecutionContext): Promise<
|
|||||||
return userRole === "guest" || !userRole;
|
return userRole === "guest" || !userRole;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks the current user is a member
|
||||||
|
* Company members can access shared drive
|
||||||
|
*
|
||||||
|
* @param {CompanyExecutionContext} context
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
export const isCompanyMember = async (context: CompanyExecutionContext): Promise<boolean> => {
|
||||||
|
if (await isCompanyApplication(context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRole = await globalResolver.services.companies.getUserRole(
|
||||||
|
context.company.id,
|
||||||
|
context.user?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return userRole === "member" || !userRole;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks the current user is a admin
|
* checks the current user is a admin
|
||||||
*
|
*
|
||||||
@@ -149,8 +169,15 @@ export const getAccessLevel = async (
|
|||||||
repository: Repository<DriveFile>,
|
repository: Repository<DriveFile>,
|
||||||
context: CompanyExecutionContext & { public_token?: string; tdrive_tab_token?: string },
|
context: CompanyExecutionContext & { public_token?: string; tdrive_tab_token?: string },
|
||||||
): Promise<DriveFileAccessLevel | "none"> => {
|
): Promise<DriveFileAccessLevel | "none"> => {
|
||||||
if (!id || id === "root")
|
const isMember = !context?.user?.id || (await isCompanyMember(context));
|
||||||
return !context?.user?.id || (await isCompanyGuest(context)) ? "none" : "manage";
|
if (!id || id === "root") {
|
||||||
|
if (!context?.user?.id || (await isCompanyGuest(context))) {
|
||||||
|
return "none";
|
||||||
|
} else {
|
||||||
|
if (isMember) return "read";
|
||||||
|
return "manage";
|
||||||
|
}
|
||||||
|
}
|
||||||
if (id === "trash")
|
if (id === "trash")
|
||||||
return (await isCompanyGuest(context)) || !context?.user?.id
|
return (await isCompanyGuest(context)) || !context?.user?.id
|
||||||
? "none"
|
? "none"
|
||||||
@@ -182,6 +209,9 @@ export const getAccessLevel = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (await isCompanyApplication(context)) {
|
if (await isCompanyApplication(context)) {
|
||||||
|
if (!id.startsWith("user_") && isMember && item.creator != context.user.id) {
|
||||||
|
return "read";
|
||||||
|
}
|
||||||
return "manage";
|
return "manage";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { CrudException, ListResult } from "../../../../core/platform/framework/a
|
|||||||
import { File } from "../../../../services/files/entities/file";
|
import { File } from "../../../../services/files/entities/file";
|
||||||
import { UploadOptions } from "../../../../services/files/types";
|
import { UploadOptions } from "../../../../services/files/types";
|
||||||
import globalResolver from "../../../../services/global-resolver";
|
import globalResolver from "../../../../services/global-resolver";
|
||||||
import { PaginationQueryParameters, ResourceWebsocket } from "../../../../utils/types";
|
import {
|
||||||
|
PaginationQueryParameters,
|
||||||
|
ResourceWebsocket,
|
||||||
|
CompanyUserRole,
|
||||||
|
} from "../../../../utils/types";
|
||||||
import { DriveFile } from "../../entities/drive-file";
|
import { DriveFile } from "../../entities/drive-file";
|
||||||
import { FileVersion } from "../../entities/file-version";
|
import { FileVersion } from "../../entities/file-version";
|
||||||
import {
|
import {
|
||||||
@@ -21,9 +25,21 @@ import {
|
|||||||
} from "../../types";
|
} from "../../types";
|
||||||
import { DriveFileDTO } from "../dto/drive-file-dto";
|
import { DriveFileDTO } from "../dto/drive-file-dto";
|
||||||
import { DriveFileDTOBuilder } from "../../services/drive-file-dto-builder";
|
import { DriveFileDTOBuilder } from "../../services/drive-file-dto-builder";
|
||||||
|
import { ExecutionContext } from "../../../../core/platform/framework/api/crud-service";
|
||||||
|
import gr from "../../../global-resolver";
|
||||||
|
import config from "config";
|
||||||
|
|
||||||
export class DocumentsController {
|
export class DocumentsController {
|
||||||
private driveFileDTOBuilder = new DriveFileDTOBuilder();
|
private driveFileDTOBuilder = new DriveFileDTOBuilder();
|
||||||
|
private rootAdmins: string[] = config.has("drive.rootAdmins")
|
||||||
|
? config.get("drive.rootAdmins")
|
||||||
|
: [];
|
||||||
|
|
||||||
|
private getCompanyUserRole(companyId: string, userId: string, context?: ExecutionContext) {
|
||||||
|
return gr.services.companies
|
||||||
|
.getCompanyUser({ id: companyId }, { id: userId }, context)
|
||||||
|
.then(a => (a ? a.level : null));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DriveFile item
|
* Creates a DriveFile item
|
||||||
@@ -166,7 +182,7 @@ export class DocumentsController {
|
|||||||
onlyUploadedNotByMe: true,
|
onlyUploadedNotByMe: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
const data = {
|
||||||
...(await globalResolver.services.documents.documents.browse(id, options, context)),
|
...(await globalResolver.services.documents.documents.browse(id, options, context)),
|
||||||
websockets: request.currentUser?.id
|
websockets: request.currentUser?.id
|
||||||
? globalResolver.platformServices.realtime.sign(
|
? globalResolver.platformServices.realtime.sign(
|
||||||
@@ -175,6 +191,8 @@ export class DocumentsController {
|
|||||||
)
|
)
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
sharedWithMe = async (
|
sharedWithMe = async (
|
||||||
@@ -257,6 +275,45 @@ export class DocumentsController {
|
|||||||
return await globalResolver.services.documents.documents.update(id, update, context);
|
return await globalResolver.services.documents.documents.update(id, update, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateLevel = async (
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Params: ItemRequestParams;
|
||||||
|
Body: Partial<DriveFile> | any;
|
||||||
|
Querystring: { public_token?: string };
|
||||||
|
}>,
|
||||||
|
): Promise<any> => {
|
||||||
|
const { id } = request.params;
|
||||||
|
const update = request.body;
|
||||||
|
|
||||||
|
if (!id) throw new CrudException("Missing id", 400);
|
||||||
|
|
||||||
|
if (!this.rootAdmins.includes(request.currentUser.email)) {
|
||||||
|
throw new CrudException("Unauthorized access. User is not a root admin.", 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == "root") {
|
||||||
|
const companyUser = await globalResolver.services.companies.getCompanyUser(
|
||||||
|
{ id: update.company_id },
|
||||||
|
{ id: update.user_id },
|
||||||
|
);
|
||||||
|
if (companyUser) {
|
||||||
|
let level = CompanyUserRole.member;
|
||||||
|
if (update.level == "manage") {
|
||||||
|
level = CompanyUserRole.admin;
|
||||||
|
}
|
||||||
|
await globalResolver.services.companies.setUserRole(
|
||||||
|
update.company_id,
|
||||||
|
update.user_id,
|
||||||
|
companyUser.role,
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new CrudException("User is not part of this company.", 406);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a drive file version.
|
* Create a drive file version.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
|
|||||||
handler: documentsController.update.bind(documentsController),
|
handler: documentsController.update.bind(documentsController),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.route({
|
||||||
|
method: "POST",
|
||||||
|
url: `${serviceUrl}/:id/level`,
|
||||||
|
preValidation: [fastify.authenticateOptional],
|
||||||
|
handler: documentsController.updateLevel.bind(documentsController),
|
||||||
|
});
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: `${serviceUrl}/:id`,
|
url: `${serviceUrl}/:id`,
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ export class CompanyServiceImpl {
|
|||||||
companyId: uuid,
|
companyId: uuid,
|
||||||
userId: uuid,
|
userId: uuid,
|
||||||
role: CompanyUserRole = "member",
|
role: CompanyUserRole = "member",
|
||||||
|
level?: any,
|
||||||
applications: string[] = [],
|
applications: string[] = [],
|
||||||
context?: ExecutionContext,
|
context?: ExecutionContext,
|
||||||
): Promise<SaveResult<CompanyUser>> {
|
): Promise<SaveResult<CompanyUser>> {
|
||||||
@@ -275,6 +276,9 @@ export class CompanyServiceImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entity.role = role;
|
entity.role = role;
|
||||||
|
if (level) {
|
||||||
|
entity.level = level;
|
||||||
|
}
|
||||||
entity.applications = applications;
|
entity.applications = applications;
|
||||||
await this.companyUserRepository.save(entity, context);
|
await this.companyUserRepository.save(entity, context);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
UserListQueryParameters,
|
UserListQueryParameters,
|
||||||
UserObject,
|
UserObject,
|
||||||
UserParameters,
|
UserParameters,
|
||||||
|
CompanyUsersParameters,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import Company from "../entities/company";
|
import Company from "../entities/company";
|
||||||
import CompanyUser from "../entities/company_user";
|
import CompanyUser from "../entities/company_user";
|
||||||
@@ -144,6 +145,36 @@ export class UsersCrudController
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async all(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Querystring: UserListQueryParameters;
|
||||||
|
Params: CompanyUsersParameters;
|
||||||
|
}>,
|
||||||
|
): Promise<ResourceListResponse<UserObject>> {
|
||||||
|
const companyId = request.params.companyId;
|
||||||
|
|
||||||
|
const users = await gr.services.users.search(
|
||||||
|
new Pagination(request.query.page_token, request.query.limit),
|
||||||
|
{
|
||||||
|
search: "",
|
||||||
|
companyId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const resUsers = await Promise.all(
|
||||||
|
users.getEntities().map(user =>
|
||||||
|
formatUser(user, {
|
||||||
|
includeCompanies: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// return users;
|
||||||
|
return {
|
||||||
|
resources: resUsers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async getUserCompanies(
|
async getUserCompanies(
|
||||||
request: FastifyRequest<{ Params: UserParameters }>,
|
request: FastifyRequest<{ Params: UserParameters }>,
|
||||||
_reply: FastifyReply,
|
_reply: FastifyReply,
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next)
|
|||||||
handler: usersController.list.bind(usersController),
|
handler: usersController.list.bind(usersController),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.route({
|
||||||
|
method: "POST",
|
||||||
|
url: `${usersUrl}/:companyId/all`,
|
||||||
|
preHandler: accessControl,
|
||||||
|
preValidation: [fastify.authenticate],
|
||||||
|
schema: getUsersSchema,
|
||||||
|
handler: usersController.all.bind(usersController),
|
||||||
|
});
|
||||||
|
|
||||||
// Get a list of companies for a user, only common companies with current user are returned.
|
// Get a list of companies for a user, only common companies with current user are returned.
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ export interface UserParameters {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CompanyUsersParameters {
|
||||||
|
/* user id */
|
||||||
|
companyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompanyParameters {
|
export interface CompanyParameters {
|
||||||
/* company id */
|
/* company id */
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -130,3 +130,8 @@ export interface JWTObject {
|
|||||||
email: string;
|
email: string;
|
||||||
track: boolean;
|
track: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CompanyUserRole {
|
||||||
|
member = 1,
|
||||||
|
admin = 0,
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ export default class TestHelpers {
|
|||||||
this.platform = platform
|
this.platform = platform
|
||||||
}
|
}
|
||||||
|
|
||||||
private async init(newUser: boolean) {
|
private async init(newUser: boolean, options?: {}) {
|
||||||
this.dbService = await TestDbService.getInstance(this.platform, true);
|
this.dbService = await TestDbService.getInstance(this.platform, true);
|
||||||
if (newUser) {
|
if (newUser) {
|
||||||
this.workspace = this.platform.workspace;
|
this.workspace = this.platform.workspace;
|
||||||
const workspacePK = {id: this.workspace.workspace_id, company_id: this.workspace.company_id};
|
const workspacePK = {id: this.workspace.workspace_id, company_id: this.workspace.company_id};
|
||||||
this.user = await this.dbService.createUser([workspacePK], {}, uuidv1());
|
this.user = await this.dbService.createUser([workspacePK], options, uuidv1());
|
||||||
} else {
|
} else {
|
||||||
this.user = this.platform.currentUser;
|
this.user = this.platform.currentUser;
|
||||||
this.workspace = this.platform.workspace;
|
this.workspace = this.platform.workspace;
|
||||||
@@ -52,9 +52,9 @@ export default class TestHelpers {
|
|||||||
this.jwt = this.getJWTTokenForUser(this.user.id);
|
this.jwt = this.getJWTTokenForUser(this.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getInstance(platform: TestPlatform, newUser = false): Promise<TestHelpers> {
|
public static async getInstance(platform: TestPlatform, newUser = false, options?: {}): Promise<TestHelpers> {
|
||||||
const helpers = new TestHelpers(platform);
|
const helpers = new TestHelpers(platform);
|
||||||
await helpers.init(newUser)
|
await helpers.init(newUser, options)
|
||||||
return helpers;
|
return helpers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ describe("The Documents Browser Window and API", () => {
|
|||||||
|
|
||||||
it("Should not be visible for other users", async () => {
|
it("Should not be visible for other users", async () => {
|
||||||
const myDriveId = "user_" + currentUser.user.id;
|
const myDriveId = "user_" + currentUser.user.id;
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
await currentUser.uploadAllFilesOneByOne(myDriveId);
|
await currentUser.uploadAllFilesOneByOne(myDriveId);
|
||||||
await new Promise(r => setTimeout(r, 5000));
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
@@ -107,8 +107,8 @@ describe("The Documents Browser Window and API", () => {
|
|||||||
|
|
||||||
it("Should contain files that were shared with the user", async () => {
|
it("Should contain files that were shared with the user", async () => {
|
||||||
const sharedWIthMeFolder = "shared_with_me";
|
const sharedWIthMeFolder = "shared_with_me";
|
||||||
const oneUser = await TestHelpers.getInstance(platform, true);
|
const oneUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
let files = await oneUser.uploadAllFilesOneByOne();
|
let files = await oneUser.uploadAllFilesOneByOne();
|
||||||
await new Promise(r => setTimeout(r, 5000));
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search for an item", async () => {
|
it("did search for an item", async () => {
|
||||||
jest.setTimeout(10000);
|
// jest.setTimeout(10000);
|
||||||
const createItemResult = await createItem();
|
const createItemResult = await createItem();
|
||||||
|
|
||||||
expect(createItemResult.id).toBeDefined();
|
expect(createItemResult.id).toBeDefined();
|
||||||
@@ -171,10 +171,10 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search for an item and check that all the fields for 'shared_with_me' view", async () => {
|
it("did search for an item and check that all the fields for 'shared_with_me' view", async () => {
|
||||||
jest.setTimeout(20000);
|
// jest.setTimeout(20000);
|
||||||
//given:: user uploaded one doc and give permission to another user
|
//given:: user uploaded one doc and give permission to another user
|
||||||
const oneUser = await TestHelpers.getInstance(platform, true);
|
const oneUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
//upload files
|
//upload files
|
||||||
const doc = await oneUser.uploadRandomFileAndCreateDocument();
|
const doc = await oneUser.uploadRandomFileAndCreateDocument();
|
||||||
await new Promise(r => setTimeout(r, 3000));
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
@@ -212,10 +212,10 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("'shared_with_me' shouldn't return files uploaded by me", async () => {
|
it("'shared_with_me' shouldn't return files uploaded by me", async () => {
|
||||||
jest.setTimeout(20000);
|
// jest.setTimeout(20000);
|
||||||
//given:: user uploaded one doc and give permission to another user
|
//given:: user uploaded one doc and give permission to another user
|
||||||
const oneUser = await TestHelpers.getInstance(platform, true);
|
const oneUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
const doc = await oneUser.uploadRandomFileAndCreateDocument();
|
const doc = await oneUser.uploadRandomFileAndCreateDocument();
|
||||||
await new Promise(r => setTimeout(r, 3000));
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
//give permissions to the file
|
//give permissions to the file
|
||||||
@@ -277,7 +277,7 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search by mime type", async () => {
|
it("did search by mime type", async () => {
|
||||||
jest.setTimeout(10000);
|
// jest.setTimeout(10000);
|
||||||
// given:: all the sample files uploaded and documents for them created
|
// given:: all the sample files uploaded and documents for them created
|
||||||
await currentUser.uploadAllFilesOneByOne();
|
await currentUser.uploadAllFilesOneByOne();
|
||||||
|
|
||||||
@@ -295,8 +295,8 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search by last modified", async () => {
|
it("did search by last modified", async () => {
|
||||||
jest.setTimeout(10000);
|
// jest.setTimeout(10000);
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
// given:: all the sample files uploaded and documents for them created
|
// given:: all the sample files uploaded and documents for them created
|
||||||
const start = new Date().getTime();
|
const start = new Date().getTime();
|
||||||
await user.uploadAllFilesOneByOne();
|
await user.uploadAllFilesOneByOne();
|
||||||
@@ -320,8 +320,8 @@ describe("the Drive feature", () => {
|
|||||||
|
|
||||||
it("did search a file shared by another user", async () => {
|
it("did search a file shared by another user", async () => {
|
||||||
//given:
|
//given:
|
||||||
const oneUser = await TestHelpers.getInstance(platform, true);
|
const oneUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
//upload files
|
//upload files
|
||||||
let files = await oneUser.uploadAllFilesOneByOne();
|
let files = await oneUser.uploadAllFilesOneByOne();
|
||||||
|
|
||||||
@@ -348,10 +348,10 @@ describe("the Drive feature", () => {
|
|||||||
}, 30000000);
|
}, 30000000);
|
||||||
|
|
||||||
it("did search a file by file owner", async () => {
|
it("did search a file by file owner", async () => {
|
||||||
jest.setTimeout(30000);
|
// jest.setTimeout(30000);
|
||||||
//given:
|
//given:
|
||||||
const oneUser = await TestHelpers.getInstance(platform, true);
|
const oneUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
//upload files
|
//upload files
|
||||||
let files = await oneUser.uploadAllFilesOneByOne();
|
let files = await oneUser.uploadAllFilesOneByOne();
|
||||||
await anotherUser.uploadAllFilesOneByOne();
|
await anotherUser.uploadAllFilesOneByOne();
|
||||||
@@ -386,8 +386,8 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search by 'added' date", async () => {
|
it("did search by 'added' date", async () => {
|
||||||
jest.setTimeout(10000);
|
// jest.setTimeout(10000);
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
// given:: all the sample files uploaded and documents for them created
|
// given:: all the sample files uploaded and documents for them created
|
||||||
await user.uploadRandomFileAndCreateDocument();
|
await user.uploadRandomFileAndCreateDocument();
|
||||||
const start = new Date().getTime();
|
const start = new Date().getTime();
|
||||||
@@ -411,7 +411,7 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search order by name", async () => {
|
it("did search order by name", async () => {
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
// given:: all the sample files uploaded and documents for them created
|
// given:: all the sample files uploaded and documents for them created
|
||||||
await user.uploadAllFilesAndCreateDocuments();
|
await user.uploadAllFilesAndCreateDocuments();
|
||||||
//wait for putting docs to elastic and its indexing
|
//wait for putting docs to elastic and its indexing
|
||||||
@@ -430,8 +430,8 @@ describe("the Drive feature", () => {
|
|||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
it("did search order by name desc", async () => {
|
it("did search order by name desc", async () => {
|
||||||
jest.setTimeout(10000);
|
// jest.setTimeout(10000);
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
// given:: all the sample files uploaded and documents for them created
|
// given:: all the sample files uploaded and documents for them created
|
||||||
await user.uploadAllFilesOneByOne();
|
await user.uploadAllFilesOneByOne();
|
||||||
//wait for putting docs to elastic and its indexing
|
//wait for putting docs to elastic and its indexing
|
||||||
@@ -450,8 +450,8 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search order by added date", async () => {
|
it("did search order by added date", async () => {
|
||||||
jest.setTimeout(10000);
|
// jest.setTimeout(10000);
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
// given:: all the sample files uploaded and documents for them created
|
// given:: all the sample files uploaded and documents for them created
|
||||||
await user.uploadAllFilesOneByOne();
|
await user.uploadAllFilesOneByOne();
|
||||||
//wait for putting docs to elastic and its indexing
|
//wait for putting docs to elastic and its indexing
|
||||||
@@ -470,8 +470,8 @@ describe("the Drive feature", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("did search order by added date desc", async () => {
|
it("did search order by added date desc", async () => {
|
||||||
jest.setTimeout(10000);
|
// jest.setTimeout(10000);
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
// given:: all the sample files uploaded and documents for them created
|
// given:: all the sample files uploaded and documents for them created
|
||||||
await user.uploadAllFilesOneByOne();
|
await user.uploadAllFilesOneByOne();
|
||||||
//wait for putting docs to elastic and its indexing
|
//wait for putting docs to elastic and its indexing
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ describe("the public links feature", () => {
|
|||||||
describe("Basic Flow", () => {
|
describe("Basic Flow", () => {
|
||||||
|
|
||||||
const createItem = async (): Promise<DriveFileMockClass> => {
|
const createItem = async (): Promise<DriveFileMockClass> => {
|
||||||
await TestDbService.getInstance(platform, true);
|
await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||||
|
|
||||||
const item = {
|
const item = {
|
||||||
name: "public file",
|
name: "public file",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: docker/tdrive-node/Dockerfile
|
dockerfile: docker/tdrive-node/Dockerfile
|
||||||
target: production
|
target: development
|
||||||
container_name: tdrive-node
|
container_name: tdrive-node
|
||||||
hostname: tdrive_node
|
hostname: tdrive_node
|
||||||
ports:
|
ports:
|
||||||
@@ -28,6 +28,7 @@ services:
|
|||||||
- PUBSUB_TYPE=local
|
- PUBSUB_TYPE=local
|
||||||
- ./docker-data/documents/:/storage/
|
- ./docker-data/documents/:/storage/
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./backend/node/src:/usr/src/app/src
|
||||||
- ./docker-data/documents/:/storage/
|
- ./docker-data/documents/:/storage/
|
||||||
depends_on:
|
depends_on:
|
||||||
- mongo
|
- mongo
|
||||||
|
|||||||
@@ -208,6 +208,7 @@
|
|||||||
"components.internal-access_cannal_info_give_back":"You will need to go to Tdrive chat to give back access to this item.",
|
"components.internal-access_cannal_info_give_back":"You will need to go to Tdrive chat to give back access to this item.",
|
||||||
"components.internal-access_specific_rules":"Specific access rules",
|
"components.internal-access_specific_rules":"Specific access rules",
|
||||||
"components.internal-access_specific_rules_you":"(you)",
|
"components.internal-access_specific_rules_you":"(you)",
|
||||||
|
"components.internal-manage_root_users":"Shared drive access rules",
|
||||||
"components.select-users_search_users":"Search users",
|
"components.select-users_search_users":"Search users",
|
||||||
"common.access-level_full_acess":"Full access",
|
"common.access-level_full_acess":"Full access",
|
||||||
"common.access-level_read":"Read",
|
"common.access-level_read":"Read",
|
||||||
@@ -306,6 +307,7 @@
|
|||||||
"components.item_context_menu.add_documents": "Add document or folder",
|
"components.item_context_menu.add_documents": "Add document or folder",
|
||||||
"components.item_context_menu.download_folder": "Download folder",
|
"components.item_context_menu.download_folder": "Download folder",
|
||||||
"components.item_context_menu.go_to_trash": "Go to trash",
|
"components.item_context_menu.go_to_trash": "Go to trash",
|
||||||
|
"components.item_context_menu.manage_users": "Manager users",
|
||||||
"components.item_context_menu.all": "All",
|
"components.item_context_menu.all": "All",
|
||||||
"components.item_context_menu.today": "Today",
|
"components.item_context_menu.today": "Today",
|
||||||
"components.item_context_menu.last_week": "Last week",
|
"components.item_context_menu.last_week": "Last week",
|
||||||
|
|||||||
@@ -192,6 +192,7 @@
|
|||||||
"components.internal-access_cannal_info_give_back":"Vous devez aller sur Tdrive chat pour redonner accès à ce fichier",
|
"components.internal-access_cannal_info_give_back":"Vous devez aller sur Tdrive chat pour redonner accès à ce fichier",
|
||||||
"components.internal-access_specific_rules":"Droit d'accès spécifique",
|
"components.internal-access_specific_rules":"Droit d'accès spécifique",
|
||||||
"components.internal-access_specific_rules_you":"(Vous)",
|
"components.internal-access_specific_rules_you":"(Vous)",
|
||||||
|
"components.internal-manage_root_users":"Règles d'accès à l'espace de travail partagé",
|
||||||
"components.select-users_search_users":"Chercher un utilisateur",
|
"components.select-users_search_users":"Chercher un utilisateur",
|
||||||
"common.access-level_full_acess":"Accès Total",
|
"common.access-level_full_acess":"Accès Total",
|
||||||
"common.access-level_read":"Lecture",
|
"common.access-level_read":"Lecture",
|
||||||
|
|||||||
@@ -192,6 +192,7 @@
|
|||||||
"components.internal-access_cannal_info_give_back":"You will need to go to Tdrive chat to give back access to this item.",
|
"components.internal-access_cannal_info_give_back":"You will need to go to Tdrive chat to give back access to this item.",
|
||||||
"components.internal-access_specific_rules":"Specific access rules",
|
"components.internal-access_specific_rules":"Specific access rules",
|
||||||
"components.internal-access_specific_rules_you":"(you)",
|
"components.internal-access_specific_rules_you":"(you)",
|
||||||
|
"components.internal-manage_root_users":"Shared drive access rules",
|
||||||
"components.select-users_search_users":"Search users",
|
"components.select-users_search_users":"Search users",
|
||||||
"common.access-level_full_acess":"Full access",
|
"common.access-level_full_acess":"Full access",
|
||||||
"common.access-level_read":"Read",
|
"common.access-level_read":"Read",
|
||||||
|
|||||||
@@ -175,4 +175,11 @@ export class DriveApiClient {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async updateLevel(companyId: string, id: string, update: any) {
|
||||||
|
return await Api.post<any, any>(
|
||||||
|
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendTdriveToken()}/level`,
|
||||||
|
update,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { DriveApiClient } from '../api-client/api-client';
|
|||||||
import { DriveItemAtom, DriveItemChildrenAtom } from '../state/store';
|
import { DriveItemAtom, DriveItemChildrenAtom } from '../state/store';
|
||||||
import { BrowseFilter, DriveItem, DriveItemVersion } from '../types';
|
import { BrowseFilter, DriveItem, DriveItemVersion } from '../types';
|
||||||
import { SharedWithMeFilterState } from '../state/shared-with-me-filter';
|
import { SharedWithMeFilterState } from '../state/shared-with-me-filter';
|
||||||
import Languages from "features/global/services/languages-service";
|
import Languages from 'features/global/services/languages-service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the children of a drive item
|
* Returns the children of a drive item
|
||||||
@@ -21,7 +21,7 @@ export const useDriveActions = () => {
|
|||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
async (parentId: string) => {
|
async (parentId: string) => {
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
const filter:BrowseFilter = {
|
const filter: BrowseFilter = {
|
||||||
company_id: companyId,
|
company_id: companyId,
|
||||||
mime_type: sharedFilter.mimeType.value,
|
mime_type: sharedFilter.mimeType.value,
|
||||||
};
|
};
|
||||||
@@ -110,5 +110,22 @@ export const useDriveActions = () => {
|
|||||||
[refresh],
|
[refresh],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { create, refresh, download, downloadZip, remove, update };
|
const updateLevel = useCallback(
|
||||||
|
async (id: string, userId: string, level: string) => {
|
||||||
|
try {
|
||||||
|
const updateBody = {
|
||||||
|
company_id: companyId,
|
||||||
|
user_id: userId,
|
||||||
|
level: level
|
||||||
|
}
|
||||||
|
await DriveApiClient.updateLevel(companyId, id, updateBody);
|
||||||
|
await refresh(id || '');
|
||||||
|
} catch (e) {
|
||||||
|
ToasterService.error(Languages.t('hooks.use-drive-actions.unable_update_file'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[refresh],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { create, refresh, download, downloadZip, remove, update, updateLevel };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const useDriveItem = (id: string) => {
|
|||||||
const item = useRecoilValue(DriveItemAtom(id));
|
const item = useRecoilValue(DriveItemAtom(id));
|
||||||
const children = useRecoilValue(DriveItemChildrenAtom(id));
|
const children = useRecoilValue(DriveItemChildrenAtom(id));
|
||||||
const [loading, setLoading] = useRecoilState(LoadingStateInitTrue('useDriveItem-' + id));
|
const [loading, setLoading] = useRecoilState(LoadingStateInitTrue('useDriveItem-' + id));
|
||||||
const { refresh: refreshItem, create, update: _update, remove: _remove } = useDriveActions();
|
const { refresh: refreshItem, create, update: _update, updateLevel: _updateLevel, remove: _remove } = useDriveActions();
|
||||||
const { uploadVersion: _uploadVersion } = useDriveUpload();
|
const { uploadVersion: _uploadVersion } = useDriveUpload();
|
||||||
|
|
||||||
const refresh = useCallback(
|
const refresh = useCallback(
|
||||||
@@ -57,6 +57,19 @@ export const useDriveItem = (id: string) => {
|
|||||||
[id, setLoading, refresh, item?.item?.parent_id],
|
[id, setLoading, refresh, item?.item?.parent_id],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updateLevel = useCallback(
|
||||||
|
async (userId: string, level: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await _updateLevel(id, userId, level);
|
||||||
|
} catch (e) {
|
||||||
|
ToasterService.error('Unable to update user access.');
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
},
|
||||||
|
[id, setLoading, refresh, item?.item?.parent_id],
|
||||||
|
);
|
||||||
|
|
||||||
const uploadVersion = useCallback(
|
const uploadVersion = useCallback(
|
||||||
async (file: File) => {
|
async (file: File) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -87,6 +100,7 @@ export const useDriveItem = (id: string) => {
|
|||||||
uploadVersion,
|
uploadVersion,
|
||||||
create,
|
create,
|
||||||
update,
|
update,
|
||||||
|
updateLevel,
|
||||||
remove,
|
remove,
|
||||||
refresh,
|
refresh,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,6 +97,28 @@ class UserAPIClientService {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async all(
|
||||||
|
companyId: string,
|
||||||
|
options?: { bufferize?: boolean; callback?: (res: UserType[]) => void },
|
||||||
|
): Promise<UserType[]> {
|
||||||
|
|
||||||
|
const res = await new Promise<UserType[]>(resolve =>
|
||||||
|
Api.post(
|
||||||
|
`/internal/services/users/v1/users/${companyId}/all`,
|
||||||
|
{
|
||||||
|
include_companies: true,
|
||||||
|
},
|
||||||
|
(res: { resources: UserType[] }): void => {
|
||||||
|
resolve(res.resources && res.resources.length ? res.resources : []);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options?.callback) options.callback(res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
async getCurrentUserCompanies(): Promise<CompanyType[]> {
|
async getCurrentUserCompanies(): Promise<CompanyType[]> {
|
||||||
return this.listCompanies(CurrentUser.get()?.id || '');
|
return this.listCompanies(CurrentUser.get()?.id || '');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||||
|
import { UserType } from '@features/users/types/user';
|
||||||
|
import UserAPIClient from '@features/users/api/user-api-client';
|
||||||
|
|
||||||
|
export const useUserCompanyList = (): UserType[] => {
|
||||||
|
const companyId = useRouterCompany();
|
||||||
|
const [users, setUsers] = useState<UserType[]>([]);
|
||||||
|
const refresh = async () => {
|
||||||
|
const updatedData = await UserAPIClient.all(companyId);
|
||||||
|
setUsers(updatedData);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
}, []);
|
||||||
|
return users;
|
||||||
|
};
|
||||||
@@ -30,6 +30,7 @@ import { CreateModalAtom } from './modals/create';
|
|||||||
import { PropertiesModal } from './modals/properties';
|
import { PropertiesModal } from './modals/properties';
|
||||||
import { AccessModal } from './modals/update-access';
|
import { AccessModal } from './modals/update-access';
|
||||||
import { VersionsModal } from './modals/versions';
|
import { VersionsModal } from './modals/versions';
|
||||||
|
import { UsersModal } from './modals/manage-users';
|
||||||
import { SharedFilesTable } from './shared-files-table';
|
import { SharedFilesTable } from './shared-files-table';
|
||||||
import useRouteState from 'app/features/router/hooks/use-route-state';
|
import useRouteState from 'app/features/router/hooks/use-route-state';
|
||||||
import { SharedWithMeFilterState } from '@features/drive/state/shared-with-me-filter';
|
import { SharedWithMeFilterState } from '@features/drive/state/shared-with-me-filter';
|
||||||
@@ -59,6 +60,7 @@ export default memo(
|
|||||||
}) => {
|
}) => {
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
const companyId = useRouterCompany();
|
const companyId = useRouterCompany();
|
||||||
|
const role = user ? (user?.companies || []).find(company => company?.company.id === companyId)?.role : "member";
|
||||||
setTdriveTabToken(tdriveTabContextToken || null);
|
setTdriveTabToken(tdriveTabContextToken || null);
|
||||||
const [filter, setFilter] = useRecoilState(SharedWithMeFilterState);
|
const [filter, setFilter] = useRecoilState(SharedWithMeFilterState);
|
||||||
const { viewId } = useRouteState();
|
const { viewId } = useRouteState();
|
||||||
@@ -189,6 +191,7 @@ export default memo(
|
|||||||
|
|
||||||
>
|
>
|
||||||
<DriveRealtimeObject id={parentId} key={parentId} />
|
<DriveRealtimeObject id={parentId} key={parentId} />
|
||||||
|
{role == "admin" && <UsersModal />}
|
||||||
<VersionsModal />
|
<VersionsModal />
|
||||||
<AccessModal />
|
<AccessModal />
|
||||||
<PropertiesModal />
|
<PropertiesModal />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { PropertiesModalAtom } from './modals/properties';
|
|||||||
import { SelectorModalAtom } from './modals/selector';
|
import { SelectorModalAtom } from './modals/selector';
|
||||||
import { AccessModalAtom } from './modals/update-access';
|
import { AccessModalAtom } from './modals/update-access';
|
||||||
import { VersionsModalAtom } from './modals/versions';
|
import { VersionsModalAtom } from './modals/versions';
|
||||||
|
import { UsersModalAtom } from './modals/manage-users';
|
||||||
import { DriveApiClient, getPublicLinkToken } from '@features/drive/api-client/api-client';
|
import { DriveApiClient, getPublicLinkToken } from '@features/drive/api-client/api-client';
|
||||||
import { useDriveActions } from '@features/drive/hooks/use-drive-actions';
|
import { useDriveActions } from '@features/drive/hooks/use-drive-actions';
|
||||||
import { getPublicLink } from '@features/drive/hooks/use-drive-item';
|
import { getPublicLink } from '@features/drive/hooks/use-drive-item';
|
||||||
@@ -40,6 +41,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
const setVersionModal = useSetRecoilState(VersionsModalAtom);
|
const setVersionModal = useSetRecoilState(VersionsModalAtom);
|
||||||
const setAccessModalState = useSetRecoilState(AccessModalAtom);
|
const setAccessModalState = useSetRecoilState(AccessModalAtom);
|
||||||
const setPropertiesModalState = useSetRecoilState(PropertiesModalAtom);
|
const setPropertiesModalState = useSetRecoilState(PropertiesModalAtom);
|
||||||
|
const setUsersModalState = useSetRecoilState(UsersModalAtom);
|
||||||
const { open: preview } = useDrivePreview();
|
const { open: preview } = useDrivePreview();
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
@@ -266,6 +268,13 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ type: 'separator', hide: parent.item!.id != 'root', },
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: Languages.t('components.item_context_menu.manage_users'),
|
||||||
|
hide: parent.item!.id != 'root',
|
||||||
|
onClick: () => setUsersModalState({ open: true }),
|
||||||
|
},
|
||||||
{ type: 'separator', hide: inTrash || parent.access === 'read' },
|
{ type: 'separator', hide: inTrash || parent.access === 'read' },
|
||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
@@ -301,6 +310,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
setCreationModalState,
|
setCreationModalState,
|
||||||
setVersionModal,
|
setVersionModal,
|
||||||
setAccessModalState,
|
setAccessModalState,
|
||||||
|
setUsersModalState,
|
||||||
setPropertiesModalState,
|
setPropertiesModalState,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import Select from '@atoms/input/input-select';
|
||||||
|
import { DriveFileAccessLevel } from '@features/drive/types';
|
||||||
|
import Languages from 'features/global/services/languages-service';
|
||||||
|
|
||||||
|
export const AccessLevel = ({
|
||||||
|
level,
|
||||||
|
onChange,
|
||||||
|
canRemove,
|
||||||
|
hiddenLevels,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
disabled?: boolean;
|
||||||
|
level: DriveFileAccessLevel | null;
|
||||||
|
onChange: (level: DriveFileAccessLevel & 'remove') => void;
|
||||||
|
canRemove?: boolean;
|
||||||
|
className?: string;
|
||||||
|
hiddenLevels?: string[];
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
className={
|
||||||
|
className +
|
||||||
|
' w-auto ' +
|
||||||
|
(level === 'none' ? '!text-rose-500 !bg-rose-100 dark-bg-rose-800' : '')
|
||||||
|
}
|
||||||
|
value={level || 'read'}
|
||||||
|
onChange={e => onChange(e.target.value as DriveFileAccessLevel & 'remove')}
|
||||||
|
>
|
||||||
|
{!hiddenLevels?.includes('manage') && <option value={'manage'}>{Languages.t('common.access-level_full_acess')}</option>}
|
||||||
|
{!hiddenLevels?.includes('read') && <option value={'read'}>{Languages.t('common.access-level_read')}</option>}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { Modal } from '@atoms/modal';
|
||||||
|
import Avatar from '@atoms/avatar';
|
||||||
|
import { Base, Info } from '@atoms/text';
|
||||||
|
import { atom, useRecoilState } from 'recoil';
|
||||||
|
import { useDriveItem } from '@features/drive/hooks/use-drive-item';
|
||||||
|
import AlertManager from '@features/global/services/alert-manager-service';
|
||||||
|
import { useCurrentUser } from '@features/users/hooks/use-current-user';
|
||||||
|
import { useUser } from '@features/users/hooks/use-user';
|
||||||
|
import currentUserService from '@features/users/services/current-user-service';
|
||||||
|
import { useUserCompanyList } from '@features/users/hooks/use-user-company-list';
|
||||||
|
import { AccessLevel } from './common';
|
||||||
|
import Languages from 'features/global/services/languages-service';
|
||||||
|
|
||||||
|
export type UsersModalType = {
|
||||||
|
open: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UsersModalAtom = atom<UsersModalType>({
|
||||||
|
key: 'UsersModalAtom',
|
||||||
|
default: {
|
||||||
|
open: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UsersModal = () => {
|
||||||
|
const [state, setState] = useRecoilState(UsersModalAtom);
|
||||||
|
const userList = useUserCompanyList();
|
||||||
|
return (
|
||||||
|
<Modal open={state.open} onClose={() => setState({ open: false })}>
|
||||||
|
<Base className="block mt-4 mb-1">
|
||||||
|
{Languages.t('components.internal-manage_root_users')}
|
||||||
|
</Base>
|
||||||
|
<div className="rounded-md border mt-2">
|
||||||
|
{userList?.map(user => (
|
||||||
|
<UserAccessLevel key={user.id} id="root" userId={user?.id || ""} username={user.username} role={user?.companies?.[0]?.role || ""} />
|
||||||
|
))}
|
||||||
|
<div className="-mb-px" />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserAccessLevel = ({
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
username,
|
||||||
|
role
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
role: string;
|
||||||
|
}) => {
|
||||||
|
const user = useUser(userId);
|
||||||
|
const { user: currentUser } = useCurrentUser();
|
||||||
|
const { item, loading, updateLevel } = useDriveItem(id);
|
||||||
|
const level = role == "admin" ? "manage" : "read";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 border-t flex flex-row items-center justify-center">
|
||||||
|
<div className="shrink-0">
|
||||||
|
<Avatar
|
||||||
|
avatar={user?.thumbnail || ''}
|
||||||
|
title={!user ? '-' : currentUserService.getFullName(user)}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grow ml-2">
|
||||||
|
<Base>{username} </Base>{' '}
|
||||||
|
{user?.id === currentUser?.id && (
|
||||||
|
<Info>{Languages.t('components.internal-access_specific_rules_you')}</Info>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="shrink-0 ml-2">
|
||||||
|
<AccessLevel
|
||||||
|
disabled={user?.id === currentUser?.id}
|
||||||
|
level={level}
|
||||||
|
canRemove
|
||||||
|
onChange={level => {
|
||||||
|
updateLevel(user?.id || '', level);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -22,4 +22,4 @@ export default () => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -147,4 +147,4 @@ export default () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user