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
Showing
with 680 additions and 345 deletions
import m from 'mithril';
import viewJob from './viewJob';
import editJob from './editJob';
import ItemController from '../itemcontroller';
import { loadingScreen } from '../layout';
export default class jobModal {
constructor() {
this.controller = new ItemController('joboffers');
}
view() {
if (!this.controller || (!this.controller.data && this.controller.modus !== 'new')) {
return m(loadingScreen);
}
if (this.controller.modus !== 'view') return m(editJob, { controller: this.controller });
return m(viewJob, { controller: this.controller });
}
}
import m from 'mithril';
import viewJob from './viewJob';
import newJob from './newJob';
export default class jobModal {
constructor() {
this.edit = false;
}
view() {
if (this.edit) {
return m(newJob);
}
return m(viewJob, { onEdit: () => { this.edit = true; } });
}
}
import m from 'mithril';
import { RaisedButton } from 'polythene-mithril';
import { EditView } from '../views/editView';
export default class newJob extends EditView {
constructor(vnode) {
super(vnode, 'joboffers', {});
}
view() {
const submitButton = m(RaisedButton, {
disabled: !this.valid,
label: 'Submit',
events: {
onclick: () => { this.submit(); },
},
});
return m('div.maincontainer', [
m('h3', 'Add a New Job Offer'),
...this.renderPage({
title_de: { type: 'text', label: 'German Title' },
}),
m('br'),
submitButton,
]);
}
}
import m from 'mithril';
import { joboffers as config } from '../resourceConfig.json';
import { DatalistController } from 'amiv-web-ui-components';
import TableView from '../views/tableView';
import DatalistController from '../listcontroller';
import { dateFormatter } from '../utils';
import { ResourceHandler } from '../auth';
/* Table of all current Jobs
......@@ -13,7 +13,8 @@ import { dateFormatter } from '../utils';
export default class JobTable {
constructor() {
this.ctrl = new DatalistController('joboffers', {}, config.tableKeys);
this.handler = new ResourceHandler('joboffers');
this.ctrl = new DatalistController((query, search) => this.handler.get({ search, ...query }));
}
getItemData(data) {
......@@ -24,17 +25,17 @@ export default class JobTable {
];
}
view() {
view(data) {
return m(TableView, {
controller: this.ctrl,
keys: config.tableKeys,
keys: [(data.title_de) ? 'title_de' : 'title_en', 'company', 'time_end'],
tileContent: this.getItemData,
titles: [
{ text: 'Titel', width: 'calc(100% - 30em)' },
{ text: 'Title', width: 'calc(100% - 30em)' },
{ text: 'Company', width: '21em' },
{ text: 'End', width: '9em' },
],
onAdd: () => { m.route.set('/newjob'); },
onAdd: () => { m.route.set('/newjoboffer'); },
});
}
}
import m from 'mithril';
import { RaisedButton } from 'polythene-mithril';
import { styler } from 'polythene-core-css';
import { Converter } from 'showdown';
import { Card } from 'polythene-mithril';
import { Chip } from 'amiv-web-ui-components';
// eslint-disable-next-line import/extensions
import { apiUrl } from 'networkConfig';
import ItemView from '../views/itemView';
import { dateFormatter } from '../utils';
import { Property } from '../views/elements';
const viewLayout = [
{
'.propertyLangIndicator': {
width: '30px',
height: '20px',
float: 'left',
'background-color': 'rgb(031,045,084)',
'border-radius': '10px',
'text-align': 'center',
'line-height': '20px',
color: 'rgb(255,255,255)',
'margin-right': '10px',
'font-size': '11px',
},
'.jobViewLeft': {
'grid-column': 1,
},
'.jobViewRight': {
'grid-column': 2,
},
'.jobViewRight h4': {
'margin-top': '0px',
},
},
];
styler.add('jobView', viewLayout);
// small helper class to display both German and English content together, dependent
// on which content is available.
class DuoLangProperty {
view({ attrs: { title, de, en } }) {
// TODO Lang indicators should be smaller and there should be less margin
// between languages
return m(
Property,
{ title },
de ? m('div', [
m('div', { className: 'propertyLangIndicator' }, 'DE'),
m('p', de),
]) : '',
en ? m('div', [
m('div', { className: 'propertyLangIndicator' }, 'EN'),
m('p', en),
]) : '',
);
}
}
import { icons, Property } from '../views/elements';
export default class viewJob extends ItemView {
constructor() {
super('joboffers');
constructor(vnode) {
super(vnode);
this.markdown = new Converter();
}
view({ attrs: { onEdit } }) {
if (!this.data) return '';
view() {
const stdMargin = { margin: '5px' };
return m('div', {
style: { height: '100%', 'overflow-y': 'scroll', padding: '10px' },
}, [
m(RaisedButton, { label: 'Edit Joboffer', events: { onclick: onEdit } }),
// this div is the title line
m('div', [
return this.layout([
m('div', { style: { height: '50px' } }, [
// company logo if existing
this.data.img_thumbnail ? m('img', {
this.data.logo ? m('img', {
src: `${apiUrl}/${this.data.logo.file}`,
height: '50px',
style: { float: 'left' },
}) : '',
m('h3', { style: { 'margin-top': '0px', 'margin-bottom': '0px' } }, [this.data.title_de || this.data.title_en]),
m('h3', {
style: { 'line-height': '50px', 'margin-top': '0px' },
}, this.data.company),
]),
m('div.maincontainer', [
m(Chip, { svg: this.data.show_website ? icons.checked : icons.clear }, 'website'),
]),
// below the title, most important details are listed
this.data.time_end ? m(Property, {
title: 'Offer Ends',
}, `${dateFormatter(this.data.time_end)}`) : '',
m('div', { style: { display: 'flex', margin: '5px 0px 0px 5px' } }, [
this.data.time_end ? m(Property, {
title: 'Offer Ends',
style: stdMargin,
}, `${dateFormatter(this.data.time_end)}`) : '',
m(Property, {
title: 'PDF',
style: stdMargin,
}, this.data.pdf
? m('a', { href: `${apiUrl}${this.data.pdf.file}`, target: '_blank' }, this.data.pdf.name)
: 'not available'),
]),
m('div.viewcontainer', [
m('div.viewcontainercolumn', m(Card, {
content: m('div.maincontainer', [
m('div.pe-card__title', this.data.title_de),
m('div', m.trust(this.markdown.makeHtml(this.data.description_de))),
]),
})),
m('div.viewcontainercolumn', m(Card, {
content: m('div.maincontainer', [
m('div.pe-card__title', this.data.title_en),
m('div', m.trust(this.markdown.makeHtml(this.data.description_en))),
]),
})),
]),
]);
}
}
import m from 'mithril';
import '@material/drawer';
import { List, ListTile, Icon, Toolbar, ToolbarTitle, Dialog } from 'polythene-mithril';
import {
List,
ListTile,
Icon,
Toolbar,
Dialog,
SVG,
Button,
IconButton,
Snackbar,
} from 'polythene-mithril';
import { styler } from 'polythene-core-css';
import { icons } from './views/elements';
import { resetSession } from './auth';
import { deleteSession, getUserRights, getSchema } from './auth';
import { colors } from './style';
const layoutStyle = [
{
body: {
padding: 0,
margin: 0,
},
body: { padding: 0, margin: 0, overflow: 'hidden' },
'.main-toolbar': {
backgroundColor: '#1f2d54',
color: '#fff',
height: '72px',
'grid-column': '1 / span 2',
'grid-row': 1,
},
......@@ -23,61 +27,145 @@ const layoutStyle = [
width: '100%',
display: 'grid',
'grid-template-columns': '200px auto',
'grid-template-rows': '72px auto',
'grid-template-rows': '64px auto',
},
'.wrapper-sidebar': {
'grid-column': 1,
'grid-row': 2,
'@media (min-width:1200px)': { 'grid-column': 1 },
'@media (max-width:1200px)': {
position: 'absolute',
top: '64px',
left: '-200px',
width: '200px',
'z-index': 100000,
},
height: '100%',
'grid-row': 2,
'overflow-y': 'auto',
background: '#cccccc',
color: 'white',
},
'.wrapper-content': {
height: 'calc(100vh - 72px)',
'grid-column': 2,
'@media (min-width:1200px)': { 'grid-column': 2 },
'@media (max-width:1200px)': { 'grid-column': '1 / span 2' },
height: 'calc(100vh - 64px)',
'grid-row': 2,
background: '#eee',
overflow: 'hidden',
},
'.menu-button': {
'@media (min-width:1200px)': { display: 'none' },
'@media (max-width:1200px)': { display: 'inline' },
},
'.content-hider': {
display: 'none',
position: 'absolute',
top: '64px',
left: '200px',
width: '100%',
height: '100%',
background: '#000000aa',
'z-index': 100000000,
},
},
];
styler.add('layout', layoutStyle);
function toggleDrawer() {
const drawer = document.querySelector('.wrapper-sidebar');
const shadow = document.querySelector('.content-hider');
if (drawer.style.left === '0px') {
drawer.style.left = '-200px';
shadow.style.display = 'none';
} else {
drawer.style.left = '0px';
shadow.style.display = 'block';
}
}
class Menupoint {
view({ attrs: { title, href, icon = null } }) {
return m(ListTile, {
url: {
href,
oncreate: m.route.link,
},
front: icon ? m(Icon, {
avatar: true,
svg: m.trust(icon),
}) : '',
url: { href, oncreate: m.route.link },
front: icon ? m(Icon, { svg: m.trust(icon) }) : '',
ink: true,
title,
});
}
}
export default class Layout {
export class loadingScreen {
view() {
return m('div', {
style: {
height: '100%',
width: '100%',
display: 'flex',
'flex-direction': 'column',
'justify-content': 'center',
'align-items': 'center',
'animation-name': 'popup',
'animation-duration': '2000ms',
},
}, m('div', { style: { height: '5vh', 'font-size': '4em' } }, 'Loading...'), m('div', {
style: {
height: '20vh',
width: '20vh',
'animation-name': 'spin',
'animation-duration': '2500ms',
'animation-iteration-count': 'infinite',
'animation-timing-function': 'linear',
},
}, m('div', {
style: { height: '20vh', width: '20vh', display: 'inline-block' },
}, m(SVG, {
style: { width: 'inherit', height: 'inherit' },
content: m.trust(icons.amivWheel),
}))));
}
}
export class Layout {
view({ children }) {
if (!getSchema()) return m(loadingScreen);
const userRights = getUserRights();
return m('div', [
m('div.wrapper-main.smooth', [
m(Toolbar, { className: 'main-toolbar' }, [
m(ToolbarTitle, { text: 'AMIV Admintools' }),
m('a', { onclick: resetSession }, 'Logout'),
m(Toolbar, {
className: 'main-toolbar',
tone: 'dark',
style: { backgroundColor: colors.amiv_blue, color: '#ffffff' },
}, [
m('div.menu-button', m(IconButton, {
className: 'menu-button',
icon: { svg: { content: m.trust(icons.menu) } },
events: { onclick: () => { toggleDrawer(); } },
style: { color: '#ffffff' },
})),
m('div', { style: { 'font-size': '18px', 'margin-left': '20px' } }, 'AMIV Admintools'),
m('a', {
href: 'https://gitlab.ethz.ch/amiv/amiv-admintool/issues/new?issuable_template=Bug',
target: '_blank',
style: {
color: '#888888',
'text-decoration': 'none',
'text-align': 'right',
'margin-right': '20px',
'margin-left': 'auto',
},
}, 'Is something not working? Report a bug!'),
m(Button, {
label: 'logout',
events: { onclick: deleteSession },
}),
]),
m(
'nav.mdc-drawer.mdc-drawer--permanent.mdc-typography.wrapper-sidebar',
'div.mdc-typography.wrapper-sidebar',
{ style: { width: '200px' } },
m(List, {
className: 'drawer-menu',
header: {
title: 'Menu',
},
header: { title: 'Menu' },
hoverable: true,
tiles: [
m(Menupoint, {
userRights.users.indexOf('POST') > -1 && m(Menupoint, {
href: '/users',
icon: icons.iconUsersSVG,
title: 'Users',
......@@ -92,22 +180,53 @@ export default class Layout {
icon: icons.group,
title: 'Groups',
}),
m(Menupoint, {
userRights.joboffers.indexOf('POST') > -1 && m(Menupoint, {
href: '/joboffers',
icon: icons.iconJobsSVG,
title: 'Job offers',
}),
m(Menupoint, {
href: '/announce',
title: 'Announce',
href: '/studydocuments',
icon: icons.studydoc,
title: 'Studydocs',
}),
m(Menupoint, {
href: '/blacklist',
icon: icons.blacklist,
title: 'Blacklist',
}),
m(Menupoint, {
href: '/infoscreen',
icon: icons.iconEventSVG,
title: 'Infoscreen',
}),
],
}),
),
m('div.wrapper-content', children),
// shadow over content in case drawer is out
m('div.content-hider'),
]),
m(Snackbar),
// dialog element will show when Dialog.show() is called, this is only a placeholder
m(Dialog),
]);
}
}
export class Error404 {
view() {
return m('div', {
style: {
height: '100%',
width: '100%',
display: 'flex',
'flex-direction': 'column',
'justify-content': 'center',
'align-items': 'center',
},
}, [
m('div', { style: { height: '5vh', 'font-size': '4em' } }, 'Error 404: Item Not Found!'),
]);
}
}
import m from 'mithril';
import Stream from 'mithril/stream';
import { ResourceHandler } from './auth';
import { debounce } from './utils';
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
export default class DatalistController {
constructor(resource, query = {}, searchKeys = false, onlineSearch = true) {
this.onlineSearch = onlineSearch;
if (onlineSearch) {
this.handler = new ResourceHandler(resource, searchKeys);
} else {
this.handler = new ResourceHandler(resource, false);
this.clientSearchKeys = searchKeys || [];
}
constructor(resource, query = {}, searchKeys = []) {
this.handler = new ResourceHandler(resource, searchKeys);
this.query = query || {};
this.search = null;
this.filter = null;
// state pointer that is counted up every time the table is refreshed so
// we can tell infinite scroll that the data-version has changed.
this.stateCounter = Stream(0);
this.refresh();
this.debouncedSearch = debounce((search) => {
this.setSearch(search);
this.refresh();
m.redraw();
}, 100);
// keep track of the total number of pages
this.totalPages = null;
}
refresh() {
......@@ -36,64 +50,66 @@ export default class DatalistController {
const query = Object.assign({}, this.query);
query.max_results = 10;
query.page = pageNum;
query.where = { ...this.filter, ...this.query.where };
// remove where again if it is empty
if (Object.keys(query.where).length === 0) delete query.where;
return new Promise((resolve) => {
this.handler.get(query).then((data) => {
// If onlineSearch is false, we filter the page-results at the client
// because the API would not understand the search pattern, e.g. for
// embedded keys like user.firstname
if (!this.onlineSearch && this.clientSearchKeys.length > 0 && this.search) {
const response = [];
// We go through all response items and will add them to response if
// they match the query.
data._items.forEach((item) => {
// Try every search Key seperately, such that any match with any
// key is sufficient
this.clientSearchKeys.some((key) => {
if (key.match(/.*\..*/)) {
// traverse the key, this is a key pointing to a sub-object
let intermediateObject = Object.assign({}, item);
key.split('.').forEach((subKey) => {
intermediateObject = intermediateObject[subKey];
});
if (intermediateObject.includes(this.search)) {
response.push(item);
// return true to end the search of this object, it is already
// matched
return true;
}
} else if (item[key] && item[key].includes(this.search)) {
response.push(item);
// return true to end the search of this object, it is already
// matched
return true;
}
return false;
});
});
resolve(response);
} else {
resolve(data._items);
// update total number of pages
this.totalPages = Math.ceil(data._meta.total / 50);
resolve(data._items);
});
});
}
/*
* Get all available pages
*/
getFullList() {
return new Promise((resolve) => {
// get first page to refresh total page count
this.getPageData(1).then((firstPage) => {
const pages = { 1: firstPage };
// save totalPages as a constant to avoid race condition with pages added during this
// process
const { totalPages } = this;
if (totalPages === 1) {
resolve(firstPage);
}
// now fetch all the missing pages
Array.from(new Array(totalPages - 1), (x, i) => i + 2).forEach((pageNum) => {
this.getPageData(pageNum).then((newPage) => {
pages[pageNum] = newPage;
// look if all pages were collected
const missingPages = Array.from(new Array(totalPages), (x, i) => i + 1).filter(
i => !(i in pages),
);
// eslint-disable-next-line no-console
console.log('missingPages', missingPages);
if (missingPages.length === 0) {
// collect all the so-far loaded pages in order (sorted keys)
// and flatten them into 1 array
resolve([].concat(...Object.keys(pages).sort().map(key => pages[key])));
}
});
});
});
});
}
setSearch(search) {
if (this.onlineSearch) {
this.search = search;
this.query.search = search;
this.refresh();
} else if (this.clientSearchKeys.length > 0) {
this.search = search;
this.refresh();
}
this.query.search = search;
}
setFilter(filter) {
this.filter = filter;
this.refresh();
}
setQuery(query) {
this.query = query;
this.query.search = this.search;
this.query = Object.assign({}, query, { search: this.query.search });
this.refresh();
}
}
......@@ -34,4 +34,3 @@ export function set(key, value, shortSession = false) {
window.localStorage.setItem(`glob-${key}`, value);
}
}
import m from 'mithril';
import EditView from './views/editView';
import SelectList from './views/selectList';
const m = require('mithril');
export class MembershipView extends EditView {
export default class MembershipView extends EditView {
constructor(vnode) {
super(vnode, 'groupmemberships', { user: 1, group: 1 });
}
......@@ -23,17 +21,3 @@ export class MembershipView extends EditView {
]);
}
}
export class NewMembership {
constructor() {
this.selectUser = new SelectList('users', ['firstname', 'lastname'], {
view(vnode) {
return m('span', `${vnode.attrs.firstname} ${vnode.attrs.lastname}`);
},
});
}
view() {
return m(this.selectUser);
}
}
{
"apiUrl": "https://api-dev.amiv.ethz.ch",
"ownUrl": "https://admin-dev.amiv.ethz.ch",
"oAuthID": "AMIV Admintool"
"hookUrl": "https://webhooks.amiv.ethz.ch/hook/websitepipeline",
"oAuthID": "AMIV Admintool Dev"
}
{
"apiUrl": "https://api-dev.amiv.ethz.ch",
"ownUrl": "http://localhost:9000",
"hookUrl": "http://0.0.0.0:5000/hook/websitepipeline",
"oAuthID": "Local Tool"
}
{
"apiUrl": "http://127.0.0.1:5000",
"ownUrl": "http://localhost:9000",
"hookUrl": "http://0.0.0.0:6000/hook/websitepipeline",
"oAuthID": "Local Tool"
}
{
"apiUrl": "https://api.amiv.ethz.ch",
"ownUrl": "https://admin.amiv.ethz.ch",
"oAuthID": "Admintools"
"hookUrl": "https://webhooks.amiv.ethz.ch/hook/websitepipeline",
"oAuthID": "AMIV Admintool"
}
{
"apiUrl": "https://api-staging.amiv.ethz.ch",
"ownUrl": "https://admin-staging.amiv.ethz.ch",
"hookUrl": "https://webhooks.amiv.ethz.ch/hook/websitepipeline",
"oAuthID": "AMIV Admintool Staging"
}
This diff is collapsed.
{
"apiUrl": "https://api-dev.amiv.ethz.ch/",
"events": {
"keyDescriptors": {
"title_de": "German Title",
"title_en": "English Title",
"location": "Location",
"show_website": "Event is shown on the website",
"priority": "Priority",
"time_end": "Ending time",
"time_register_end": "Deadline for registration",
"time_start": "Starting time",
"spots": "Spots available",
"allow_email_signup": "Event open for non-AMIV members",
"price": "Price",
"signup_count": "Signed-up participants",
"catchphrase_en": "Catchphrase in English. Announce and Website.",
"catchphrase_de": "Schlagwort auf Deutsch",
"description_de": "Beschreibung auf Deutsch",
"description_en": "Description in English",
"img_banner": "Banner as png",
"img_poster": "Poster as png",
"img_thumbnail": "Thumbnail as png",
"show_infoscreen": "Does the event show on the infoscreen?",
"img_infoscreen": "Infoscreen as png",
"time_advertising_end": "Advertisment ends on",
"time_advertising_start": "Advertisement starts on",
"selection_strategy": "TODO what is this?",
"show_announce": "Does it belong to announce?",
"_id": "TODO Event ID how is this generated?"
},
"tableKeys": [
"title_de",
"time_start",
"time_end",
"time_register_end",
"show_website",
"priority"
],
"notPatchableKeys": [
"signup_count"
]
},
"users": {
"keyDescriptors": {
"legi": "Legi Number",
"firstname": "First Name",
"lastname": "Last Name",
"rfid": "RFID",
"phone": "Phone",
"nethz": "nethz Account",
"gender": "Gender",
"department": "Department",
"email": "Email"
},
"tableKeys": [
"firstname",
"lastname",
"nethz",
"legi",
"membership"
],
"searchKeys": [
"firstname",
"lastname",
"nethz",
"legi",
"department"
],
"notPatchableKeys": [
"password_set"
]
},
"joboffers":{
"keyDescriptors": {
"company": "Company",
"email": "Email",
"description_en": "Job description",
"description_de": "Job Beschreibung",
"logo": "Logo as png",
"pdf": "PDF provided by company",
"time_end": "Application deadline",
"title_de": "Stelle auf Deutsch",
"title_de": "Position title in English",
"show_website": "Is the job listed on the website?",
"_id":"Job ID."
},
"tableKeys": [
"title_de",
"time_end",
"show_website"
]
},
"groups": {
"keyDescriptors": {
"name": "Name"
},
"searchKeys": ["name"],
"patchableKeys": ["name"]
},
"groupmemberships": {
"patchableKeys": ["user", "group"]
},
"eventsignups": {
"patchableKeys": ["event"],
"tableKeys": [
"_created",
"user.lastname",
"user.firstname",
"email"
],
"searchKeys": []
}
}
This diff is collapsed.
import m from 'mithril';
import viewDoc from './viewDoc';
import editDoc from './editDoc';
import ItemController from '../itemcontroller';
import { loadingScreen } from '../layout';
export default class studydocItem {
constructor() {
this.controller = new ItemController('studydocuments');
}
view() {
if (!this.controller || (!this.controller.data && this.controller.modus !== 'new')) {
return m(loadingScreen);
}
if (this.controller.modus !== 'view') return m(editDoc, { controller: this.controller });
return m(viewDoc, { controller: this.controller });
}
}
This diff is collapsed.
This diff is collapsed.