Add decentralized services
This commit is contained in:
parent
d2422d5036
commit
c7d0adf780
19 changed files with 346 additions and 19 deletions
|
|
@ -5,11 +5,10 @@ COPY . .
|
||||||
RUN go get -d ./...
|
RUN go get -d ./...
|
||||||
RUN go install ./...
|
RUN go install ./...
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
RUN apk --no-cache add ca-certificates tzdata
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /go/bin/app .
|
COPY --from=builder /go/bin/spaceapi.ccc.de ./app
|
||||||
COPY config.yaml config.yaml
|
COPY config.yaml config.yaml
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["./app"]
|
CMD ["./app"]
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,6 @@ type ConfigFile struct {
|
||||||
SharedSecret string `yaml:"shared_secret,omitempty"`
|
SharedSecret string `yaml:"shared_secret,omitempty"`
|
||||||
MongoDbServer string `yaml:"mongodb_server,omitempty"`
|
MongoDbServer string `yaml:"mongodb_server,omitempty"`
|
||||||
MongoDbDatabase string `yaml:"mongodb_database,omitempty"`
|
MongoDbDatabase string `yaml:"mongodb_database,omitempty"`
|
||||||
|
DokuWikiUser string `yaml:"doku_wiki_user,omitempty"`
|
||||||
|
DokuWikiPassword string `yaml:"doku_wiki_password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,27 @@ func writeCalendar(calendar Calendar) {
|
||||||
c.Upsert(bson.M{"space": calendar.Space}, calendar)
|
c.Upsert(bson.M{"space": calendar.Space}, calendar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeDecentralizedServices(services []DecentrealizedService) {
|
||||||
|
if len(services) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := mgo.Dial(config.MongoDbServer)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
session.SetMode(mgo.Monotonic, true)
|
||||||
|
|
||||||
|
c := session.DB(config.MongoDbDatabase).C("decentralized_services")
|
||||||
|
|
||||||
|
c.DropCollection()
|
||||||
|
for _, service := range(services) {
|
||||||
|
c.Insert(service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateSpaceurl(spaceUrl SpaceUrl) {
|
func updateSpaceurl(spaceUrl SpaceUrl) {
|
||||||
session, err := mgo.Dial(config.MongoDbServer)
|
session, err := mgo.Dial(config.MongoDbServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -131,3 +152,20 @@ func readCalendar() []Calendar {
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func readServices() []DecentrealizedService {
|
||||||
|
session, err := mgo.Dial(config.MongoDbServer)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
session.SetMode(mgo.Monotonic, true)
|
||||||
|
|
||||||
|
c := session.DB(config.MongoDbDatabase).C("decentralized_services")
|
||||||
|
var result []DecentrealizedService
|
||||||
|
c.Find(bson.M{}).Iter().All(&result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -22,11 +24,16 @@ func main() {
|
||||||
panic("Can't load config")
|
panic("Can't load config")
|
||||||
}
|
}
|
||||||
config.SharedSecret = os.Getenv("SHARED_SECRET")
|
config.SharedSecret = os.Getenv("SHARED_SECRET")
|
||||||
|
config.DokuWikiUser = os.Getenv("DOKU_WIKI_USER")
|
||||||
|
config.DokuWikiPassword = os.Getenv("DOKU_WIKI_PASSWORD")
|
||||||
|
|
||||||
|
updateDecentralizedServicesList()
|
||||||
|
|
||||||
c := cron.New()
|
c := cron.New()
|
||||||
_, err = c.AddFunc("@hourly", func() {
|
err = c.AddFunc("@hourly", func() {
|
||||||
loadSpaceData()
|
loadSpaceData()
|
||||||
getCalendars()
|
getCalendars()
|
||||||
|
updateDecentralizedServicesList()
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Can't start cron %v", err)
|
log.Printf("Can't start cron %v", err)
|
||||||
|
|
@ -61,6 +68,10 @@ func CalendarIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
ReturnJson(w, readCalendar())
|
ReturnJson(w, readCalendar())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DecentralizedServicesIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ReturnJson(w, readServices())
|
||||||
|
}
|
||||||
|
|
||||||
func SpaceUrlAdd(w http.ResponseWriter, r *http.Request) {
|
func SpaceUrlAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceUrl := SpaceUrl{}
|
spaceUrl := SpaceUrl{}
|
||||||
createEntry(&spaceUrl, w, r)
|
createEntry(&spaceUrl, w, r)
|
||||||
|
|
@ -130,3 +141,48 @@ func refreshData(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.WriteHeader(204)
|
w.WriteHeader(204)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDecentralizedServicesList() {
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "https://doku.ccc.de/index.php?title=Spezial:Ask&x=-5B-5BKategorie%3ADienste-5D-5D%2F-3FService-2DName%3DName%2F-3FService-2DProduct%3DProdukt%2F-3FService-2DIs-2DCentralized%3DZentralisiert%2F-3FService-2DVisibility%3DSichtbarkeit%2F-3FService-2DOrg%3DOrganisation%2F-3FService-2DType%3DTyp%2F-3FService-2DURL%3DURL%2F-3FService-2DContact%3DKontakt%2F-3FService-2DCheck-23ISO%3DOnline-20Check%2F-3FService-2DState%3DOnline-3F%2F-3FService-2DEncryption%3DVerschl%C3%BCsselung%2F-3FService-2DHas-2DIPv4%3DIPv4%2F-3FService-2DHas-2DIPv6%3DIPv6&mainlabel=-&limit=50&offset=0&format=csv&headers=show&searchlabel=CSV&default=%21no%20result%21&sep=%3B&valuesep=%3B&filename=dienste.csv", nil)
|
||||||
|
if err != nil{
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
req.SetBasicAuth(config.DokuWikiUser, config.DokuWikiPassword)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil{
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := csv.NewReader(resp.Body)
|
||||||
|
r.Comma = ';'
|
||||||
|
|
||||||
|
var DecentrealizedServiceList []DecentrealizedService
|
||||||
|
for {
|
||||||
|
record, err := r.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if record[3] == "public" && record[9] == "wahr" && record[6] != "" {
|
||||||
|
foo := DecentrealizedService{
|
||||||
|
record[0],
|
||||||
|
record[1],
|
||||||
|
record[6],
|
||||||
|
}
|
||||||
|
|
||||||
|
DecentrealizedServiceList = append(DecentrealizedServiceList, foo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDecentralizedServices(DecentrealizedServiceList)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecentrealizedService struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
|
||||||
15
backend/go.mod
Normal file
15
backend/go.mod
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
module github.com/gidsi/spaceapi.ccc.de
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade
|
||||||
|
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 // indirect
|
||||||
|
github.com/gidsi/ics-golang v0.0.0-20190331185529-e95e328c4a08
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
github.com/gorilla/mux v1.7.3
|
||||||
|
github.com/robfig/cron v1.2.0
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
||||||
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
|
)
|
||||||
18
backend/go.sum
Normal file
18
backend/go.sum
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade h1:odEkSCl2gLWPtvraEdCyBZbeYyMMTysWPLMurnB8sUY=
|
||||||
|
github.com/PuloV/ics-golang v0.0.0-20190808201353-a3394d3bcade/go.mod h1:f1P3hjG+t54/IrnXMnnw+gRmFCDR/ryj9xSQ7MPMkQw=
|
||||||
|
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:o64h9XF42kVEUuhuer2ehqrlX8rZmvQSU0+Vpj1rF6Q=
|
||||||
|
github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0=
|
||||||
|
github.com/gidsi/ics-golang v0.0.0-20190331185529-e95e328c4a08 h1:x57vmmjqLdRGv3bZQqoyzt4S08z5pTk/vkWaqXGnswI=
|
||||||
|
github.com/gidsi/ics-golang v0.0.0-20190331185529-e95e328c4a08/go.mod h1:oIDQdzzcgVLYQRfohyxBaOVr7pyfRydoPcEdVNXry4s=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||||
|
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
@ -36,6 +36,12 @@ var IndexRoutes = Routes{
|
||||||
"/calendar",
|
"/calendar",
|
||||||
CalendarIndex,
|
CalendarIndex,
|
||||||
},
|
},
|
||||||
|
Route{
|
||||||
|
"ServicesIndex",
|
||||||
|
"GET",
|
||||||
|
"/services",
|
||||||
|
DecentralizedServicesIndex,
|
||||||
|
},
|
||||||
Route{
|
Route{
|
||||||
"SpaceUrlAdd",
|
"SpaceUrlAdd",
|
||||||
"POST",
|
"POST",
|
||||||
|
|
|
||||||
BIN
backend/spaceapi.ccc.de
Executable file
BIN
backend/spaceapi.ccc.de
Executable file
Binary file not shown.
|
|
@ -19,6 +19,8 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- SHARED_SECRET=${SHARED_SECRET:-secret}
|
- SHARED_SECRET=${SHARED_SECRET:-secret}
|
||||||
|
- DOKU_WIKI_USER=${DOKU_WIKI_USER}
|
||||||
|
- DOKU_WIKI_PASSWORD=${DOKU_WIKI_PASSWORD}
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
database:
|
database:
|
||||||
|
|
|
||||||
13
frontend/package-lock.json
generated
Normal file
13
frontend/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "eva-frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": {
|
||||||
|
"version": "2.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
|
||||||
|
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,26 +3,27 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^3.9.3",
|
"@material-ui/core": "^4.9.1",
|
||||||
"@material-ui/icons": "^3.0.2",
|
"@material-ui/icons": "^4.9.1",
|
||||||
"leaflet": "^1.4.0",
|
"classnames": "^2.2.6",
|
||||||
|
"leaflet": "^1.6.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.12.0",
|
||||||
"react-leaflet": "^2.2.1",
|
"react-leaflet": "^2.6.1",
|
||||||
"react-redux": "^6.0.1",
|
"react-redux": "^7.1.3",
|
||||||
"react-router-dom": "^5.0.0",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-tap-event-plugin": "^3.0.3",
|
"react-tap-event-plugin": "^3.0.3",
|
||||||
"react-virtualized": "^9.21.0",
|
"react-virtualized": "^9.21.2",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.5",
|
||||||
"redux-actions": "^2.6.5",
|
"redux-actions": "^2.6.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"rrule": "^2.6.0",
|
"rrule": "^2.6.4",
|
||||||
"superagent": "^5.0.2"
|
"superagent": "^5.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"react-scripts": "2.1.8"
|
"react-scripts": "3.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { MuiThemeProvider } from '@material-ui/core/styles';
|
||||||
import theme from './style/theme';
|
import theme from './style/theme';
|
||||||
import store from './redux/store';
|
import store from './redux/store';
|
||||||
import IndexContainer from './views/Index';
|
import IndexContainer from './views/Index';
|
||||||
|
import ServicesList from "./views/ServicesList";
|
||||||
import SpaceList from './views/SpaceList';
|
import SpaceList from './views/SpaceList';
|
||||||
import UrlListView from './views/UrlListView';
|
import UrlListView from './views/UrlListView';
|
||||||
import layout from './layout';
|
import layout from './layout';
|
||||||
|
|
@ -19,6 +20,7 @@ const App = () => (
|
||||||
<Router>
|
<Router>
|
||||||
<div>
|
<div>
|
||||||
<Route path="/list" component={layout(<SpaceList />)} />
|
<Route path="/list" component={layout(<SpaceList />)} />
|
||||||
|
<Route path="/services" component={layout(<ServicesList />)} />
|
||||||
<Route path="/urls" component={layout(<UrlListView />)} />
|
<Route path="/urls" component={layout(<UrlListView />)} />
|
||||||
<Route exact path="/" component={layout(<IndexContainer />)} />
|
<Route exact path="/" component={layout(<IndexContainer />)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class Map extends React.Component {
|
||||||
style={{ width: '100vw', height: 'calc(50vh - 60px)', margin: 0, padding: 0, maxWidth: '100%' }}
|
style={{ width: '100vw', height: 'calc(50vh - 60px)', margin: 0, padding: 0, maxWidth: '100%' }}
|
||||||
>
|
>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
url="https://spaceapi.ccc.de/map/tiles/{z}/{x}/{y}.png"
|
url="/map/tiles/{z}/{x}/{y}.png"
|
||||||
/>
|
/>
|
||||||
{this.props.spacedata.items.map(
|
{this.props.spacedata.items.map(
|
||||||
spacedata => (
|
spacedata => (
|
||||||
|
|
|
||||||
100
frontend/src/components/ServiceList.jsx
Normal file
100
frontend/src/components/ServiceList.jsx
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Table from '@material-ui/core/Table';
|
||||||
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
|
import TableCell from '@material-ui/core/TableCell';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { serviceStruct } from '../redux/modules/services';
|
||||||
|
import {withStyles} from "@material-ui/core";
|
||||||
|
|
||||||
|
const styles = theme => ({
|
||||||
|
table: {
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
border: 0,
|
||||||
|
},
|
||||||
|
flexContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
},
|
||||||
|
tableRow: {},
|
||||||
|
tableRowHover: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.grey[600],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tableRowEven: {
|
||||||
|
backgroundColor: theme.palette.grey[700],
|
||||||
|
},
|
||||||
|
tableCell: {
|
||||||
|
flex: 1,
|
||||||
|
color: '#fff',
|
||||||
|
border: 0,
|
||||||
|
},
|
||||||
|
noClick: {
|
||||||
|
cursor: 'initial',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatType = type => {
|
||||||
|
if (type.indexOf("[") === 0) {
|
||||||
|
return type.substring(type.indexOf(" ") + 1, type.length - 1);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServiceList extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
fetchServices: PropTypes.func.isRequired,
|
||||||
|
services: serviceStruct,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
services: {
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.fetchServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const items = this.props.services.items.sort(
|
||||||
|
(a, b) => formatType(a.type).toUpperCase().localeCompare(formatType(b.type).toUpperCase())
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div style={{ paddingTop:'60px' }}>
|
||||||
|
<Table>
|
||||||
|
<TableBody>
|
||||||
|
{items
|
||||||
|
.map((service, index) => (
|
||||||
|
<TableRow key={service.name} className={classNames({
|
||||||
|
[this.props.classes.tableRowEven]: index % 2
|
||||||
|
})} >
|
||||||
|
<TableCell style={{ color: '#fff', border: 0 }}>
|
||||||
|
{service.name}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell style={{ color: '#fff', border: 0 }}>
|
||||||
|
{formatType(service.type)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell style={{ color: '#fff', border: 0 }}>
|
||||||
|
<a
|
||||||
|
href={service.url}
|
||||||
|
style={{ textDecoration: 'none', color: 'white' }}
|
||||||
|
>
|
||||||
|
{service.url}
|
||||||
|
</a>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(ServiceList);
|
||||||
|
|
@ -71,6 +71,12 @@ class MyToolbar extends React.Component {
|
||||||
>
|
>
|
||||||
Spaces
|
Spaces
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => window.location.href = '/services'}
|
||||||
|
containerElement={<Link to="/services" />}
|
||||||
|
>
|
||||||
|
Services
|
||||||
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => window.location.href = 'http://ccc.de/de/imprint'}
|
onClick={() => window.location.href = 'http://ccc.de/de/imprint'}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
13
frontend/src/containers/ServiceList.jsx
Normal file
13
frontend/src/containers/ServiceList.jsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { actions as serviceActions } from '../redux/modules/services';
|
||||||
|
import Component from '../components/ServiceList';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
services: state.services,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
...serviceActions,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Component);
|
||||||
44
frontend/src/redux/modules/services.js
Normal file
44
frontend/src/redux/modules/services.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import request from 'superagent';
|
||||||
|
import { createAction, handleActions } from 'redux-actions';
|
||||||
|
import config from '../../api/config';
|
||||||
|
|
||||||
|
export const serviceElementStruct = PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
});
|
||||||
|
export const itemsStruct = PropTypes.arrayOf(serviceElementStruct);
|
||||||
|
export const serviceStruct = PropTypes.shape({
|
||||||
|
items: itemsStruct,
|
||||||
|
});
|
||||||
|
|
||||||
|
const SERVICES_FETCHED = 'SERVICES_FETCHED';
|
||||||
|
|
||||||
|
export const fetched = createAction(SERVICES_FETCHED, result => result);
|
||||||
|
|
||||||
|
export const fetchServices = () => (dispatch) => {
|
||||||
|
request
|
||||||
|
.get(`${config.api.url}/services`)
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.end(
|
||||||
|
(err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
dispatch(fetched(res.body));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
fetchServices,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handleActions({
|
||||||
|
[SERVICES_FETCHED]: (state, { payload }) => (
|
||||||
|
{
|
||||||
|
...state,
|
||||||
|
items: payload,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}, { items: [] });
|
||||||
|
|
@ -2,9 +2,11 @@ import { combineReducers } from 'redux';
|
||||||
import spacedataReducer from './modules/spacedata';
|
import spacedataReducer from './modules/spacedata';
|
||||||
import calendarsReducer from './modules/calendar';
|
import calendarsReducer from './modules/calendar';
|
||||||
import spaceUrlsReducer from './modules/spaceurl';
|
import spaceUrlsReducer from './modules/spaceurl';
|
||||||
|
import servicesReducer from './modules/services';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
spacedata: spacedataReducer,
|
spacedata: spacedataReducer,
|
||||||
calendars: calendarsReducer,
|
calendars: calendarsReducer,
|
||||||
spaceurls: spaceUrlsReducer,
|
spaceurls: spaceUrlsReducer,
|
||||||
|
services: servicesReducer,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
10
frontend/src/views/ServicesList.jsx
Normal file
10
frontend/src/views/ServicesList.jsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ServiceList from '../containers/ServiceList';
|
||||||
|
|
||||||
|
const ServiceListView = () => (
|
||||||
|
<div>
|
||||||
|
<ServiceList />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ServiceListView;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue