mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-31 17:10:56 +03:00
Compare commits
16 Commits
4.3-prep
...
add-permis
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d1f93acfb | ||
|
|
077a0d8fdb | ||
|
|
703848e4e5 | ||
|
|
74315b8c76 | ||
|
|
a9e95c5bb8 | ||
|
|
fe45a889c9 | ||
|
|
e726e991cc | ||
|
|
940267651d | ||
|
|
2dc68139f7 | ||
|
|
301451d021 | ||
|
|
a7f8795e7e | ||
|
|
162094a9b9 | ||
|
|
e843b4c97f | ||
|
|
c784091ad6 | ||
|
|
fb404d3cee | ||
|
|
68c2ee26ff |
6
.github/workflows/docker-release.yml
vendored
6
.github/workflows/docker-release.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
- name: Set image tag
|
- name: Set image tag
|
||||||
id: tags
|
id: tags
|
||||||
run: |
|
run: |
|
||||||
if [ ${{ github.event.action }} == "workflow_dispatch" ]; then
|
if [ "${{ github.event.inputs.tags }}" != "" ]; then
|
||||||
echo "::set-output name=tags::${{ github.event.inputs.tags }}"
|
echo "::set-output name=tags::${{ github.event.inputs.tags }}"
|
||||||
elif [ ${{ github.event.action }} == "release" ]; then
|
elif [ ${{ github.event.action }} == "release" ]; then
|
||||||
echo "::set-output name=tags::${{ github.event.release.tag_name }}"
|
echo "::set-output name=tags::${{ github.event.release.tag_name }}"
|
||||||
@@ -53,8 +53,8 @@ jobs:
|
|||||||
${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
|
${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
|
||||||
ghcr.io/${{ github.repository_owner }}/${{ secrets.DOCKERHUB_REPO }}
|
ghcr.io/${{ github.repository_owner }}/${{ secrets.DOCKERHUB_REPO }}
|
||||||
tags: |
|
tags: |
|
||||||
raw=${{ steps.tags.outputs.tags }}
|
type=raw,value=${{ steps.tags.outputs.tags }}
|
||||||
raw=latest
|
type=raw,value=latest
|
||||||
|
|
||||||
- name: setup platform emulator
|
- name: setup platform emulator
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ RUN npm config set strict-ssl false && \
|
|||||||
|
|
||||||
# Final image
|
# Final image
|
||||||
FROM base
|
FROM base
|
||||||
RUN apt update && \
|
RUN npm install -g pm2 && \
|
||||||
|
apt update && \
|
||||||
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley && \
|
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley && \
|
||||||
apt clean && \
|
apt clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ db.defaults(
|
|||||||
configWriteFlag: false,
|
configWriteFlag: false,
|
||||||
downloads: {},
|
downloads: {},
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
files_to_db_migration_complete: false
|
files_to_db_migration_complete: false,
|
||||||
|
tasks_manager_role_migration_complete: false
|
||||||
}).write();
|
}).write();
|
||||||
|
|
||||||
users_db.defaults(
|
users_db.defaults(
|
||||||
@@ -187,13 +188,22 @@ async function checkMigrations() {
|
|||||||
if (!new_db_system_migration_complete) {
|
if (!new_db_system_migration_complete) {
|
||||||
logger.info('Beginning migration: 4.2->4.3+')
|
logger.info('Beginning migration: 4.2->4.3+')
|
||||||
let success = await db_api.importJSONToDB(db.value(), users_db.value());
|
let success = await db_api.importJSONToDB(db.value(), users_db.value());
|
||||||
|
await tasks_api.setupTasks(); // necessary as tasks were not properly initialized at first
|
||||||
// sets migration to complete
|
// sets migration to complete
|
||||||
db.set('new_db_system_migration_complete', true).write();
|
db.set('new_db_system_migration_complete', true).write();
|
||||||
if (success) { logger.info('4.2->4.3+ migration complete!'); }
|
if (success) { logger.info('4.2->4.3+ migration complete!'); }
|
||||||
else { logger.error('Migration failed: 4.2->4.3+'); }
|
else { logger.error('Migration failed: 4.2->4.3+'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tasks_manager_role_migration_complete = db.get('tasks_manager_role_migration_complete').value();
|
||||||
|
if (!tasks_manager_role_migration_complete) {
|
||||||
|
logger.info('Checking if tasks manager role permissions exist for admin user...');
|
||||||
|
const success = await auth_api.changeRolePermissions('admin', 'tasks_manager', 'yes');
|
||||||
|
if (success) logger.info('Task manager permissions check complete!');
|
||||||
|
else logger.error('Failed to auto add tasks manager permissions to admin role!');
|
||||||
|
db.set('tasks_manager_role_migration_complete', true).write();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,7 +585,11 @@ async function watchSubscriptions() {
|
|||||||
|
|
||||||
if (!subscriptions) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
const valid_subscriptions = subscriptions.filter(sub => !sub.paused);
|
// auto pause deprecated streamingOnly mode
|
||||||
|
const streaming_only_subs = subscriptions.filter(sub => sub.streamingOnly);
|
||||||
|
subscriptions_api.updateSubscriptionPropertyMultiple(streaming_only_subs, {paused: true});
|
||||||
|
|
||||||
|
const valid_subscriptions = subscriptions.filter(sub => !sub.paused && !sub.streamingOnly);
|
||||||
|
|
||||||
let subscriptions_amount = valid_subscriptions.length;
|
let subscriptions_amount = valid_subscriptions.length;
|
||||||
let delay_interval = calculateSubcriptionRetrievalDelay(subscriptions_amount);
|
let delay_interval = calculateSubcriptionRetrievalDelay(subscriptions_amount);
|
||||||
|
|||||||
@@ -361,7 +361,6 @@ exports.userHasPermission = async function(user_uid, permission) {
|
|||||||
logger.error('Invalid role ' + role);
|
logger.error('Invalid role ' + role);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const role_permissions = (await db_api.getRecords('roles'))['permissions'];
|
|
||||||
|
|
||||||
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
||||||
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
||||||
@@ -376,7 +375,8 @@ exports.userHasPermission = async function(user_uid, permission) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no overrides, let's check if the role has the permission
|
// no overrides, let's check if the role has the permission
|
||||||
if (role_permissions.includes(permission)) {
|
const role_has_permission = await exports.roleHasPermissions(role, permission);
|
||||||
|
if (role_has_permission) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
|
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
|
||||||
@@ -384,6 +384,16 @@ exports.userHasPermission = async function(user_uid, permission) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.roleHasPermissions = async function(role, permission) {
|
||||||
|
const role_obj = await db_api.getRecord('roles', {key: role})
|
||||||
|
if (!role) {
|
||||||
|
logger.error(`Role ${role} does not exist!`);
|
||||||
|
}
|
||||||
|
const role_permissions = role_obj['permissions'];
|
||||||
|
if (role_permissions && role_permissions.includes(permission)) return true;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
exports.userPermissions = async function(user_uid) {
|
exports.userPermissions = async function(user_uid) {
|
||||||
let user_permissions = [];
|
let user_permissions = [];
|
||||||
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
|
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
|
||||||
|
|||||||
@@ -221,7 +221,8 @@ exports.AVAILABLE_PERMISSIONS = [
|
|||||||
'subscriptions',
|
'subscriptions',
|
||||||
'sharing',
|
'sharing',
|
||||||
'advanced_download',
|
'advanced_download',
|
||||||
'downloads_manager'
|
'downloads_manager',
|
||||||
|
'tasks_manager'
|
||||||
];
|
];
|
||||||
|
|
||||||
exports.DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
|
exports.DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
|
||||||
|
|||||||
@@ -925,6 +925,7 @@ exports.importJSONToDB = async (db_json, users_json) => {
|
|||||||
const createFilesRecords = (files, subscriptions) => {
|
const createFilesRecords = (files, subscriptions) => {
|
||||||
for (let i = 0; i < subscriptions.length; i++) {
|
for (let i = 0; i < subscriptions.length; i++) {
|
||||||
const subscription = subscriptions[i];
|
const subscription = subscriptions[i];
|
||||||
|
if (!subscription['videos']) continue;
|
||||||
subscription['videos'] = subscription['videos'].map(file => ({ ...file, sub_id: subscription['id'], user_uid: subscription['user_uid'] ? subscription['user_uid'] : undefined}));
|
subscription['videos'] = subscription['videos'].map(file => ({ ...file, sub_id: subscription['id'], user_uid: subscription['user_uid'] ? subscription['user_uid'] : undefined}));
|
||||||
files = files.concat(subscriptions[i]['videos']);
|
files = files.concat(subscriptions[i]['videos']);
|
||||||
}
|
}
|
||||||
@@ -985,7 +986,7 @@ exports.backupDB = async () => {
|
|||||||
const backup_file_name = `${using_local_db ? 'local' : 'remote'}_db.json.${Date.now()/1000}.bak`;
|
const backup_file_name = `${using_local_db ? 'local' : 'remote'}_db.json.${Date.now()/1000}.bak`;
|
||||||
const path_to_backups = path.join(backup_dir, backup_file_name);
|
const path_to_backups = path.join(backup_dir, backup_file_name);
|
||||||
|
|
||||||
logger.verbose(`Backing up ${using_local_db ? 'local' : 'remote'} DB to ${path_to_backups}`);
|
logger.info(`Backing up ${using_local_db ? 'local' : 'remote'} DB to ${path_to_backups}`);
|
||||||
|
|
||||||
const table_to_records = {};
|
const table_to_records = {};
|
||||||
for (let i = 0; i < tables_list.length; i++) {
|
for (let i = 0; i < tables_list.length; i++) {
|
||||||
@@ -1032,10 +1033,11 @@ exports.transferDB = async (local_to_remote) => {
|
|||||||
table_to_records[table] = await exports.getRecords(table);
|
table_to_records[table] = await exports.getRecords(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info('Backup up DB...');
|
||||||
|
await exports.backupDB(); // should backup always
|
||||||
|
|
||||||
using_local_db = !local_to_remote;
|
using_local_db = !local_to_remote;
|
||||||
if (local_to_remote) {
|
if (local_to_remote) {
|
||||||
logger.debug('Backup up DB...');
|
|
||||||
await exports.backupDB();
|
|
||||||
const db_connected = await exports.connectToDB(5, true);
|
const db_connected = await exports.connectToDB(5, true);
|
||||||
if (!db_connected) {
|
if (!db_connected) {
|
||||||
logger.error('Failed to transfer database - could not connect to MongoDB. Verify that your connection URL is valid.');
|
logger.error('Failed to transfer database - could not connect to MongoDB. Verify that your connection URL is valid.');
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pm2": "^5.2.0",
|
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"ps-node": "^0.1.6",
|
"ps-node": "^0.1.6",
|
||||||
"read-last-lines": "^1.7.2",
|
"read-last-lines": "^1.7.2",
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ async function updateSubscription(sub) {
|
|||||||
|
|
||||||
async function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
|
async function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
|
||||||
subs.forEach(async sub => {
|
subs.forEach(async sub => {
|
||||||
await updateSubscriptionProperty(sub, assignment_obj, sub.user_uid);
|
await updateSubscriptionProperty(sub, assignment_obj);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,11 +172,13 @@ function getExpectedFileSize(input_info_jsons) {
|
|||||||
const formats = info_json['format_id'].split('+');
|
const formats = info_json['format_id'].split('+');
|
||||||
let individual_expected_filesize = 0;
|
let individual_expected_filesize = 0;
|
||||||
formats.forEach(format_id => {
|
formats.forEach(format_id => {
|
||||||
info_json.formats.forEach(available_format => {
|
if (info_json.formats !== undefined) {
|
||||||
if (available_format.format_id === format_id && (available_format.filesize || available_format.filesize_approx)) {
|
info_json.formats.forEach(available_format => {
|
||||||
|
if (available_format.format_id === format_id && (available_format.filesize || available_format.filesize_approx)) {
|
||||||
individual_expected_filesize += (available_format.filesize ? available_format.filesize : available_format.filesize_approx);
|
individual_expected_filesize += (available_format.filesize ? available_format.filesize : available_format.filesize_approx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
expected_filesize += individual_expected_filesize;
|
expected_filesize += individual_expected_filesize;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
|
<a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
|
||||||
<a *ngIf="postsService.config && allowSubscriptions && postsService.hasPermission('subscriptions')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
<a *ngIf="postsService.config && allowSubscriptions && postsService.hasPermission('subscriptions')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||||
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||||
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/tasks'><ng-container i18n="Navigation menu Tasks Page title">Tasks</ng-container></a>
|
<a *ngIf="postsService.config && postsService.hasPermission('tasks_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/tasks'><ng-container i18n="Navigation menu Tasks Page title">Tasks</ng-container></a>
|
||||||
<ng-container *ngIf="postsService.config && postsService.hasPermission('settings')">
|
<ng-container *ngIf="postsService.config && postsService.hasPermission('settings')">
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/settings'><ng-container i18n="Settings menu label">Settings</ng-container></a>
|
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/settings'><ng-container i18n="Settings menu label">Settings</ng-container></a>
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ export class ManageRoleComponent implements OnInit {
|
|||||||
permissions = null;
|
permissions = null;
|
||||||
|
|
||||||
permissionToLabel = {
|
permissionToLabel = {
|
||||||
'filemanager': 'File manager',
|
'filemanager': $localize`File manager`,
|
||||||
'settings': 'Settings access',
|
'settings': $localize`Settings access`,
|
||||||
'subscriptions': 'Subscriptions',
|
'subscriptions': $localize`Subscriptions`,
|
||||||
'sharing': 'Share files',
|
'sharing': $localize`Share files`,
|
||||||
'advanced_download': 'Use advanced download mode',
|
'advanced_download': $localize`Use advanced download mode`,
|
||||||
'downloads_manager': 'Use downloads manager'
|
'downloads_manager': $localize`Use downloads manager`,
|
||||||
|
'tasks_manager': $localize`Use tasks manager`,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public postsService: PostsService, private dialogRef: MatDialogRef<ManageRoleComponent>,
|
constructor(public postsService: PostsService, private dialogRef: MatDialogRef<ManageRoleComponent>,
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="position: relative;" *ngIf="usePaginator && selectedIndex > 0">
|
<div style="position: relative;" *ngIf="paged_data && paged_data.length > 0 && usePaginator && selectedIndex > 0">
|
||||||
<div style="position: absolute; margin-left: 8px; margin-top: 5px; scale: 0.8">
|
<div style="position: absolute; margin-left: 8px; margin-top: 5px; scale: 0.8">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label><ng-container i18n="File type">File type</ng-container></mat-label>
|
<mat-label><ng-container i18n="File type">File type</ng-container></mat-label>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
|
|||||||
import 'rxjs/add/operator/catch';
|
import 'rxjs/add/operator/catch';
|
||||||
import 'rxjs/add/observable/throw';
|
import 'rxjs/add/observable/throw';
|
||||||
import { THEMES_CONFIG } from '../themes';
|
import { THEMES_CONFIG } from '../themes';
|
||||||
import { Router, CanActivate } from '@angular/router';
|
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { DOCUMENT } from '@angular/common';
|
import { DOCUMENT } from '@angular/common';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
@@ -229,12 +229,15 @@ export class PostsService implements CanActivate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
canActivate(route, state): Promise<boolean> {
|
canActivate(route: ActivatedRouteSnapshot, state): Promise<boolean> {
|
||||||
return new Promise(resolve => {
|
const PATH_TO_REQUIRED_PERM = {
|
||||||
resolve(true);
|
settings: 'settings',
|
||||||
})
|
subscriptions: 'subscriptions',
|
||||||
console.log(route);
|
downloads: 'downloads_manager',
|
||||||
throw new Error('Method not implemented.');
|
tasks: 'tasks_manager'
|
||||||
|
}
|
||||||
|
const required_perm = PATH_TO_REQUIRED_PERM[route.routeConfig.path];
|
||||||
|
return required_perm ? this.hasPermission(required_perm) : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTheme(theme) {
|
setTheme(theme) {
|
||||||
|
|||||||
@@ -645,6 +645,55 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Close</note>
|
<note priority="1" from="description">Close</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="4250042184551786923" datatype="html">
|
||||||
|
<source>File manager</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
|
||||||
|
<context context-type="linenumber">17</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8525406024045742391" datatype="html">
|
||||||
|
<source>Settings access</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
|
||||||
|
<context context-type="linenumber">18</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1812379335568847528" datatype="html">
|
||||||
|
<source>Subscriptions</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
|
||||||
|
<context context-type="linenumber">19</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4744991787069301975" datatype="html">
|
||||||
|
<source>Share files</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
|
||||||
|
<context context-type="linenumber">20</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5029464708330583545" datatype="html">
|
||||||
|
<source>Use advanced download mode</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
|
||||||
|
<context context-type="linenumber">21</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4278268519633335280" datatype="html">
|
||||||
|
<source>Use downloads manager</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
|
||||||
|
<context context-type="linenumber">22</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="3426988753455920032" datatype="html">
|
||||||
|
<source>Use tasks manager</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
|
||||||
|
<context context-type="linenumber">23</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="2bd201aea09e43fbfd3cd15ec0499b6755302329" datatype="html">
|
<trans-unit id="2bd201aea09e43fbfd3cd15ec0499b6755302329" datatype="html">
|
||||||
<source>Manage user</source>
|
<source>Manage user</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
|
|||||||
2877
src/assets/i18n/messages.sv.xlf
Normal file
2877
src/assets/i18n/messages.sv.xlf
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user