Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • maspect/amiv-admintool
  • emustafa/amiv-admintool
  • dvruette/amiv-admintool
  • amiv/amiv-admintool
4 results
Show changes
Commits on Source (445)
Showing with 18764 additions and 313 deletions
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
{
"name": "amiv-admintool",
"image": "mcr.microsoft.com/vscode/devcontainers/javascript-node:14",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [9000],
// Uncomment the next line to run commands after the container is created.
"postCreateCommand": "npm install"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
//"remoteUser": "node"
}
......@@ -7,9 +7,18 @@ module.exports = {
"browser": true,
},
"rules": {
"no-console": 0,
"no-console": 1,
"class-methods-use-this": 0,
"prefer-destructuring": 1,
"no-underscore-dangle": 0,
"linebreak-style": 0,
"import/no-unresolved": [ "error", { "ignore": [ 'networkConfig' ] } ], // hack until resolving import properly
"object-curly-newline": [ "error", {
ObjectExpression: { multiline: true, consistent: true },
ObjectPattern: { multiline: true, consistent: true },
ImportDeclaration: { minProperties: 7, consistent: true },
ExportDeclaration: { minProperties: 7, consistent: true },
}],
"max-len": [ "error", { "code": 100, ignorePattern: ".*<svg.+>" }],
}
};
.ftpconfig
node_modules/
.idea/
dist/
.DS_Store
stages:
- test
- build
- deploy
dev_deploy:
stage: deploy
image: alpine:latest
when: manual
eslint:
stage: test
image: node:latest
before_script:
- npm install
script:
- npm run lint
build_master:
stage: build
image: docker:stable
services:
- docker:dind
before_script:
- 'which ssh-agent || ( apk update -y && apk add openssh-client -y )'
- mkdir -p ~/.ssh
- eval $(ssh-agent -s)
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
- echo "$DEPLOY_PRIVATE_KEY" | ssh-add -
- echo "$CI_DOCKER_REGISTRY_TOKEN" | docker login -u "$CI_DOCKER_REGISTRY_USER" --password-stdin
script:
- ssh -p22 amivadmin@amiv-zoidberg.ethz.ch "docker build -t admintools ./amiv-containers/admintools/"
- ssh -p22 amivadmin@amiv-zoidberg.ethz.ch "cd ./amiv-containers/ && docker-compose up -d"
- docker build --build-arg NPM_BUILD_COMMAND=build --pull -t "$CI_REGISTRY_IMAGE:prod" ./
- docker build --build-arg NPM_BUILD_COMMAND=build-staging --pull -t "$CI_REGISTRY_IMAGE:staging" ./
- docker build --build-arg NPM_BUILD_COMMAND=build-local --pull -t "$CI_REGISTRY_IMAGE:local" ./
- docker push "$CI_REGISTRY_IMAGE:prod"
- docker push "$CI_REGISTRY_IMAGE:staging"
- docker push "$CI_REGISTRY_IMAGE:local"
environment:
name: production
url: https://admin.amiv.ethz.ch
only:
- master
build_dev:
stage: build
image: docker:stable
services:
- docker:dind
before_script:
- echo "$CI_DOCKER_REGISTRY_TOKEN_DEV" | docker login -u "$CI_DOCKER_REGISTRY_USER_DEV" --password-stdin
script:
- docker build --build-arg NPM_BUILD_COMMAND=build-dev --pull -t "$CI_REGISTRY_IMAGE_DEV" ./
- docker push "$CI_REGISTRY_IMAGE_DEV"
environment:
name: development
url: https://amiv-admin.amiv.ethz.ch
url: https://admin-dev.amiv.ethz.ch
deploy:
stage: deploy
image: amiveth/ansible-ci-helper
script:
- python /main.py
File moved
# Summary
> Hi! Thanks for contributing to the admintools by reporting a new Bug!
> In order to fix the bug soon, please help us to figure out what causes the
> malfunction!
> describe the issue here
# Steps to Reproduce
> What is the main menu point (left menu bar) where this issue occurs?
> Copy and Paste the url from the window where the issue occured
> Describe in your own words what steps you did before the issue occured
# Additional Debug Information
> Please add information on what browser and operating system you are using
> If possible, can you make a screenshot of the bug?
> Please open the "developer tools" of your browser and copy and paste any
> printouts in the "console"
/label ~bug
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/src/relationlistcontroller.js"
}
]
}
\ No newline at end of file
# First stage: Build project
FROM node:14 as build
ARG NPM_BUILD_COMMAND=build
# Copy files and install dependencies
COPY ./ /
RUN npm install
# Build project
RUN npm run $NPM_BUILD_COMMAND
# Second stage: Server to deliver files
FROM nginx:1.15-alpine
# Copy files from first stage
COPY --from=build /index.html /var/www/
COPY --from=build /dist /var/www/dist
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Admintool
### Software:
* ```ubuntu /14.04.1```
* ```nginx /1.4.6```
### Dependencies:
* ```jQuery /2.2.2```
* ```bootstrap /3.3.6```
# Developer Installation
## w/ Docker
Start the dev Container in VS Code
run ```npm run start``` inside the container.
The development server is available under localhost:9000. It refreshes automatically as soon as you save changes in any `.js` file.
## w/o Docker
```
npm install
......@@ -16,17 +17,30 @@ npm install
And now, start developing:
```
npm start
npm run start
```
*Warning*: For installation on Ubuntu 16.04 (and possibly similar), you need to install nodejs from the repos source.
1. Remove nodejs if you already have it (ONLY IF YOU REALLY WANT!):
```
sudo apt remove nodejs
```
2. Add nodejs10 from repo (download): `curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -`
3. Install: `sudo apt install -y nodejs`
4. Clean-up and install the packages for amiv-admintools
```
rm -rf node_modules/
npm install
npm run start
```
This will open up a local server outputting the current version of the admintools. It refreshes automatically as soon as you save changes in any `.js` file.
### File Structure:
* admin (Admintool)
* res (Resources)
* bg (big pictures and backgrounds)
* favicon
* fonts
* logo
* bootstrap
* js
* src
* public (Website)
* res (Resources)
- favicon
- logo
* src
- views (reusable view components, etc for Tables and selection lists)
- `index.js` main file
- `*Tool.js` main file per API resource, e.g. for all user-related UIs.
......@@ -20,9 +20,19 @@
<link rel="icon" type="image/png" sizes="96x96" href="res/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="res/favicon/favicon-16x16.png">
<link href="//cdn.rawgit.com/noelboss/featherlight/1.7.10/release/featherlight.min.css" type="text/css" rel="stylesheet" />
<!--link href="lib/cust/main.css" rel="stylesheet"-->
<style>
@keyframes spin {
from { transform:rotate(0deg); }
to { transform:rotate(360deg); }
}
<link href="lib/cust/main.css" rel="stylesheet">
@keyframes popup {
from { opacity: 0; }
90% { opacity: 0; }
to { opacity: 100%; }
}
} </style>
</head>
<body>
......
/*
IMPORTS
*/
@font-face {
font-family: DINPro;
font-weight: normal;
src: url(../../res/fonts/DINPro-Light.ttf);
}
@font-face {
font-family: DINPro;
font-weight: bold;
src: url(../../res/fonts/DINPro-Bold.ttf);
}
/*
GENERAL SETUP
*/
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
}
body {
font-family: DINPro;
overflow: hidden;
}
/*
UTILITY CLASSES
*/
.smooth {
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
[contenteditable=true]:empty:before {
content: attr(placeholder);
display: block; /* For Firefox */
}
/*
LOGIN PANEL
*/
.loginPanel {
width: 100%;
height: 100%;
top: 0%;
left: 0%;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
z-index: 20;
//background: rgba(30,30,30,.9);
background: rgba(31, 45, 84, 1);
}
.loginPanel>div {
box-shadow: 0 0 2em #000;
}
.loginPanel .login-logo {
width: 70%;
}
/*
MAIN FRAMEWORK
*/
.wrapper-main {
height: 100%;
width: 100%;
display: grid;
grid-template-columns: 280px auto;
}
.wrapper-sidebar {
z-index: 10;
grid-column: 1;
height: 100%;
overflow-y: auto;
position: fixed;
background: #222;
color: #fff;
box-shadow: 0 0 1em rgba(0, 0, 0, 0.5);
}
.wrapper-sidebar .navbar li {
float: none;
display: block;
}
.wrapper-sidebar ul li a {
color: inherit;
}
.wrapper-sidebar ul li a:hover, .wrapper-sidebar ul li a:active, .wrapper-sidebar ul li a:focus {
color: #000;
}
.wrapper-sidebar>div {
padding: 1em;
}
.wrapper-sidebar .sidebar-logo {
width: 100%;
}
.wrapper-content {
height: 100vh;
grid-column: 2;
background: #eee;
/*width: 100%;*/
overflow: auto;
}
/*
MAIN NAVBAR
*/
.navbar-main .container-fluid>ul>li {
float: left;
}
/*
LOG BAR
*/
.alertCont {
position: fixed;
z-index: 10000;
left: 50%;
top: 10px;
transform: translateX(-50%);
}
/*
ANNOUNCE TOOL
*/
.selected{
background: #98FB98;
}
.featured{
background: salmon;
}
.selectedcolor{
background: #98FB98;
}
.featuredcolor{
background: salmon;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www/;
index index.html;
try_files $uri /index.html =404;
}
This diff is collapsed.
......@@ -6,7 +6,10 @@
"scripts": {
"start": "webpack-dev-server --hot --inline",
"build": "webpack -p --config webpack.config.prod.js",
"lint": "eslint src/**"
"build-dev": "webpack -p --config webpack.config.dev.js",
"build-staging": "webpack -p --config webpack.config.staging.js",
"build-local": "webpack -p --config webpack.config.local.js",
"lint": "eslint src/**/*.js src/*.js"
},
"repository": {
"type": "git",
......@@ -14,31 +17,39 @@
},
"author": "Hermann Blum et al",
"dependencies": {
"@material/drawer": "^0.30.0",
"@material/select": "^0.35.1",
"ajv": "^5.5.0",
"announcetool": "git+ssh://git@gitlab.ethz.ch:amiv/amivapi-announce-tool.git#webpack-version",
"amiv-web-ui-components": "git+https://git@gitlab.ethz.ch/amiv/web-ui-components.git#360e65da63f4511db1f6ac56ce103be9254d1d9f",
"axios": "^0.17.1",
"client-oauth2": "^4.2.0",
"mithril": "^1.1.6",
"mithril-infinite": "^1.2.0",
"polythene-core-css": "^1.0.0",
"polythene-css": "^1.0.0",
"polythene-mithril": "^1.0.0",
"querystring": "^0.2.0"
"mithril-infinite": "^1.2.4",
"polythene-core-css": "^1.2.0",
"polythene-css": "^1.2.0",
"polythene-mithril": "1.2.0",
"querystring": "^0.2.0",
"showdown": "^1.9.0"
},
"devDependencies": {
"eslint": "^4.10.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0",
"eslint-loader": "^1.9.0",
"webpack-dev-server": "^2.9.3",
"babel-core": "^6.26.0",
"babel-cli": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"compression-webpack-plugin": "^1.0.1",
"file-loader": "^1.1.5",
"css-loader": "^0.28.7",
"style-loader": "^0.19.0",
"uglifyjs-webpack-plugin": "^1.0.1",
"webpack": "^3.8.1"
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-object-rest-spread": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.4",
"compression-webpack-plugin": "^2.0.0",
"css-loader": "^2.1.0",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-import-resolver-webpack": "^0.10.1",
"eslint-loader": "^3.0.0",
"eslint-plugin-import": "^2.14.0",
"file-loader": "^3.0.1",
"style-loader": "^0.23.1",
"webpack": "^4.28.3",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.14"
}
}
File deleted
File deleted
import tool from 'announcetool';
const m = require('mithril');
export default class AnnounceTool {
oncreate() {
if (tool.wasRenderedOnce()) {
// jQuery catches the first document.ready, but afterwards we have to
// trigger a render
tool.render();
}
}
view() {
return m('div', [
m('div#tableset', [
m('p#events'),
m('div#buttonrow', [
m('button#preview.btn.btn-default', 'Preview'),
m('button#reset.btn.btn-default', 'Reset'),
m('button#send.btn.btn-default', 'Send'),
]),
]),
m('br'),
m('hr'),
m('textarea#target'),
]);
}
}
import m from 'mithril';
import axios from 'axios';
import ClientOAuth2 from 'client-oauth2';
import { Snackbar } from 'polythene-mithril';
// eslint-disable-next-line import/extensions
import { apiUrl, ownUrl, oAuthID } from 'networkConfig';
import * as localStorage from './localStorage';
import config from './config.json';
const m = require('mithril');
// Object which stores the current login-state
const APISession = {
authenticated: false,
token: '',
userID: null,
schema: null,
rights: {
users: [],
joboffers: [],
studydocuments: [],
},
};
export function resetSession() {
APISession.authenticated = false;
APISession.token = '';
m.route.set('/login');
if (!APISession.schema) {
m.request(`${apiUrl}/docs/api-docs`).then((schema) => { APISession.schema = schema; });
}
const amivapi = axios.create({
baseURL: config.apiUrl,
timeout: 10000,
baseURL: apiUrl,
headers: { 'Content-Type': 'application/json' },
validateStatus: () => true,
});
// OAuth Handler
const oauth = new ClientOAuth2({
clientId: oAuthID,
authorizationUri: `${apiUrl}/oauth`,
redirectUri: `${ownUrl}/oauthcallback`,
});
function resetSession() {
APISession.authenticated = false;
APISession.token = '';
localStorage.remove('token');
window.location.replace(oauth.token.getUri());
}
function checkToken(token) {
// check if a token is still valid
return new Promise((resolve, reject) => {
amivapi.get('users', {
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
amivapi.get(`sessions/${token}`, {
headers: { 'Content-Type': 'application/json', Authorization: token },
}).then((response) => {
console.log(response.data);
if (response.status === 200) resolve();
if (response.status === 200) resolve(response.data);
else reject();
}).catch(reject);
});
......@@ -44,16 +61,24 @@ export function checkAuthenticated() {
return new Promise((resolve) => {
if (APISession.authenticated) resolve();
else {
console.log('looking for token');
// let's see if we have a stored token
const token = localStorage.get('token');
console.log(`found this token: ${token}`);
if (token !== '') {
// check of token is valid
checkToken(token).then(() => {
checkToken(token).then((session) => {
APISession.token = token;
APISession.authenticated = true;
resolve();
APISession.userID = session.user;
amivapi.get('/', {
headers: { 'Content-Type': 'application/json', Authorization: token },
}).then((response) => {
const rights = {};
response.data._links.child.forEach(({ href, methods }) => {
rights[href] = methods;
});
APISession.rights = rights;
resolve();
});
}).catch(resetSession);
} else resetSession();
}
......@@ -65,43 +90,107 @@ export function getSession() {
return new Promise((resolve) => {
checkAuthenticated().then(() => {
const authenticatedSession = axios.create({
baseURL: config.apiUrl,
timeout: 10000,
baseURL: apiUrl,
headers: {
'Content-Type': 'application/json',
Authorization: APISession.token,
},
validateStatus: () => true,
});
resolve(authenticatedSession);
}).catch(resetSession);
});
}
export function login(username, password) {
export function deleteSession() {
return new Promise((resolve, reject) => {
amivapi.post('sessions', { username, password })
.then((response) => {
if (response.status === 201) {
APISession.token = response.data.token;
APISession.authenticated = true;
localStorage.set('token', response.data.token);
resolve();
}
reject();
getSession().then((api) => {
api.get(`sessions/${APISession.token}`).then((response) => {
if (response.status === 200) {
api.delete(
`sessions/${response.data._id}`,
{ headers: { 'If-Match': response.data._etag } },
).then((deleteResponse) => {
if (deleteResponse.status === 204) {
resetSession();
resolve(deleteResponse.data);
} else reject();
}).catch(reject);
} else reject();
}).catch(reject);
});
});
}
export function getCurrentUser() {
return APISession.userID;
}
export function getUserRights() {
return APISession.rights;
}
export function getSchema() {
return APISession.schema;
}
// Mapper for resource vs schema-object names
const objectNameForResource = {
users: 'User',
groupmemberships: 'Group Membership',
groups: 'Group',
eventsignups: 'Event Signup',
events: 'Event',
studydocuments: 'Study Document',
joboffers: 'Job Offer',
blacklist: 'Blacklist',
sessions: 'Session',
};
export class ResourceHandler {
constructor(resource, searchKeys = false) {
this.resource = resource;
this.searchKeys = searchKeys || config[resource].searchKeys;
this.patchKeys = config[resource].patchableKeys;
/* Handler to get and manipulate resource items
*
* resource: String of the resource to accessm e,g, 'events'
* searchKeys: keys in the resource item on which to perform search, i.e.
* when search is set, any of these keys may match the search pattern
* E.g. for an event, this may be ['title_de', 'title_en', 'location']
*/
constructor(resource, searchKeys = []) {
// backwards compatibiliity, should be removed in future
// eslint-disable-next-line no-param-reassign
if (!searchKeys) searchKeys = [];
checkAuthenticated();
this.resource = resource;
this.rights = [];
this.schema = JSON.parse(JSON.stringify(getSchema().components.schemas[
objectNameForResource[this.resource]]));
// readOnly fields should be removed before patch
this.noPatchKeys = Object.keys(this.schema.properties).filter(
key => this.schema.properties[key].readOnly,
);
// any field that is a string can be searched
const possibleSearchKeys = Object.keys(this.schema.properties).filter((key) => {
const field = this.schema.properties[key];
return field.type === 'string' && field.format !== 'objectid'
&& field.format !== 'date-time' && !key.startsWith('_');
});
// special case for users, we don't allow reverse search by legi or rfid
if (resource === 'users') this.searchKeys = ['firstname', 'lastname', 'nethz'];
else this.searchKeys = searchKeys.length === 0 ? possibleSearchKeys : searchKeys;
checkAuthenticated().then(() => {
// again special case for users
if (resource === 'users' && APISession.isUserAdmin) {
this.searchKeys = searchKeys.length === 0 ? possibleSearchKeys : searchKeys;
}
});
}
// definitions of query parameters in addition to API go here
/*
* query is a dictionary of different queries
* Additional to anything specified from eve
* (http://python-eve.org/features.html#filtering)
* we support the key `search`, which is translated into a `where` filter
*/
buildQuerystring(query) {
const queryKeys = Object.keys(query);
......@@ -109,15 +198,38 @@ export class ResourceHandler {
const fullQuery = {};
if ('search' in query && query.search.length > 0) {
if ('search' in query && query.search && query.search.length > 0
&& this.searchKeys.length !== 0) {
// translate search into where, we just look if any field contains search
const searchQuery = {
$or: this.searchKeys.map((key) => {
const fieldQuery = {};
fieldQuery[key] = query.search;
return fieldQuery;
}),
};
// The search-string may match any of the keys in the object specified in the
// constructor
let searchQuery;
if (query.search.split(' ').length > 1) {
searchQuery = {
$and: query.search.split(' ').map(searchPhrase => ({
$or: this.searchKeys.map((key) => {
const fieldQuery = {};
fieldQuery[key] = {
$regex: `${searchPhrase}`,
$options: 'i',
};
return fieldQuery;
}),
})),
};
} else {
searchQuery = {
$or: this.searchKeys.map((key) => {
const fieldQuery = {};
fieldQuery[key] = {
$regex: `${query.search}`,
$options: 'i',
};
return fieldQuery;
}),
};
}
// if there exists already a where-filter, AND them together
if ('where' in query) {
......@@ -125,20 +237,34 @@ export class ResourceHandler {
} else {
fullQuery.where = JSON.stringify(searchQuery);
}
} else {
if (query.where) {
fullQuery.where = JSON.stringify(query.where);
}
} else if ('where' in query) {
fullQuery.where = JSON.stringify(query.where);
}
// add all other keys
queryKeys.filter(key => (key !== 'where' && key !== 'search'))
.forEach((key) => { fullQuery[key] = JSON.stringify(query[key]); });
console.log(fullQuery);
// now we can acutally build the query string
return `?${m.buildQueryString(fullQuery)}`;
const maxResults = 50; // max_results for setting the page size of the return.
return `?${m.buildQueryString(fullQuery)}&max_results=${maxResults}`;
}
networkError(e) {
// eslint-disable-next-line no-console
console.log(e);
Snackbar.show({ title: 'Network error, try again.', style: { color: 'red' } });
}
// in future, we may communicate based on the data available
// therefore, require data already here
// eslint-disable-next-line no-unused-vars
error422(data) {
Snackbar.show({ title: 'Errors in object, please fix.' });
}
successful(title) {
Snackbar.show({ title, style: { color: 'green' } });
}
get(query) {
......@@ -148,13 +274,15 @@ export class ResourceHandler {
if (Object.keys(query).length > 0) url += this.buildQuerystring(query);
api.get(url).then((response) => {
if (response.status >= 400) {
resetSession();
Snackbar.show({ title: response.data, style: { color: 'red' } });
if (response.status === 401) resetSession();
reject();
} else {
this.rights = response.data._links.self.methods;
resolve(response.data);
}
}).catch((e) => {
console.log(e);
this.networkError(e);
reject(e);
});
});
......@@ -168,18 +296,21 @@ export class ResourceHandler {
// in case embedded is specified, append to url
if (Object.keys(embedded).length > 0) {
url += `?${m.buildQueryString({
embedded: JSON.stringify(this.embedded),
embedded: JSON.stringify(embedded),
})}`;
}
api.get(url).then((response) => {
if (response.status >= 400) {
resetSession();
if (response.status === 404) {
m.route.set('/404');
} else if (response.status >= 400) {
Snackbar.show({ title: response.data, style: { color: 'red' } });
if (response.status === 401) resetSession();
reject();
} else {
resolve(response.data);
}
}).catch((e) => {
console.log(e);
this.networkError(e);
reject(e);
});
});
......@@ -190,14 +321,21 @@ export class ResourceHandler {
return new Promise((resolve, reject) => {
getSession().then((api) => {
api.post(this.resource, item).then((response) => {
if (response.status >= 400) {
resetSession();
if (response.code === 201) {
this.successful('Creation successful.');
resolve({});
} else if (response.status === 422) {
this.error422(response.data);
reject(response.data);
} else if (response.status >= 400) {
Snackbar.show({ title: response.data, style: { color: 'red' } });
if (response.status === 401) resetSession();
reject();
} else {
resolve(response.data);
}
}).catch((e) => {
console.log(e);
this.networkError(e);
reject(e);
});
});
......@@ -205,25 +343,37 @@ export class ResourceHandler {
}
patch(item) {
const isFormData = item instanceof FormData;
let patchInfo = {};
if (isFormData) patchInfo = { id: item.get('_id'), etag: item.get('_etag') };
else patchInfo = { id: item._id, etag: item._etag };
return new Promise((resolve, reject) => {
getSession().then((api) => {
let submitData = item;
if (!isFormData) submitData = Object.assign({}, item);
// not all fields in the item can be patched. We filter out the fields
// we are allowed to send
const submitData = {};
this.patchFields.forEach((key) => { submitData[key] = this.data[key]; });
api.patch(`${this.resource}/${item._id}`, {
headers: { 'If-Match': item._etag },
data: submitData,
this.noPatchKeys.forEach((key) => {
if (isFormData) submitData.delete(key);
else delete submitData[key];
});
api.patch(`${this.resource}/${patchInfo.id}`, submitData, {
headers: { 'If-Match': patchInfo.etag },
}).then((response) => {
if (response.status >= 400) {
resetSession();
if (response.status === 422) {
this.error422(response.data);
reject(response.data);
} else if (response.status >= 400) {
Snackbar.show({ title: response.data, style: { color: 'red' } });
if (response.status === 401) resetSession();
reject();
} else {
this.successful('Change successful.');
resolve(response.data);
}
}).catch((e) => {
console.log(e);
this.networkError(e);
reject(e);
});
});
......@@ -237,16 +387,32 @@ export class ResourceHandler {
headers: { 'If-Match': item._etag },
}).then((response) => {
if (response.status >= 400) {
resetSession();
Snackbar.show({ title: response.data, style: { color: 'red' } });
if (response.status === 401) resetSession();
reject();
} else {
this.successful('Delete successful.');
resolve();
}
}).catch((e) => {
console.log(e);
this.networkError(e);
reject(e);
});
});
});
}
}
export class OauthRedirect {
view() {
oauth.token.getToken(m.route.get()).then((auth) => {
localStorage.set('token', auth.accessToken);
checkAuthenticated().then(() => {
// checkAuthenticated will check whetehr the token is valid
// and store all relevant session info for easy access
m.route.set('/');
}).catch(resetSession);
});
return 'redirecting...';
}
}
/* eslint-disable operator-assignment */
/* eslint-disable no-restricted-globals */
/* eslint-disable no-bitwise */
/* eslint-disable no-plusplus */
/* eslint-disable no-param-reassign */
/**
*
* Base64 encode / decode
* http://www.webtoolkit.info
*
* */
const Base64 = {
// private property
_keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
// public method for encoding
encode(input) {
let output = '';
let chr1; let chr2; let chr3; let enc1; let enc2; let enc3; let enc4;
let i = 0;
input = Base64._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = 64;
enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output
+ this._keyStr.charAt(enc1)
+ this._keyStr.charAt(enc2)
+ this._keyStr.charAt(enc3)
+ this._keyStr.charAt(enc4);
} // Whend
return output;
}, // End Function encode
// public method for decoding
decode(input) {
let output = '';
let chr1; let chr2; let chr3;
let enc1; let enc2; let enc3; let enc4;
let i = 0;
// eslint-disable-next-line no-useless-escape
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
while (i < input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
// eslint-disable-next-line eqeqeq
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
// eslint-disable-next-line eqeqeq
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
} // Whend
output = Base64._utf8_decode(output);
return output;
}, // End Function decode
// private method for UTF-8 encoding
_utf8_encode(string) {
let utftext = '';
string = string.replace(/\r\n/g, '\n');
for (let n = 0; n < string.length; n++) {
const c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if (c > 127 && c < 2048) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
} // Next n
return utftext;
}, // End Function _utf8_encode
// private method for UTF-8 decoding
_utf8_decode(utftext) {
let string = '';
let i = 0;
let c; let c2; let c3;
c = 0;
c2 = 0;
while (i < utftext.length) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
} else if (c > 191 && c < 224) {
c2 = utftext.charCodeAt(i + 1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = utftext.charCodeAt(i + 1);
c3 = utftext.charCodeAt(i + 2);
string += String.fromCharCode(
((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63),
);
i += 3;
}
} // Whend
return string;
}, // End Function _utf8_decode
};
export default Base64;