From 4557aa8251fafbd394062f0e15a325196692b6df Mon Sep 17 00:00:00 2001 From: erichhasl Date: Thu, 31 Aug 2017 22:40:53 +0200 Subject: [PATCH] initial commit --- .gitignore | 3 + App/Error.hs | 19 + App/Info.hs | 16 + App/Login.hs | 84 +++ App/Logout.hs | 32 ++ App/Private.hs | 19 + App/Startpage.hs | 22 + AppMonad.hs | 48 ++ Database.hs | 41 ++ DatabaseData.hs | 65 +++ DatabaseTest.hs | 44 ++ Main.hs | 27 + Request.hs | 35 ++ Routing.hs | 31 + Shared.hs | 27 + Template.hs | 54 ++ TestHeist.hs | 23 + haskell.conf | 36 ++ spawn.sh | 4 + templates/base.tpl | 30 + templates/confirm_logout.tpl | 12 + templates/css/base.css | 211 +++++++ templates/css/logo.png | Bin 0 -> 5093 bytes templates/css/logo.svg | 1027 ++++++++++++++++++++++++++++++++++ templates/css/nav-back.png | Bin 0 -> 423 bytes templates/error404.tpl | 13 + templates/footer.tpl | 2 + templates/header.tpl | 15 + templates/info.tpl | 3 + templates/logged_in.tpl | 7 + templates/logged_out.tpl | 8 + templates/login.tpl | 20 + templates/private.tpl | 9 + templates/register.tpl | 21 + templates/registered.tpl | 10 + templates/startpage.tpl | 25 + 36 files changed, 2043 insertions(+) create mode 100644 .gitignore create mode 100644 App/Error.hs create mode 100644 App/Info.hs create mode 100644 App/Login.hs create mode 100644 App/Logout.hs create mode 100644 App/Private.hs create mode 100644 App/Startpage.hs create mode 100644 AppMonad.hs create mode 100644 Database.hs create mode 100644 DatabaseData.hs create mode 100644 DatabaseTest.hs create mode 100644 Main.hs create mode 100644 Request.hs create mode 100644 Routing.hs create mode 100644 Shared.hs create mode 100644 Template.hs create mode 100644 TestHeist.hs create mode 100644 haskell.conf create mode 100755 spawn.sh create mode 100644 templates/base.tpl create mode 100644 templates/confirm_logout.tpl create mode 100644 templates/css/base.css create mode 100644 templates/css/logo.png create mode 100644 templates/css/logo.svg create mode 100644 templates/css/nav-back.png create mode 100644 templates/error404.tpl create mode 100644 templates/footer.tpl create mode 100644 templates/header.tpl create mode 100644 templates/info.tpl create mode 100644 templates/logged_in.tpl create mode 100644 templates/logged_out.tpl create mode 100644 templates/login.tpl create mode 100644 templates/private.tpl create mode 100644 templates/register.tpl create mode 100644 templates/registered.tpl create mode 100644 templates/startpage.tpl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59250e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.cgi +*.hi +*.o diff --git a/App/Error.hs b/App/Error.hs new file mode 100644 index 0000000..4425f6e --- /dev/null +++ b/App/Error.hs @@ -0,0 +1,19 @@ +module App.Error ( + PageNotFound(..) +) where + +import Network.FastCGI +import Network.URI +import Template + +import Request + +data PageNotFound = PageNotFound + +instance RequestHandler PageNotFound where + handle handler = do + uri <- requestURI + let path = uriPath uri + res <- render "error404" [ ("pagetitle", "Seite nicht gefunden") + , ("path", path)] + output res diff --git a/App/Info.hs b/App/Info.hs new file mode 100644 index 0000000..6167581 --- /dev/null +++ b/App/Info.hs @@ -0,0 +1,16 @@ +{-# LANGUAGE OverloadedStrings #-} + +module App.Info ( + Info(..) +) where + +import Request +import Template +import Network.FastCGI + +data Info = Info + +instance RequestHandler Info where + handle handler = do + result <- render "info" [("pagetitle", "Info")] + output result diff --git a/App/Login.hs b/App/Login.hs new file mode 100644 index 0000000..da813b7 --- /dev/null +++ b/App/Login.hs @@ -0,0 +1,84 @@ +module App.Login ( + Login(..) +) where + +import Control.Monad.State (gets) + +import Request +import Template +import Network.FastCGI +import AppMonad (numVisited, App) +import Database +import Shared + +data Login = Login + | Register + +instance RequestHandler Login where + handle Login = do + loginStatus <- isLoggedIn + if loginStatus then alreadyLoggedIn else login + handle Register = do + loginStatus <- isLoggedIn + if loginStatus then alreadyLoggedIn else register + +loginSucceeded :: App CGIResult +loginSucceeded = do + mayFrom <- getInput "from" + case mayFrom of + Just from -> redirect from + Nothing -> + render "logged_in" [ ("pagetitle", "Angemeldet") + , ("kind", "jetzt") ] >>= output + +loginFailed :: App CGIResult +loginFailed = do + result <- render "login" [("pagetitle", "Anmelden"), + ("errormsg", "Ungültige Anmeldedaten")] + output result + +alreadyLoggedIn :: App CGIResult +alreadyLoggedIn = do + result <- render "logged_in" [ ("pagetitle", "Angemeldet") + , ("kind", "bereits") ] + output result + +login :: App CGIResult +login = do + mayLogin <- sequence <$> sequence [getInput "username", getInput "password"] + case mayLogin of + Just [username, password] -> + validate username password <: loginSucceeded <-> loginFailed + Nothing -> do + result <- render "login" [ ("pagetitle", "Anmelden") + , ("errormsg", "") ] + output result + +register :: App CGIResult +register = do + mayRegister <- sequence <$> sequence [ getInput "username" + , getInput "password" + , getInput "confirm_password" ] + case mayRegister of + Just [username, password, password_confirm] + | password /= password_confirm -> + registerFailed "Passwörter stimmen nicht überein!" + | otherwise -> isAvailable username <: + goRegister username password <-> + registerFailed "Benutername wird schon verwendet!" + Nothing -> do + res <- render "register" [ ("pagetitle", "Registrierung") + , ("errormsg", "") ] + output res + +goRegister :: Username -> Password -> App CGIResult +goRegister username password = do + addUser username password + res <- render "registered" [("pagetitle", "Registrierung abgeschlossen")] + output res + +registerFailed :: String -> App CGIResult +registerFailed reason = do + res <- render "register" [ ("pagetitle", "Registrierung") + , ("errormsg", reason ) ] + output res diff --git a/App/Logout.hs b/App/Logout.hs new file mode 100644 index 0000000..319b9fc --- /dev/null +++ b/App/Logout.hs @@ -0,0 +1,32 @@ +module App.Logout ( + Logout(..) +) where + +import Network.FastCGI + +import Request +import Database +import AppMonad +import Template + +data Logout = Logout + +instance RequestHandler Logout where + handle handler = do + loginStatus <- isLoggedIn + if loginStatus then confirmLogout else redirect "/login" + +confirmLogout :: App CGIResult +confirmLogout = do + method <- requestMethod + case method of + "POST" -> logout + _ -> render "confirm_logout" [ ("pagetitle", "Abmelden") ] >>= output + +logout :: App CGIResult +logout = do + deleteCookie $ newCookie "username" "" + deleteCookie $ newCookie "login_string" "" + res <- render "logged_out" [ ("pagetitle", "Abgemeldet") + , ("kind", "jetzt")] + output res diff --git a/App/Private.hs b/App/Private.hs new file mode 100644 index 0000000..e74d37e --- /dev/null +++ b/App/Private.hs @@ -0,0 +1,19 @@ +module App.Private ( + Private(..) +) where + +import Control.Monad.State (gets) + +import Request +import Template +import Network.FastCGI +import AppMonad (numVisited, App) +import Database + +data Private = Private + +instance RequestHandler Private where + requireLogin _ = True + handle handler = do + res <- render "private" [ ("pagetitle", "Geheimes Zeug") ] + output res diff --git a/App/Startpage.hs b/App/Startpage.hs new file mode 100644 index 0000000..40727c6 --- /dev/null +++ b/App/Startpage.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE OverloadedStrings #-} + +module App.Startpage ( + Startpage(..) +) where + +import Text.XHtml +import Control.Monad.State (gets) + +import Request +import Template +import Network.FastCGI +import AppMonad (numVisited) + +data Startpage = Startpage + +instance RequestHandler Startpage where + handle handler = do + n <- gets numVisited + result <- render "startpage" [("pagetitle", "Startseite"), + ("visited", show n)] + output result diff --git a/AppMonad.hs b/AppMonad.hs new file mode 100644 index 0000000..29b5b06 --- /dev/null +++ b/AppMonad.hs @@ -0,0 +1,48 @@ +{-# LANGUAGE FlexibleInstances, + GeneralizedNewtypeDeriving #-} + +module AppMonad ( + AppState(..), App, runApp, setHeistState, setNumVisited +) where + +import Control.Monad.State +import Control.Monad.Catch + +import Network.FastCGI +import Network.CGI.Monad +import Heist + +import DatabaseData + +data AppState = AppState { heist :: Maybe (HeistState App), + numVisited :: Int, + connection :: Connection } + +setHeistState :: HeistState App -> AppState -> AppState +setHeistState hs as = as { heist = Just hs } + +setNumVisited :: Int -> AppState -> AppState +setNumVisited n as = as { numVisited = n } + +newtype AppT m a = App { getState :: StateT AppState (CGIT m) a } + deriving (Monad, MonadIO, MonadState AppState, + Applicative, Functor) + +type App = AppT IO + +instance MonadCGI (AppT IO) where + cgiAddHeader n v = App . lift $ cgiAddHeader n v + cgiGet x = App . lift $ cgiGet x + +instance MonadThrow (AppT IO) where + throwM e = App . lift $ throwM e + +instance MonadCatch (AppT IO) where + {-catch :: AppT IO a -> (e -> AppT IO a) -> AppT IO a-} + catch (App x) f = App $ catch x (getState . f) + +runApp :: AppState -> App CGIResult -> IO () +runApp as x = do + let (App handled) = handleErrors x + y = evalStateT handled as + runFastCGIorCGI y diff --git a/Database.hs b/Database.hs new file mode 100644 index 0000000..e7f1f06 --- /dev/null +++ b/Database.hs @@ -0,0 +1,41 @@ +{-# OPTIONS -Wall #-} + +module Database ( + connect, addUser, validate, Connection, isLoggedIn, isAvailable, + Username, Password +) where + +import Control.Monad.State + +import Network.CGI + +import AppMonad +import DatabaseData +import Shared + +validate :: Username -> Password -> App Bool +validate username password = do + c <- gets connection + isValid <- liftIO $ validateC c username password + when isValid $ do + setCookie $ newCookie "username" username + (Just str) <- liftIO $ getLoginStringC c username + setCookie $ newCookie "login_string" str + return isValid + +isLoggedIn :: App Bool +isLoggedIn = defRunMaybeT False $ do + c <- gets connection + name <- MaybeT $ getCookie "username" + loginString <- MaybeT $ getCookie "login_string" + liftIO $ isLoggedInC c name loginString + +isAvailable :: Username -> App Bool +isAvailable username = do + c <- gets connection + liftIO $ isAvailableC c username + +addUser :: Username -> Password -> App () +addUser username password = do + c <- gets connection + liftIO $ addUserC c username password diff --git a/DatabaseData.hs b/DatabaseData.hs new file mode 100644 index 0000000..ba67df1 --- /dev/null +++ b/DatabaseData.hs @@ -0,0 +1,65 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS -Wall #-} + +module DatabaseData ( + connect, addUserC, validateC, Connection, Username, Password, + isLoggedInC, getLoginStringC, isAvailableC +) where + +import System.Random (newStdGen, randomRs) +import Control.Monad.Trans (liftIO) + +import qualified Crypto.Hash as C (SHA3_512, Digest, hash) +import Data.ByteString.Char8 (ByteString, pack) + +import Database.HDBC +import Database.HDBC.PostgreSQL + +import Shared + +type Username = String +type Password = String +type Salt = String + +connect :: IO Connection +connect = connectPostgreSQL "user=christian dbname=haskweb" + +addUserC :: Connection -> Username -> Password -> IO () +addUserC conn username password = do + gen <- newStdGen + let salt = take 32 $ randomRs ('a', 'z') gen + hashed = hash (password ++ salt) + _ <- run conn + "INSERT INTO webuser (name, password, salt) VALUES (?, ?, ?)" + [toSql username, toSql hashed, toSql salt] + commit conn + +getLoginC :: Connection -> Username -> MaybeT IO (Password, Salt) +getLoginC conn username = do + [[pwd, salt]] <- liftIO $ quickQuery' conn + "SELECT password, salt FROM webuser WHERE name=?" + [toSql username] + return (fromSql pwd, fromSql salt) + +validateC :: Connection -> Username -> Password -> IO Bool +validateC conn username password = defRunMaybeT False (do + (pwd, salt) <- getLoginC conn username + return $ pwd == hash (password ++ salt) ) + +isLoggedInC :: Connection -> Username -> String -> IO Bool +isLoggedInC conn username loginString = defRunMaybeT False (do + (pwd, _) <- getLoginC conn username + return $ hash (pwd ++ username) == loginString) + +getLoginStringC :: Connection -> Username -> IO (Maybe String) +getLoginStringC conn username = runMaybeT $ do + (pwd, _) <- getLoginC conn username + return $ hash (pwd ++ username) + +isAvailableC :: Connection -> Username -> IO Bool +isAvailableC conn username = do + res <- quickQuery' conn "SELECT 1 FROM webuser WHERE name=?" [toSql username] + return $ length res == 0 + +hash :: String -> String +hash = show . (C.hash :: ByteString -> C.Digest C.SHA3_512) . pack diff --git a/DatabaseTest.hs b/DatabaseTest.hs new file mode 100644 index 0000000..3149b24 --- /dev/null +++ b/DatabaseTest.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS -Wall #-} + +import System.Random +import Control.Monad.Trans.Maybe +import Control.Monad (guard) +import Control.Monad.Trans +import Data.Maybe + +import qualified Crypto.Hash as C (SHA3_512, Digest, hash) +import Data.ByteString.Char8 (ByteString, pack) + +import Database.HDBC +import Database.HDBC.PostgreSQL + +type Username = String +type Password = String + +connect :: IO Connection +connect = connectPostgreSQL "user=christian dbname=haskweb" + +addUser :: Connection -> Username -> Password -> IO () +addUser conn username password = do + gen <- newStdGen + let salt = take 32 $ randomRs ('a', 'z') gen + hashed = hash (password ++ salt) + _ <- run conn + "INSERT INTO webuser (name, password, salt) VALUES (?, ?, ?)" + [toSql username, toSql hashed, toSql salt] + commit conn + +validate :: Connection -> Username -> Password -> IO Bool +validate conn username password = fromMaybe False <$> runMaybeT (do + res <- liftIO $ quickQuery' conn + "SELECT password, salt FROM webuser WHERE name=?" + [toSql username] + guard $ length res == 1 + guard $ length (head res) == 2 + let [pwd, salt] = head res + guard $ fromSql pwd == hash (password ++ fromSql salt) + return True) + +hash :: String -> String +hash = show . (C.hash :: ByteString -> C.Digest C.SHA3_512) . pack diff --git a/Main.hs b/Main.hs new file mode 100644 index 0000000..9e729c5 --- /dev/null +++ b/Main.hs @@ -0,0 +1,27 @@ +import Data.Maybe +import Control.Monad.State (modify, gets) + +import Network.FastCGI +import Network.URI +import Network.CGI + +import Routing +import AppMonad +import Template +import Database +import Shared +import Text.XHtml + +cgiMain :: App CGIResult +cgiMain = do + initTemplates + (fromMaybe 0) <$> read <$$> getCookie "visited" >>= modify . setNumVisited + newCookie "visited" . show . (+1) <$> gets numVisited >>= setCookie + uri <- requestURI + routeRequest uri + +main :: IO () +main = do + conn <- connect + let as = AppState Nothing 0 conn + runApp as cgiMain diff --git a/Request.hs b/Request.hs new file mode 100644 index 0000000..9cbfbfd --- /dev/null +++ b/Request.hs @@ -0,0 +1,35 @@ +{-# LANGUAGE ExistentialQuantification #-} + +module Request ( + RequestHandler(..), handlePath +) where + +import Network.URI +import Network.FastCGI +import Text.Regex.PCRE +import Database + +import AppMonad + +class RequestHandler a where + handle :: a -> App CGIResult + requireLogin :: a -> Bool + requireLogin _ = False + handleRequest :: a -> App CGIResult + handleRequest x = do + status <- isLoggedIn + path <- uriPath <$> requestURI + if not status && requireLogin x + then redirect $ "/login?from=" ++ path + else handle x + +data RH = forall h. RequestHandler h => RH h + +instance RequestHandler RH where + requireLogin (RH x) = requireLogin x + handle (RH x) = handle x + +handlePath :: RequestHandler a => String -> String -> a -> Maybe RH +handlePath path pattern handler + | path =~ pattern = Just (RH handler) + | otherwise = Nothing diff --git a/Routing.hs b/Routing.hs new file mode 100644 index 0000000..d0c881e --- /dev/null +++ b/Routing.hs @@ -0,0 +1,31 @@ +module Routing ( + routeRequest +) where + +import Control.Applicative ((<|>)) + +import Network.URI +import Network.FastCGI + +import App.Startpage +import App.Info +import App.Login +import App.Logout +import App.Error +import App.Private + +import Request +import AppMonad + +routeRequest :: URI -> App CGIResult +routeRequest uri = do + let path = uriPath uri + mayHandler = handlePath path "^/info" Info + <|> handlePath path "^/login" Login + <|> handlePath path "^/logout" Logout + <|> handlePath path "^/register" Register + <|> handlePath path "^/private" Private + <|> handlePath path "^/$" Startpage + case mayHandler of + Just handler -> handleRequest handler + Nothing -> handleRequest PageNotFound diff --git a/Shared.hs b/Shared.hs new file mode 100644 index 0000000..55cfa6d --- /dev/null +++ b/Shared.hs @@ -0,0 +1,27 @@ +module Shared ( + defRunMaybeT, (<$$>), MaybeT(..), ifF, ifM, (<:), (<->) +) where + +import Data.Maybe +import Control.Monad.Trans.Maybe + +defRunMaybeT :: Monad m => a -> MaybeT m a -> m a +defRunMaybeT defValue m = fromMaybe defValue <$> runMaybeT m + +(<$$>) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b) +(<$$>) = fmap . fmap + +ifF :: Bool -> a -> a -> a +ifF cond x y = if cond then x else y + +ifF' :: a -> a -> Bool -> a +ifF' x y cond = if cond then x else y + +ifM :: Monad m => m Bool -> m a -> m a -> m a +ifM cond m1 m2 = cond >>= ifF' m1 m2 + +(<:) :: Monad m => m Bool -> m a -> m a -> m a +(<:) = ifM + +(<->) :: (a -> b) -> a -> b +(<->) = ($) diff --git a/Template.hs b/Template.hs new file mode 100644 index 0000000..efc678c --- /dev/null +++ b/Template.hs @@ -0,0 +1,54 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS -Wall #-} + +module Template ( + initTemplates, render, Context +) where + +import Control.Monad.State +import Data.ByteString.Char8 (pack) +import Data.Binary.Builder +import Control.Lens (set) +import Data.ByteString.Lazy (toStrict) +import qualified Data.Text as T (pack, unpack) +import Data.Text.Encoding + +import Heist +import Heist.Interpreted + +import AppMonad +import Database + +type TemplateName = String + +type Context = [(String, String)] + +basePath :: FilePath +basePath = "/home/christian/work/haskell/haskweb/" + +heistConfig :: HeistConfig App +heistConfig = + (set hcNamespace "") $ + (set hcLoadTimeSplices defaultLoadTimeSplices) $ + (set hcTemplateLocations [loadTemplates $ basePath ++ "templates"]) $ + emptyHeistConfig + +initTemplates :: App () +initTemplates = do + heistState <- liftIO $ either (error . concat) id + <$> initHeist heistConfig + modify $ setHeistState heistState + +render :: TemplateName -> Context -> App String +render tpl oldContext = do + hs <- maybe (error "Template engine not initialized!") id <$> gets heist + loginStatus <- isLoggedIn + let context = ("login_path", if loginStatus then "/logout" else "/login") : + ("login_name", if loginStatus then "Abmelden" else "Anmelden") : + oldContext + hs' = foldr (\(k, v) h -> bindString (T.pack k) (T.pack v) h) hs context + mayBuilder <- renderTemplate hs' (pack tpl) + case mayBuilder of + Just (builder, _) -> + return . T.unpack . decodeUtf8 . toStrict . toLazyByteString $ builder + Nothing -> return $ "Could not load template!" diff --git a/TestHeist.hs b/TestHeist.hs new file mode 100644 index 0000000..acf050f --- /dev/null +++ b/TestHeist.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +import qualified Data.ByteString as B +import Blaze.ByteString.Builder (toByteStringIO) +import Control.Applicative +import Control.Monad.Trans.Either (runEitherT) +import Heist +import Heist.Compiled (renderTemplate) +import Control.Lens + +heistConfig = + (set hcNamespace "") $ + -- (set hcInterpretedSplices defaultInterpretedSplices) $ + (set hcLoadTimeSplices defaultLoadTimeSplices) $ + (set hcTemplateLocations [loadTemplates "."]) $ + emptyHeistConfig + +main = do + heistState <- either (error "oops") id <$> + (runEitherT $ initHeist heistConfig) + builder <- maybe (error "oops") fst $ + renderTemplate heistState "billy" + toByteStringIO B.putStr builder diff --git a/haskell.conf b/haskell.conf new file mode 100644 index 0000000..cc62ad1 --- /dev/null +++ b/haskell.conf @@ -0,0 +1,36 @@ +# configuration of the server +server { + # the port your site will be served on + listen 80; + # the domain name it will serve for + server_name .example.com; # substitute your machine's IP address or FQDN + charset utf-8; + + # max upload size + client_max_body_size 75M; # adjust to taste + + location /css { + alias /home/christian/work/haskell/haskweb/templates/css; + } + + # Finally, send all non-media requests to the Django server. + location / { + #include /home/christian/work/repos/areion_web/uwsgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + } +} diff --git a/spawn.sh b/spawn.sh new file mode 100755 index 0000000..6813e32 --- /dev/null +++ b/spawn.sh @@ -0,0 +1,4 @@ +#!/bin/bash +ghc --make -o Main.cgi Main.hs + +sudo service haskweb restart diff --git a/templates/base.tpl b/templates/base.tpl new file mode 100644 index 0000000..256abe2 --- /dev/null +++ b/templates/base.tpl @@ -0,0 +1,30 @@ + + + <pagetitle/> + + + + + + +
+ + + +
+ +
+ +
+ + + + + + + + diff --git a/templates/confirm_logout.tpl b/templates/confirm_logout.tpl new file mode 100644 index 0000000..07dbe08 --- /dev/null +++ b/templates/confirm_logout.tpl @@ -0,0 +1,12 @@ + +
+

Logout

+ +

Willst du dich wirklich abmelden?

+ +
+ +
+
+ +
diff --git a/templates/css/base.css b/templates/css/base.css new file mode 100644 index 0000000..65c247b --- /dev/null +++ b/templates/css/base.css @@ -0,0 +1,211 @@ +body,html{ + font-size: 16px; + font-family: "Georgia", serif; + line-height: 29px; + color:#0c1a30; + padding:0; + height: 100%; + margin: 0 0 1px; +} + +#main{ + background:#fff scroll repeat-x 0px -100px; + margin: 0; + padding: 0; + width:100%; + min-height: 100%; + /*background: #FFFFFF;*/ +} + +#content { + width: 960px; + margin: 0 auto; + overflow:hidden; + position: relative; +} + +h1 { + font-size: 22px; +} + +h2{ + font-size: 22px; +} + +h3 a:link, h3 a:visited, h2 a:link, h2 a:visited { + color: #2C558A; +} + +h3 { + font-size: 19px; +} + +h4 { + font-size: 17px; +} + +h1,h2,h3,h4{ + color: #2c558a; + font-weight: normal; + text-shadow: #fff 0px 1px 1px; + letter-spacing:0.1em; + font-family: "Gill Sans", "PT Sans", "Arial", sans-serif; + text-transform: uppercase; +} + +ul { + list-style: none; + margin: 0 0 29px 10px; + padding-left: 10px; + text-indent: -13px; +} + +p.error { + color: #EE3333; +} + +a:link,a:visited{ + color: #2B7EA1; + text-decoration: none; +} + +#footer { + color: green; +} + +#header { + background-color: white; +} + +#heading { + width: 100%; + background: #132742 url(nav-back.png) scroll repeat-x bottom left; + color: #C4E7FF; + padding: 25px 0 0 0; + margin:0; + height: 60px; + overflow:hidden; +} + +#heading .inner{ + width: 960px; + margin:0 auto; + overflow:hidden; + line-height: 1em; + vertical-align: top; + height: 100px; + padding: 0; + position: relative; + /*height: 4em;*/ +} + +#heading h1 { + margin:0; + padding: 0; + margin: 0 auto; + position: absolute; + top: 10px; + left: 50px; + vertical-align: top; +} + +#heading img { + width: 32px; + margin:0; + padding: 0; + margin: 0 auto; + position: absolute; + top: 0px; + left: 0px; + vertical-align: top; +} + +#title a { + color: fff; +} + +#nav { + overflow: hidden; + color: #C4E7FF; + height: 36px; + padding: 4px 0 0 0; margin: 0; + float: right; +} + +#nav ul { + padding: 0; + margin: 0; + height: 100%; + display: inline; +} + +#nav li { + vertical-align: top; + padding: 15 0 10px 0; + height: 30px; + display: block; + list-style-type: none; + float: left; + letter-spacing: -0.01em; + margin: 0 20px; + font-family: "Droid Sans", sans-serif; + text-shadow: 1px 1px #142845; +} + +#nav a:link, #nav a:visited { + color: inherit; + vertical-align: top; + height: 100%; + display: block; +} + +#nav a:hover, #nav a:active { + color: #fff; + vertical-align: top; + height: 100%; + display: block; +} + +a.nolink:link, a.nolink:visited { + text-decoration: none; + color: #FF3333; +} + +a.nolink:hover { + text-decoration: underline; +} + +.singlecolumn h2 { + line-height: 44px; + border-bottom: 1px solid #C2D1E1; + margin-bottom: 13px; + padding: 0px; +} + +#footer { + position: relative; + top: 0; + left: 0; + width: 100%; + clear: both; + border-top: 1px solid #C2D1E1; + color: #a0acba; + padding: 4px 0px; + height: 31px; + font-family: "PT Sans", sans-serif; + font-size: 0.75em; + text-align: right; + background: #f5f9ff; + vertical-align: middle; + line-height: 31px; + overflow: hidden; +} + +#footer p { + padding: 0 20px; + margin: 0; +} + +#footer p.left { + float: left; +} diff --git a/templates/css/logo.png b/templates/css/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..57a3c3d085d1bbb25acf349fb17e816ef13fe978 GIT binary patch literal 5093 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 + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/css/nav-back.png b/templates/css/nav-back.png new file mode 100644 index 0000000000000000000000000000000000000000..f6eb4b872bb07dcbf80d0c516a3621936beee922 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^EI=H}!3-oL|8A87QZoa5LR^K_oP^b!h1Fez)m=q2 z+(b0pMKnD`wLC?&yhOEu$Xis~M@$EZe8qJA#B}||^#a881I6`&Bn*Nj3_>IffhbhM zFig@2h{7d}BP5L@rA(rvOrxbtW2DVurOo1`&Euub6J#tBWh|3qER$ueQe>@CWv$a> zt +

+

Seite nicht gefunden

+

+ Die seite existiert nicht. Vielleicht war der Link veraltet. + Hier kommst du wieder zur Startseite. +

+ +

+ Bei weiteren Problemene kannst du dich an den Systemadministrator wenden. +

+
+ diff --git a/templates/footer.tpl b/templates/footer.tpl new file mode 100644 index 0000000..49c6c81 --- /dev/null +++ b/templates/footer.tpl @@ -0,0 +1,2 @@ +

Powered by Haskell und FastCGI

+

Copyright (C) Christian Merten 2017

diff --git a/templates/header.tpl b/templates/header.tpl new file mode 100644 index 0000000..b52a001 --- /dev/null +++ b/templates/header.tpl @@ -0,0 +1,15 @@ +
+
+ +

Haskweb

+ + +
+
diff --git a/templates/info.tpl b/templates/info.tpl new file mode 100644 index 0000000..d453d6b --- /dev/null +++ b/templates/info.tpl @@ -0,0 +1,3 @@ + +

Hier gibts tolle Infos!

+
diff --git a/templates/logged_in.tpl b/templates/logged_in.tpl new file mode 100644 index 0000000..b8e3d82 --- /dev/null +++ b/templates/logged_in.tpl @@ -0,0 +1,7 @@ + +
+

Login

+

Du bist angemeldet!

+

Hier kannst du dich abmelden.

+
+
diff --git a/templates/logged_out.tpl b/templates/logged_out.tpl new file mode 100644 index 0000000..05e1d7d --- /dev/null +++ b/templates/logged_out.tpl @@ -0,0 +1,8 @@ + +
+

Login

+

Du bist abgemeldet!

+

Hier kannst du dich anmelden.

+
+ +
diff --git a/templates/login.tpl b/templates/login.tpl new file mode 100644 index 0000000..e9923ea --- /dev/null +++ b/templates/login.tpl @@ -0,0 +1,20 @@ + +
+

Login

+ +

+ Hier kannst du dich anmelden, um auf geschützte Bereiche zuzugreifen.
+ Du hast noch keine Account? Dann kann du dich hier registrieren.
+

+ +

+

+ Benutzername:
+
+ Passwort:
+

+ +
+
+ +
diff --git a/templates/private.tpl b/templates/private.tpl new file mode 100644 index 0000000..b793634 --- /dev/null +++ b/templates/private.tpl @@ -0,0 +1,9 @@ + +
+

Geheimes Zeug

+

+ Jetzt kannst du die spannenden Sachen sehen, für die + man angemeldet sein muss! +

+
+
diff --git a/templates/register.tpl b/templates/register.tpl new file mode 100644 index 0000000..da7bede --- /dev/null +++ b/templates/register.tpl @@ -0,0 +1,21 @@ + +
+

Registrierung

+

+ Hier kannst du dich registrieren.
+ Du hast schon einen Account? Dann melde dich hier an.
+

+ +

+

+ Benutzername:
+
+ Passwort:
+
+ Passwort bestätigen:
+

+ +
+
+ +
diff --git a/templates/registered.tpl b/templates/registered.tpl new file mode 100644 index 0000000..b446073 --- /dev/null +++ b/templates/registered.tpl @@ -0,0 +1,10 @@ + +
+

Registrierung abgeschlossen

+

+ Du hast dich erfolgreich registriert.
+ Hier gehts zur Anmeldung. +

+
+ +
diff --git a/templates/startpage.tpl b/templates/startpage.tpl new file mode 100644 index 0000000..2c8b97a --- /dev/null +++ b/templates/startpage.tpl @@ -0,0 +1,25 @@ + +
+

Startseite

+ +

+ Das ist die Startseite der tollen Webseite! + Du wart hier heute schon mal!
+ Schau dich hier ruhig um, es gibt total viel tolles zu sehen! Jede + Menge sinnloser Text! Vielleicht wird es hier in Zukunft auch + die ein oder andere Dokumentation zu sehen geben. +

+ +

Neuigkeiten

+

+ Hier werden tolle Informationen veröffentlicht! Aktuell gibt es aber + nichts neues! Vielleicht ja später mal! Es lohnt sich definitiv + hier nicht regelmäßig vorbei zu schauen, da sich hier eh nichts + ändert! +

+ +

Lala

+

Ab und zu steht hier auch mal sinnvolles Zeug, meistens aber nur Müll!

+ +
+