├── akka-epl ├── project │ ├── build.properties │ ├── assembly.sbt │ └── project │ │ └── target │ │ └── config-classes │ │ └── $731f18e1a71d9df9db04.cache ├── README.md ├── manifest.yml ├── src │ ├── main │ │ ├── scala │ │ │ └── com │ │ │ │ └── epl │ │ │ │ └── akka │ │ │ │ ├── DocumentType.scala │ │ │ │ ├── CrawlingApp.scala │ │ │ │ ├── URLValidator.scala │ │ │ │ ├── ResultTable.scala │ │ │ │ ├── CorsSupport.scala │ │ │ │ ├── TeamTable.scala │ │ │ │ ├── WebCrawler.scala │ │ │ │ ├── AkkaHTTPClient.scala │ │ │ │ ├── SoccerMainController.scala │ │ │ │ ├── DBOperation.scala │ │ │ │ ├── CloudantReaderWriter.scala │ │ │ │ ├── WebHttpClient.scala │ │ │ │ └── HTMLParser.scala │ │ └── resources │ │ │ ├── logback.xml │ │ │ └── application.conf │ └── test │ │ └── scala │ │ └── com │ │ └── epl │ │ └── akka │ │ └── AkkaHTTPClientSpec.scala ├── LICENSE └── build.sbt ├── soccer-epl-ui ├── .jshintrc ├── .babelrc ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── actions │ │ ├── teamStandingAction.js │ │ ├── headerStyleAction.js │ │ ├── sidebarStyleAction.js │ │ └── headerBannerAction.js │ ├── static │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── material-ui-icons.eot │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ └── css │ │ │ └── App.css │ ├── sass │ │ ├── footer │ │ │ └── _footer.scss │ │ ├── base │ │ │ ├── _forms.scss │ │ │ ├── _page-content.scss │ │ │ ├── _typography.scss │ │ │ ├── _buttons.scss │ │ │ └── _scaffolding.scss │ │ ├── App.scss │ │ ├── sidebar │ │ │ ├── _navbar.scss │ │ │ └── _sidebar.scss │ │ ├── components │ │ │ └── _panel.scss │ │ └── header │ │ │ └── _header.scss │ ├── components │ │ ├── footer.js │ │ ├── buttons │ │ │ └── raisedButtons.js │ │ └── panel.js │ ├── reducers │ │ ├── style-switcher-reducers │ │ │ ├── headeractiveReducer.js │ │ │ ├── sidebarActiveReducer.js │ │ │ ├── headerBannerActiveReducer.js │ │ │ ├── headerStyleReducers.js │ │ │ ├── headerBannerStyleReducers.js │ │ │ └── sidebarStyleReducers.js │ │ └── index.js │ ├── index.js │ ├── containers │ │ └── header.js │ ├── routers │ │ ├── routesComponent.js │ │ ├── routesList.js │ │ └── routers.js │ ├── App.js │ └── pages │ │ ├── results.js │ │ ├── fixtures.js │ │ ├── teamstanding.js │ │ └── home.js ├── .eslintrc ├── .editorconfig ├── gulpfile.babel.js └── package.json ├── assets ├── actors.jpeg ├── fixtures.png ├── results.png ├── dashboard.png ├── teamstanding.png ├── actor-components.png ├── actor-communication.jpeg └── soccer epl architecture.jpg ├── .travis.yml ├── .gitignore └── README.md /akka-epl/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /soccer-epl-ui/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } 4 | -------------------------------------------------------------------------------- /soccer-epl-ui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /akka-epl/project/assembly.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") -------------------------------------------------------------------------------- /akka-epl/README.md: -------------------------------------------------------------------------------- 1 | # akka-epl 2 | A dashboard generated from scraping premierleague.com 3 | -------------------------------------------------------------------------------- /akka-epl/project/project/target/config-classes/$731f18e1a71d9df9db04.cache: -------------------------------------------------------------------------------- 1 | sbt.internals.DslEntry -------------------------------------------------------------------------------- /assets/actors.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/actors.jpeg -------------------------------------------------------------------------------- /assets/fixtures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/fixtures.png -------------------------------------------------------------------------------- /assets/results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/results.png -------------------------------------------------------------------------------- /assets/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/dashboard.png -------------------------------------------------------------------------------- /assets/teamstanding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/teamstanding.png -------------------------------------------------------------------------------- /assets/actor-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/actor-components.png -------------------------------------------------------------------------------- /assets/actor-communication.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/actor-communication.jpeg -------------------------------------------------------------------------------- /soccer-epl-ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/soccer-epl-ui/public/favicon.ico -------------------------------------------------------------------------------- /assets/soccer epl architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/assets/soccer epl architecture.jpg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.12.2 4 | before_script: cd akka-epl 5 | script: 6 | - sbt compile 7 | - sbt test 8 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/actions/teamStandingAction.js: -------------------------------------------------------------------------------- 1 | export const setVisibilityFilter = (filter) => ({ 2 | type: 'SET_VISIBILITY_FILTER', 3 | filter 4 | }) -------------------------------------------------------------------------------- /soccer-epl-ui/src/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/soccer-epl-ui/src/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /akka-epl/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: akka-react-cloudant 4 | instances: 1 5 | host: soccerapi 6 | path: target/scala-2.12/akka-epl.jar 7 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/static/fonts/material-ui-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/soccer-epl-ui/src/static/fonts/material-ui-icons.eot -------------------------------------------------------------------------------- /soccer-epl-ui/src/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/soccer-epl-ui/src/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /soccer-epl-ui/src/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/soccer-epl-ui/src/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /soccer-epl-ui/src/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/soccer-epl-ui/src/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /soccer-epl-ui/src/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/akka-react-cloudant/HEAD/soccer-epl-ui/src/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /soccer-epl-ui/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "ecmaFeatures": { 9 | "modules": true, 10 | "jsx": true 11 | } 12 | } -------------------------------------------------------------------------------- /soccer-epl-ui/src/actions/headerStyleAction.js: -------------------------------------------------------------------------------- 1 | // header left background color changing action 2 | export const selectedHeaderStyle = (color) => { 3 | return { 4 | type: "HEADER_STYLE_SELECTED", 5 | payload: color 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/actions/sidebarStyleAction.js: -------------------------------------------------------------------------------- 1 | // sidebar background color changing action 2 | export const selectedSidebarStyle = (color) => { 3 | return { 4 | type: "SIDEBAR_STYLE_SELECTED", 5 | payload: color 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/footer/_footer.scss: -------------------------------------------------------------------------------- 1 | .readmin-footer { 2 | 3 | position: relative; 4 | text-align: center; 5 | padding: $padding-large 0; 6 | padding-bottom: 0; 7 | 8 | p { 9 | opacity: .7; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => ( 4 | 7 | ); 8 | 9 | export default Footer; 10 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/actions/headerBannerAction.js: -------------------------------------------------------------------------------- 1 | // header banner background color changing action 2 | export const selectedHeaderBannerStyle = (color) => { 3 | return { 4 | type: "HEADER_BANNER_STYLE_SELECTED", 5 | payload: color 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/reducers/style-switcher-reducers/headeractiveReducer.js: -------------------------------------------------------------------------------- 1 | export default function (state={}, action) { 2 | switch(action.type) { 3 | case "HEADER_STYLE_SELECTED": 4 | return action.payload; 5 | default: 6 | return state; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/reducers/style-switcher-reducers/sidebarActiveReducer.js: -------------------------------------------------------------------------------- 1 | export default function (state={}, action) { 2 | switch(action.type) { 3 | case "SIDEBAR_STYLE_SELECTED": 4 | return action.payload; 5 | default: 6 | return state; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/DocumentType.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | /** 4 | * Created by sanjeevghimire on 9/15/17. 5 | */ 6 | object DocumentType extends Enumeration{ 7 | type Documenttype = Value 8 | val TeamTable,Fixtures,Results = Value 9 | } 10 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/reducers/style-switcher-reducers/headerBannerActiveReducer.js: -------------------------------------------------------------------------------- 1 | export default function (state={}, action) { 2 | switch(action.type) { 3 | case "HEADER_BANNER_STYLE_SELECTED": 4 | return action.payload; 5 | default: 6 | return state; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /soccer-epl-ui/.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.py] 15 | indent_size = 4 16 | 17 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/base/_forms.scss: -------------------------------------------------------------------------------- 1 | .readmin-form { 2 | position: relative; 3 | width: 100%; 4 | 5 | &.inline { 6 | @media (max-width: 560px) { 7 | .inline-btn { 8 | margin-top: $margin-medium; 9 | } 10 | 11 | } 12 | } 13 | 14 | input { 15 | width: 100%; 16 | } 17 | label { 18 | margin: 0 !important; 19 | } 20 | 21 | .align-items-center { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/CrawlingApp.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | import com.epl.akka.WebCrawler.CrawlRequest 5 | 6 | object CrawlingApp extends App { 7 | val system = ActorSystem("Crawler") 8 | val webCrawler = system.actorOf(Props[WebCrawler], "WebCrawler") 9 | // start the crawling 10 | webCrawler ! CrawlRequest("https://www.premierleague.com/", true) 11 | } 12 | -------------------------------------------------------------------------------- /akka-epl/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import {createStore} from 'redux'; 5 | import {Provider} from 'react-redux'; 6 | 7 | import allReducers from './reducers'; 8 | let store = createStore( 9 | allReducers, 10 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), 11 | ) 12 | 13 | ReactDOM.render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/base/_page-content.scss: -------------------------------------------------------------------------------- 1 | .readmin-page-content { 2 | position: relative; 3 | width: 100%; 4 | padding: $padding-large; 5 | padding-top: $padding-large * 3 + 10; 6 | @include transition(); 7 | 8 | @media (max-width: 520px) { 9 | padding: $padding-base; 10 | padding-top: $padding-large * 3 + 10; 11 | } 12 | 13 | &.menu-open { 14 | padding-left: 256 + $padding-large; 15 | 16 | @media (max-width: 991px) { 17 | padding-left: $padding-large; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/App.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | // Base 4 | @import './base/scaffolding'; 5 | @import './base/typography'; 6 | @import './base/buttons'; 7 | @import './base/page-content'; 8 | 9 | // Header 10 | @import './header/header'; 11 | @import './header/notification'; 12 | 13 | @import './sidebar/sidebar'; 14 | @import './sidebar/navbar'; 15 | 16 | // Components 17 | @import './components/panel'; 18 | @import './components/table'; 19 | 20 | // Utilities 21 | @import './utilities/utilities'; 22 | 23 | // Footer 24 | @import './footer/footer'; -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/sidebar/_navbar.scss: -------------------------------------------------------------------------------- 1 | .nav-menu { 2 | .childmenu { 3 | + div { 4 | padding: 0 !important; 5 | } 6 | .active { 7 | .childmenu & { 8 | background: $color-primary !important; 9 | color: $font-color-white !important; 10 | } 11 | i { 12 | color: $font-color-white !important; 13 | } 14 | } 15 | } 16 | 17 | .active { 18 | background: $color-primary !important; 19 | color: $font-color-white !important; 20 | i { 21 | color: $font-color-white !important; 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/containers/header.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | 4 | class Header extends Component { 5 | 6 | render () { 7 | const style = { 8 | background: this.props.colorHeaderBanner.color 9 | } 10 | 11 | return ( 12 |
13 |
14 |
Hello Soccer Fans!
15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | function mapStateToProps (state) { 22 | return { 23 | colorHeaderBanner: state.headerBAnnerActiveStyle 24 | } 25 | } 26 | 27 | export default connect(mapStateToProps)(Header); 28 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/reducers/style-switcher-reducers/headerStyleReducers.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return [ 3 | { color: '#258df2', value: 'Default' }, 4 | { color: '#B71C1C', value: 'Red' }, 5 | { color: '#D81B60', value: 'Pink' }, 6 | { color: '#4A148C', value: 'Purple' }, 7 | { color: '#3F51B5', value: 'Indigo' }, 8 | { color: '#1565C0', value: 'Blue' }, 9 | { color: '#00BCD4', value: 'Cyan' }, 10 | { color: '#009688', value: 'Teal' }, 11 | { color: '#2E7D32', value: 'Green' }, 12 | { color: '#827717', value: 'Lime' }, 13 | { color: '#E65100', value: 'Orange' }, 14 | { color: '#3E2723', value: 'Brown' }, 15 | { color: '#424242', value: 'Gray' }, 16 | { color: '#4527A0', value: 'Deep purple' }, 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/reducers/style-switcher-reducers/headerBannerStyleReducers.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return [ 3 | { color: '#258df2', value: 'Default' }, 4 | { color: '#B71C1C', value: 'Red' }, 5 | { color: '#D81B60', value: 'Pink' }, 6 | { color: '#4A148C', value: 'Purple' }, 7 | { color: '#3F51B5', value: 'Indigo' }, 8 | { color: '#1565C0', value: 'Blue' }, 9 | { color: '#00BCD4', value: 'Cyan' }, 10 | { color: '#009688', value: 'Teal' }, 11 | { color: '#2E7D32', value: 'Green' }, 12 | { color: '#827717', value: 'Lime' }, 13 | { color: '#E65100', value: 'Orange' }, 14 | { color: '#3E2723', value: 'Brown' }, 15 | { color: '#424242', value: 'Gray' }, 16 | { color: '#4527A0', value: 'Deep purple' }, 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/components/buttons/raisedButtons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import RaisedButton from 'material-ui/RaisedButton'; 3 | import Panel from '../panel.js'; 4 | 5 | const style = { 6 | margin: 12, 7 | }; 8 | 9 | const RaisedButtons = () => ( 10 | 13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export default RaisedButtons; 26 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/routers/routesComponent.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Route } from 'react-router-dom'; 3 | 4 | import Home from '../pages/home.js'; 5 | import Fixtures from '../pages/fixtures.js'; 6 | import Results from '../pages/results.js'; 7 | import TeamStanding from '../pages/teamstanding.js'; 8 | 9 | class RoutesComponent extends Component { 10 | render() { 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 |
18 | ); 19 | } 20 | } 21 | 22 | export default RoutesComponent; 23 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/URLValidator.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.actor.{Actor, ActorLogging, ActorRef} 4 | import com.epl.akka.URLValidator.ValidateUrl 5 | import com.epl.akka.WebCrawler.Crawl 6 | 7 | 8 | object URLValidator { 9 | case class ValidateUrl(url: String,isRootUrl: Boolean) {} 10 | 11 | case class GetCrawledURLs(links: Set[String]) {} 12 | } 13 | 14 | /** 15 | * Created by sanjeevghimire on 9/1/17. 16 | */ 17 | class URLValidator extends Actor with ActorLogging{ 18 | var visitedUrl = Set.empty[String] 19 | var childUrls = Set.empty[ActorRef] 20 | 21 | override def receive: Receive = { 22 | case ValidateUrl(url,isRootUrl) => 23 | if (!visitedUrl(url)) { 24 | context.parent ! Crawl(url, isRootUrl) 25 | visitedUrl += url 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /akka-epl/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loggers = ["akka.event.slf4j.Slf4jLogger"] 3 | loglevel = "DEBUG" 4 | logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" 5 | } 6 | 7 | http { 8 | host = "localhost" 9 | port = 9000 10 | } 11 | 12 | cors { 13 | allowed-origin = "http://localhost:3000" 14 | } 15 | 16 | cloudant { 17 | username = "" 18 | password = "" 19 | host = "" 20 | port = 443 21 | url = "" 22 | dbname = "" 23 | key = "" 24 | passcode = "" 25 | post_url = "/" 26 | get_results_url = "//_design//_view/?include_docs=true" 27 | get_fixtures_url = "//_design//_view/?include_docs=true" 28 | get_tables_url = "//_design//_view/?include_docs=true" 29 | get_unexpireddoc_url = "//_design//_view/?include_docs=true" 30 | } 31 | -------------------------------------------------------------------------------- /soccer-epl-ui/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import plumber from 'gulp-plumber'; 3 | import notify from 'gulp-notify'; 4 | import sass from 'gulp-sass'; 5 | import watch from 'gulp-watch'; 6 | import livereload from 'gulp-livereload'; 7 | 8 | 9 | 10 | gulp.task('styles', function() { 11 | return watch('./src/sass/**/*.scss', function() { 12 | gulp.src(['./src/sass/App.scss', './src/sass/vendor-styles.scss' ]) 13 | .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') })) 14 | .pipe(sass()) 15 | .pipe(notify({ 16 | title: 'Gulp', 17 | subtitle: 'success', 18 | message: 'Admin Sass Task', 19 | sound: 'Pop', 20 | })) 21 | .pipe(livereload()) 22 | .pipe(gulp.dest('./src/static/css/')); 23 | }); 24 | }); 25 | 26 | 27 | gulp.task('watch', () => { 28 | livereload.listen(); 29 | gulp.watch('./src/sass/**/*.scss', ['styles']); 30 | }); 31 | 32 | gulp.task('default', ['styles']); 33 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/components/_panel.scss: -------------------------------------------------------------------------------- 1 | .readmin-panel { 2 | position: relative; 3 | width: 100%; 4 | background: $color-bg-white; 5 | margin-bottom: $margin-large; 6 | @include border-radius($border-radius); 7 | @include an-box-shadow-small; 8 | 9 | &.righticonmenu { 10 | .panel-heading { 11 | padding: $padding-5 $padding-large; 12 | padding-right: $padding-base; 13 | } 14 | } 15 | 16 | &.body-text-center { 17 | .panel-body { 18 | text-align: center; 19 | } 20 | } 21 | 22 | .panel-heading { 23 | position: relative; 24 | width: 100%; 25 | border-bottom: 1px solid darken($color-body-bg, 5%); 26 | padding: $padding-base + 2 $padding-large; 27 | padding-right: $padding-base; 28 | @include flex-space-between; 29 | 30 | h5 { 31 | margin: 0; 32 | } 33 | } 34 | .panel-body { 35 | padding: $padding-medium; 36 | width: 100%; 37 | display: inline-block; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 3 | import SidebarMenuRouters from './routers/routers.js'; 4 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 5 | 6 | 7 | import './static/css/App.css'; 8 | import './static/css/vendor-styles.css'; 9 | 10 | import injectTapEventPlugin from 'react-tap-event-plugin'; 11 | injectTapEventPlugin(); 12 | 13 | //import Header from './containers/header.js'; 14 | 15 | 16 | const muiTheme = getMuiTheme({ 17 | palette: { 18 | primary1Color: '#258df2', 19 | accent1Color: '#40c741', 20 | } 21 | }); 22 | 23 | class App extends Component { 24 | 25 | render() { 26 | return ( 27 |
28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 | ); 36 | } 37 | } 38 | 39 | export default App; 40 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import headerStyleReducers from './style-switcher-reducers/headerStyleReducers.js'; 3 | import headerActiveStyleReducer from './style-switcher-reducers/headeractiveReducer.js'; 4 | import sidebarStyleReducers from './style-switcher-reducers/sidebarStyleReducers.js'; 5 | import sidebarActiveStyleReducer from './style-switcher-reducers/sidebarActiveReducer.js'; 6 | import headerBannerStyleReducers from './style-switcher-reducers/headerBannerStyleReducers.js'; 7 | import headerBannerActiveStyleReducer from './style-switcher-reducers/headerBannerActiveReducer.js'; 8 | 9 | const allReducers = combineReducers({ 10 | headerStyle: headerStyleReducers, 11 | headerActiveStyle: headerActiveStyleReducer, 12 | headerBannerStyle: headerBannerStyleReducers, 13 | headerBAnnerActiveStyle: headerBannerActiveStyleReducer, 14 | sidebarStyle: sidebarStyleReducers, 15 | sidebarActiveStyle: sidebarActiveStyleReducer, 16 | }); 17 | 18 | export default allReducers; 19 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/reducers/style-switcher-reducers/sidebarStyleReducers.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return [ 3 | { color: '#258df2', value: 'Primary' }, 4 | { color: '#B71C1C', value: 'Red' }, 5 | { color: '#D81B60', value: 'Pink' }, 6 | { color: '#4A148C', value: 'Purple' }, 7 | { color: '#3F51B5', value: 'Indigo' }, 8 | { color: '#1565C0', value: 'Blue' }, 9 | { color: '#00BCD4', value: 'Cyan' }, 10 | { color: '#009688', value: 'Teal' }, 11 | { color: '#2E7D32', value: 'Green' }, 12 | { color: '#827717', value: 'Lime' }, 13 | { color: '#E65100', value: 'Orange' }, 14 | { color: '#3E2723', value: 'Brown' }, 15 | { color: '#424242', value: 'Gray' }, 16 | { color: '#4527A0', value: 'Deep purple' }, 17 | { color: '#1b202a', value: 'Dark' }, 18 | { color: '#2E313A', value: 'Dark Deep' }, 19 | { color: '#1c2236', value: 'Dark purple' }, 20 | { color: '#202432', value: 'Dark' }, 21 | { color: '#212a42', value: 'Dark' }, 22 | { color: '#141a2d', value: 'Dark' }, 23 | { color: '#101524', value: 'Dark' }, 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | *.iml 4 | # sbt specific 5 | .cache 6 | .history 7 | */.lib/ 8 | */dist/* 9 | */target/ 10 | */project/target/* 11 | */lib_managed/ 12 | */src_managed/ 13 | */project/boot/ 14 | */project/plugins/project/ 15 | 16 | # Scala-IDE specific 17 | */.scala_dependencies 18 | */.worksheet 19 | akka-epl/.idea/ 20 | 21 | */.idea/ 22 | 23 | # ENSIME specific 24 | */.ensime_cache/ 25 | */.ensime 26 | */ensime.sbt 27 | 28 | 29 | # react 30 | .DS_STORE 31 | */node_modules 32 | *~ 33 | *.pyc 34 | .grunt 35 | */_SpecRunner.html 36 | */__benchmarks__ 37 | */build/ 38 | *//remote-repo/ 39 | */coverage/ 40 | */.module-cache 41 | */*.gem 42 | */docs/.bundle 43 | */docs/code 44 | */docs/_site 45 | */docs/.sass-cache 46 | */docs/js/* 47 | */docs/downloads/*.zip 48 | */docs/vendor/bundle 49 | */fixtures/dom/public/react-dom.js 50 | */fixtures/dom/public/react.js 51 | */test/the-files-to-test.generated.js 52 | */*.log* 53 | */chrome-user-data 54 | */*.sublime-project 55 | */*.sublime-workspace 56 | */.idea 57 | */*.iml 58 | v.vscode 59 | */*.swp 60 | */*.swo 61 | */*react*min*.js 62 | */!src/node_modules -------------------------------------------------------------------------------- /akka-epl/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sanjeev Ghimire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/ResultTable.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import play.api.libs.json.Json 4 | 5 | /** 6 | * Created by sanjeevghimire on 9/15/17. 7 | */ 8 | case class ResultTable( 9 | homeTeam: String, 10 | awayTeam: String, 11 | homeScore: Int, 12 | awayScore: Int, 13 | venue: String, 14 | dateTime: Long 15 | ) extends Serializable 16 | 17 | object ResultTable { 18 | 19 | implicit val jsonFormat = Json.format[ResultTable] 20 | 21 | def of(items: List[Any]): ResultTable ={ 22 | ResultTable( 23 | homeTeam = items(0).toString, 24 | awayTeam = items(1).toString, 25 | homeScore = items(2).toString.toInt, 26 | awayScore = items(3).toString.toInt, 27 | venue = items(4).toString, 28 | dateTime = items(5).toString.toLong 29 | ) 30 | } 31 | } 32 | 33 | case class ResultByDate( 34 | date: String, 35 | results: List[ResultTable] 36 | ) extends Serializable 37 | 38 | object ResultByDate { 39 | 40 | implicit val jsonFormat = Json.format[ResultByDate] 41 | 42 | 43 | 44 | } -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/CorsSupport.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.http.scaladsl.model.HttpMethods._ 4 | import akka.http.scaladsl.model.{StatusCodes, HttpResponse} 5 | import akka.http.scaladsl.model.headers._ 6 | import akka.http.scaladsl.server.Directives._ 7 | import akka.http.scaladsl.server.{Directive0, Route} 8 | import com.typesafe.config.ConfigFactory 9 | 10 | trait CorsSupport { 11 | lazy val allowedOrigin = { 12 | val config = ConfigFactory.load() 13 | val sAllowedOrigin = config.getString("cors.allowed-origin") 14 | HttpOrigin(sAllowedOrigin) 15 | } 16 | 17 | //this directive adds access control headers to normal responses 18 | private def addAccessControlHeaders: Directive0 = { 19 | respondWithHeaders( 20 | `Access-Control-Allow-Origin`(allowedOrigin), 21 | `Access-Control-Allow-Credentials`(true), 22 | `Access-Control-Allow-Headers`("Authorization", "Content-Type", "X-Requested-With") 23 | ) 24 | } 25 | 26 | //this handles preflight OPTIONS requests. 27 | private def preflightRequestHandler: Route = options { 28 | complete(HttpResponse(StatusCodes.OK).withHeaders(`Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE))) 29 | } 30 | 31 | def corsHandler(r: Route) = addAccessControlHeaders { 32 | preflightRequestHandler ~ r 33 | } 34 | } -------------------------------------------------------------------------------- /akka-epl/build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys._ 2 | 3 | name := "akka-epl" 4 | 5 | version := "1.0" 6 | 7 | scalaVersion := "2.12.2" 8 | 9 | // set the main class for packaging the main jar 10 | mainClass in assembly := Some("com.epl.akka.SoccerMainController") 11 | 12 | assemblyJarName in assembly := "akka-epl.jar" 13 | 14 | val akkaVersion = "2.5.4" 15 | val akkaHttpVersion = "10.0.10" 16 | 17 | libraryDependencies ++= Seq( 18 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 19 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 20 | "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, 21 | 22 | "com.typesafe.akka" % "akka-slf4j_2.12" % akkaVersion, 23 | "ch.qos.logback" % "logback-classic" % "1.2.3", 24 | 25 | "org.jsoup" % "jsoup" % "1.10.3", 26 | "com.ning" % "async-http-client" % "1.9.40", 27 | "com.typesafe.play" % "play-json_2.12" % "2.6.3", 28 | "com.cloudant" % "cloudant-client" % "2.9.0", 29 | 30 | "com.github.detro.ghostdriver" % "phantomjsdriver" % "1.0.1", 31 | "org.seleniumhq.selenium" % "selenium-java" % "3.5.3" 32 | 33 | ) 34 | 35 | 36 | //TEST dependencies 37 | libraryDependencies ++= Seq( 38 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 39 | "org.scalatest" %% "scalatest" % "3.0.1" % Test, 40 | "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test 41 | ) 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/TeamTable.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import play.api.libs.json.Json 4 | 5 | /** 6 | * Created by sanjeevghimire on 9/6/17. 7 | */ 8 | case class TeamTable(position: Int, 9 | teamName: String, 10 | teamShortName: String, 11 | played: Int, 12 | won: Int, 13 | drawn: Int, 14 | lost: Int, 15 | goalFor: Int, 16 | goalAgainst: Int, 17 | goalDifference: Int, 18 | points: Int 19 | ) extends Serializable 20 | 21 | 22 | object TeamTable { 23 | 24 | implicit val jsonFormat = Json.format[TeamTable] 25 | 26 | def of(teamName:String,item: List[Any]) ={ 27 | TeamTable( 28 | position = item(0).toString.toInt, 29 | teamName = item(1).toString, 30 | teamShortName = item(2).toString, 31 | played = item(3).toString.toInt, 32 | won = item(4).toString.toInt, 33 | drawn = item(5).toString.toInt, 34 | lost = item(6).toString.toInt, 35 | goalFor = item(7).toString.toInt, 36 | goalAgainst = item(8).toString.toInt, 37 | goalDifference = item(9).toString.toInt, 38 | points = item(10).toString.toInt 39 | ) 40 | } 41 | 42 | 43 | } -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/header/_header.scss: -------------------------------------------------------------------------------- 1 | .an-header { 2 | position: fixed; 3 | width: 100%; 4 | left: 0; 5 | top: 0; 6 | background: $white; 7 | padding: $padding-small $padding-medium; 8 | z-index: 1300; 9 | //border-bottom: 1px solid $color-primary; 10 | @include flex-space-between(); 11 | justify-content: flex-end; 12 | @include an-box-shadow(); 13 | height: 65px; 14 | 15 | @media (max-width: 520px) { 16 | z-index: 1400; 17 | } 18 | 19 | .brand { 20 | position: relative; 21 | color: $font-color-white; 22 | font-size: $font-size-h4; 23 | img { 24 | width: 130px; 25 | } 26 | } 27 | 28 | .header-right { 29 | @include flex-center(); 30 | > a { 31 | margin: 0 $margin-small 0 $margin-base; 32 | } 33 | button { 34 | padding: 0 !important; 35 | } 36 | } 37 | 38 | .header-left { 39 | position: relative; 40 | @include flex-center(); 41 | .brand { 42 | margin-right: $margin-base * 4 + 10; 43 | } 44 | 45 | } 46 | 47 | } 48 | 49 | .menubtn { 50 | padding: 0; 51 | background: transparent; 52 | cursor: pointer; 53 | outline: 0; 54 | button { 55 | } 56 | i { 57 | 58 | @include transition(); 59 | } 60 | 61 | &:hover { 62 | i { 63 | color: $color-primary; 64 | } 65 | } 66 | } 67 | 68 | 69 | 70 | .material-icons { 71 | color: #757575; 72 | } 73 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/components/panel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Filename: panel.js 3 | * Responsible all cmponent with headding 4 | * and child components 5 | */ 6 | 7 | import React from 'react'; 8 | import IconButton from 'material-ui/IconButton'; 9 | import IconMenu from 'material-ui/IconMenu'; 10 | import MenuItem from 'material-ui/MenuItem'; 11 | import classNames from 'classnames'; 12 | 13 | // Right icon option component 14 | const RightIconOption = (props) => ( 15 | more_vert 19 | } 20 | targetOrigin={{horizontal: 'right', vertical: 'top'}} 21 | anchorOrigin={{horizontal: 'right', vertical: 'top'}} 22 | > 23 | 25 | 26 | 27 | 28 | ); 29 | 30 | const Panel = (props) => { 31 | const panelClass = classNames ({ 32 | 'readmin-panel' : true, 33 | 'righticonmenu': props.righticon, 34 | 'body-text-center': props.center 35 | }); 36 | 37 | return ( 38 |
39 |
40 |
{props.title}
41 | { props.righticon ? : ''} 42 |
43 |
44 | {props.children} 45 |
46 |
47 | ); 48 | } 49 | 50 | export default Panel; 51 | -------------------------------------------------------------------------------- /soccer-epl-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.16.1", 7 | "classnames": "^2.2.5", 8 | "intl-locales-supported": "^1.0.0", 9 | "material-ui": "^0.18.3", 10 | "material-ui-persian-date-picker-utils": "^0.1.1", 11 | "moment": "^2.18.1", 12 | "prop-types": "15.5.10", 13 | "react": "^15.5.4", 14 | "react-custom-scrollbars": "^4.1.2", 15 | "react-dom": "^15.5.4", 16 | "react-redux": "^5.0.4", 17 | "react-router-dom": "^4.1.1", 18 | "react-star-rating": "^1.4.2", 19 | "react-star-rating-component": "^1.2.4", 20 | "react-swipeable-views": "^0.12.3", 21 | "react-tap-event-plugin": "^2.0.1", 22 | "recharts": "^1.0.0-alpha.1", 23 | "redux": "^3.6.0", 24 | "superagent": "^3.5.2" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^6.25.0", 28 | "babel-preset-es2015": "^6.24.1", 29 | "gulp": "^3.9.1", 30 | "gulp-livereload": "^3.8.1", 31 | "gulp-notify": "^3.0.0", 32 | "gulp-plumber": "^1.1.0", 33 | "gulp-sass": "^3.1.0", 34 | "gulp-watch": "^4.3.11", 35 | "node-sass": "^4.5.2", 36 | "react-scripts": "1.0.7" 37 | }, 38 | "scripts": { 39 | "start": "npm run styles & react-scripts start", 40 | "build": "react-scripts build", 41 | "test": "react-scripts test --env=jsdom", 42 | "eject": "react-scripts eject", 43 | "styles": "gulp" 44 | }, 45 | "false": {} 46 | } 47 | -------------------------------------------------------------------------------- /soccer-epl-ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | Soccer EPL 19 | 20 | 21 |
22 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/sidebar/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .readmin-sidebar { 2 | position: relative; 3 | 4 | .menudivider { 5 | padding: $padding-base 0; 6 | } 7 | 8 | .menu-drawer { 9 | > div { 10 | padding-top: $padding-base * 5; 11 | } 12 | 13 | &.has-bg { 14 | .nav-menu { 15 | a, div, svg, i { 16 | color: $font-color-white !important; 17 | opacity: .97; 18 | } 19 | i { 20 | opacity: .7; 21 | } 22 | .childmenu { 23 | .active { 24 | .childmenu & { 25 | background: rgba($font-color-base, .3) !important; 26 | } 27 | } 28 | } 29 | .active { 30 | background: rgba($font-color-base, .3) !important; 31 | } 32 | .menudivider hr { 33 | background-color: $font-color-white !important; 34 | opacity: .3; 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | .an-header { 42 | position: fixed; 43 | left: 0; 44 | top: 0; 45 | width: 256px; 46 | z-index: 14000; 47 | background: #258df2; 48 | @include transition(); 49 | &.menu-close { 50 | } 51 | 52 | @media (max-width: 520px) { 53 | width: auto; 54 | } 55 | 56 | .brand { 57 | @media (max-width: 520px) { 58 | font-size: $font-size-base; 59 | margin-right: $margin-large; 60 | } 61 | } 62 | 63 | } 64 | } 65 | 66 | 67 | .sidebar-initial-color { 68 | background-color: #1b202a; 69 | } 70 | -------------------------------------------------------------------------------- /akka-epl/src/test/scala/com/epl/akka/AkkaHTTPClientSpec.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | import akka.testkit.{ImplicitSender, TestKit, TestProbe} 5 | import com.epl.akka.AkkaHTTPClient.GET 6 | import org.scalatest.{BeforeAndAfterAll, FlatSpecLike, Matchers} 7 | 8 | import scala.concurrent.duration._ 9 | import scala.util.{Failure, Success} 10 | 11 | 12 | /** 13 | * Created by sanjeevghimire on 9/19/17. 14 | */ 15 | class AkkaHTTPClientSpec(_system: ActorSystem) 16 | extends TestKit(_system) 17 | with ImplicitSender 18 | with Matchers 19 | with FlatSpecLike 20 | with BeforeAndAfterAll { 21 | 22 | def this() = this(ActorSystem("AkkaHTTPClientSpec")) 23 | 24 | override def afterAll: Unit = { 25 | shutdown(system) 26 | } 27 | 28 | 29 | // "A AkkaHttpClient Actor" should "give HTML response when instructed to" in { 30 | // val testProbe = TestProbe() 31 | // 32 | // val url = "http://akka.io" 33 | // val akkaHTTPClientActor = system.actorOf(AkkaHTTPClient.props(),"AkkaHttpClient") 34 | // akkaHTTPClientActor ! GET(url) 35 | // //expectMsg(20.seconds, "OK response") 36 | // 37 | // } 38 | 39 | "A AkkaHttpClient Actor" should "give HTML response when instructed to" in { 40 | implicit val executionContext = system.dispatcher 41 | 42 | val url = "http://akka.io" 43 | 44 | // WebHttpClient.getUsingAkkaHttp(url) onComplete { 45 | // case Success(body) => 46 | // println(body) 47 | // case Failure(err) => 48 | // println("http request error:" + err.getMessage) 49 | // 50 | // } 51 | } 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/WebCrawler.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.actor.{Actor, ActorLogging, Props} 4 | import com.epl.akka.CloudantReaderWriter.{ExpireCurrentDocument, SaveToCloudantDatabase} 5 | import com.epl.akka.HTMLParser.{CrawlAndGetValidLinks, CrawlAndPrepareJson} 6 | import com.epl.akka.URLValidator.ValidateUrl 7 | import com.epl.akka.WebCrawler._ 8 | 9 | 10 | object WebCrawler { 11 | case class CrawlRequest(url: String, isRootUrl: Boolean) {} 12 | case class CrawlResponse(links: Set[String]) {} 13 | case class Crawl(url: String,isRootUrl: Boolean){} 14 | case class SaveJsonToCloudant(jsonString: String){} 15 | case class GetJsonOutput(documentType: DocumentType.Documenttype){} 16 | } 17 | 18 | 19 | /** 20 | * Created by sanjeevghimire on 8/30/17. 21 | */ 22 | class WebCrawler extends Actor with ActorLogging{ 23 | 24 | val urlValidator = context.actorOf(Props[URLValidator](new URLValidator())) 25 | val htmlParser = context.actorOf(Props[HTMLParser](new HTMLParser())) 26 | val cloudantWriter = context.actorOf(Props[CloudantReaderWriter](new CloudantReaderWriter())) 27 | 28 | override def receive: Receive = { 29 | case CrawlRequest(url, isRootUrl) => 30 | // if (isRootUrl) { 31 | // cloudantWriter ! ExpireCurrentDocument() 32 | // } 33 | urlValidator ! ValidateUrl(url,isRootUrl) 34 | 35 | case Crawl(url, isRootUrl) => 36 | if(isRootUrl) { 37 | htmlParser ! CrawlAndGetValidLinks(url) 38 | }else{ 39 | htmlParser ! CrawlAndPrepareJson(url) 40 | } 41 | 42 | case SaveJsonToCloudant(jsonString) => 43 | cloudantWriter ! SaveToCloudantDatabase(jsonString) 44 | 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/routers/routesList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {List, ListItem} from 'material-ui/List'; 4 | import {NavLink} from 'react-router-dom' 5 | 6 | class RoutesList extends Component { 7 | render() { 8 | // Parent list style 9 | const style = { 10 | padding: '16px 16px 16px 55px', 11 | fontSize: '14px', 12 | } 13 | 14 | return ( 15 | 16 | dashboard} 20 | containerElement={} 21 | /> 22 | keyboard_arrow_right} 26 | containerElement={} 27 | /> 28 | keyboard_arrow_right} 32 | containerElement={} 33 | /> 34 | keyboard_arrow_right} 38 | containerElement={} 39 | /> 40 | 41 | ); 42 | } 43 | } 44 | 45 | function mapStateToProps(state) { 46 | return { 47 | apiData: state.apiData 48 | } 49 | } 50 | 51 | export default connect(mapStateToProps)(RoutesList); 52 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/AkkaHTTPClient.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props} 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.model.{HttpMethods, HttpRequest, HttpResponse, StatusCodes} 6 | import akka.stream.{ActorMaterializer, ActorMaterializerSettings} 7 | import akka.util.ByteString 8 | import com.epl.akka.AkkaHTTPClient.GET 9 | 10 | /** 11 | * Created by sanjeevghimire on 9/19/17. 12 | */ 13 | class AkkaHTTPClient() extends Actor 14 | with ActorLogging { 15 | 16 | import akka.pattern.pipe 17 | import context.dispatcher 18 | 19 | final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system)) 20 | 21 | val http = Http(context.system) 22 | 23 | //var originalSender: Option[ActorRef] = None // <-- added 24 | 25 | override def receive: Receive = { 26 | case GET(uri: String) => 27 | //val s = sender // <-- added 28 | //originalSender = Option(s) // <-- added 29 | log.info("getting the url") 30 | http 31 | .singleRequest(HttpRequest(HttpMethods.GET,uri = uri)) 32 | .pipeTo(self) 33 | 34 | case HttpResponse(StatusCodes.OK, headers, entity, _) => 35 | entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body => 36 | println("Got response, body: " + body.utf8String) 37 | } 38 | //originalSender.foreach(_ ! "OK response") // <-- added 39 | 40 | case resp @ HttpResponse(code, _, _, _) => 41 | log.info("Request failed, response code: " + code) 42 | resp.discardEntityBytes() 43 | 44 | 45 | } 46 | 47 | } 48 | 49 | 50 | object AkkaHTTPClient { 51 | 52 | def props() = 53 | Props[AkkaHTTPClient] 54 | 55 | final case class GET(uri: String){} 56 | 57 | } 58 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/pages/results.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Table, 4 | TableBody, 5 | TableHeader, 6 | TableHeaderColumn, 7 | TableRow, 8 | TableRowColumn, 9 | } from 'material-ui/Table'; 10 | 11 | class Results extends React.Component { 12 | constructor() { 13 | super(); 14 | this.state={resultsByDate: [], 15 | fixedHeader: true, 16 | stripedRows: true, 17 | showRowHover: true, 18 | showCheckboxes: false, 19 | height: '100%'}; 20 | } 21 | 22 | componentDidMount(){ 23 | fetch(`http://localhost:9000/results`) 24 | .then(result=>result.json()) 25 | .then(items=>this.setState({resultsByDate: items.rows[0].doc.results})) 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 |

Results

32 | 33 | {this.state.resultsByDate.length ? this.state.resultsByDate.map(resultByDate => 34 | 35 | 39 | 42 | 43 | 44 | {resultByDate.date} 45 | 46 | 47 | 48 | 53 | 54 | {resultByDate.results.map(result => 55 | 56 | 57 | {result.homeTeam} 58 | {result.homeScore} 59 | {result.awayScore} 60 | {result.awayTeam} 61 | 62 | )} 63 | 64 |
65 | 66 | ) 67 | : 'Loading...'} 68 | 69 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | ); 77 | } 78 | } 79 | 80 | export default Results; -------------------------------------------------------------------------------- /soccer-epl-ui/src/pages/fixtures.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Table, 4 | TableBody, 5 | TableHeader, 6 | TableHeaderColumn, 7 | TableRow, 8 | TableRowColumn, 9 | } from 'material-ui/Table'; 10 | 11 | class Fixtures extends React.Component { 12 | constructor() { 13 | super(); 14 | this.state={fixtures:'', 15 | fixedHeader: true, 16 | stripedRows: true, 17 | showRowHover: true, 18 | showCheckboxes: false, 19 | height: '100%'}; 20 | } 21 | 22 | componentDidMount(){ 23 | fetch(`http://localhost:9000/fixtures`) 24 | .then(result=>result.json()) 25 | .then(items=>this.setState({fixtures: items.rows[0].doc.games})) 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 |

Fixtures

32 | { this.state.fixtures ? Object.keys(this.state.fixtures).map((date) => 33 | 34 | 38 | 41 | 42 | 43 | {date} 44 | 45 | 46 | 47 | 52 | 53 | { 54 | this.state.fixtures[date].map(game => 55 | 56 | {game.teams[0].team.name} 57 | {game.kickOffTime} 58 | {game.teams[1].team.name} 59 | {game.ground.name} 60 | 61 | )} 62 | 63 |
64 | ) 65 | : 'loading...' 66 | } 67 |
68 | ); 69 | } 70 | } 71 | 72 | export default Fixtures; -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/SoccerMainController.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.server.Directives.{complete, get, path} 6 | import akka.http.scaladsl.server.Route 7 | import akka.pattern.ask 8 | import akka.stream.ActorMaterializer 9 | import com.epl.akka.CloudantReaderWriter.GetDocument 10 | import akka.util.Timeout 11 | import scala.concurrent.Future 12 | import scala.io.StdIn 13 | import scala.concurrent.duration._ 14 | import akka.http.scaladsl.server.Directives._ 15 | 16 | 17 | object SoccerMainController extends App with CorsSupport{ 18 | 19 | case class Document(json: String) 20 | 21 | 22 | implicit val system = ActorSystem("SoccerSystem") 23 | implicit val materializer = ActorMaterializer() 24 | // needed for the future flatMap/onComplete in the end 25 | implicit val executionContext = system.dispatcher 26 | 27 | val config = system.settings.config 28 | 29 | val cloudantReader = 30 | system.actorOf(Props[CloudantReaderWriter], "cloudantWriter") 31 | 32 | implicit val timeout: Timeout = 10.seconds 33 | 34 | val route: Route = 35 | path("fixtures") { 36 | get { 37 | val fixtureJson:Future[String] = (cloudantReader ? GetDocument(DocumentType.Fixtures)).mapTo[String] 38 | complete( 39 | fixtureJson 40 | ) 41 | } 42 | } ~ 43 | path("teamtable") { 44 | get { 45 | val teamTableJson:Future[String] = (cloudantReader ? GetDocument(DocumentType.TeamTable)).mapTo[String] 46 | complete( 47 | teamTableJson 48 | ) 49 | } 50 | } ~ 51 | path("results") { 52 | get { 53 | val resultsJson:Future[String] = (cloudantReader ? GetDocument(DocumentType.Results)).mapTo[String] 54 | complete( 55 | resultsJson 56 | ) 57 | } 58 | } 59 | 60 | val bindingFuture = Http().bindAndHandle(corsHandler(route), 61 | config.getString("http.host"), 62 | config.getInt("http.port")) 63 | 64 | println( 65 | s"Server online at ${config.getString("http.host")}: ${config.getString("http.port")}\nPress RETURN to stop...") 66 | StdIn.readLine() // let it run until user presses return 67 | bindingFuture 68 | .flatMap(_.unbind()) // trigger unbinding from the port 69 | .onComplete(_ => system.terminate()) // and shutdown when done 70 | 71 | } 72 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/DBOperation.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | 4 | import java.util.Properties 5 | 6 | import akka.actor.{Actor, ActorLogging, Status} 7 | import com.cloudant.client.api.{ClientBuilder, CloudantClient} 8 | import com.epl.akka.DBOperation.{SaveToCloudantDatabase, SaveToCloudantDatabaseUsingRESTAPI} 9 | import play.api.libs.json.{JsValue, Json} 10 | 11 | import scala.util.{Failure, Success} 12 | 13 | 14 | object DBOperation{ 15 | case class SaveToCloudantDatabase(jsValue: JsValue){} 16 | 17 | case class SaveToCloudantDatabaseUsingRESTAPI(jsonString: String){} 18 | } 19 | 20 | 21 | 22 | /** 23 | * Created by sanjeevghimire on 9/5/17. 24 | */ 25 | class DBOperation(jsonString: String) extends Actor with ActorLogging{ 26 | 27 | implicit val ec = context.dispatcher 28 | 29 | val prop = new Properties() 30 | 31 | val dbName = "akka-epl" 32 | self ! SaveToCloudantDatabaseUsingRESTAPI(jsonString) 33 | 34 | override def receive: Receive = { 35 | 36 | case SaveToCloudantDatabase(jsValue: JsValue) => 37 | getCloudantClient().database(dbName, false) 38 | .save(jsValue) 39 | println("Saved: "+ jsValue) 40 | context.stop(self) 41 | 42 | case SaveToCloudantDatabaseUsingRESTAPI(jsonString: String) => 43 | loadProperties() 44 | WebHttpClient.post(prop.getProperty("post_url"),jsonString,prop.getProperty("username"),prop.getProperty("password")) onComplete { 45 | case Success(body) => println("Successfully Saved:: "+ body) 46 | case Failure(err) => 47 | println(err) 48 | context.stop(self) 49 | } 50 | context.stop(self) 51 | 52 | 53 | } 54 | 55 | /** 56 | * Get cloudant connection using connection parameters from config.properties 57 | */ 58 | def getCloudantClient(): CloudantClient = { 59 | loadProperties() 60 | 61 | ClientBuilder.account(prop.getProperty("host")) 62 | .username(prop.getProperty("username")) 63 | .password(prop.getProperty("password")) 64 | .build() 65 | } 66 | 67 | 68 | def loadProperties() = { 69 | val (host, port, url, username, password, dbname, key, passcode) = 70 | try { 71 | prop.load(getClass().getResourceAsStream("/config.properties")) 72 | ( 73 | prop.getProperty("host"), 74 | new Integer(prop.getProperty("port")), 75 | prop.getProperty("url"), 76 | prop.getProperty("username"), 77 | prop.getProperty("password"), 78 | prop.getProperty("dbname"), 79 | prop.getProperty("key"), 80 | prop.getProperty("passcode")) 81 | } catch { 82 | case e: Exception => 83 | e.printStackTrace() 84 | sys.exit(1) 85 | } 86 | 87 | } 88 | 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/CloudantReaderWriter.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import akka.actor.{Actor, ActorLogging} 4 | import com.epl.akka.CloudantReaderWriter.{ExpireCurrentDocument, GetDocument, SaveToCloudantDatabase} 5 | import com.epl.akka.SoccerMainController.Document 6 | import play.api.libs.json.{JsArray, JsObject, JsValue, Json} 7 | import akka.pattern.pipe 8 | 9 | import scala.util.{Failure, Success} 10 | 11 | 12 | object CloudantReaderWriter { 13 | final case class SaveToCloudantDatabase(jsonString: String) 14 | final case class GetDocument(documentType: DocumentType.Documenttype) 15 | final case class ExpireCurrentDocument() 16 | } 17 | 18 | /** 19 | * Created by sanjeevghimire on 9/19/17. 20 | */ 21 | class CloudantReaderWriter extends Actor with ActorLogging{ 22 | 23 | implicit val ec = context.dispatcher 24 | 25 | private val config = context.system.settings.config 26 | 27 | override def receive: Receive = { 28 | case SaveToCloudantDatabase(jsonString: String) => 29 | WebHttpClient.post(config.getString("cloudant.post_url"),jsonString,config.getString("cloudant.username"),config.getString("cloudant.password")) onComplete { 30 | case Success(body) => 31 | log.info("Successfully Saved:: "+ body) 32 | case Failure(err) => 33 | log.error(err,"Error while saving to Cloudant") 34 | } 35 | 36 | case GetDocument(documentType) => 37 | val url: String = getUrl(documentType) 38 | val res = WebHttpClient.getWithHeader(url,config.getString("cloudant.username"),config.getString("cloudant.password")) 39 | res pipeTo sender() 40 | 41 | 42 | case ExpireCurrentDocument() => 43 | val url: String = config.getString("cloudant.get_unexpireddoc_url") 44 | WebHttpClient.getWithHeader(url,config.getString("cloudant.username"),config.getString("cloudant.password")) onComplete { 45 | case Success(unexpiredDocumentJson) => 46 | val jsValue: JsValue = Json.parse(unexpiredDocumentJson) 47 | (jsValue \ "docs").as[Seq[JsObject]].foreach( doc => { 48 | val updateJsonObject = doc + ("expired" -> Json.toJson("YES")) 49 | val updateUrl = config.getString("cloudant.post_url") + "/" + (updateJsonObject \ "_id") 50 | WebHttpClient.put(updateUrl,Json.stringify(updateJsonObject),config.getString("cloudant.username"),config.getString("cloudant.password")) onComplete { 51 | case Success(body) => 52 | log.info("Successfully deleted:: "+ body) 53 | case Failure(err) => 54 | log.error(err,"Error while saving to Cloudant") 55 | } 56 | }) 57 | case Failure(err) => 58 | log.error(err,"Error while retrieving from Cloudant DB") 59 | } 60 | 61 | } 62 | 63 | def getUrl(documentType: DocumentType.Documenttype): String ={ 64 | var url: String = null 65 | if(documentType.equals(DocumentType.TeamTable)){ 66 | url = config.getString("cloudant.get_tables_url") 67 | }else if(documentType.equals(DocumentType.Results)){ 68 | url = config.getString("cloudant.get_results_url") 69 | }else{ 70 | url = config.getString("cloudant.get_fixtures_url") 71 | } 72 | url 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/base/_typography.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Headings 3 | // 4 | 5 | h1, h2, h3, h4, h5, h6, 6 | .h1, .h2, .h3, .h4, .h5, .h6 { 7 | margin-bottom: $headings-margin-bottom; 8 | font-family: $headings-font-family; 9 | font-weight: $font-weight-medium; 10 | line-height: $headings-line-height; 11 | color: $font-color-base; 12 | } 13 | 14 | h1, .h1 { font-size: $font-size-h1; } 15 | h2, .h2 { font-size: $font-size-h2; } 16 | h3, .h3 { font-size: $font-size-h3; } 17 | h4, .h4 { font-size: $font-size-h4; } 18 | h5, .h5 { font-size: $font-size-h5; } 19 | h6, .h6 { font-size: $font-size-h6; } 20 | 21 | .lead { 22 | font-size: $lead-font-size; 23 | font-weight: $lead-font-weight; 24 | } 25 | 26 | h4, h5, h6 { 27 | font-weight: $font-weight-base; 28 | } 29 | 30 | // Type display classes 31 | .display-1 { 32 | font-size: $display1-size; 33 | font-weight: $display1-weight; 34 | line-height: $display-line-height; 35 | } 36 | .display-2 { 37 | font-size: $display2-size; 38 | font-weight: $display2-weight; 39 | line-height: $display-line-height; 40 | } 41 | .display-3 { 42 | font-size: $display3-size; 43 | font-weight: $display3-weight; 44 | line-height: $display-line-height; 45 | } 46 | .display-4 { 47 | font-size: $display4-size; 48 | font-weight: $display4-weight; 49 | line-height: $display-line-height; 50 | } 51 | 52 | 53 | // 54 | // Horizontal rules 55 | // 56 | 57 | hr { 58 | margin-top: $spacer-y; 59 | margin-bottom: $spacer-y; 60 | border: 0; 61 | border-top: $hr-border-width solid $hr-border-color; 62 | } 63 | 64 | 65 | // 66 | // Emphasis 67 | // 68 | 69 | small, 70 | .small { 71 | font-size: $small-font-size; 72 | font-weight: $font-weight-normal; 73 | } 74 | 75 | mark, 76 | .mark { 77 | padding: $mark-padding; 78 | background-color: $mark-bg; 79 | } 80 | 81 | 82 | // 83 | // Lists 84 | // 85 | 86 | .list-unstyled { 87 | @include list-unstyled; 88 | } 89 | 90 | // Inline turns list items into inline-block 91 | .list-inline { 92 | @include list-unstyled; 93 | } 94 | .list-inline-item { 95 | display: inline-block; 96 | 97 | &:not(:last-child) { 98 | margin-right: $list-inline-padding; 99 | } 100 | } 101 | 102 | 103 | // 104 | // Misc 105 | // 106 | 107 | // Builds on `abbr` 108 | .initialism { 109 | font-size: 90%; 110 | text-transform: uppercase; 111 | } 112 | 113 | // Blockquotes 114 | .blockquote { 115 | padding: ($spacer / 2) $spacer; 116 | margin-bottom: $spacer; 117 | font-size: $blockquote-font-size; 118 | border-left: $blockquote-border-width solid $blockquote-border-color; 119 | } 120 | 121 | .blockquote-footer { 122 | display: block; 123 | font-size: 80%; // back to default font-size 124 | color: $blockquote-small-color; 125 | 126 | &::before { 127 | content: "\2014 \00A0"; // em dash, nbsp 128 | } 129 | } 130 | 131 | // Opposite alignment of blockquote 132 | .blockquote-reverse { 133 | padding-right: $spacer; 134 | padding-left: 0; 135 | text-align: right; 136 | border-right: $blockquote-border-width solid $blockquote-border-color; 137 | border-left: 0; 138 | } 139 | 140 | .blockquote-reverse .blockquote-footer { 141 | &::before { 142 | content: ""; 143 | } 144 | &::after { 145 | content: "\00A0 \2014"; // nbsp, em dash 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/pages/teamstanding.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Table, 4 | TableBody, 5 | TableHeader, 6 | TableHeaderColumn, 7 | TableRow, 8 | TableRowColumn, 9 | } from 'material-ui/Table'; 10 | 11 | class TeamStanding extends React.Component { 12 | constructor() { 13 | super(); 14 | this.state={teamStandings:[], 15 | fixedHeader: true, 16 | stripedRows: true, 17 | showRowHover: true, 18 | showCheckboxes: false, 19 | height: '100%'}; 20 | } 21 | 22 | componentDidMount(){ 23 | fetch(`http://localhost:9000/teamtable`) 24 | .then(result=>result.json()) 25 | .then(items=>this.setState({teamStandings: items.rows[0].doc.teams})) 26 | } 27 | 28 | render() { 29 | return( 30 |
31 |

EPL Team Standings

32 | 36 | 39 | 40 | Position 41 | Name 42 | Short Name 43 | Played 44 | Won 45 | Draw 46 | Lost 47 | Goal For 48 | Goal Difference 49 | Goal Against 50 | Points 51 | 52 | 53 | 54 | 59 | 60 | {this.state.teamStandings.length ? this.state.teamStandings.map(item => 61 | 62 | {item.position} 63 | {item.teamName} 64 | {item.teamShortName} 65 | {item.played} 66 | {item.won} 67 | {item.drawn} 68 | {item.lost} 69 | {item.goalFor} 70 | {item.goalAgainst} 71 | {item.goalDifference} 72 | {item.points} 73 | 74 | ) 75 | 76 | :
Loading...
} 77 |
78 |
79 |
80 | ); 81 | 82 | } 83 | } 84 | export default TeamStanding; 85 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/routers/routers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * filename: routers.js 3 | * mainly responsivle for all routes component 4 | * change and sidebar routlist menu item 5 | * */ 6 | 7 | import React, {Component} from 'react'; 8 | import {connect} from 'react-redux'; 9 | import Drawer from 'material-ui/Drawer'; 10 | import classNames from 'classnames'; 11 | import {BrowserRouter as Router} from 'react-router-dom' 12 | import RoutesList from './routesList.js'; 13 | import RoutesComponent from './routesComponent.js'; 14 | import { Scrollbars } from 'react-custom-scrollbars'; 15 | import Footer from '../components/footer.js'; 16 | 17 | import Header from '../containers/header.js'; 18 | 19 | 20 | class SidebarMenuRouters extends Component { 21 | constructor () { 22 | super(); 23 | this.state = { menuOpen: true }; 24 | this.menuCollapseWithResize = this.menuCollapseWithResize.bind(this); 25 | this.toggleMenu = this.toggleMenu.bind(this); 26 | } 27 | 28 | // menu collapse when on mobile function 29 | menuCollapseWithResize (){ 30 | if (window.innerWidth < 991) { 31 | this.setState({menuOpen: false}); 32 | } 33 | if (window.innerWidth > 991) { 34 | this.setState({menuOpen: true}); 35 | } 36 | } 37 | 38 | // Sidebar collapse when tablet 39 | componentDidMount () { 40 | window.addEventListener('resize', this.menuCollapseWithResize); 41 | 42 | if (window.innerWidth < 991) { 43 | this.setState({menuOpen: false}); 44 | } 45 | } 46 | 47 | // Sidebar collapse when tablet 48 | componentWillUnmount() { 49 | window.removeEventListener('resize', this.menuCollapseWithResize); 50 | } 51 | 52 | // Sidebar toggle 53 | toggleMenu() { 54 | this.setState(prevState => ({ 55 | menuOpen: !prevState.menuOpen 56 | })); 57 | } 58 | 59 | render() { 60 | 61 | const headerStyle = { 62 | background: this.props.colorHeader.color 63 | } 64 | 65 | const containerStyle = { 66 | background: this.props.colorSidebar.color 67 | } 68 | 69 | // Page content class change based on menu toggle 70 | const pageContent = classNames ({ 71 | 'readmin-page-content' : true, 72 | 'menu-open': this.state.menuOpen 73 | }); 74 | 75 | // Sidebar class based on bg color 76 | const sidebarClass = classNames ({ 77 | 'menu-drawer' : true, 78 | 'has-bg': true, 79 | }); 80 | 81 | // header left part with logo and toggle button 82 | const HeaderLogoWithMenu = () => ( 83 |
84 |
85 | Soccer EPL @ IBM 86 |
87 |
88 | ); 89 | 90 | return ( 91 | 92 |
93 | { 94 | /* Added header component here istead of app component */ 95 | } 96 |
97 |
98 | 99 | 104 | 105 | 106 | 107 | 108 |
109 |
110 | 111 |
112 |
113 |
114 |
115 | ); 116 | } 117 | } 118 | 119 | function mapStateToProps(state) { 120 | return { 121 | colorHeader: state.headerActiveStyle, 122 | colorSidebar: state.sidebarActiveStyle 123 | } 124 | } 125 | 126 | export default connect(mapStateToProps)(SidebarMenuRouters); 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Soccer Dashboard with Akka and ReactJS 2 | 3 | [![Build Status](https://travis-ci.org/IBM/akka-react-cloudant.svg?branch=master)](https://travis-ci.org/IBM/akka-react-cloudant) 4 | 5 | In this code pattern, we will create a Soccer Dashboard for English Premier League. The dashboard is created by web crawling the https://www.premierleague.com/ website. The back end utilizes Akka Actor, the front end is done with ReactJS and the data storage is using IBM Cloudant. And the code is deployed on Cloud Foundry. 6 | 7 | # Flow 8 | ![Architecture](./assets/soccer%20epl%20architecture.jpg?raw=true "Architecture") 9 | 10 | 1. Create actors for Akka 11 | 2. Expose Akka rest APIs 12 | 3. Crawler actor will start crawling and store information into DB 13 | 4. Deploy the app into IBM Cloud Foundry 14 | 5. Ready for user to interact with the app 15 | 16 | # Included components 17 | 1. Akka: A reactive stream toolkit 18 | 2. ReactJS: A JavaScript library for building user interfaces 19 | 3. Cloudant DB: A highly scalable and performant JSON database service 20 | 4. Cloud Foundry: An open source, multi cloud application platform as a service project 21 | 22 | 23 | # Steps 24 | We will be deploying to Cloud Foundry for exposing the APIs from Akka that connects to the IBM Cloudant Database. 25 | 26 | # Deploying Locally 27 | 1. clone the project using `git clone git@github.com:sanjeevghimire/akka-react-cloudant.git` 28 | 2. Login to IBM Bluemix account, create a IBM cloudant database and save the credentials and add those credentials in `application.conf` in `akka-epl/src/main/resources/application.conf` 29 | 2. cd akka-epl 30 | 3. Run `sbt` followed by commands `compile` and `run` . Make sure you choose `CrawlingApp.scala` as running class. This will crawl https://www.premierleague.com/ website and save data as JSON to IBM cloudant database 31 | 4. In another command line window or tab, run `sbt` followed by commands `compile` and `run`. Make sure you choose `SoccerMainController.scala` as running class 32 | 5. In another command line tab, `cd soccer-epl-ui` and run `npm start` 33 | 6. you can now access the Dashboard in url: `http://locahost:3000` 34 | 35 | # Deploying to Cloud Foundry 36 | In order to deploy to Cloud Foundry, make sure you have an IBM Bluemix account. And you have to install the following to get started. 37 | 38 | 1. Install [Cloud Foundry CLI](https://github.com/cloudfoundry/cli) 39 | 2. Login to CF using: `cf login --sso` and use one-time password from a given URL to login 40 | 3. Create a fat jar using: `sbt assembly` after going to directory: `/akka-epl` 41 | 4. You need to have `manifest.yml` file as in the code repository to push it to the cloud foundry app 42 | 5. You can push the app using command: `cf push` 43 | 6. For Debugging you can see the logs to make sure your app is successfully pushed or not using `cf logs akka-react-cloudant --recent` 44 | 7. you can also ssh to the application machine using command: `cf enable-ssh ` and  `cf ssh ` 45 | 46 | # Sample Output 47 | ![Dashboard](./assets/dashboard.png?raw=true "Dashboard") 48 | ![Team Standing](./assets/teamstanding.png?raw=true "Team Standing") 49 | ![Results](./assets/results.png?raw=true "Results") 50 | ![Fixtures](./assets/fixtures.png?raw=true "Fixtures") 51 | 52 | # Links 53 | 54 | 61 | 62 | 63 | # License 64 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 65 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/base/_buttons.scss: -------------------------------------------------------------------------------- 1 | 2 | // scss-lint:disable QualifyingElement 3 | 4 | // 5 | // Base styles 6 | // 7 | 8 | .btn { 9 | display: inline-block; 10 | font-weight: $btn-font-weight; 11 | line-height: $btn-line-height; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: middle; 15 | user-select: none; 16 | border: $input-btn-border-width solid transparent; 17 | @include button-size($btn-padding-y, $btn-padding-x, $font-size-base, $btn-border-radius); 18 | @include transition($btn-transition); 19 | 20 | // Share hover and focus styles 21 | @include hover-focus { 22 | text-decoration: none; 23 | } 24 | &:focus, 25 | &.focus { 26 | outline: 0; 27 | box-shadow: $btn-focus-box-shadow; 28 | } 29 | 30 | // Disabled comes first so active can properly restyle 31 | &.disabled, 32 | &:disabled { 33 | cursor: $cursor-disabled; 34 | opacity: .65; 35 | @include box-shadow(none); 36 | } 37 | 38 | &:active, 39 | &.active { 40 | background-image: none; 41 | @include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow); 42 | } 43 | } 44 | 45 | // Future-proof disabling of clicks on `` elements 46 | a.btn.disabled, 47 | fieldset[disabled] a.btn { 48 | pointer-events: none; 49 | } 50 | 51 | 52 | 53 | 54 | 55 | .an-circle-icon-btn { 56 | position: relative; 57 | width: $header-icon-width; 58 | height: $header-icon-width; 59 | background: rgba($color-body-bg, .7); 60 | color: $font-color-base; 61 | border: 0; 62 | outline: none; 63 | cursor: pointer; 64 | @include border-radius(50%); 65 | @include flex-center(); 66 | @include transition(); 67 | @include an-box-shadow(); 68 | 69 | &.notification { 70 | background: rgba($color-warning, .3); 71 | color: $color-warning; 72 | &:hover, 73 | &:focus{ 74 | background: rgba($color-warning, .4); 75 | color: $color-warning; 76 | outline: 0; 77 | } 78 | } 79 | 80 | &.primary { 81 | background: rgba($color-primary, .3); 82 | color: $color-primary; 83 | &:hover, 84 | &:focus{ 85 | background: rgba($color-primary, .4); 86 | color: $color-primary; 87 | } 88 | } 89 | } 90 | 91 | 92 | .an-btn-primary { 93 | background-color: $color-primary !important; 94 | button { 95 | background-color: $color-primary !important; 96 | } 97 | } 98 | 99 | 100 | // 101 | // Alternate buttons 102 | // 103 | 104 | .btn-primary { 105 | @include button-variant($btn-primary-color, $btn-primary-bg, $btn-primary-border); 106 | } 107 | .btn-secondary { 108 | @include button-variant($btn-secondary-color, $btn-secondary-bg, $btn-secondary-border); 109 | } 110 | .btn-info { 111 | @include button-variant($btn-info-color, $btn-info-bg, $btn-info-border); 112 | } 113 | .btn-success { 114 | @include button-variant($btn-success-color, $btn-success-bg, $btn-success-border); 115 | } 116 | .btn-warning { 117 | @include button-variant($btn-warning-color, $btn-warning-bg, $btn-warning-border); 118 | } 119 | .btn-danger { 120 | @include button-variant($btn-danger-color, $btn-danger-bg, $btn-danger-border); 121 | } 122 | 123 | // Remove all backgrounds 124 | .btn-outline-primary { 125 | @include button-outline-variant($btn-primary-bg); 126 | } 127 | .btn-outline-secondary { 128 | @include button-outline-variant($btn-secondary-border); 129 | } 130 | .btn-outline-info { 131 | @include button-outline-variant($btn-info-bg); 132 | } 133 | .btn-outline-success { 134 | @include button-outline-variant($btn-success-bg); 135 | } 136 | .btn-outline-warning { 137 | @include button-outline-variant($btn-warning-bg); 138 | } 139 | .btn-outline-danger { 140 | @include button-outline-variant($btn-danger-bg); 141 | } 142 | 143 | 144 | // 145 | // Link buttons 146 | // 147 | 148 | // Make a button look and behave like a link 149 | .btn-link { 150 | font-weight: $font-weight-normal; 151 | color: $link-color; 152 | border-radius: 0; 153 | 154 | &, 155 | &:active, 156 | &.active, 157 | &:disabled { 158 | background-color: transparent; 159 | @include box-shadow(none); 160 | } 161 | &, 162 | &:focus, 163 | &:active { 164 | border-color: transparent; 165 | } 166 | @include hover { 167 | border-color: transparent; 168 | } 169 | @include hover-focus { 170 | color: $link-hover-color; 171 | text-decoration: $link-hover-decoration; 172 | background-color: transparent; 173 | } 174 | &:disabled { 175 | color: $btn-link-disabled-color; 176 | 177 | @include hover-focus { 178 | text-decoration: none; 179 | } 180 | } 181 | } 182 | 183 | 184 | // 185 | // Button Sizes 186 | // 187 | 188 | .btn-lg { 189 | // line-height: ensure even-numbered height of button next to large input 190 | @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $font-size-lg, $btn-border-radius-lg); 191 | } 192 | .btn-sm { 193 | // line-height: ensure proper height of button next to small input 194 | @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $font-size-sm, $btn-border-radius-sm); 195 | } 196 | 197 | 198 | // 199 | // Block button 200 | // 201 | 202 | .btn-block { 203 | display: block; 204 | width: 100%; 205 | } 206 | 207 | // Vertically space out multiple block buttons 208 | .btn-block + .btn-block { 209 | margin-top: $btn-block-spacing-y; 210 | } 211 | 212 | // Specificity overrides 213 | input[type="submit"], 214 | input[type="reset"], 215 | input[type="button"] { 216 | &.btn-block { 217 | width: 100%; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/WebHttpClient.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | 4 | import java.util.concurrent.Executors 5 | 6 | import akka.actor.ActorSystem 7 | import akka.http.scaladsl.Http 8 | 9 | import scala.util.{Failure, Success} 10 | import akka.http.scaladsl.model.{HttpRequest, HttpResponse} 11 | import akka.stream.{ActorMaterializer, Materializer} 12 | import akka.stream.scaladsl.Sink 13 | import akka.util.ByteString 14 | import com.ning.http.client._ 15 | import org.apache.commons.codec.binary.Base64 16 | import org.openqa.selenium.WebDriver 17 | import org.openqa.selenium.phantomjs.{PhantomJSDriver, PhantomJSDriverService} 18 | import org.openqa.selenium.remote.DesiredCapabilities 19 | import org.openqa.selenium.support.ui.WebDriverWait 20 | 21 | import scala.concurrent.{ExecutionContext, Future, Promise} 22 | 23 | 24 | 25 | /** 26 | * HTTPClient to get the response from the URL. 27 | * In this case this returns the html output of the url 28 | * being crawled 29 | */ 30 | object WebHttpClient { 31 | 32 | val PHANTOMJS_PAGE_CUSTOMHEADERS_PREFIX: String = "phantomjs.page.customHeaders." 33 | val PHANTOMJS_CLI_ARGS: String = "phantomjs.cli.args" 34 | 35 | val config = new AsyncHttpClientConfig.Builder() 36 | val client = new AsyncHttpClient(config 37 | .setFollowRedirect(true) 38 | .setExecutorService(Executors.newWorkStealingPool(64)) 39 | .build()) 40 | 41 | val USER_AGENT: String= "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36" 42 | System.setProperty("phantomjs.page.settings.userAgent", USER_AGENT) 43 | var driver: WebDriver = null 44 | 45 | 46 | import scala.concurrent.duration._ 47 | 48 | implicit val system: ActorSystem = ActorSystem() 49 | implicit val materializer: Materializer = ActorMaterializer() 50 | 51 | 52 | 53 | def get(url: String): Future[String] = { 54 | val promise = Promise[String]() 55 | val request = client.prepareGet(url).build() 56 | client.executeRequest(request, new AsyncCompletionHandler[Response]() { 57 | override def onCompleted(response: Response): Response = { 58 | promise.success(response.getResponseBody) 59 | response 60 | } 61 | 62 | override def onThrowable(t: Throwable): Unit = { 63 | promise.failure(t) 64 | } 65 | }) 66 | promise.future 67 | } 68 | 69 | 70 | def getWithHeader(url: String,username: String, password: String): Future[String] = { 71 | val promise = Promise[String]() 72 | val encodedUserPass: String = new String(Base64.encodeBase64((username + ":" + password).getBytes())); 73 | val request = new RequestBuilder("GET") 74 | .setUrl(url) 75 | .addHeader("Content-Type", "application/json") 76 | .addHeader("Accept", "application/json") 77 | .addHeader("Authorization", "Basic " + encodedUserPass) 78 | .build() 79 | client.prepareRequest(request) 80 | client.executeRequest(request, new AsyncCompletionHandler[Response]() { 81 | override def onCompleted(response: Response): Response = { 82 | promise.success(response.getResponseBody) 83 | response 84 | } 85 | 86 | override def onThrowable(t: Throwable): Unit = { 87 | promise.failure(t) 88 | } 89 | }) 90 | promise.future 91 | } 92 | 93 | 94 | // def getUsingAkkaHttp(url: String)(implicit system: ActorSystem, mat: Materializer): Future[String] = { 95 | // implicit val executionContext = system.dispatcher 96 | // 97 | // val req = HttpRequest(uri = url) 98 | // Http().singleRequest(req) flatMap { resp => 99 | // resp.entity.toStrict(5.seconds).map(_.data.decodeString("UTF-8")) 100 | // } 101 | // } 102 | 103 | def post(url: String, jsonString: String, username: String, password: String): Future[String] = { 104 | val promise = Promise[String]() 105 | val encodedUserPass: String = new String(Base64.encodeBase64((username + ":" + password).getBytes())); 106 | val request = new RequestBuilder("POST") 107 | .setUrl(url) 108 | .addHeader("Content-Type", "application/json") 109 | .addHeader("Accept", "application/json") 110 | .addHeader("Authorization", "Basic " + encodedUserPass) 111 | .setBody(jsonString) 112 | .build() 113 | client.prepareRequest(request) 114 | 115 | client.executeRequest(request, new AsyncCompletionHandler[Response]() { 116 | override def onCompleted(response: Response): Response = { 117 | promise.success(response.getResponseBody) 118 | response 119 | } 120 | 121 | override def onThrowable(t: Throwable): Unit = { 122 | promise.failure(t) 123 | } 124 | }) 125 | promise.future 126 | } 127 | 128 | def put(url: String, jsonString: String, username: String, password: String): Future[String] = { 129 | val promise = Promise[String]() 130 | val encodedUserPass: String = new String(Base64.encodeBase64((username + ":" + password).getBytes())); 131 | val request = new RequestBuilder("PUT") 132 | .setUrl(url) 133 | .addHeader("Content-Type", "application/json") 134 | .addHeader("Accept", "application/json") 135 | .addHeader("Authorization", "Basic " + encodedUserPass) 136 | .setBody(jsonString) 137 | .build() 138 | client.prepareRequest(request) 139 | 140 | client.executeRequest(request, new AsyncCompletionHandler[Response]() { 141 | override def onCompleted(response: Response): Response = { 142 | promise.success(response.getResponseBody) 143 | response 144 | } 145 | 146 | override def onThrowable(t: Throwable): Unit = { 147 | promise.failure(t) 148 | } 149 | }) 150 | promise.future 151 | } 152 | 153 | 154 | 155 | def initPhantomJS(){ 156 | val desiredCaps: DesiredCapabilities = DesiredCapabilities.phantomjs() 157 | desiredCaps.setJavascriptEnabled(true) 158 | desiredCaps.setCapability("takeScreenshot",false: Any) 159 | desiredCaps.setCapability( PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY , "/usr/local/Cellar/phantomjs/2.1.1/bin/phantomjs" : Any) 160 | desiredCaps.setCapability(PHANTOMJS_PAGE_CUSTOMHEADERS_PREFIX + "User-Agent" , USER_AGENT: Any) 161 | 162 | val cliArgsCap: List[String] = List("--takeScreenshot=true","--web-security=false","--ssl-protocol=any","--ignore-ssl-errors=true","--webdriver-loglevel=ERROR") 163 | desiredCaps.setCapability(PHANTOMJS_CLI_ARGS, cliArgsCap); 164 | 165 | driver = new PhantomJSDriver(desiredCaps); 166 | } 167 | 168 | 169 | def getUsingPhantom(url: String): String = { 170 | initPhantomJS() 171 | driver.get(url) 172 | val isTables = url.endsWith("tables") 173 | if (!isTables) { 174 | val wait: WebDriverWait = new WebDriverWait(driver,10) 175 | Thread.sleep(10000) 176 | } 177 | val pageSource:String = driver.getPageSource 178 | driver.quit() 179 | pageSource 180 | } 181 | 182 | 183 | 184 | } 185 | -------------------------------------------------------------------------------- /akka-epl/src/main/scala/com/epl/akka/HTMLParser.scala: -------------------------------------------------------------------------------- 1 | package com.epl.akka 2 | 3 | import java.net.URL 4 | 5 | import akka.actor.{Actor, ActorLogging, Status} 6 | import com.epl.akka.HTMLParser._ 7 | import com.epl.akka.WebCrawler.{CrawlRequest, SaveJsonToCloudant} 8 | import org.jsoup.Jsoup 9 | import play.api.libs.json.{JsObject, JsValue, Json} 10 | 11 | import scala.util.{Failure, Success} 12 | 13 | 14 | object HTMLParser { 15 | 16 | case class CrawlAndGetValidLinks(url: String){} 17 | 18 | case class CrawlAndPrepareJson(url: String){} 19 | 20 | } 21 | 22 | 23 | /** 24 | * Created by sanjeevghimire on 9/1/17. 25 | */ 26 | class HTMLParser() extends Actor with ActorLogging{ 27 | 28 | val EXPIRED_NO = "NO" 29 | val EXPIRED_YES = "YES" 30 | 31 | implicit val ec = context.dispatcher 32 | import scala.collection.JavaConverters._ 33 | 34 | 35 | def getValidLinks(content: String, url:String): Iterator[String] = { 36 | Jsoup.parse(content, url).select("a[href]").iterator().asScala.map(_.absUrl("href")) 37 | } 38 | 39 | def stop(): Unit = { 40 | context.stop(self) 41 | } 42 | 43 | def getFixturesAndConvertToJson(fixturesBody: String,url:String): String ={ 44 | val resultsDetail = Jsoup.parse(fixturesBody, url) 45 | .select("section.fixtures") 46 | .select("div[data-competition-matches-list]") 47 | .iterator().asScala 48 | .map(divElement => { 49 | val dateTimeStr = divElement.attr("data-competition-matches-list") 50 | val resultsByDateList = divElement.select("ul.matchList") 51 | .select("li") 52 | .iterator().asScala 53 | .map(liElement => { 54 | val jsonString = liElement.select("span").attr("data-ui-args") 55 | Json.parse(jsonString) 56 | }).toList 57 | dateTimeStr -> resultsByDateList 58 | }).toMap 59 | 60 | val resultsMap = Map("games" -> resultsDetail) 61 | 62 | val jsValue: JsValue = Json.toJson(resultsMap) 63 | val returnJson: JsObject = jsValue.as[JsObject] + ("expired" -> Json.toJson(EXPIRED_NO)) + ("documentType" -> Json.toJson(DocumentType.Fixtures.toString)) 64 | Json.stringify(returnJson) 65 | } 66 | 67 | def getTablesAndConvertToJson(tablesBody: String,url:String): String ={ 68 | val tableDetail = Jsoup.parse(tablesBody, url) 69 | .select("tbody.tableBodyContainer").first() 70 | .select("tr") 71 | .iterator().asScala 72 | .filterNot(element => element.hasClass("expandable")) 73 | .map(trElement => { 74 | val teamName = trElement.attr("data-filtered-table-row-name") 75 | val teamDetails = trElement.select("td").iterator().asScala 76 | .filterNot(tdElement => tdElement.hasClass("nextMatchCol hideMed")) 77 | .filterNot(tdElement => tdElement.hasClass("revealMore")) 78 | .filterNot(tdElement => tdElement.hasClass("form hideMed")) 79 | .flatMap({ 80 | tdElement => 81 | if (tdElement.hasClass("team")) { 82 | tdElement.select("a").select("span").iterator().asScala 83 | .filterNot(_.classNames().contains("badge-25")) 84 | .map(_.text()).toSeq 85 | } else if(tdElement.hasClass("pos button-tooltip")){ 86 | Seq(tdElement.select("span.value").first().text()) 87 | }else{ 88 | Seq(tdElement.text()) 89 | } 90 | 91 | }).toList 92 | TeamTable.of(teamName,teamDetails) 93 | }).toList 94 | 95 | val teamTableMap = Map("teams" -> tableDetail.sortBy(teamTable => teamTable.position)) 96 | val jsValue: JsValue = Json.toJson(teamTableMap) 97 | val returnJson: JsObject = jsValue.as[JsObject] + ("expired" -> Json.toJson(EXPIRED_NO)) + ("documentType" -> Json.toJson(DocumentType.Results.toString)) 98 | val string = Json.stringify(returnJson) 99 | string 100 | 101 | } 102 | 103 | def getResultsAndConvertToJson(resultsBody: String, url:String): String ={ 104 | val resultsDetail = Jsoup.parse(resultsBody, url) 105 | .select("section.fixtures") 106 | .select("div[data-competition-matches-list]") 107 | .iterator().asScala 108 | .map(divElement => { 109 | val dateTimeStr = divElement.attr("data-competition-matches-list") 110 | val resultsByDateList = divElement.select("ul.matchList") 111 | .select("li") 112 | .iterator().asScala 113 | .map(liElement => { 114 | val homeTeam = liElement.attr("data-home") 115 | val awayTeam = liElement.attr("data-away") 116 | val venue = liElement.attr("data-venue") 117 | val scoreStr = liElement.select("span.score").text().split("-").map(_.toInt) 118 | val dateTimeInMillis = liElement.attr("data-comp-match-item-ko") 119 | ResultTable.of(List(homeTeam,awayTeam,scoreStr(0),scoreStr(1),venue,dateTimeInMillis)) 120 | }).toList 121 | ResultByDate(dateTimeStr,resultsByDateList) 122 | }).toList 123 | 124 | val resultsMap = Map("results" -> resultsDetail) 125 | 126 | val jsValue: JsValue = Json.toJson(resultsMap) 127 | val returnJson: JsObject = jsValue.as[JsObject] + ("expired" -> Json.toJson(EXPIRED_NO)) + ("documentType" -> Json.toJson(DocumentType.Results.toString)) 128 | Json.stringify(returnJson) 129 | } 130 | 131 | override def receive = { 132 | case CrawlAndGetValidLinks(url: String) => 133 | WebHttpClient.get(url) onComplete { 134 | case Success(body) => 135 | getValidLinks(body, url) 136 | .filter(link => link != null && link.length > 0) 137 | .filter(link => !link.contains("mailto")) 138 | .filter(link => !link.equals(url)) 139 | .filter(link => new URL(url).getHost == new URL(link).getHost) 140 | //.filter(link => link.endsWith("fixtures") || link.endsWith("tables") || link.endsWith("results")) 141 | .filter(link => link.endsWith("fixtures")) 142 | .foreach(context.parent ! CrawlRequest(_,false)) 143 | case Failure(err) => 144 | log.error(err, "Error while crawling url: "+ url) 145 | stop() 146 | } 147 | 148 | 149 | case CrawlAndPrepareJson(url: String) => 150 | val isFixtures = url.endsWith("fixtures") 151 | val isTables = url.endsWith("tables") 152 | val isResults = url.endsWith("results") 153 | val body: String = WebHttpClient.getUsingPhantom(url) 154 | var jsonString: String = null 155 | if(isFixtures) { 156 | jsonString = getFixturesAndConvertToJson(body,url) 157 | }else if(isTables){ 158 | jsonString = getTablesAndConvertToJson(body,url) 159 | }else if(isResults){ 160 | jsonString = getResultsAndConvertToJson(body,url) 161 | } 162 | log.info("HTML to JSON: "+jsonString) 163 | context.parent ! SaveJsonToCloudant(jsonString) 164 | 165 | case _: Status.Failure => stop() 166 | 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/pages/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Panel from '../components/panel.js'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | import {Link} from 'react-router-dom' 5 | import { 6 | Table, 7 | TableBody, 8 | TableHeader, 9 | TableHeaderColumn, 10 | TableRow, 11 | TableRowColumn, 12 | } from 'material-ui/Table'; 13 | 14 | const style = { 15 | margin: 12, 16 | }; 17 | 18 | class Dashboard extends React.Component { 19 | constructor() { 20 | super(); 21 | this.state={ 22 | results: [], 23 | standings: [], 24 | fixtures: [], 25 | fixedHeader: true, 26 | stripedRows: true, 27 | showRowHover: true, 28 | showCheckboxes: false, 29 | height: '100%'}; 30 | } 31 | 32 | componentDidMount(){ 33 | fetch(`http://localhost:9000/results`) 34 | .then(result=>result.json()) 35 | .then(items=>this.setState({results: items.rows[0].doc.results})) 36 | 37 | fetch(`http://localhost:9000/fixtures`) 38 | .then(result=>result.json()) 39 | .then(items=>this.setState({fixtures: items.rows[0].doc.games})) 40 | 41 | fetch(`http://localhost:9000/teamtable`) 42 | .then(result=>result.json()) 43 | .then(items=>this.setState({standings: items.rows[0].doc.teams})) 44 | 45 | } 46 | 47 | 48 | render() { 49 | var standingsRow=[]; 50 | var resultsRow=[]; 51 | var fixturesRow=[]; 52 | var fixturesKey='' 53 | if(this.state.standings.length){ 54 | for(var i=0;i<4;i++){ 55 | standingsRow.push(this.state.standings[i]) 56 | } 57 | } 58 | if(this.state.results.length){ 59 | for(i=0;i<2;i++){ 60 | resultsRow.push(this.state.results[i]) 61 | } 62 | } 63 | if(this.state.fixtures){ 64 | for (var attr in this.state.fixtures){ 65 | fixturesKey = attr; 66 | fixturesRow = this.state.fixtures[attr]; 67 | break; 68 | } 69 | } 70 | 71 | return ( 72 |
73 |

Dashboard

74 |
75 |
76 | 80 | 84 | 87 | 88 | Position 89 | Short Name 90 | Played 91 | Goal Difference 92 | Points 93 | 94 | 95 | 100 | {standingsRow.length ? standingsRow.map(item => 101 | 102 | 103 | {item.position} 104 | {item.teamShortName} 105 | {item.played} 106 | {item.goalDifference} 107 | {item.points} 108 | 109 | ) 110 | 111 | :
Loading...
} 112 |
113 |
114 | 115 | } /> 116 | 117 |
118 |
119 | 120 |
121 | 125 | {resultsRow.length ? resultsRow.map(resultByDate => 126 | 127 | 131 | 134 | 135 | 136 | {resultByDate.date} 137 | 138 | 139 | 140 | 145 | 146 | {resultByDate.results.map(result => 147 | 148 | {result.homeTeam} 149 | {result.homeScore} 150 | {result.awayScore} 151 | {result.awayTeam} 152 | 153 | )} 154 | 155 |
156 | 157 | ) 158 | : 'Loading...'} 159 | } /> 160 |
161 |
162 |
163 |
164 |
165 | 169 | { fixturesRow ? 170 | 171 | 175 | 178 | 179 | 180 | {fixturesKey} 181 | 182 | 183 | 184 | 189 | 190 | { 191 | fixturesRow.map(game => 192 | 193 | {game.teams[0].team.name} 194 | {game.kickOffTime} 195 | {game.teams[1].team.name} 196 | {game.ground.name} 197 | 198 | )} 199 | 200 |
201 | : 'loading...' 202 | } 203 | } /> 204 |
205 |
206 |
207 |
208 | ); 209 | } 210 | } 211 | 212 | export default Dashboard; 213 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/sass/base/_scaffolding.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | html { 4 | box-sizing: border-box; 5 | } 6 | 7 | *, 8 | *::before, 9 | *::after { 10 | box-sizing: inherit; 11 | } 12 | 13 | 14 | 15 | @at-root { 16 | @-ms-viewport { width: device-width; } 17 | } 18 | 19 | 20 | // 21 | // Reset HTML, body, and more 22 | // 23 | 24 | html { 25 | 26 | -ms-overflow-style: scrollbar; 27 | 28 | // Changes the default tap highlight to be completely transparent in iOS. 29 | -webkit-tap-highlight-color: rgba(0,0,0,0); 30 | } 31 | 32 | body { 33 | font-family: $font-family-primary; 34 | font-size: $font-size-base; 35 | font-weight: $font-weight-base; 36 | line-height: $line-height-base; 37 | // Go easy on the eyes and use something other than `#000` for text 38 | color: $font-color-base; 39 | // By default, `` has no `background-color` so we set one as a best practice. 40 | background-color: $color-body-bg; 41 | margin: 0; 42 | } 43 | 44 | 45 | [tabindex="-1"]:focus { 46 | outline: none !important; 47 | } 48 | 49 | 50 | // 51 | // Typography 52 | // 53 | 54 | // Remove top margins from headings 55 | // 56 | // By default, `

