Merge branch 'develop' into feature/web-plugins

This commit is contained in:
Alexey Safronov 2023-09-29 17:21:34 +04:00
commit 0d8bd872d6
34 changed files with 10140 additions and 125 deletions

View File

@ -7,7 +7,7 @@ BUILD_PATH=${BUILD_PATH:-${SRC_PATH}/publish}
BUILD_DOTNET_CORE_ARGS=${BUILD_DOTNET_CORE_ARGS:-"false"}
PROPERTY_BUILD=${PROPERTY_BUILD:-"all"}
BACKEND_NODEJS_SERVICES=${BACKEND_NODEJS_SERVICES:-"ASC.Socket.IO, ASC.SsoAuth"}
BACKEND_NODEJS_SERVICES=${BACKEND_NODEJS_SERVICES:-"ASC.Socket.IO, ASC.SsoAuth, ASC.TelegramReports"}
BACKEND_DOTNETCORE_SERVICES=${BACKEND_DOTNETCORE_SERVICES:-"ASC.Files, ASC.People, ASC.Data.Backup, ASC.Files.Service, ASC.Notify, \
ASC.Studio.Notify, ASC.Web.Api, ASC.Web.Studio, ASC.Data.Backup.BackgroundTasks, ASC.ClearEvents, ASC.ApiSystem, ASC.Web.HealthChecks.UI"}
SELF_CONTAINED=${SELF_CONTAINED:-"false"}
@ -179,6 +179,12 @@ function backend-nodejs-publish {
yarn install --cwd ${SRC_PATH}/common/${ARRAY_NAME_SERVICES[$i]} --frozen-lockfile && \
mkdir -p ${BUILD_PATH}/services/${ARRAY_NAME_SERVICES[$i]}/service/ && \
cp -rfv ${SRC_PATH}/common/${ARRAY_NAME_SERVICES[$i]}/* ${BUILD_PATH}/services/${ARRAY_NAME_SERVICES[$i]}/service/
if [[ ${ARRAY_NAME_SERVICES[$i]} == "ASC.TelegramReports" ]]
then
# build before run
yarn --cwd ${BUILD_PATH}/services/ASC.TelegramReports/service/ build
fi
if [[ ${DOCKER_ENTRYPOINT} != "false" ]]
then
echo "== ADD ${DOCKER_ENTRYPOINT} to ${ARRAY_NAME_SERVICES[$i]} =="

View File

@ -101,6 +101,7 @@ done
services_name_backend_nodejs=()
services_name_backend_nodejs+=(ASC.Socket.IO)
services_name_backend_nodejs+=(ASC.SsoAuth)
services_name_backend_nodejs+=(ASC.TelegramReports)
# Publish backend services (Nodejs)
for i in ${!services_name_backend_nodejs[@]}; do

View File

@ -100,6 +100,7 @@
API_HOST=${CONTAINER_PREFIX}api
STUDIO_HOST=${CONTAINER_PREFIX}studio
SSOAUTH_HOST=${CONTAINER_PREFIX}ssoauth
TELEGRAMREPORTS_HOST=${CONTAINER_PREFIX}telegramreports
MIGRATION_RUNNER_HOST=${CONTAINER_PREFIX}migration-runner
PROXY_HOST=${CONTAINER_PREFIX}proxy
ROUTER_HOST=${CONTAINER_PREFIX}router
@ -122,6 +123,7 @@
SERVICE_API=${API_HOST}:${SERVICE_PORT}
SERVICE_STUDIO=${STUDIO_HOST}:${SERVICE_PORT}
SERVICE_SSOAUTH=${SSOAUTH_HOST}:${SERVICE_PORT}
SERVICE_TELEGRAMREPORTS=${TELEGRAMREPORTS_HOST}:${SERVICE_PORT}
SERVICE_DOCEDITOR=${DOCEDITOR_HOST}:5013
SERVICE_LOGIN=${LOGIN_HOST}:5011
SERVICE_HELTHCHECKS=${HELTHCHECKS_HOST}:${SERVICE_PORT}

View File

@ -1,8 +1,8 @@
version: "3.8"
services:
onlyoffice-dns:
dnsmasq:
image: jpillora/dnsmasq
container_name: onlyoffice-dns
container_name: dnsmasq
restart: always
expose:
- "5380"

View File

@ -267,6 +267,7 @@ services:
- SERVICE_API_SYSTEM=${SERVICE_API_SYSTEM}
- SERVICE_STUDIO=${SERVICE_STUDIO}
- SERVICE_SSOAUTH=${SERVICE_SSOAUTH}
- SERVICE_TELEGRAMREPORTS=${SERVICE_TELEGRAMREPORTS}
- SERVICE_DOCEDITOR=${SERVICE_DOCEDITOR}
- SERVICE_LOGIN=${SERVICE_LOGIN}
- SERVICE_HELTHCHECKS=${SERVICE_HELTHCHECKS}

View File

@ -53,10 +53,13 @@ public class DistributedTaskProgress : DistributedTask
[ProtoMember(3)]
protected int StepCount { get; set; }
protected CancellationToken CancellationToken { get; set; }
public virtual async Task RunJob(DistributedTask _, CancellationToken cancellationToken)
{
Percentage = 0;
Status = DistributedTaskStatus.Running;
CancellationToken = cancellationToken;
await DoJob();
}

View File

@ -47,8 +47,6 @@ public class ReassignProgressItem : DistributedTaskProgress
private bool _notify;
private bool _deleteProfile;
private CancellationToken _cancellationToken;
public ReassignProgressItem(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
@ -70,13 +68,6 @@ public class ReassignProgressItem : DistributedTaskProgress
IsCompleted = false;
}
public override async Task RunJob(DistributedTask distributedTask, CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
await base.RunJob(distributedTask, cancellationToken);
}
protected override async Task DoJob()
{
await using var scope = _serviceScopeFactory.CreateAsyncScope();
@ -167,7 +158,7 @@ public class ReassignProgressItem : DistributedTaskProgress
PublishChanges();
}
_cancellationToken.ThrowIfCancellationRequested();
CancellationToken.ThrowIfCancellationRequested();
}
private async Task SendSuccessNotifyAsync(UserManager userManager, StudioNotifyService studioNotifyService, MessageService messageService, MessageTarget messageTarget, DisplayUserSettingsHelper displayUserSettingsHelper)

View File

@ -0,0 +1,31 @@
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.json",
tsconfigRootDir: __dirname,
sourceType: "module",
},
plugins: ["@typescript-eslint/eslint-plugin"],
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: [".eslintrc.js"],
"prettier/prettier": [
"error",
{
endOfLine: "auto",
},
],
rules: {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
},
};

35
common/ASC.TelegramReports/.gitignore vendored Executable file
View File

@ -0,0 +1,35 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

View File

@ -0,0 +1,14 @@
# ASC Telegram Reports
ASC.TelegramReports
### Installation and usage
```bash
$ npm install
$ npm start
```
### License
[GNU GPL V3](LICENSE)

View File

@ -0,0 +1,7 @@
{
"app": {
"port": 5016,
"appsettings": "../../../../config",
"environment": "Development"
}
}

View File

@ -0,0 +1,26 @@
import * as nconf from "nconf";
import * as path from "path";
import * as fs from "fs";
import * as conf from "./config.json";
nconf.argv().env().file("config", path.join(__dirname, "config.json"));
getAndSaveAppsettings();
export default nconf;
function getAndSaveAppsettings() {
var appsettings = nconf.get("app").appsettings;
if (!path.isAbsolute(appsettings)) {
appsettings = path.join(__dirname, appsettings);
}
var env = nconf.get("app").environment;
console.log('environment: ' + env);
nconf.file("appsettingsWithEnv", path.join(appsettings, 'appsettings.' + env + '.json'));
nconf.file("appsettings", path.join(appsettings, 'appsettings.json'));
nconf.file("telegramConf", path.join(appsettings, "telegram.json"));
}

View File

@ -0,0 +1,31 @@
import firebase from "firebase/compat/app";
import "firebase/compat/database";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./src/app/app.module";
import { AppService } from "./src/app/app.service";
import * as config from "./config";
const winston = require("./src/log.js");
const firebaseConfig = config.default.get("firebase");
firebase.initializeApp(firebaseConfig);
async function bootstrap() {
try {
const app = await NestFactory.create(AppModule);
const appService = app.get(AppService);
winston.info(`Start TelegramReports Service listening`);
const ref = firebase.database().ref("reports").limitToLast(1);
ref.on("child_added", (data) => {
appService.sendMessage(data.val())
});
} catch (e) {
winston.error(e);
}
}
bootstrap();

View File

@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"entryFile": "index.js",
"compilerOptions": {
"deleteOutDir": true
}
}

View File

@ -0,0 +1,75 @@
{
"name": "telegram-reports",
"version": "1.0.0",
"description": "Server for telegram-reports",
"private": true,
"scripts": {
"prebuild": "rimraf dist",
"build": "rimraf dist & nest build",
"start": "rimraf dist & nest start",
"start:dev": "rimraf dist & nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/index",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
},
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.199.0",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/typeorm": "^9.0.0",
"date-and-time": "^2.4.1",
"firebase": "^10.4.0",
"mysql2": "^2.3.3",
"nconf": "^0.12.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"telegraf": "^4.13.1",
"typeorm": "^0.3.7",
"winston": "^3.8.2",
"winston-cloudwatch": "^6.1.1",
"winston-daily-rotate-file": "^4.5.5"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.4",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.2",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.5",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.0.0",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -0,0 +1,9 @@
import { Module } from "@nestjs/common";
import { AppService } from "./app.service";
@Module({
imports: [],
controllers: [],
providers: [AppService],
})
export class AppModule { }

View File

@ -0,0 +1,43 @@
import { Injectable } from "@nestjs/common";
import { Telegraf } from "telegraf";
import * as config from "../../config";
const winston = require("../log.js");
const { botKey, chatId } = config.default.get("telegramConf");
const MAX_LENGTH = 4096; //TG Limit
@Injectable()
export class AppService {
bot = new Telegraf(botKey);
chunkMessage = (str: string, size: number): Array<string> =>
Array.from({ length: Math.ceil(str.length / size) }, (_, i) =>
str.slice(i * size, i * size + size),
);
async sendMessage(report): Promise<string> {
if (!botKey) throw new Error("Empty bot key");
if (!chatId) throw new Error("Empty chat ID");
const message = "New bug report:\n" + JSON.stringify(report);
try {
if (message.length > MAX_LENGTH) {
for (const part of this.chunkMessage(message, MAX_LENGTH)) {
await this.bot.telegram.sendMessage(chatId, part);
}
} else {
await this.bot.telegram.sendMessage(chatId, message);
}
winston.info(`Report sent successfully`, message);
return "Report sent successfully"
} catch (e) {
winston.error(e);
return e;
}
}
}

View File

@ -0,0 +1,103 @@
import * as winston from "winston";
import * as WinstonCloudWatch from "winston-cloudwatch";
import * as date from "date-and-time";
import * as os from "os";
import * as config from "../config";
import { randomUUID } from "crypto";
import "winston-daily-rotate-file";
import * as path from "path";
import * as fs from "fs";
let logpath = process.env.logpath || null;
if (logpath != null) {
if (!path.isAbsolute(logpath)) {
logpath = path.join(__dirname, "..", logpath);
}
}
const fileName = logpath
? path.join(logpath, "telegramreports.%DATE%.log")
: path.join(__dirname, "..", "..", "..", "..", "Logs", "telegramreports.%DATE%.log");
const dirName = path.dirname(fileName);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
}
const aws = config.default.get("aws").cloudWatch;
const accessKeyId = aws.accessKeyId;
const secretAccessKey = aws.secretAccessKey;
const awsRegion = aws.region;
const logGroupName = aws.logGroupName;
const logStreamName = aws.logStreamName.replace("${hostname}", os.hostname())
.replace("${applicationContext}", "TelegramReports")
.replace("${guid}", randomUUID())
.replace("${date}", date.format(new Date(), 'YYYY/MM/DDTHH.mm.ss'));
const options = {
file: {
filename: fileName,
datePattern: "MM-DD",
handleExceptions: true,
humanReadableUnhandledException: true,
zippedArchive: true,
maxSize: "50m",
maxFiles: "30d",
json: true,
},
console: {
level: "debug",
handleExceptions: true,
json: false,
colorize: true,
},
cloudWatch: {
name: 'aws',
level: "debug",
logStreamName: logStreamName,
logGroupName: logGroupName,
awsRegion: awsRegion,
jsonMessage: true,
awsOptions: {
credentials: {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey
}
}
}
};
const transports: winston.transport[] = [
new winston.transports.Console(options.console),
new winston.transports.DailyRotateFile(options.file)
];
if (aws != null && aws.accessKeyId !== '') {
transports.push(new WinstonCloudWatch(options.cloudWatch));
}
const customFormat = winston.format(info => {
const now = new Date();
info.date = date.format(now, 'YYYY-MM-DD HH:mm:ss');
info.applicationContext = "TelegramReports";
info.level = info.level.toUpperCase();
const hostname = os.hostname();
info["instance-id"] = hostname;
return info;
})();
module.exports = winston.createLogger({
format: winston.format.combine(
customFormat,
winston.format.json()
),
transports: transports,
exitOnError: false,
});

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"resolveJsonModule": true
}
}

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ASC.Core;
namespace ASC.ApiCache;
public class Startup
@ -117,6 +119,8 @@ public class Startup
{
app.UseCors(CustomCorsPolicyName);
}
app.UseSynchronizationContextMiddleware();
app.UseAuthentication();

View File

@ -139,6 +139,8 @@ public class Startup
{
app.UseCors(CustomCorsPolicyName);
}
app.UseSynchronizationContextMiddleware();
app.UseAuthentication();

6
config/telegram.json Normal file
View File

@ -0,0 +1,6 @@
{
"telegramConf": {
"botKey": "",
"chatId": ""
}
}

View File

@ -88,6 +88,9 @@ export default function withFileActions(WrappedFileItem) {
e.target.classList.contains("item-file-name") ||
e.target.classList.contains("row-content-link");
if ((isRoomsFolder || isArchiveFolder) && isFileName && !isSelected)
setBufferSelection(item);
if (
isPrivacy ||
isTrashFolder ||

View File

@ -5,33 +5,12 @@ import AutoSizer from "react-virtualized-auto-sizer";
import CustomScrollbarsVirtualList from "@docspace/components/scrollbar/custom-scrollbars-virtual-list";
import InfiniteLoader from "react-window-infinite-loader";
import User from "./User";
import { tablet, mobile, isMobile } from "@docspace/components/utils/device";
import { isMobile } from "@docspace/components/utils/device";
import throttle from "lodash/throttle";
import Loaders from "@docspace/common/components/Loaders";
const StyledMembersList = styled.div`
height: ${({ withBanner, isPublicRoomType }) =>
isPublicRoomType
? withBanner
? "calc(100vh - 442px)"
: "calc(100vh - 286px)"
: "calc(100vh - 266px)"};
@media ${tablet} {
height: ${({ withBanner, isPublicRoomType }) =>
isPublicRoomType
? withBanner
? "calc(100vh - 362px)"
: "calc(100vh - 206px)"
: "calc(100vh - 186px)"};
}
@media ${mobile} {
height: ${({ withBanner, isPublicRoomType }) =>
isPublicRoomType
? withBanner
? "calc(100vh - 426px)"
: "calc(100vh - 270px)"
: "calc(100vh - 250px)"};
}
height: ${({ offsetTop }) => `calc(100vh - ${offsetTop})`};
`;
const Item = memo(({ data, index, style }) => {
@ -53,6 +32,18 @@ const Item = memo(({ data, index, style }) => {
const user = members[index];
if (!user) {
return (
<div style={{ ...style, width: "calc(100% - 8px)", margin: "0 -16px" }}>
<Loaders.SelectorRowLoader
isMultiSelect={false}
isContainer={true}
isUser={true}
/>
</div>
);
}
return (
<div key={user.id} style={{ ...style, width: "calc(100% - 8px)" }}>
<User
@ -96,21 +87,34 @@ const MembersList = (props) => {
itemCount,
onRepeatInvitation,
loadNextPage,
isPublicRoomType,
withBanner,
} = props;
const itemsCount = members.length;
const itemsCount = hasNextPage ? members.length + 1 : members.length;
const canInviteUserInRoomAbility = security?.EditAccess;
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
const [isMobileView, setIsMobileView] = useState(isMobile());
const onResize = () => {
const [offsetTop, setOffsetTop] = useState(0);
const onResize = throttle(() => {
const isMobileView = isMobile();
setIsMobileView(isMobileView);
setOffset();
}, 300);
const setOffset = () => {
const rect = document
.getElementById("infoPanelMembersList")
?.getBoundingClientRect();
setOffsetTop(Math.ceil(rect?.top) + 2 + "px");
};
useEffect(() => {
setOffset();
}, [members]);
useEffect(() => {
window.addEventListener("resize", onResize);
@ -138,10 +142,7 @@ const MembersList = (props) => {
);
return (
<StyledMembersList
withBanner={withBanner}
isPublicRoomType={isPublicRoomType}
>
<StyledMembersList id="infoPanelMembersList" offsetTop={offsetTop}>
<AutoSizer>
{({ height, width }) => (
<InfiniteLoader

View File

@ -8,7 +8,7 @@ import {
RoomsType,
ShareAccessRights,
} from "@docspace/common/constants";
import Loaders from "@docspace/common/components/Loaders";
import MembersHelper from "../../helpers/MembersHelper";
import PublicRoomBlock from "./sub-components/PublicRoomBlock";
import MembersList from "./MembersList";
@ -38,14 +38,16 @@ const Members = ({
setExternalLinks,
membersFilter,
externalLinks,
members,
setMembersList,
}) => {
const [isLoading, setIsLoading] = useState(false);
const membersHelper = new MembersHelper({ t });
const [members, setMembers] = useState(null);
const security = selectionParentRoom ? selectionParentRoom.security : {};
const fetchMembers = async (roomId, clearFilter = true) => {
if (isLoading) return;
const isPublic = selection?.roomType ?? selectionParentRoom?.roomType;
const requests = [getRoomMembers(roomId, clearFilter)];
@ -53,7 +55,12 @@ const Members = ({
requests.push(getRoomLinks(roomId));
}
let timerId;
if (clearFilter) timerId = setTimeout(() => setIsLoading(true), 300);
const [data, links] = await Promise.all(requests);
clearFilter && setIsLoading(false);
clearTimeout(timerId);
links && setExternalLinks(links);
@ -131,11 +138,10 @@ const Members = ({
};
const updateSelectionParentRoomActionSelection = useCallback(async () => {
if (!selection.isRoom) return;
if (!selection.isRoom || selection.id === members?.roomId) return;
const fetchedMembers = await fetchMembers(selection.id);
setMembers(fetchedMembers);
setMembersList(fetchedMembers);
setSelectionParentRoom({
...selection,
@ -159,7 +165,7 @@ const Members = ({
members: fetchedMembers,
});
setMembers(fetchedMembers);
setMembersList(fetchedMembers);
}, [selectionParentRoom, selection?.id, updateRoomMembers]);
useEffect(() => {
@ -179,12 +185,6 @@ const Members = ({
.catch((err) => toastr.error(err));
};
if (!selectionParentRoom || !members) return null;
const [currentMember] = members.administrators.filter(
(member) => member.id === selfId
);
const loadNextPage = async () => {
const roomId = selectionParentRoom.id;
const fetchedMembers = await fetchMembers(roomId, false);
@ -196,9 +196,16 @@ const Members = ({
expected: [...members.expected, ...expected],
};
setMembers(newMembers);
setMembersList(newMembers);
};
if (isLoading) return <Loaders.InfoPanelViewLoader view="members" />;
else if (!members) return <></>;
const [currentMember] = members.administrators.filter(
(member) => member.id === selfId
);
const { administrators, users, expected } = members;
const membersList = [...administrators, ...users, ...expected];
@ -224,11 +231,11 @@ const Members = ({
changeUserType={changeUserType}
setIsScrollLocked={setIsScrollLocked}
hasNextPage={membersList.length - headersCount < membersFilter.total}
itemCount={membersFilter.total}
itemCount={membersFilter.total + headersCount}
onRepeatInvitation={onRepeatInvitation}
isPublicRoomType={isPublicRoomType}
withBanner={isPublicRoomType && externalLinks.length > 0}
setMembers={setMembers}
setMembers={setMembersList}
/>
</>
);
@ -238,7 +245,6 @@ export default inject(
({ auth, filesStore, peopleStore, selectedFolderStore, publicRoomStore }) => {
const {
selectionParentRoom,
selection,
setSelectionParentRoom,
setView,
roomsView,
@ -247,6 +253,8 @@ export default inject(
setUpdateRoomMembers,
setIsScrollLocked,
membersList,
setMembersList,
} = auth.infoPanelStore;
const {
getRoomMembers,
@ -289,6 +297,8 @@ export default inject(
setExternalLinks,
membersFilter,
externalLinks: roomLinks,
members: membersList,
setMembersList,
};
}
)(

View File

@ -1,17 +1,21 @@
import InfoPanelRoomEmptyScreenSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url";
import InfoPanelRoomEmptyScreenDarkSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate_dark.svg?url";
import React from "react";
import React, { useEffect } from "react";
import { inject, observer } from "mobx-react";
import Text from "@docspace/components/text";
import { StyledNoItemContainer } from "../../styles/noItem";
const NoRoomItem = ({ t, theme }) => {
const NoRoomItem = ({ t, theme, setMembersList }) => {
const imageSrc = theme.isBase
? InfoPanelRoomEmptyScreenSvgUrl
: InfoPanelRoomEmptyScreenDarkSvgUrl;
useEffect(() => {
setMembersList(null);
}, []);
return (
<StyledNoItemContainer className="info-panel_gallery-empty-screen">
<div className="no-thumbnail-img-wrapper">
@ -27,5 +31,6 @@ const NoRoomItem = ({ t, theme }) => {
export default inject(({ auth }) => {
return {
theme: auth.settingsStore.theme,
setMembersList: auth.infoPanelStore.setMembersList,
};
})(observer(NoRoomItem));

View File

@ -1,24 +1,30 @@
import IntegrationSvgUrl from "PUBLIC_DIR/images/integration.svg?url";
import IntegrationDarkSvgUrl from "PUBLIC_DIR/images/integration.dark.svg?url";
import React from "react";
import PropTypes from "prop-types";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import { showLoader, hideLoader } from "@docspace/common/utils";
import Box from "@docspace/components/box";
import Text from "@docspace/components/text";
import Link from "@docspace/components/link";
import Badge from "@docspace/components/badge";
import toastr from "@docspace/components/toast/toastr";
import Button from "@docspace/components/button";
import { showLoader, hideLoader } from "@docspace/common/utils";
import ConsumerItem from "./sub-components/consumerItem";
import ConsumerModalDialog from "./sub-components/consumerModalDialog";
import { inject, observer } from "mobx-react";
import {
mobile,
smallTablet,
isSmallTablet,
} from "@docspace/components/utils/device";
import IntegrationSvgUrl from "PUBLIC_DIR/images/integration.svg?url";
import IntegrationDarkSvgUrl from "PUBLIC_DIR/images/integration.dark.svg?url";
import ConsumerItem from "./sub-components/consumerItem";
import ConsumerModalDialog from "./sub-components/consumerModalDialog";
import ThirdPartyLoader from "./sub-components/thirdPartyLoader";
const RootContainer = styled(Box)`
max-width: 700px;
@ -40,7 +46,6 @@ const RootContainer = styled(Box)`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(293px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.consumer-item-wrapper {
@ -66,10 +71,11 @@ const RootContainer = styled(Box)`
}
.business-plan {
grid-column: 1 / -1;
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 16px;
margin-bottom: -4px;
}
`;
@ -218,54 +224,56 @@ class ThirdPartyServices extends React.Component {
scale={isSmallTablet()}
/>
</Box>
<div className="consumers-list-container">
{freeConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
</div>
{!isThirdPartyAvailable && (
<div className="business-plan">
<Text fontSize="16px" fontWeight={700}>
{t("IncludedInBusiness")}
</Text>
<Badge
backgroundColor="#EDC409"
label={t("Common:Paid")}
isPaidBadge={true}
/>
{!consumers.length ? (
<ThirdPartyLoader />
) : (
<div className="consumers-list-container">
{freeConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
{!isThirdPartyAvailable && (
<div className="business-plan">
<Text fontSize="16px" fontWeight={700}>
{t("IncludedInBusiness")}
</Text>
<Badge
backgroundColor="#EDC409"
label={t("Common:Paid")}
isPaidBadge={true}
/>
</div>
)}
{paidConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
</div>
)}
<div className="consumers-list-container">
{paidConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
</div>
</RootContainer>
{dialogVisible && (
<ConsumerModalDialog

View File

@ -28,7 +28,7 @@ const StyledBox = styled(Box)`
css`
path {
fill: #ffffff;
opacity: ${props.isSet ? 1 : 0.16};
opacity: 1;
}
${props.isLinkedIn &&
css`
@ -45,7 +45,7 @@ const StyledBox = styled(Box)`
${(props) =>
!props.isThirdPartyAvailable &&
!props.isSet &&
props.canSet &&
css`
path {
opacity: 0.5;
@ -81,7 +81,7 @@ class ConsumerItem extends React.Component {
widthProp="100%"
>
<StyledBox
isSet={isSet}
canSet={consumer.canSet}
isLinkedIn={consumer.name === "linkedin"}
isThirdPartyAvailable={isThirdPartyAvailable}
>

View File

@ -0,0 +1,25 @@
import styled from "styled-components";
import Loaders from "@docspace/common/components/Loaders";
const StyledLoader = styled.div`
max-width: 700px;
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(293px, 1fr));
gap: 20px;
`;
const ThirdPartyLoader = () => {
const rectangles = new Array(6).fill(0);
return (
<StyledLoader>
{rectangles.map((_) => (
<Loaders.Rectangle height="120px" borderRadius="6px" />
))}
</StyledLoader>
);
};
export default ThirdPartyLoader;

View File

@ -2480,7 +2480,7 @@ class FilesStore {
};
getRoomMembers = (id, clearFilter = true) => {
let newFilter = this.membersFilter;
let newFilter = clone(this.membersFilter);
if (clearFilter) {
newFilter = this.getDefaultMembersFilter();
@ -2496,7 +2496,6 @@ class FilesStore {
};
return api.rooms.getRoomMembers(id, membersFilters).then((res) => {
const newFilter = clone(this.membersFilter);
newFilter.total = res.total;
this.setMembersFilter(newFilter);

View File

@ -38,6 +38,7 @@ class InfoPanelStore {
filesStore = null;
selectedFolderStore = null;
treeFoldersStore = null;
membersList = null;
constructor() {
makeAutoObservable(this);
@ -76,6 +77,7 @@ class InfoPanelStore {
this.roomsView = view;
this.fileView = view === "info_members" ? "info_history" : view;
this.isScrollLocked = false;
this.setMembersList(null);
};
setUpdateRoomMembers = (updateRoomMembers) => {
@ -329,6 +331,10 @@ class InfoPanelStore {
const pathname = givenPathName || window.location.pathname.toLowerCase();
return pathname.indexOf("files/trash") !== -1;
};
setMembersList = (membersList) => {
this.membersList = membersList;
};
}
export default InfoPanelStore;

View File

@ -214,7 +214,6 @@ abstract class FileOperation<T, TId> : FileOperation where T : FileOperationData
protected ILinkDao LinkDao { get; private set; }
protected IProviderDao ProviderDao { get; private set; }
protected ILogger Logger { get; private set; }
protected CancellationToken CancellationToken { get; private set; }
protected internal List<TId> Folders { get; private set; }
protected internal List<TId> Files { get; private set; }
protected ExternalShareData CurrentShareData { get; private set; }