`-`

` all receive top and bottom margins. We nuke the top 57 | // margin for easier control within type scales as it avoids margin collapsing. 58 | h1, h2, h3, h4, h5, h6 { 59 | margin-top: 0; 60 | margin-bottom: .5rem; 61 | } 62 | 63 | // Reset margins on paragraphs 64 | // 65 | // Similarly, the top margin on `

`s get reset. However, we also reset the 66 | // bottom margin to use `rem` units instead of `em`. 67 | p { 68 | margin-top: 0; 69 | margin-bottom: 1rem; 70 | } 71 | 72 | // Abbreviations 73 | abbr[title], 74 | // Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 75 | abbr[data-original-title] { 76 | cursor: help; 77 | } 78 | 79 | address { 80 | margin-bottom: 1rem; 81 | font-style: normal; 82 | line-height: inherit; 83 | } 84 | 85 | ol, 86 | ul, 87 | dl { 88 | margin-top: 0; 89 | margin-bottom: 1rem; 90 | } 91 | 92 | ol ol, 93 | ul ul, 94 | ol ul, 95 | ul ol { 96 | margin-bottom: 0; 97 | } 98 | 99 | dt { 100 | font-weight: $dt-font-weight; 101 | } 102 | 103 | dd { 104 | margin-bottom: .5rem; 105 | margin-left: 0; // Undo browser default 106 | } 107 | 108 | blockquote { 109 | margin: 0 0 1rem; 110 | } 111 | 112 | 113 | // 114 | // Links 115 | // 116 | 117 | a { 118 | color: $color-primary; 119 | text-decoration: $link-decoration; 120 | @include transition(); 121 | 122 | @include hover-focus { 123 | color: $color-primary-hover; 124 | text-decoration: $link-decoration; 125 | } 126 | } 127 | 128 | // And undo these styles for placeholder links/named anchors (without href) 129 | // which have not been made explicitly keyboard-focusable (without tabindex). 130 | // It would be more straightforward to just use a[href] in previous block, but that 131 | // causes specificity issues in many other styles that are too complex to fix. 132 | // See https://github.com/twbs/bootstrap/issues/19402 133 | 134 | a:not([href]):not([tabindex]) { 135 | color: inherit; 136 | text-decoration: none; 137 | 138 | @include hover-focus { 139 | color: inherit; 140 | text-decoration: none; 141 | } 142 | 143 | &:focus { 144 | outline: 0; 145 | } 146 | } 147 | 148 | 149 | // 150 | // Code 151 | // 152 | 153 | pre { 154 | // Remove browser default top margin 155 | margin-top: 0; 156 | // Reset browser default of `1em` to use `rem`s 157 | margin-bottom: 1rem; 158 | // Normalize v4 removed this property, causing `

` content to break out of wrapping code snippets
159 |     overflow: auto;
160 |   }
161 | 
162 | 
163 |   //
164 |   // Figures
165 |   //
166 | 
167 |   figure {
168 |     // Normalize adds `margin` to `figure`s as browsers apply it inconsistently.
169 |     // We reset that to create a better flow in-page.
170 |     margin: 0 0 1rem;
171 |   }
172 | 
173 | 
174 |   //
175 |   // Images
176 |   //
177 | 
178 |   img {
179 |     // By default, ``s are `inline-block`. This assumes that, and vertically
180 |     // centers them. This won't apply should you reset them to `block` level.
181 |     vertical-align: middle;
182 |     // Note: ``s are deliberately not made responsive by default.
183 |     // For the rationale behind this, see the comments on the `.img-fluid` class.
184 |   }
185 | 
186 | 
187 |   // iOS "clickable elements" fix for role="button"
188 |   //
189 |   // Fixes "clickability" issue (and more generally, the firing of events such as focus as well)
190 |   // for traditionally non-focusable elements with role="button"
191 |   // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
192 | 
193 |   [role="button"] {
194 |     cursor: pointer;
195 |   }
196 | 
197 | 
198 |   // Avoid 300ms click delay on touch devices that support the `touch-action` CSS property.
199 |   //
200 |   // In particular, unlike most other browsers, IE11+Edge on Windows 10 on touch devices and IE Mobile 10-11
201 |   // DON'T remove the click delay when `` is present.
202 |   // However, they DO support removing the click delay via `touch-action: manipulation`.
203 |   // See:
204 |   // * https://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch
205 |   // * http://caniuse.com/#feat=css-touch-action
206 |   // * https://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay
207 | 
208 |   a,
209 |   area,
210 |   button,
211 |   [role="button"],
212 |   input,
213 |   label,
214 |   select,
215 |   summary,
216 |   textarea {
217 |     touch-action: manipulation;
218 |   }
219 | 
220 | 
221 |   //
222 |   // Tables
223 |   //
224 | 
225 |   table {
226 |     // No longer part of Normalize since v4
227 |     border-collapse: collapse;
228 |     // Reset for nesting within parents with `background-color`.
229 |     background-color: $table-bg;
230 |   }
231 | 
232 |   caption {
233 |     padding-top: $table-cell-padding;
234 |     padding-bottom: $table-cell-padding;
235 |     color: $text-muted;
236 |     text-align: left;
237 |     caption-side: bottom;
238 |   }
239 | 
240 |   th {
241 |     // Centered by default, but left-align-ed to match the `td`s below.
242 |     text-align: left;
243 |   }
244 | 
245 | 
246 |   //
247 |   // Forms
248 |   //
249 | 
250 |   label {
251 |     // Allow labels to use `margin` for spacing.
252 |     display: inline-block;
253 |     margin-bottom: .5rem;
254 |   }
255 | 
256 |   // Work around a Firefox/IE bug where the transparent `button` background
257 |   // results in a loss of the default `button` focus styles.
258 |   //
259 |   // Credit: https://github.com/suitcss/base/
260 |   button:focus {
261 |     outline: 1px dotted;
262 |     outline: 5px auto -webkit-focus-ring-color;
263 |   }
264 | 
265 |   input,
266 |   button,
267 |   select,
268 |   textarea {
269 |     // Normalize includes `font: inherit;`, so `font-family`. `font-size`, etc are
270 |     // properly inherited. However, `line-height` isn't inherited there.
271 |     line-height: inherit;
272 |   }
273 | 
274 |   input[type="radio"],
275 |   input[type="checkbox"] {
276 |     // Apply a disabled cursor for radios and checkboxes.
277 |     //
278 |     // Note: Neither radios nor checkboxes can be readonly.
279 |     &:disabled {
280 |       cursor: $cursor-disabled;
281 |     }
282 |   }
283 | 
284 | 
285 |   input[type="date"],
286 |   input[type="time"],
287 |   input[type="datetime-local"],
288 |   input[type="month"] {
289 |     // Remove the default appearance of temporal inputs to avoid a Mobile Safari
290 |     // bug where setting a custom line-height prevents text from being vertically
291 |     // centered within the input.
292 |     // See https://bugs.webkit.org/show_bug.cgi?id=139848
293 |     // and https://github.com/twbs/bootstrap/issues/11266
294 |     -webkit-appearance: listbox;
295 |   }
296 | 
297 |   textarea {
298 |     // Textareas should really only resize vertically so they don't break their (horizontal) containers.
299 |     resize: vertical;
300 |   }
301 | 
302 |   fieldset {
303 |     // Browsers set a default `min-width: min-content;` on fieldsets,
304 |     // unlike e.g. `
`s, which have `min-width: 0;` by default. 305 | // So we reset that to ensure fieldsets behave more like a standard block element. 306 | // See https://github.com/twbs/bootstrap/issues/12359 307 | // and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements 308 | min-width: 0; 309 | // Reset the default outline behavior of fieldsets so they don't affect page layout. 310 | padding: 0; 311 | margin: 0; 312 | border: 0; 313 | } 314 | 315 | legend { 316 | // Reset the entire legend element to match the `fieldset` 317 | display: block; 318 | width: 100%; 319 | padding: 0; 320 | margin-bottom: .5rem; 321 | font-size: 1.5rem; 322 | line-height: inherit; 323 | } 324 | 325 | input[type="search"] { 326 | // This overrides the extra rounded corners on search inputs in iOS so that our 327 | // `.form-control` class can properly style them. Note that this cannot simply 328 | // be added to `.form-control` as it's not specific enough. For details, see 329 | // https://github.com/twbs/bootstrap/issues/11586. 330 | -webkit-appearance: none; 331 | } 332 | 333 | // todo: needed? 334 | output { 335 | display: inline-block; 336 | // font-size: $font-size-base; 337 | // line-height: $line-height; 338 | // color: $input-color; 339 | } 340 | 341 | // Always hide an element with the `hidden` HTML attribute (from PureCSS). 342 | [hidden] { 343 | display: none !important; 344 | } 345 | -------------------------------------------------------------------------------- /soccer-epl-ui/src/static/css/App.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; } 3 | 4 | *, 5 | *::before, 6 | *::after { 7 | box-sizing: inherit; } 8 | 9 | @-ms-viewport { 10 | width: device-width; } 11 | 12 | html { 13 | -ms-overflow-style: scrollbar; 14 | -webkit-tap-highlight-color: transparent; } 15 | 16 | body { 17 | font-family: "Roboto", sans-serif; 18 | font-size: 1rem; 19 | font-weight: 400; 20 | line-height: 1.5; 21 | color: #2a3134; 22 | background-color: #fbfcfd; 23 | margin: 0; } 24 | 25 | [tabindex="-1"]:focus { 26 | outline: none !important; } 27 | 28 | h1, h2, h3, h4, h5, h6 { 29 | margin-top: 0; 30 | margin-bottom: .5rem; } 31 | 32 | p { 33 | margin-top: 0; 34 | margin-bottom: 1rem; } 35 | 36 | abbr[title], 37 | abbr[data-original-title] { 38 | cursor: help; } 39 | 40 | address { 41 | margin-bottom: 1rem; 42 | font-style: normal; 43 | line-height: inherit; } 44 | 45 | ol, 46 | ul, 47 | dl { 48 | margin-top: 0; 49 | margin-bottom: 1rem; } 50 | 51 | ol ol, 52 | ul ul, 53 | ol ul, 54 | ul ol { 55 | margin-bottom: 0; } 56 | 57 | dt { 58 | font-weight: 600; } 59 | 60 | dd { 61 | margin-bottom: .5rem; 62 | margin-left: 0; } 63 | 64 | blockquote { 65 | margin: 0 0 1rem; } 66 | 67 | a { 68 | color: #258df2; 69 | text-decoration: none; 70 | transition: all 0.3s ease; } 71 | a:focus, a:hover { 72 | color: #0d73d7; 73 | text-decoration: none; } 74 | 75 | a:not([href]):not([tabindex]) { 76 | color: inherit; 77 | text-decoration: none; } 78 | a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { 79 | color: inherit; 80 | text-decoration: none; } 81 | a:not([href]):not([tabindex]):focus { 82 | outline: 0; } 83 | 84 | pre { 85 | margin-top: 0; 86 | margin-bottom: 1rem; 87 | overflow: auto; } 88 | 89 | figure { 90 | margin: 0 0 1rem; } 91 | 92 | img { 93 | vertical-align: middle; } 94 | 95 | [role="button"] { 96 | cursor: pointer; } 97 | 98 | a, 99 | area, 100 | button, 101 | [role="button"], 102 | input, 103 | label, 104 | select, 105 | summary, 106 | textarea { 107 | touch-action: manipulation; } 108 | 109 | table { 110 | border-collapse: collapse; 111 | background-color: transparent; } 112 | 113 | caption { 114 | padding-top: 0.75rem; 115 | padding-bottom: 0.75rem; 116 | color: #636c72; 117 | text-align: left; 118 | caption-side: bottom; } 119 | 120 | th { 121 | text-align: left; } 122 | 123 | label { 124 | display: inline-block; 125 | margin-bottom: .5rem; } 126 | 127 | button:focus { 128 | outline: 1px dotted; 129 | outline: 5px auto -webkit-focus-ring-color; } 130 | 131 | input, 132 | button, 133 | select, 134 | textarea { 135 | line-height: inherit; } 136 | 137 | input[type="radio"]:disabled, 138 | input[type="checkbox"]:disabled { 139 | cursor: not-allowed; } 140 | 141 | input[type="date"], 142 | input[type="time"], 143 | input[type="datetime-local"], 144 | input[type="month"] { 145 | -webkit-appearance: listbox; } 146 | 147 | textarea { 148 | resize: vertical; } 149 | 150 | fieldset { 151 | min-width: 0; 152 | padding: 0; 153 | margin: 0; 154 | border: 0; } 155 | 156 | legend { 157 | display: block; 158 | width: 100%; 159 | padding: 0; 160 | margin-bottom: .5rem; 161 | font-size: 1.5rem; 162 | line-height: inherit; } 163 | 164 | input[type="search"] { 165 | -webkit-appearance: none; } 166 | 167 | output { 168 | display: inline-block; } 169 | 170 | [hidden] { 171 | display: none !important; } 172 | 173 | h1, h2, h3, h4, h5, h6, 174 | .h1, .h2, .h3, .h4, .h5, .h6 { 175 | margin-bottom: 0.5rem; 176 | font-family: inherit; 177 | font-weight: 500; 178 | line-height: 1.1; 179 | color: #2a3134; } 180 | 181 | h1, .h1 { 182 | font-size: 2.5rem; } 183 | 184 | h2, .h2 { 185 | font-size: 2rem; } 186 | 187 | h3, .h3 { 188 | font-size: 1.75rem; } 189 | 190 | h4, .h4 { 191 | font-size: 1.5rem; } 192 | 193 | h5, .h5 { 194 | font-size: 1.25rem; } 195 | 196 | h6, .h6 { 197 | font-size: 1rem; } 198 | 199 | .lead { 200 | font-size: 1.25rem; 201 | font-weight: 300; } 202 | 203 | h4, h5, h6 { 204 | font-weight: 400; } 205 | 206 | .display-1 { 207 | font-size: 6rem; 208 | font-weight: 300; 209 | line-height: 1.1; } 210 | 211 | .display-2 { 212 | font-size: 5.5rem; 213 | font-weight: 300; 214 | line-height: 1.1; } 215 | 216 | .display-3 { 217 | font-size: 4.5rem; 218 | font-weight: 300; 219 | line-height: 1.1; } 220 | 221 | .display-4 { 222 | font-size: 3.5rem; 223 | font-weight: 300; 224 | line-height: 1.1; } 225 | 226 | hr { 227 | margin-top: 1rem; 228 | margin-bottom: 1rem; 229 | border: 0; 230 | border-top: 1px solid rgba(0, 0, 0, 0.1); } 231 | 232 | small, 233 | .small { 234 | font-size: 80%; 235 | font-weight: normal; } 236 | 237 | mark, 238 | .mark { 239 | padding: 0.2em; 240 | background-color: #fcf8e3; } 241 | 242 | .list-unstyled, .feature-lists { 243 | padding-left: 0; 244 | list-style: none; } 245 | 246 | .list-inline, .small-social { 247 | padding-left: 0; 248 | list-style: none; } 249 | 250 | .list-inline-item { 251 | display: inline-block; } 252 | .list-inline-item:not(:last-child) { 253 | margin-right: 5px; } 254 | 255 | .initialism { 256 | font-size: 90%; 257 | text-transform: uppercase; } 258 | 259 | .blockquote { 260 | padding: 0.5rem 1rem; 261 | margin-bottom: 1rem; 262 | font-size: 1.25rem; 263 | border-left: 0.25rem solid #eceeef; } 264 | 265 | .blockquote-footer { 266 | display: block; 267 | font-size: 80%; 268 | color: #636c72; } 269 | .blockquote-footer::before { 270 | content: "\2014 \00A0"; } 271 | 272 | .blockquote-reverse { 273 | padding-right: 1rem; 274 | padding-left: 0; 275 | text-align: right; 276 | border-right: 0.25rem solid #eceeef; 277 | border-left: 0; } 278 | 279 | .blockquote-reverse .blockquote-footer::before { 280 | content: ""; } 281 | 282 | .blockquote-reverse .blockquote-footer::after { 283 | content: "\00A0 \2014"; } 284 | 285 | .btn { 286 | display: inline-block; 287 | font-weight: normal; 288 | line-height: 1.25; 289 | text-align: center; 290 | white-space: nowrap; 291 | vertical-align: middle; 292 | user-select: none; 293 | border: 1px solid transparent; 294 | padding: 0.5rem 1rem; 295 | font-size: 1rem; 296 | border-radius: 3px; 297 | transition: all 0.2s ease-in-out 0.3s ease; } 298 | .btn:focus, .btn:hover { 299 | text-decoration: none; } 300 | .btn:focus, .btn.focus { 301 | outline: 0; 302 | box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.25); } 303 | .btn.disabled, .btn:disabled { 304 | cursor: not-allowed; 305 | opacity: .65; } 306 | .btn:active, .btn.active { 307 | background-image: none; } 308 | 309 | a.btn.disabled, 310 | fieldset[disabled] a.btn { 311 | pointer-events: none; } 312 | 313 | .an-circle-icon-btn { 314 | position: relative; 315 | width: 35px; 316 | height: 35px; 317 | background: rgba(251, 252, 253, 0.7); 318 | color: #2a3134; 319 | border: 0; 320 | outline: none; 321 | cursor: pointer; 322 | border-radius: 50%; 323 | display: flex; 324 | align-items: center; 325 | justify-content: center; 326 | transition: all 0.3s ease; 327 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 328 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 329 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); } 330 | .an-circle-icon-btn.notification { 331 | background: rgba(253, 186, 44, 0.3); 332 | color: #fdba2c; } 333 | .an-circle-icon-btn.notification:hover, .an-circle-icon-btn.notification:focus { 334 | background: rgba(253, 186, 44, 0.4); 335 | color: #fdba2c; 336 | outline: 0; } 337 | .an-circle-icon-btn.primary { 338 | background: rgba(37, 141, 242, 0.3); 339 | color: #258df2; } 340 | .an-circle-icon-btn.primary:hover, .an-circle-icon-btn.primary:focus { 341 | background: rgba(37, 141, 242, 0.4); 342 | color: #258df2; } 343 | 344 | .an-btn-primary { 345 | background-color: #258df2 !important; } 346 | .an-btn-primary button { 347 | background-color: #258df2 !important; } 348 | 349 | .btn-primary { 350 | color: #fff; 351 | background-color: #0275d8; 352 | border-color: #0275d8; } 353 | .btn-primary:hover { 354 | color: #fff; 355 | background-color: #025aa5; 356 | border-color: #01549b; } 357 | .btn-primary:focus, .btn-primary.focus { 358 | box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5); } 359 | .btn-primary.disabled, .btn-primary:disabled { 360 | background-color: #0275d8; 361 | border-color: #0275d8; } 362 | .btn-primary:active, .btn-primary.active, 363 | .show > .btn-primary.dropdown-toggle { 364 | color: #fff; 365 | background-color: #025aa5; 366 | background-image: none; 367 | border-color: #01549b; } 368 | 369 | .btn-secondary { 370 | color: #292b2c; 371 | background-color: #fff; 372 | border-color: #ccc; } 373 | .btn-secondary:hover { 374 | color: #292b2c; 375 | background-color: #e6e6e6; 376 | border-color: #adadad; } 377 | .btn-secondary:focus, .btn-secondary.focus { 378 | box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5); } 379 | .btn-secondary.disabled, .btn-secondary:disabled { 380 | background-color: #fff; 381 | border-color: #ccc; } 382 | .btn-secondary:active, .btn-secondary.active, 383 | .show > .btn-secondary.dropdown-toggle { 384 | color: #292b2c; 385 | background-color: #e6e6e6; 386 | background-image: none; 387 | border-color: #adadad; } 388 | 389 | .btn-info { 390 | color: #fff; 391 | background-color: #5bc0de; 392 | border-color: #5bc0de; } 393 | .btn-info:hover { 394 | color: #fff; 395 | background-color: #31b0d5; 396 | border-color: #2aabd2; } 397 | .btn-info:focus, .btn-info.focus { 398 | box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5); } 399 | .btn-info.disabled, .btn-info:disabled { 400 | background-color: #5bc0de; 401 | border-color: #5bc0de; } 402 | .btn-info:active, .btn-info.active, 403 | .show > .btn-info.dropdown-toggle { 404 | color: #fff; 405 | background-color: #31b0d5; 406 | background-image: none; 407 | border-color: #2aabd2; } 408 | 409 | .btn-success { 410 | color: #fff; 411 | background-color: #5cb85c; 412 | border-color: #5cb85c; } 413 | .btn-success:hover { 414 | color: #fff; 415 | background-color: #449d44; 416 | border-color: #419641; } 417 | .btn-success:focus, .btn-success.focus { 418 | box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5); } 419 | .btn-success.disabled, .btn-success:disabled { 420 | background-color: #5cb85c; 421 | border-color: #5cb85c; } 422 | .btn-success:active, .btn-success.active, 423 | .show > .btn-success.dropdown-toggle { 424 | color: #fff; 425 | background-color: #449d44; 426 | background-image: none; 427 | border-color: #419641; } 428 | 429 | .btn-warning { 430 | color: #fff; 431 | background-color: #f0ad4e; 432 | border-color: #f0ad4e; } 433 | .btn-warning:hover { 434 | color: #fff; 435 | background-color: #ec971f; 436 | border-color: #eb9316; } 437 | .btn-warning:focus, .btn-warning.focus { 438 | box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5); } 439 | .btn-warning.disabled, .btn-warning:disabled { 440 | background-color: #f0ad4e; 441 | border-color: #f0ad4e; } 442 | .btn-warning:active, .btn-warning.active, 443 | .show > .btn-warning.dropdown-toggle { 444 | color: #fff; 445 | background-color: #ec971f; 446 | background-image: none; 447 | border-color: #eb9316; } 448 | 449 | .btn-danger { 450 | color: #fff; 451 | background-color: #d9534f; 452 | border-color: #d9534f; } 453 | .btn-danger:hover { 454 | color: #fff; 455 | background-color: #c9302c; 456 | border-color: #c12e2a; } 457 | .btn-danger:focus, .btn-danger.focus { 458 | box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5); } 459 | .btn-danger.disabled, .btn-danger:disabled { 460 | background-color: #d9534f; 461 | border-color: #d9534f; } 462 | .btn-danger:active, .btn-danger.active, 463 | .show > .btn-danger.dropdown-toggle { 464 | color: #fff; 465 | background-color: #c9302c; 466 | background-image: none; 467 | border-color: #c12e2a; } 468 | 469 | .btn-outline-primary { 470 | color: #0275d8; 471 | background-image: none; 472 | background-color: transparent; 473 | border-color: #0275d8; } 474 | .btn-outline-primary:hover { 475 | color: #fff; 476 | background-color: #0275d8; 477 | border-color: #0275d8; } 478 | .btn-outline-primary:focus, .btn-outline-primary.focus { 479 | box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5); } 480 | .btn-outline-primary.disabled, .btn-outline-primary:disabled { 481 | color: #0275d8; 482 | background-color: transparent; } 483 | .btn-outline-primary:active, .btn-outline-primary.active, 484 | .show > .btn-outline-primary.dropdown-toggle { 485 | color: #fff; 486 | background-color: #0275d8; 487 | border-color: #0275d8; } 488 | 489 | .btn-outline-secondary { 490 | color: #ccc; 491 | background-image: none; 492 | background-color: transparent; 493 | border-color: #ccc; } 494 | .btn-outline-secondary:hover { 495 | color: #fff; 496 | background-color: #ccc; 497 | border-color: #ccc; } 498 | .btn-outline-secondary:focus, .btn-outline-secondary.focus { 499 | box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5); } 500 | .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { 501 | color: #ccc; 502 | background-color: transparent; } 503 | .btn-outline-secondary:active, .btn-outline-secondary.active, 504 | .show > .btn-outline-secondary.dropdown-toggle { 505 | color: #fff; 506 | background-color: #ccc; 507 | border-color: #ccc; } 508 | 509 | .btn-outline-info { 510 | color: #5bc0de; 511 | background-image: none; 512 | background-color: transparent; 513 | border-color: #5bc0de; } 514 | .btn-outline-info:hover { 515 | color: #fff; 516 | background-color: #5bc0de; 517 | border-color: #5bc0de; } 518 | .btn-outline-info:focus, .btn-outline-info.focus { 519 | box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5); } 520 | .btn-outline-info.disabled, .btn-outline-info:disabled { 521 | color: #5bc0de; 522 | background-color: transparent; } 523 | .btn-outline-info:active, .btn-outline-info.active, 524 | .show > .btn-outline-info.dropdown-toggle { 525 | color: #fff; 526 | background-color: #5bc0de; 527 | border-color: #5bc0de; } 528 | 529 | .btn-outline-success { 530 | color: #5cb85c; 531 | background-image: none; 532 | background-color: transparent; 533 | border-color: #5cb85c; } 534 | .btn-outline-success:hover { 535 | color: #fff; 536 | background-color: #5cb85c; 537 | border-color: #5cb85c; } 538 | .btn-outline-success:focus, .btn-outline-success.focus { 539 | box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5); } 540 | .btn-outline-success.disabled, .btn-outline-success:disabled { 541 | color: #5cb85c; 542 | background-color: transparent; } 543 | .btn-outline-success:active, .btn-outline-success.active, 544 | .show > .btn-outline-success.dropdown-toggle { 545 | color: #fff; 546 | background-color: #5cb85c; 547 | border-color: #5cb85c; } 548 | 549 | .btn-outline-warning { 550 | color: #f0ad4e; 551 | background-image: none; 552 | background-color: transparent; 553 | border-color: #f0ad4e; } 554 | .btn-outline-warning:hover { 555 | color: #fff; 556 | background-color: #f0ad4e; 557 | border-color: #f0ad4e; } 558 | .btn-outline-warning:focus, .btn-outline-warning.focus { 559 | box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5); } 560 | .btn-outline-warning.disabled, .btn-outline-warning:disabled { 561 | color: #f0ad4e; 562 | background-color: transparent; } 563 | .btn-outline-warning:active, .btn-outline-warning.active, 564 | .show > .btn-outline-warning.dropdown-toggle { 565 | color: #fff; 566 | background-color: #f0ad4e; 567 | border-color: #f0ad4e; } 568 | 569 | .btn-outline-danger { 570 | color: #d9534f; 571 | background-image: none; 572 | background-color: transparent; 573 | border-color: #d9534f; } 574 | .btn-outline-danger:hover { 575 | color: #fff; 576 | background-color: #d9534f; 577 | border-color: #d9534f; } 578 | .btn-outline-danger:focus, .btn-outline-danger.focus { 579 | box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5); } 580 | .btn-outline-danger.disabled, .btn-outline-danger:disabled { 581 | color: #d9534f; 582 | background-color: transparent; } 583 | .btn-outline-danger:active, .btn-outline-danger.active, 584 | .show > .btn-outline-danger.dropdown-toggle { 585 | color: #fff; 586 | background-color: #d9534f; 587 | border-color: #d9534f; } 588 | 589 | .btn-link { 590 | font-weight: normal; 591 | color: #0275d8; 592 | border-radius: 0; } 593 | .btn-link, .btn-link:active, .btn-link.active, .btn-link:disabled { 594 | background-color: transparent; } 595 | .btn-link, .btn-link:focus, .btn-link:active { 596 | border-color: transparent; } 597 | .btn-link:hover { 598 | border-color: transparent; } 599 | .btn-link:focus, .btn-link:hover { 600 | color: #014c8c; 601 | text-decoration: underline; 602 | background-color: transparent; } 603 | .btn-link:disabled { 604 | color: #636c72; } 605 | .btn-link:disabled:focus, .btn-link:disabled:hover { 606 | text-decoration: none; } 607 | 608 | .btn-lg { 609 | padding: 0.75rem 1.5rem; 610 | font-size: 1.25rem; 611 | border-radius: 0.3rem; } 612 | 613 | .btn-sm { 614 | padding: 0.25rem 0.5rem; 615 | font-size: 0.875rem; 616 | border-radius: 0.2rem; } 617 | 618 | .btn-block { 619 | display: block; 620 | width: 100%; } 621 | 622 | .btn-block + .btn-block { 623 | margin-top: 0.5rem; } 624 | 625 | input[type="submit"].btn-block, 626 | input[type="reset"].btn-block, 627 | input[type="button"].btn-block { 628 | width: 100%; } 629 | 630 | .readmin-page-content { 631 | position: relative; 632 | width: 100%; 633 | padding: 30px; 634 | padding-top: 100px; 635 | transition: all 0.3s ease; } 636 | @media (max-width: 520px) { 637 | .readmin-page-content { 638 | padding: 15px; 639 | padding-top: 100px; } } 640 | .readmin-page-content.menu-open { 641 | padding-left: 286px; } 642 | @media (max-width: 991px) { 643 | .readmin-page-content.menu-open { 644 | padding-left: 30px; } } 645 | 646 | .readmin-form { 647 | position: relative; 648 | width: 100%; } 649 | @media (max-width: 560px) { 650 | .readmin-form.inline .inline-btn { 651 | margin-top: 20px; } } 652 | .readmin-form input { 653 | width: 100%; } 654 | .readmin-form label { 655 | margin: 0 !important; } 656 | 657 | .an-header { 658 | position: fixed; 659 | width: 100%; 660 | left: 0; 661 | top: 0; 662 | background: #fff; 663 | padding: 10px 20px; 664 | z-index: 1300; 665 | display: flex; 666 | align-items: center; 667 | justify-content: space-between; 668 | flex-wrap: wrap; 669 | justify-content: flex-end; 670 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 671 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 672 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 673 | height: 65px; } 674 | @media (max-width: 520px) { 675 | .an-header { 676 | z-index: 1400; } } 677 | .an-header .brand { 678 | position: relative; 679 | color: #fff; 680 | font-size: 1.5rem; } 681 | .an-header .brand img { 682 | width: 130px; } 683 | .an-header .header-right { 684 | display: flex; 685 | align-items: center; 686 | justify-content: center; } 687 | .an-header .header-right > a { 688 | margin: 0 10px 0 15px; } 689 | .an-header .header-right button { 690 | padding: 0 !important; } 691 | .an-header .header-left { 692 | position: relative; 693 | display: flex; 694 | align-items: center; 695 | justify-content: center; } 696 | .an-header .header-left .brand { 697 | margin-right: 70px; } 698 | 699 | .menubtn { 700 | padding: 0; 701 | background: transparent; 702 | cursor: pointer; 703 | outline: 0; } 704 | .menubtn i { 705 | transition: all 0.3s ease; } 706 | .menubtn:hover i { 707 | color: #258df2; } 708 | 709 | .material-icons { 710 | color: #757575; } 711 | 712 | .slide-content { 713 | position: fixed; 714 | top: 65px; 715 | right: -300px; 716 | width: 300px; 717 | background: #fff; 718 | height: calc(100% - 55px); 719 | padding: 20px; 720 | overflow: scroll; 721 | border-top: 2px solid #40c741; 722 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 723 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 724 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 725 | transition: all 0.3s ease; } 726 | .slide-content.toggle { 727 | right: 0; } 728 | .slide-content h6 { 729 | padding-bottom: 20px; } 730 | 731 | .notification-single { 732 | position: relative; 733 | width: 100%; 734 | padding-left: 45px; 735 | padding-bottom: 20px; } 736 | .notification-single .avatar { 737 | background-position: center center !important; 738 | background-repeat: no-repeat !important; 739 | background-size: cover !important; 740 | width: 30px; 741 | height: 30px; 742 | position: absolute; 743 | left: 0; 744 | border-radius: 50%; 745 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 746 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 747 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); } 748 | .notification-single .avatar.avatar-icon { 749 | background: rgba(37, 141, 242, 0.3); 750 | color: #258df2; 751 | font-size: 12px; 752 | display: flex; 753 | align-items: center; 754 | justify-content: center; 755 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 756 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 757 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); } 758 | .notification-single .avatar.success { 759 | background: rgba(64, 199, 65, 0.3); 760 | color: #40c741; } 761 | .notification-single .avatar.warning { 762 | background: rgba(253, 186, 44, 0.3); 763 | color: #fdba2c; } 764 | .notification-single .avatar.danger { 765 | background: rgba(230, 64, 67, 0.3); 766 | color: #e64043; } 767 | .notification-single .details { 768 | color: #63737b; 769 | font-size: 14px; 770 | margin-bottom: 8px; } 771 | .notification-single .details a { 772 | color: #2a3134; 773 | padding-right: 5px; } 774 | .notification-single .details a:hover { 775 | color: #258df2; } 776 | .notification-single .time { 777 | font-size: 12px; 778 | color: #98a6ac; 779 | margin: 0; } 780 | 781 | .readmin-sidebar { 782 | position: relative; } 783 | .readmin-sidebar .menudivider { 784 | padding: 15px 0; } 785 | .readmin-sidebar .menu-drawer > div { 786 | padding-top: 75px; } 787 | .readmin-sidebar .menu-drawer.has-bg .nav-menu a, .readmin-sidebar .menu-drawer.has-bg .nav-menu div, .readmin-sidebar .menu-drawer.has-bg .nav-menu svg, .readmin-sidebar .menu-drawer.has-bg .nav-menu i { 788 | color: #fff !important; 789 | opacity: .97; } 790 | .readmin-sidebar .menu-drawer.has-bg .nav-menu i { 791 | opacity: .7; } 792 | .childmenu .readmin-sidebar .menu-drawer.has-bg .nav-menu .childmenu .active { 793 | background: rgba(42, 49, 52, 0.3) !important; } 794 | .readmin-sidebar .menu-drawer.has-bg .nav-menu .active { 795 | background: rgba(42, 49, 52, 0.3) !important; } 796 | .readmin-sidebar .menu-drawer.has-bg .nav-menu .menudivider hr { 797 | background-color: #fff !important; 798 | opacity: .3; } 799 | .readmin-sidebar .an-header { 800 | position: fixed; 801 | left: 0; 802 | top: 0; 803 | width: 256px; 804 | z-index: 14000; 805 | background: #258df2; 806 | transition: all 0.3s ease; } 807 | @media (max-width: 520px) { 808 | .readmin-sidebar .an-header { 809 | width: auto; } } 810 | @media (max-width: 520px) { 811 | .readmin-sidebar .an-header .brand { 812 | font-size: 1rem; 813 | margin-right: 30px; } } 814 | 815 | .sidebar-initial-color { 816 | background-color: #1b202a; } 817 | 818 | .nav-menu .childmenu + div { 819 | padding: 0 !important; } 820 | 821 | .childmenu .nav-menu .childmenu .active { 822 | background: #258df2 !important; 823 | color: #fff !important; } 824 | 825 | .nav-menu .childmenu .active i { 826 | color: #fff !important; } 827 | 828 | .nav-menu .active { 829 | background: #258df2 !important; 830 | color: #fff !important; } 831 | .nav-menu .active i { 832 | color: #fff !important; } 833 | 834 | .iconbox-wrapper { 835 | position: relative; 836 | width: 100%; } 837 | .iconbox-wrapper.alter { 838 | background: #fff; 839 | padding: 30px; 840 | box-shadow: 0 4px 16px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.02); 841 | border-radius: 3px; 842 | margin-bottom: 30px; } 843 | @media (ma-width: 520px) { 844 | .iconbox-wrapper.alter { 845 | padding: 15px; } } 846 | .iconbox-wrapper.alter .col-md-3:last-child .iconbox-single { 847 | border-right: 0; } 848 | .iconbox-wrapper.alter .iconbox-single { 849 | box-shadow: none; 850 | border-right: 1px solid #eaeff5; 851 | margin: 0; 852 | padding-left: 15px; } 853 | @media (max-width: 768px) { 854 | .iconbox-wrapper.alter .iconbox-single { 855 | border-right: 0; } } 856 | .iconbox-wrapper.alter .iconbox-single .circle-icon-alter { 857 | border: 1px solid #eaeff5; 858 | position: relative; 859 | width: 60px; 860 | height: 60px; 861 | margin-right: 20px; 862 | border-radius: 50%; 863 | display: flex; 864 | align-items: center; 865 | justify-content: center; } 866 | 867 | .iconbox-single { 868 | position: relative; 869 | background: #fff; 870 | padding: 30px; 871 | margin-bottom: 30px; 872 | border-radius: 3px; 873 | box-shadow: 0 4px 16px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.02); 874 | display: flex; 875 | align-items: center; 876 | justify-content: space-between; 877 | flex-wrap: wrap; } 878 | @media (ma-width: 520px) { 879 | .iconbox-single { 880 | padding: 10px; } } 881 | .iconbox-single .circle-icon { 882 | position: relative; 883 | width: 50px; 884 | height: 50px; 885 | margin-right: 20px; 886 | border-radius: 50%; 887 | display: flex; 888 | align-items: center; 889 | justify-content: center; } 890 | .iconbox-single .circle-icon .material-icons { 891 | color: #fff; } 892 | .iconbox-single .box-title { 893 | position: relative; 894 | display: flex; 895 | align-items: center; 896 | justify-content: center; } 897 | .iconbox-single h5 { 898 | margin: 0; } 899 | .iconbox-single p { 900 | margin: 0; 901 | opacity: .6; } 902 | 903 | .readmin-panel { 904 | position: relative; 905 | width: 100%; 906 | background: #fff; 907 | margin-bottom: 30px; 908 | border-radius: 3px; 909 | box-shadow: 0 4px 16px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.02); } 910 | .readmin-panel.righticonmenu .panel-heading { 911 | padding: 5px 30px; 912 | padding-right: 15px; } 913 | .readmin-panel.body-text-center .panel-body { 914 | text-align: center; } 915 | .readmin-panel .panel-heading { 916 | position: relative; 917 | width: 100%; 918 | border-bottom: 1px solid #eaeff5; 919 | padding: 17px 30px; 920 | padding-right: 15px; 921 | display: flex; 922 | align-items: center; 923 | justify-content: space-between; 924 | flex-wrap: wrap; } 925 | .readmin-panel .panel-heading h5 { 926 | margin: 0; } 927 | .readmin-panel .panel-body { 928 | padding: 20px; 929 | width: 100%; 930 | display: inline-block; } 931 | 932 | .secondary-appbar { 933 | background: #40c741 !important; } 934 | 935 | .table thead th { 936 | border-top: 0; } 937 | 938 | .product-table tbody tr td { 939 | vertical-align: middle; } 940 | 941 | .panel-body .recharts-wrapper { 942 | width: 100% !important; } 943 | .panel-body .recharts-wrapper .recharts-surface { 944 | width: 100%; } 945 | .panel-body .recharts-wrapper .recharts-legend-wrapper .recharts-surface { 946 | width: auto; } 947 | 948 | .panel-body .recharts-responsive-container { 949 | height: 300px !important; } 950 | 951 | .grid-example { 952 | position: relative; 953 | padding: 30px; } 954 | .grid-example .row { 955 | margin-bottom: 15px; } 956 | 957 | .grid-example .row > .col, .grid-example .row > [class^=col-] { 958 | padding-top: .75rem; 959 | padding-bottom: .75rem; 960 | background-color: rgba(86, 61, 124, 0.15); 961 | border: 1px solid rgba(86, 61, 124, 0.2); } 962 | 963 | .pt20 { 964 | padding-top: 20px; } 965 | 966 | .pb20 { 967 | padding-bottom: 20px; } 968 | 969 | .product-table-img { 970 | max-width: 50px; 971 | max-height: 50px; } 972 | 973 | .readmin-products { 974 | position: relative; 975 | width: 100%; } 976 | 977 | .product-single { 978 | position: relative; 979 | width: 100%; 980 | padding: 20px; 981 | padding-bottom: 30px; 982 | background: #fff; 983 | text-align: center; 984 | box-shadow: 0 4px 16px -2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.02); 985 | border-radius: 3px; 986 | margin-bottom: 30px; } 987 | .product-single img { 988 | height: 240px; 989 | margin: 0 auto; } 990 | .product-single .favorite { 991 | position: absolute; 992 | right: 20px; 993 | top: 20px; 994 | cursor: pointer; } 995 | .product-single h5 { 996 | padding-top: 10px; 997 | padding-bottom: 5px; } 998 | .product-single .price { 999 | margin: 0; 1000 | color: #258df2; 1001 | padding-bottom: 10px; } 1002 | 1003 | .table-responsive table { 1004 | min-width: 500px; } 1005 | 1006 | .review-item { 1007 | position: relative; 1008 | width: 100%; 1009 | margin-bottom: 30px; } 1010 | .review-item.totals { 1011 | background: #f4f7fa; 1012 | padding: 30px; } 1013 | .review-item.totals ul { 1014 | margin-bottom: 0; } 1015 | .review-item h6 { 1016 | text-transform: uppercase; 1017 | padding-bottom: 15px; 1018 | margin: 0; 1019 | letter-spacing: 1px; 1020 | font-weight: bold; } 1021 | .review-item ul { 1022 | list-style: none; 1023 | margin: 0; 1024 | padding: 0; 1025 | padding-top: 15px; 1026 | position: relative; 1027 | margin-bottom: 45px; } 1028 | .review-item ul li { 1029 | padding: 5px 0; 1030 | display: flex; 1031 | align-items: center; 1032 | justify-content: space-between; 1033 | flex-wrap: wrap; } 1034 | .review-item ul li span { 1035 | opacity: .7; 1036 | padding-right: 30px; } 1037 | .review-item ul li strong { 1038 | font-weight: normal; } 1039 | 1040 | .re-page-banner { 1041 | position: relative; 1042 | height: 250px; 1043 | margin: -30px; 1044 | margin-top: -35px; 1045 | margin-bottom: 60px; 1046 | display: flex; 1047 | align-items: center; 1048 | justify-content: center; } 1049 | .re-page-banner .overlay { 1050 | position: absolute; 1051 | width: 100%; 1052 | height: 100%; 1053 | left: 0; 1054 | top: 0; 1055 | background: rgba(0, 0, 0, 0.6); 1056 | z-index: 9; 1057 | transition: all 0.3s ease; } 1058 | .re-page-banner h1 { 1059 | position: relative; 1060 | z-index: 20; 1061 | color: #fff; 1062 | margin: 0; 1063 | font-size: 3.25rem; 1064 | font-weight: 400; } 1065 | 1066 | .about-box-single { 1067 | position: relative; 1068 | margin-bottom: 30px; } 1069 | .about-box-single p { 1070 | opacity: .8; } 1071 | 1072 | .inner-box-title { 1073 | position: relative; 1074 | padding: 0 0 15px; 1075 | border-bottom: 2px solid #258df2; 1076 | font-size: 1.25rem; 1077 | text-transform: uppercase; 1078 | margin-top: 0; } 1079 | .inner-box-title.full { 1080 | border-bottom: 0; } 1081 | 1082 | .an-team-single { 1083 | position: relative; 1084 | width: 100%; 1085 | transition: all 0.3s ease; 1086 | margin-bottom: 30px; 1087 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1088 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1089 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); } 1090 | .an-team-single:hover { 1091 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1092 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1093 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); } 1094 | .an-team-single:hover .image-container .overlay { 1095 | opacity: 1; 1096 | visibility: visible; } 1097 | .an-team-single.light-bg .user-details { 1098 | background: #fff; } 1099 | .an-team-single.light-bg .user-details h4 a, .an-team-single.light-bg .user-details p { 1100 | color: #2a3134; } 1101 | .an-team-single .image-container { 1102 | position: relative; } 1103 | .an-team-single .image-container .overlay { 1104 | position: absolute; 1105 | width: 100%; 1106 | height: 100%; 1107 | left: 0; 1108 | top: 0; 1109 | background: rgba(0, 0, 0, 0.6); 1110 | z-index: 9; 1111 | transition: all 0.3s ease; 1112 | display: flex; 1113 | align-items: center; 1114 | justify-content: center; 1115 | transition: all 0.3s ease; 1116 | opacity: 0; 1117 | visibility: hidden; } 1118 | .an-team-single .image-container .overlay .small-social li a i { 1119 | font-size: 1.75rem; } 1120 | .an-team-single .image-container .overlay .small-social li a:hover { 1121 | color: #258df2; } 1122 | .an-team-single img { 1123 | width: 100%; 1124 | display: block; } 1125 | .an-team-single .user-details { 1126 | padding: 15px; 1127 | background: #258df2; } 1128 | .an-team-single .user-details h4 { 1129 | margin: 0; 1130 | line-height: 1.5; 1131 | text-transform: uppercase; 1132 | font-size: 1.25rem; } 1133 | .an-team-single .user-details h4 a { 1134 | color: #fff; 1135 | display: block; } 1136 | .an-team-single .user-details p { 1137 | margin: 0; 1138 | color: #2a3134; 1139 | opacity: .7; 1140 | font-size: 1rem; } 1141 | .an-team-single .user-details .small-social { 1142 | padding-top: 15px; } 1143 | 1144 | .small-social { 1145 | margin: 0; } 1146 | .small-social li { 1147 | display: inline-block; 1148 | padding: 7px; } 1149 | .small-social li a { 1150 | color: #fff; } 1151 | 1152 | .an-services { 1153 | position: relative; } 1154 | .an-services.center-align .an-service-single { 1155 | text-align: center; } 1156 | .an-services.center-align .an-service-single i { 1157 | margin: 0 auto 30px; } 1158 | 1159 | .an-service-single { 1160 | position: relative; 1161 | margin-bottom: 30px; 1162 | background: #fff; 1163 | padding: 30px; 1164 | text-align: center; 1165 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1166 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1167 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); } 1168 | .an-service-single:hover i { 1169 | color: #258df2; 1170 | border-color: #258df2; } 1171 | .an-service-single i { 1172 | color: #258df2; 1173 | font-size: 2.5rem; 1174 | padding-bottom: 20px; } 1175 | .an-service-single h4 { 1176 | padding-bottom: 15px; 1177 | font-size: 1.25rem; 1178 | margin: 0; 1179 | line-height: 1.5; } 1180 | .an-service-single p { 1181 | color: #63737b; 1182 | margin: 0; 1183 | font-weight: 400; } 1184 | 1185 | .an-address-single { 1186 | position: relative; 1187 | width: 100%; } 1188 | .an-address-single p { 1189 | padding-left: 30px; 1190 | position: relative; } 1191 | .an-address-single i { 1192 | position: absolute; 1193 | left: 0; 1194 | top: 4px; 1195 | color: #258df2; } 1196 | 1197 | .contact-form > div { 1198 | margin-bottom: 20px; } 1199 | 1200 | .blog-single { 1201 | position: relative; 1202 | background: #fff; 1203 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1204 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1205 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1206 | margin-bottom: 30px; } 1207 | .blog-single img { 1208 | width: 100%; } 1209 | .blog-single .details { 1210 | padding: 20px; } 1211 | .blog-single .details p { 1212 | margin: 0; 1213 | opacity: .6; 1214 | color: #2a3134; } 1215 | 1216 | .an-pricing { 1217 | position: relative; 1218 | width: 100%; 1219 | padding: 45px 0 15px; } 1220 | 1221 | .an-pricing-table-single { 1222 | position: relative; 1223 | width: 100%; 1224 | background: #fff; 1225 | margin-bottom: 30px; 1226 | border-radius: 3px; 1227 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1228 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1229 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1230 | transition: all 0.3s ease; 1231 | text-align: center; 1232 | padding: 30px; 1233 | padding-bottom: 0; } 1234 | .an-pricing-table-single .price-header { 1235 | position: relative; 1236 | text-align: center; } 1237 | .an-pricing-table-single .price-header .plan-name { 1238 | margin-top: 0; } 1239 | .an-pricing-table-single .price-header .plan-price { 1240 | color: #258df2; 1241 | margin-bottom: 0; 1242 | font-size: 2.5rem; } 1243 | .an-pricing-table-single .price-header .plan-price span { 1244 | font-size: 1rem; 1245 | position: relative; 1246 | top: -13px; 1247 | padding-right: 5px; 1248 | color: #258df2; } 1249 | .an-pricing-table-single .price-header .plan-price small { 1250 | font-size: 1rem; 1251 | color: #98a6ac; 1252 | display: block; } 1253 | .an-pricing-table-single .price-header p { 1254 | margin: 0; 1255 | text-transform: uppercase; 1256 | font-size: 1rem; 1257 | padding-top: 10px; } 1258 | .an-pricing-table-single .helper-text { 1259 | position: relative; 1260 | padding: 25px 0 0; 1261 | opacity: .7; } 1262 | 1263 | .feature-lists { 1264 | padding: 15px 0; 1265 | text-align: center; 1266 | margin: 0; } 1267 | .feature-lists li { 1268 | padding: 7px 0; 1269 | color: #4c595e; 1270 | border-bottom: 1px solid #eaeff5; 1271 | font-size: -1rem; } 1272 | 1273 | .price-footer { 1274 | padding: 20px; 1275 | position: relative; } 1276 | 1277 | .login-wrapper { 1278 | position: relative; 1279 | width: 100%; 1280 | max-width: 400px; 1281 | background: #258df2; 1282 | margin: 0 auto; 1283 | margin-top: 60px; 1284 | margin-bottom: 60px; } 1285 | 1286 | .login-fields { 1287 | position: relative; 1288 | width: 100%; 1289 | background: #fff; 1290 | padding: 45px; 1291 | top: 30px; 1292 | right: 30px; 1293 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1294 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1295 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); } 1296 | .login-fields h3 a { 1297 | font-size: 13px; 1298 | font-weight: 400; 1299 | padding-left: 30px; 1300 | color: #2a3134; 1301 | opacity: .7; } 1302 | .login-fields h3 a:hover { 1303 | color: #258df2; } 1304 | .login-fields h3 i { 1305 | font-size: 5rem; } 1306 | .login-fields h3.title-404 { 1307 | font-size: 7.5rem; } 1308 | 1309 | .readmin-footer { 1310 | position: relative; 1311 | text-align: center; 1312 | padding: 30px 0; 1313 | padding-bottom: 0; } 1314 | .readmin-footer p { 1315 | opacity: .7; } 1316 | 1317 | .readmin-style-switcher { 1318 | position: fixed; 1319 | right: -350px; 1320 | top: 0; 1321 | background: #fff; 1322 | height: 100%; 1323 | width: 350px; 1324 | -webkit-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1325 | -moz-box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1326 | box-shadow: 0 6px 25px 0 rgba(38, 50, 56, 0.2); 1327 | z-index: 99999; 1328 | padding: 30px; 1329 | transition: all 0.3s ease; } 1330 | .readmin-style-switcher.toggle { 1331 | right: 0; } 1332 | .readmin-style-switcher .clear-btn { 1333 | position: absolute !important; 1334 | right: 0; 1335 | top: 0; } 1336 | .readmin-style-switcher p { 1337 | opacity: .6; 1338 | margin-bottom: 15px; } 1339 | .readmin-style-switcher h6 { 1340 | padding-bottom: 15px; 1341 | padding-top: 15px; 1342 | text-transform: uppercase; } 1343 | .readmin-style-switcher .single-radio { 1344 | width: 50%; 1345 | float: left; } 1346 | .readmin-style-switcher .radio-group { 1347 | position: relative; 1348 | display: inline-block; } 1349 | .readmin-style-switcher .radio-group > div { 1350 | width: 40px !important; 1351 | float: left; 1352 | margin-bottom: 10px; } 1353 | .readmin-style-switcher .style-toggle { 1354 | position: absolute; 1355 | top: 150px; 1356 | left: -50px; 1357 | width: 50px; 1358 | height: 50px; 1359 | background: #258df2; 1360 | border-radius: 3px 0 0 3px; 1361 | border: 0; 1362 | outline: 0; 1363 | cursor: pointer; 1364 | display: flex; 1365 | align-items: center; 1366 | justify-content: center; } 1367 | .readmin-style-switcher .style-toggle i { 1368 | color: #fff; } 1369 | .readmin-style-switcher .style-toggle:hover { 1370 | background: #0e80ef; } 1371 | .readmin-style-switcher .overflow-content { 1372 | overflow: auto; 1373 | width: 100%; 1374 | height: 100%; } 1375 | 1376 | /* Standard syntax */ 1377 | @keyframes rotate { 1378 | 0% { 1379 | transform: rotate(0deg); } 1380 | 50% { 1381 | transform: rotate(360deg); } 1382 | 100% { 1383 | transform: rotate(0deg); } } 1384 | --------------------------------------------------------------------------------