Compare commits
51 Commits
em_change-
...
fix-link
Author | SHA1 | Date | |
---|---|---|---|
3fff2db563
|
|||
cc696ee0c8 | |||
bbf8b3baa5 | |||
a5230dfde3 | |||
9ab689f69e | |||
e566a6eef4 | |||
209cdc2770 | |||
2482caf2e0 | |||
d15df0f9e4 | |||
91f8340ac8 | |||
6851d2fe1f | |||
e2f9cdd7e2 | |||
6ed6cbb33e | |||
41ccf212a9 | |||
382d36d001 | |||
2dec76fbfd | |||
377c8a7d04 | |||
f7ffc49b76 | |||
338da444d8 | |||
57b4db9bb2 | |||
70307aad9e | |||
af5565dcc5 | |||
4a375f3d5f | |||
d8b2192240 | |||
65ef1d2772 | |||
c50635319b | |||
32366b7bf7 | |||
dbb701a2a4 | |||
b761f39247 | |||
a7435c3d37 | |||
429cb371a8 | |||
774c59bfa8 | |||
39c3122230 | |||
a7ea4207ce | |||
6769455c27 | |||
984c205299 | |||
5b28441102 | |||
d278ec2ad4 | |||
3f10abe1c5 | |||
1a7ade0f76 | |||
0777f7c008 | |||
69a7fbc036 | |||
4c5e151615 | |||
9548d0715e | |||
68669b7445 | |||
1c306e8607
|
|||
375cafb946
|
|||
910d0bf2ae
|
|||
31f8a0be69 | |||
9c4522dceb | |||
83f0919cd8 |
19
.drone.yml
19
.drone.yml
@ -1,24 +1,25 @@
|
|||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: deploy to dev.apps.coopcloud.tech
|
name: deploy to recipes.coopcloud.tech
|
||||||
steps:
|
steps:
|
||||||
- name: build image
|
- name: build image
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
username:
|
username: 3wordchant
|
||||||
from_secret: docker_reg_username_3wc
|
|
||||||
password:
|
password:
|
||||||
from_secret: docker_reg_passwd_3wc
|
from_secret: git_coopcloud_tech_token_3wc
|
||||||
repo: 3wordchant/abra-apps
|
repo: git.coopcloud.tech/coop-cloud/recipes.coopcloud.tech
|
||||||
tags: latest
|
tags: latest
|
||||||
|
registry: git.coopcloud.tech
|
||||||
|
|
||||||
- name: deployment
|
- name: deployment
|
||||||
image: decentral1se/stack-ssh-deploy:latest
|
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
|
||||||
settings:
|
settings:
|
||||||
stack: dev_apps_coopcloud_tech
|
stack: recipes_coopcloud_tech
|
||||||
|
host: swarm-0.coopcloud.tech
|
||||||
deploy_key:
|
deploy_key:
|
||||||
from_secret: drone_ssh_swarm-demo.autonomic.zone
|
from_secret: drone_ssh_swarm-0_coopcloud_tech
|
||||||
host: swarm-demo.autonomic.zone
|
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- main
|
- main
|
||||||
|
8
AUTHORS.md
Normal file
8
AUTHORS.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# authors
|
||||||
|
|
||||||
|
> If you're looking at this and you hack on `abra` and you're not listed here,
|
||||||
|
> please do add yourself! This is a community project, let's show some :heart:
|
||||||
|
|
||||||
|
- 3wordchant
|
||||||
|
- eejum
|
||||||
|
- hazelnot
|
11
Dockerfile
11
Dockerfile
@ -1,4 +1,4 @@
|
|||||||
FROM node:12-alpine
|
FROM node:21-alpine AS build
|
||||||
|
|
||||||
RUN mkdir /code
|
RUN mkdir /code
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
@ -11,5 +11,10 @@ ENV PATH=$PATH:/code/node_modules/elm-linter/bin:/code/node_modules/elm-format/b
|
|||||||
# Add remainder of files
|
# Add remainder of files
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/npm"]
|
RUN ["npm", "run", "build"]
|
||||||
CMD ["run", "prod"]
|
|
||||||
|
FROM nginx
|
||||||
|
|
||||||
|
COPY --from=build /code/public/ /usr/share/nginx/html/
|
||||||
|
|
||||||
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# recipes.coopcloud.tech
|
# recipes.coopcloud.tech
|
||||||
|
|
||||||
> WIP! https://dev.apps.coopcloud.tech is the current live site
|
> https://recipes.coopcloud.tech is the current live site
|
||||||
|
|
||||||
[](https://drone.autonomic.zone/coop-cloud/recipes.coopcloud.tech)
|
[](https://build.coopcloud.tech/coop-cloud/recipes.coopcloud.tech)
|
||||||
|
|
||||||
The Co-op Cloud recipes catalogue. Our digital configuration commons, browsable in a simple and pleasant web UI.
|
The Co-op Cloud recipes catalogue. Our digital configuration commons, browsable in a simple and pleasant web UI.
|
||||||
|
|
||||||
@ -33,6 +33,8 @@ __Build command:__ `npm run build`
|
|||||||
|
|
||||||
__Publish directory:__ `public`
|
__Publish directory:__ `public`
|
||||||
|
|
||||||
|
Then, run `make` to deploy the new version to `mellor.coopcloud.tech`.
|
||||||
|
|
||||||
## thanks
|
## thanks
|
||||||
|
|
||||||
created using [`elm-spa`](https://elm-spa.dev)
|
created using [`elm-spa`](https://elm-spa.dev)
|
||||||
|
25
compose.yml
25
compose.yml
@ -3,25 +3,28 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: "3wordchant/abra-apps:latest"
|
image: "git.coopcloud.tech/coop-cloud/recipes.coopcloud.tech:latest"
|
||||||
networks:
|
networks:
|
||||||
- proxy
|
- proxy
|
||||||
healthcheck:
|
# healthcheck:
|
||||||
test: "nodejs -e \"http.get('http://localhost:8000', (res) => { console.log('status: ', res.statusCode); if (res.statusCode == 200) { process.exit(0); } else { process.exit(1); } });\""
|
# test: "nodejs -e \"http.get('http://localhost:8000', (res) => { console.log('status: ', res.statusCode); if (res.statusCode == 200) { process.exit(0); } else { process.exit(1); } });\""
|
||||||
interval: 30s
|
# interval: 30s
|
||||||
timeout: 10s
|
# timeout: 10s
|
||||||
retries: 3
|
# retries: 3
|
||||||
start_period: 1m
|
# start_period: 1m
|
||||||
deploy:
|
deploy:
|
||||||
update_config:
|
update_config:
|
||||||
failure_action: rollback
|
failure_action: rollback
|
||||||
order: start-first
|
order: start-first
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.services.abra-apps-dev.loadbalancer.server.port=8000"
|
- "traefik.http.services.abra-recipes.loadbalancer.server.port=80"
|
||||||
- "traefik.http.routers.abra-apps-dev.rule=Host(`recipes.coopcloud.tech`)"
|
- "traefik.http.routers.abra-recipes.rule=(Host(`dev.apps.coopcloud.tech`)||Host(`recipes.coopcloud.tech`))"
|
||||||
- "traefik.http.routers.abra-apps-dev.entrypoints=web-secure"
|
- "traefik.http.routers.abra-recipes.entrypoints=web-secure"
|
||||||
- "traefik.http.routers.abra-apps-dev.tls.certresolver=production"
|
- "traefik.http.routers.abra-recipes.tls.certresolver=production"
|
||||||
|
- "traefik.http.routers.abra-recipes.middlewares=abra-recipes-redirect"
|
||||||
|
- "traefik.http.middlewares.abra-recipes-redirect.headers.SSLForceHost=true"
|
||||||
|
- "traefik.http.middlewares.abra-recipes-redirect.headers.SSLHost=recipes.coopcloud.tech"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
proxy:
|
proxy:
|
||||||
|
1
elm.json
1
elm.json
@ -16,6 +16,7 @@
|
|||||||
"elm/url": "1.0.0",
|
"elm/url": "1.0.0",
|
||||||
"elm-community/json-extra": "4.3.0",
|
"elm-community/json-extra": "4.3.0",
|
||||||
"elm-community/string-extra": "4.0.1",
|
"elm-community/string-extra": "4.0.1",
|
||||||
|
"fapian/elm-html-aria": "1.4.0",
|
||||||
"pablohirafuji/elm-markdown": "2.0.5"
|
"pablohirafuji/elm-markdown": "2.0.5"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
4
makefile
4
makefile
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
@DOCKER_CONTEXT=mellor.coopcloud.tech \
|
@DOCKER_CONTEXT=mellor.coopcloud.tech \
|
||||||
docker stack rm dev_apps_coopcloud_tech && \
|
docker stack rm recipes_coopcloud_tech && \
|
||||||
DOCKER_CONTEXT=mellor.coopcloud.tech \
|
DOCKER_CONTEXT=mellor.coopcloud.tech \
|
||||||
docker stack deploy -c compose.yml dev_apps_coopcloud_tech
|
docker stack deploy -c compose.yml recipes_coopcloud_tech
|
||||||
|
17
nginx.conf
Normal file
17
nginx.conf
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name localhost;
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# redirect server error pages to the static page /50x.html
|
||||||
|
#
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
560
package-lock.json
generated
560
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -22,14 +22,14 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chokidar-cli": "2.1.0",
|
"chokidar-cli": "3.0.0",
|
||||||
"elm": "0.19.1-3",
|
"elm": "0.19.1-6",
|
||||||
"elm-live": "4.0.2",
|
"elm-live": "4.0.2",
|
||||||
"elm-spa": "5.0.4",
|
"elm-spa": "5.0.4",
|
||||||
"elm-test": "0.19.1-revision2",
|
"elm-test": "0.19.1-revision2",
|
||||||
"npm-run-all": "4.1.5"
|
"npm-run-all": "4.1.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"elm-format": "^0.8.5"
|
"elm-format": "^0.8.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<!-- CSS goes here -->
|
<!-- CSS goes here -->
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
<title>abra recipes</title>
|
<title>Co-op Cloud Recipes</title>
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||||
<!--<link rel="stylesheet"
|
<!--<link rel="stylesheet"
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<nav class="navbar navbar-expand-lg sticky-top font-weight-bold" style="background: rgb(255, 79, 136) none repeat scroll 0% 0%;">
|
<nav class="navbar navbar-expand-lg sticky-top font-weight-bold" style="background: rgb(255, 79, 136) none repeat scroll 0% 0%;">
|
||||||
<a class="navbar-brand text-dark" href="/">
|
<a class="navbar-brand text-dark" href="/">
|
||||||
<img src="/logo.png" class="d-inline-block align-top mr-2" alt="" width="30" height="30">
|
<img src="/logo.png" class="d-inline-block align-top mr-2" alt="" width="30" height="30">
|
||||||
abra recipes</a>
|
Co-op Cloud Recipes</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="pt-3">
|
<div class="pt-3">
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2>
|
<h2>
|
||||||
abra recipes</h2>
|
Co-op Cloud Recipes</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>
|
<p>
|
||||||
|
@ -29,16 +29,51 @@ i.fas, i.fab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Categories */
|
/* Filtering */
|
||||||
|
|
||||||
|
#filter {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--light-blue);
|
||||||
|
padding-top: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#filter > form > div {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#filter h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.help {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--dark-pink);
|
||||||
|
}
|
||||||
|
|
||||||
.category-tile {
|
.category-tile {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-tile:hover {
|
.category-tile:hover {
|
||||||
|
color: var(--dark-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tile.active {
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
background-color: var(--light-blue);
|
}
|
||||||
border-radius: 5px;
|
|
||||||
|
/* Intro */
|
||||||
|
|
||||||
|
.card#intro {
|
||||||
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cards */
|
/* Cards */
|
||||||
@ -46,6 +81,7 @@ i.fas, i.fab {
|
|||||||
.app-category {
|
.app-category {
|
||||||
background-color: var(--light-blue);
|
background-color: var(--light-blue);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-description {
|
.card-description {
|
||||||
@ -73,10 +109,14 @@ i.fas, i.fab {
|
|||||||
|
|
||||||
.card-img-top {
|
.card-img-top {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 16vw;
|
height: 5vw;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-img-large {
|
||||||
|
height: 10vw;
|
||||||
|
}
|
||||||
|
|
||||||
.smaller-card {
|
.smaller-card {
|
||||||
border: 1px solid var(--dark-pink);
|
border: 1px solid var(--dark-pink);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@ -95,31 +135,6 @@ i.fas, i.fab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Search bar*/
|
|
||||||
|
|
||||||
.search-bar-container {
|
|
||||||
margin: 2em;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-input, .search-dropdown {
|
|
||||||
margin: 0.5em;
|
|
||||||
height: calc(1.5em + .75rem + 2px);
|
|
||||||
background-color: var(--white);
|
|
||||||
border: 1px solid var(--dark-pink);
|
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-input {
|
|
||||||
width: 30rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-dropdown {
|
|
||||||
width: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navbar */
|
/* Navbar */
|
||||||
.navbar-text {
|
.navbar-text {
|
||||||
color: var(--dark-blue);
|
color: var(--dark-blue);
|
||||||
@ -132,7 +147,7 @@ i.fas, i.fab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.card-body p {
|
.smaller-card .card-body p {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,3 +156,10 @@ i.fas, i.fab {
|
|||||||
.card-body p {
|
.card-body p {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#filter {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
3
renovate.json
Normal file
3
renovate.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
@ -37,14 +37,15 @@ view { params } =
|
|||||||
elm_link = a [ href "https://elm-lang.org/" ] [ text "Elm" ]
|
elm_link = a [ href "https://elm-lang.org/" ] [ text "Elm" ]
|
||||||
coopcloud_link = a [ href "https://coopcloud.tech/" ] [ text "Co-op Cloud" ]
|
coopcloud_link = a [ href "https://coopcloud.tech/" ] [ text "Co-op Cloud" ]
|
||||||
source_link = a [ href "https://git.coopcloud.tech/coop-cloud/abra-apps" ] [ text "source" ]
|
source_link = a [ href "https://git.coopcloud.tech/coop-cloud/abra-apps" ] [ text "source" ]
|
||||||
|
autonomic_link = a [ href "https://autonomic.zone" ] [ text "Autonomic" ]
|
||||||
in
|
in
|
||||||
{ title = "about – abra recipes"
|
{ title = "About – Co-op Cloud Recipes"
|
||||||
, body =
|
, body =
|
||||||
[ div [ class "pt-3" ]
|
[ div [ class "pt-3" ]
|
||||||
[ div [ class "col-md-6 col-sm-10 mb-3 offset-md-3 offset-sm-1" ]
|
[ div [ class "col-md-6 col-sm-10 mb-3 offset-md-3 offset-sm-1" ]
|
||||||
[ div [ class "card" ]
|
[ div [ class "card" ]
|
||||||
[ div [ class "card-header" ]
|
[ div [ class "card-header" ]
|
||||||
[ h2 [] [ text "abra recipes" ]
|
[ h2 [] [ text "Co-op Cloud Recipes" ]
|
||||||
]
|
]
|
||||||
, div [ class "card-body" ]
|
, div [ class "card-body" ]
|
||||||
[ p []
|
[ p []
|
||||||
@ -52,7 +53,7 @@ view { params } =
|
|||||||
text "a lil' "
|
text "a lil' "
|
||||||
, text " tool to display "
|
, text " tool to display "
|
||||||
, coopcloud_link
|
, coopcloud_link
|
||||||
, text " apps."
|
, text " recipes."
|
||||||
]
|
]
|
||||||
, p []
|
, p []
|
||||||
[
|
[
|
||||||
@ -63,7 +64,9 @@ view { params } =
|
|||||||
]
|
]
|
||||||
, div [ class "card-footer" ]
|
, div [ class "card-footer" ]
|
||||||
[ p []
|
[ p []
|
||||||
[ text "by @3wc ("
|
[ text "made with 🤪 at "
|
||||||
|
, autonomic_link
|
||||||
|
, text " ("
|
||||||
, source_link
|
, source_link
|
||||||
, text ")"
|
, text ")"
|
||||||
]
|
]
|
||||||
|
@ -37,9 +37,9 @@ type alias App =
|
|||||||
{ name : String
|
{ name : String
|
||||||
, category : String
|
, category : String
|
||||||
, repository : Maybe String
|
, repository : Maybe String
|
||||||
, versions : Maybe (List String)
|
, versions : List String
|
||||||
, icon : Maybe String
|
, icon : Maybe String
|
||||||
, status : String
|
, status : Int
|
||||||
, slug : String
|
, slug : String
|
||||||
, default_branch : String
|
, default_branch : String
|
||||||
, website : Maybe String
|
, website : Maybe String
|
||||||
@ -151,13 +151,13 @@ title : Model -> String
|
|||||||
title model =
|
title model =
|
||||||
case model.status of
|
case model.status of
|
||||||
Loading ->
|
Loading ->
|
||||||
"loading – abra recipes"
|
"loading – Co-op Cloud Recipes"
|
||||||
|
|
||||||
Failure ->
|
Failure ->
|
||||||
"error –- abra recipes"
|
"error –- Co-op Cloud Recipes"
|
||||||
|
|
||||||
Success app ->
|
Success app ->
|
||||||
app.name ++ " – abra recipes"
|
app.name ++ " – Co-op Cloud Recipes"
|
||||||
|
|
||||||
|
|
||||||
body : Model -> Html Msg
|
body : Model -> Html Msg
|
||||||
@ -192,23 +192,23 @@ viewStatusBadge app =
|
|||||||
let
|
let
|
||||||
status_class =
|
status_class =
|
||||||
case app.status of
|
case app.status of
|
||||||
"1" ->
|
5 ->
|
||||||
"badge-success"
|
"badge-success"
|
||||||
|
|
||||||
"2" ->
|
4 ->
|
||||||
"badge-info"
|
"badge-info"
|
||||||
|
|
||||||
"3" ->
|
3 ->
|
||||||
"badge-warning"
|
"badge-warning"
|
||||||
|
|
||||||
"4" ->
|
2 ->
|
||||||
"badge-danger"
|
"badge-danger"
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
"badge-dark"
|
"badge-dark"
|
||||||
in
|
in
|
||||||
span [ class ("card-link badge " ++ status_class) ]
|
span [ class ("card-link badge " ++ status_class) ]
|
||||||
[ text ("Score: " ++ app.status) ]
|
[ text ("Score: " ++ String.fromInt app.status) ]
|
||||||
|
|
||||||
|
|
||||||
viewApp : App -> String -> Html Msg
|
viewApp : App -> String -> Html Msg
|
||||||
@ -265,13 +265,16 @@ viewApp app readme =
|
|||||||
, repository_link
|
, repository_link
|
||||||
, website_link
|
, website_link
|
||||||
]
|
]
|
||||||
, img [ class "card-img-top", src icon_url, alt ("icon for " ++ app.name) ] []
|
, img [ class "card-img-top card-img-large", src icon_url, alt ("icon for " ++ app.name) ] []
|
||||||
, div [ class "card-body" ]
|
, div [ class "card-body" ]
|
||||||
-- render Markdown with no special options
|
-- render Markdown with no special options
|
||||||
[ div [] (Markdown.toHtml Nothing readme)
|
[ div [] (Markdown.toHtml Nothing readme)
|
||||||
]
|
]
|
||||||
, div [ class "card-footer" ]
|
, div [ class "card-footer" ]
|
||||||
[]
|
[ h5 [] [ text "Versions" ]
|
||||||
|
, ul []
|
||||||
|
(List.map (\version -> li [] [ text version ]) app.versions)
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -283,7 +286,7 @@ viewApp app readme =
|
|||||||
loadApp : Cmd Msg
|
loadApp : Cmd Msg
|
||||||
loadApp =
|
loadApp =
|
||||||
Http.get
|
Http.get
|
||||||
{ url = "https://apps.coopcloud.tech/"
|
{ url = "https://recipes.coopcloud.tech/recipes.json"
|
||||||
, expect = Http.expectJson GotApps appListDecoder
|
, expect = Http.expectJson GotApps appListDecoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,27 +307,37 @@ loadREADME app =
|
|||||||
in
|
in
|
||||||
Http.get
|
Http.get
|
||||||
-- FIXME use live Gitea link
|
-- FIXME use live Gitea link
|
||||||
{ url = "https://cors-container.herokuapp.com/https://git.coopcloud.tech/coop-cloud/" ++ app.slug ++ "/raw/branch/" ++ app.default_branch ++ "/README.md"
|
{ url = "https://git.coopcloud.tech/coop-cloud/" ++ app.slug ++ "/raw/branch/" ++ app.default_branch ++ "/README.md"
|
||||||
, expect = Http.expectString GotText
|
, expect = Http.expectString GotText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
featuresDecoder : Decode.Decoder String
|
|
||||||
featuresDecoder =
|
featuresDecoder =
|
||||||
-- get features.status if it's there
|
|
||||||
Decode.oneOf
|
Decode.oneOf
|
||||||
[ Decode.at [ "status" ] Decode.string
|
[ Decode.at [ "status" ] Decode.int
|
||||||
, Decode.succeed ""
|
, Decode.succeed 5
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
versionsDecoder : Decode.Decoder (List String)
|
||||||
|
versionsDecoder =
|
||||||
|
Decode.list (Decode.keyValuePairs Decode.value)
|
||||||
|
|> Decode.map buildVersions
|
||||||
|
|
||||||
|
|
||||||
|
buildVersions : List (List ( String, Decode.Value )) -> List String
|
||||||
|
buildVersions versions =
|
||||||
|
List.concatMap (List.map (\( version, _ ) -> version)) versions
|
||||||
|
|
||||||
|
|
||||||
appDecoder : Decode.Decoder App
|
appDecoder : Decode.Decoder App
|
||||||
appDecoder =
|
appDecoder =
|
||||||
Decode.succeed App
|
Decode.succeed App
|
||||||
|> andMap (Decode.field "name" Decode.string)
|
|> andMap (Decode.field "name" Decode.string)
|
||||||
|> andMap (Decode.field "category" Decode.string)
|
|> andMap (Decode.field "category" Decode.string)
|
||||||
|> andMap (Decode.maybe (Decode.field "repository" Decode.string))
|
|> andMap (Decode.maybe (Decode.field "repository" Decode.string))
|
||||||
|> andMap (Decode.succeed Nothing)
|
|> andMap (Decode.at [ "versions" ] versionsDecoder)
|
||||||
|
-- |> andMap (Decode.succeed Nothing)
|
||||||
|> andMap (Decode.maybe (Decode.field "icon" Decode.string))
|
|> andMap (Decode.maybe (Decode.field "icon" Decode.string))
|
||||||
|> andMap (Decode.at [ "features" ] featuresDecoder)
|
|> andMap (Decode.at [ "features" ] featuresDecoder)
|
||||||
|> andMap (Decode.succeed "")
|
|> andMap (Decode.succeed "")
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
module Pages.Top exposing (Model, Msg, Params, page)
|
module Pages.Top exposing (Model, Msg, Params, page)
|
||||||
|
|
||||||
import Enum exposing (Enum)
|
import Enum exposing (Enum)
|
||||||
import Html exposing (Html, a, button, div, h2, h5, i, img, li, p, span, text, ul, form, label, select, option, input)
|
import Html exposing (Html, a, button, div, form, h2, h3, h5, i, img, input, label, li, option, p, select, span, text, ul)
|
||||||
import Html.Attributes exposing (alt, class, href, src, style, id, for, value)
|
import Html.Attributes exposing (alt, class, classList, for, href, id, src, style, target, type_, value)
|
||||||
|
import Html.Attributes.Aria exposing (ariaLabel)
|
||||||
import Html.Events exposing (onClick, onInput)
|
import Html.Events exposing (onClick, onInput)
|
||||||
import Http
|
import Http
|
||||||
import Json.Decode as Decode
|
import Json.Decode as Decode
|
||||||
@ -26,6 +27,7 @@ page =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- INIT
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
@ -52,8 +54,8 @@ type alias Model =
|
|||||||
, filter_category : Maybe Category
|
, filter_category : Maybe Category
|
||||||
, filter_text : Maybe String
|
, filter_text : Maybe String
|
||||||
, status : Status
|
, status : Status
|
||||||
, apps : (List App)
|
, apps : List App
|
||||||
, results : (List App)
|
, results : List App
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -74,14 +76,13 @@ type Category
|
|||||||
categories : Enum Category
|
categories : Enum Category
|
||||||
categories =
|
categories =
|
||||||
Enum.create
|
Enum.create
|
||||||
[ ( "Apps", Apps )
|
[ ( "(Everything)", All )
|
||||||
|
, ( "Apps", Apps )
|
||||||
, ( "Utilities", Utilities )
|
, ( "Utilities", Utilities )
|
||||||
, ( "Development", Development )
|
, ( "Development", Development )
|
||||||
, ("All", All)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
init : Url Params -> ( Model, Cmd Msg )
|
init : Url Params -> ( Model, Cmd Msg )
|
||||||
init { params } =
|
init { params } =
|
||||||
( default_model, loadApps )
|
( default_model, loadApps )
|
||||||
@ -94,8 +95,14 @@ default_image =
|
|||||||
|
|
||||||
default_model : Model
|
default_model : Model
|
||||||
default_model =
|
default_model =
|
||||||
{ filter_score = Nothing, filter_category = Nothing, filter_text = Nothing,
|
{ filter_score = Nothing
|
||||||
status = Loading, apps = [], results = [] }
|
, filter_category = Nothing
|
||||||
|
, filter_text = Nothing
|
||||||
|
, status = Loading
|
||||||
|
, apps = []
|
||||||
|
, results = []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- UPDATE
|
-- UPDATE
|
||||||
@ -113,41 +120,55 @@ filterAppsScore : List App -> Maybe Int -> List App
|
|||||||
filterAppsScore apps score =
|
filterAppsScore apps score =
|
||||||
case score of
|
case score of
|
||||||
Just s ->
|
Just s ->
|
||||||
List.filter (\app ->
|
List.filter
|
||||||
app.status <= s
|
(\app ->
|
||||||
) apps
|
app.status >= s
|
||||||
|
)
|
||||||
|
apps
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
apps
|
apps
|
||||||
|
|
||||||
|
|
||||||
filterAppsCategory : List App -> Maybe Category -> List App
|
filterAppsCategory : List App -> Maybe Category -> List App
|
||||||
filterAppsCategory apps category =
|
filterAppsCategory apps category =
|
||||||
case category of
|
case category of
|
||||||
Just c ->
|
Just c ->
|
||||||
if c == All then
|
if c == All then
|
||||||
apps
|
apps
|
||||||
|
|
||||||
else
|
else
|
||||||
List.filter (\app ->
|
List.filter
|
||||||
|
(\app ->
|
||||||
app.category == categories.toString c
|
app.category == categories.toString c
|
||||||
) apps
|
)
|
||||||
|
apps
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
apps
|
apps
|
||||||
|
|
||||||
|
|
||||||
filterAppsText : List App -> Maybe String -> List App
|
filterAppsText : List App -> Maybe String -> List App
|
||||||
filterAppsText apps text =
|
filterAppsText apps text =
|
||||||
case text of
|
case text of
|
||||||
Just "" ->
|
Just "" ->
|
||||||
apps
|
apps
|
||||||
|
|
||||||
Just t ->
|
Just t ->
|
||||||
List.filter (\app ->
|
List.filter
|
||||||
String.contains (String.toLower t) (
|
(\app ->
|
||||||
String.toLower app.name ++ String.toLower (
|
String.contains (String.toLower t)
|
||||||
Maybe.withDefault "" app.description
|
(String.toLower app.name
|
||||||
|
++ String.toLower
|
||||||
|
(Maybe.withDefault "" app.description)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) apps
|
apps
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
apps
|
apps
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
update msg model =
|
update msg model =
|
||||||
case msg of
|
case msg of
|
||||||
@ -156,39 +177,53 @@ update msg model =
|
|||||||
|
|
||||||
FilterScore filter ->
|
FilterScore filter ->
|
||||||
let
|
let
|
||||||
filter_score = String.toInt filter
|
filter_score =
|
||||||
|
String.toInt filter
|
||||||
in
|
in
|
||||||
( { model
|
( { model
|
||||||
| filter_score = filter_score
|
| filter_score = filter_score
|
||||||
, results = filterAppsScore (
|
, results =
|
||||||
filterAppsCategory (
|
filterAppsScore
|
||||||
filterAppsText model.apps model.filter_text
|
(filterAppsCategory
|
||||||
) model.filter_category
|
(filterAppsText model.apps model.filter_text)
|
||||||
) (String.toInt filter)
|
model.filter_category
|
||||||
}, Cmd.none )
|
)
|
||||||
|
(String.toInt filter)
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
FilterCategory filter ->
|
FilterCategory filter ->
|
||||||
let
|
let
|
||||||
category = categories.fromString filter
|
category =
|
||||||
|
categories.fromString filter
|
||||||
in
|
in
|
||||||
( { model
|
( { model
|
||||||
| filter_category = category
|
| filter_category = category
|
||||||
, results = filterAppsCategory (
|
, results =
|
||||||
filterAppsScore (
|
filterAppsCategory
|
||||||
filterAppsText model.apps model.filter_text
|
(filterAppsScore
|
||||||
)model.filter_score
|
(filterAppsText model.apps model.filter_text)
|
||||||
) category
|
model.filter_score
|
||||||
}, Cmd.none )
|
)
|
||||||
|
category
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
FilterText filter ->
|
FilterText filter ->
|
||||||
( { model
|
( { model
|
||||||
| filter_text = Just filter
|
| filter_text = Just filter
|
||||||
, results = filterAppsText (
|
, results =
|
||||||
filterAppsScore (
|
filterAppsText
|
||||||
filterAppsCategory model.apps model.filter_category
|
(filterAppsScore
|
||||||
) model.filter_score
|
(filterAppsCategory model.apps model.filter_category)
|
||||||
) (Just filter)
|
model.filter_score
|
||||||
}, Cmd.none )
|
)
|
||||||
|
(Just filter)
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
GotApps result ->
|
GotApps result ->
|
||||||
case result of
|
case result of
|
||||||
@ -196,11 +231,17 @@ update msg model =
|
|||||||
( { default_model
|
( { default_model
|
||||||
| status = Success
|
| status = Success
|
||||||
, apps = apps
|
, apps = apps
|
||||||
, results = apps }, Cmd.none )
|
, results = apps
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
Err _ ->
|
Err _ ->
|
||||||
( { default_model
|
( { default_model
|
||||||
| status = Failure}, Cmd.none )
|
| status = Failure
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
subscriptions : Model -> Sub Msg
|
||||||
@ -214,14 +255,14 @@ subscriptions model =
|
|||||||
|
|
||||||
view : Model -> Document Msg
|
view : Model -> Document Msg
|
||||||
view model =
|
view model =
|
||||||
{ title = "abra recipes"
|
{ title = "Co-op Cloud Recipes"
|
||||||
, body = [ body model ]
|
, body = [ body model ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
body : Model -> Html Msg
|
body : Model -> Html Msg
|
||||||
body model =
|
body model =
|
||||||
div [ class "pt-3" ]
|
div []
|
||||||
[ viewApps model
|
[ viewApps model
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -231,16 +272,16 @@ viewStatusBadge app =
|
|||||||
let
|
let
|
||||||
status_class =
|
status_class =
|
||||||
case app.status of
|
case app.status of
|
||||||
1 ->
|
5 ->
|
||||||
"badge-success"
|
"badge-success"
|
||||||
|
|
||||||
2 ->
|
4 ->
|
||||||
"badge-info"
|
"badge-info"
|
||||||
|
|
||||||
3 ->
|
3 ->
|
||||||
"badge-warning"
|
"badge-warning"
|
||||||
|
|
||||||
4 ->
|
2 ->
|
||||||
"badge-danger"
|
"badge-danger"
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
@ -303,16 +344,21 @@ viewApp app =
|
|||||||
, p [ class "card-description" ] [ text (ellipsis 100 (withDefault "" app.description)) ]
|
, p [ class "card-description" ] [ text (ellipsis 100 (withDefault "" app.description)) ]
|
||||||
]
|
]
|
||||||
, div [ class "footer" ]
|
, div [ class "footer" ]
|
||||||
[ span [ class "badge app-category" ] [ text app.category ]]
|
[ viewStatusBadge app, span [ class "badge app-category" ] [ text app.category ] ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewCategory : Model -> ( String, Category ) -> Html Msg
|
||||||
viewCategories : (String, Category) -> Html Msg
|
viewCategory model category =
|
||||||
viewCategories category =
|
div
|
||||||
div [ class "category-tile", onClick (FilterCategory (Tuple.first category)) ] [text (Tuple.first category)]
|
[ classList
|
||||||
|
[ ( "category-tile", True )
|
||||||
|
, ( "active", categories.toString (Maybe.withDefault All model.filter_category) == Tuple.first category )
|
||||||
|
]
|
||||||
|
, onClick (FilterCategory (Tuple.first category))
|
||||||
|
]
|
||||||
|
[ text (Tuple.first category) ]
|
||||||
|
|
||||||
|
|
||||||
viewApps : Model -> Html Msg
|
viewApps : Model -> Html Msg
|
||||||
@ -335,52 +381,86 @@ viewApps model =
|
|||||||
|
|
||||||
Success ->
|
Success ->
|
||||||
div [ class "row justify-content-center" ]
|
div [ class "row justify-content-center" ]
|
||||||
[ div [ class "col-md-3" ] [
|
[ div [ class "col-md-3", id "filter" ]
|
||||||
h2 [ class "app-headings" ] [text "Categories"]
|
[ h2 [ class "app-headings" ] [ text "Finding things" ]
|
||||||
, div [] (List.map viewCategories categories.list)
|
, form []
|
||||||
|
[ div []
|
||||||
|
[ h3 [] [ text "Search" ]
|
||||||
|
, input [ ariaLabel "search", id "text", onInput FilterText ] []
|
||||||
]
|
]
|
||||||
, div [ class "col-md-6" ]
|
, div []
|
||||||
|
[ h3 [] [ text "Categories" ]
|
||||||
|
, div [] (List.map (viewCategory model) categories.list)
|
||||||
|
]
|
||||||
|
, div []
|
||||||
|
[ h3 [] [ text "Status" ]
|
||||||
|
, div []
|
||||||
|
[ label [] [ text "Minimum score: " ]
|
||||||
|
, a [ class "help", target "_blank", href "https://docs.coopcloud.tech/abra/recipes/#status-features-score" ] [ text "(help)" ]
|
||||||
|
]
|
||||||
|
, select [ class "search-dropdown", id "level", onInput FilterScore ]
|
||||||
|
[ option [] [ text "any" ]
|
||||||
|
, option [ value "5" ] [ text "5 (amazing)" ]
|
||||||
|
, option [ value "4" ] [ text "4 (good)" ]
|
||||||
|
, option [ value "3" ] [ text "3 (ok)" ]
|
||||||
|
, option [ value "2" ] [ text "2 (basic)" ]
|
||||||
|
, option [ value "1" ] [ text "1 (chaos)" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "col-md-6 offset-md-3" ]
|
||||||
[ div [ class "row" ]
|
[ div [ class "row" ]
|
||||||
[ div [ class "col-sm-12" ] [
|
[ div [ class "col-sm-12" ]
|
||||||
div [] [
|
[ div []
|
||||||
form [ class "search-bar-container" ] [
|
[]
|
||||||
label [ for "text" ] [ text "Search" ]
|
|
||||||
, input [ class "search-bar-input", id "text", onInput FilterText ] []
|
|
||||||
, label [ for "level" ] [ text " and show only items that have at least " ]
|
|
||||||
, select [ class "search-dropdown", id "level", onInput FilterScore ] [
|
|
||||||
option [ ] [ text "any" ]
|
|
||||||
, option [ value "1" ] [ text "1 (production)" ]
|
|
||||||
, option [ value "2" ] [ text "2 (beta)" ]
|
|
||||||
, option [ value "3" ] [ text "3 (alpha)" ]
|
|
||||||
, option [ value "4" ] [ text "4 (pre-alpha)" ]
|
|
||||||
]
|
|
||||||
, text "builds "
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
, div [ id "intro", class "card" ]
|
||||||
|
[ div [ class "card-body" ]
|
||||||
|
[ h2 [ class "app-headings card-title" ] [ text "Co-op Cloud Recipes" ]
|
||||||
|
, text "You can use these recipes ("
|
||||||
|
, a
|
||||||
|
[ href
|
||||||
|
"https://docs.coopcloud.tech/intro/glossary/#recipe"
|
||||||
|
]
|
||||||
|
[ text "What's a recipe?"
|
||||||
|
]
|
||||||
|
, text ") with "
|
||||||
|
, a [ href "https://coopcloud.tech" ]
|
||||||
|
[ text "Co-op Cloud"
|
||||||
|
]
|
||||||
|
, text "."
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, h2 [ class "app-headings" ] [ text "Apps" ]
|
|
||||||
, div [ class "row" ]
|
, div [ class "row" ]
|
||||||
(List.map viewApp
|
(List.map viewApp
|
||||||
(model.results
|
(model.results
|
||||||
|> List.sortWith
|
|> List.sortWith
|
||||||
(by .status ASC
|
(by .status DESC
|
||||||
|> andThen (String.toLower << .name) ASC
|
|> andThen (String.toLower << .name) ASC
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
, div [class "col-md-3"] []
|
, div [ class "col-md-3" ] []
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- HTTP
|
-- HTTP
|
||||||
|
|
||||||
|
|
||||||
loadApps : Cmd Msg
|
loadApps : Cmd Msg
|
||||||
loadApps =
|
loadApps =
|
||||||
Http.get
|
Http.request
|
||||||
{ url = "https://apps.coopcloud.tech/"
|
{ url = "https://recipes.coopcloud.tech/recipes.json"
|
||||||
, expect = Http.expectJson GotApps appListDecoder
|
, expect = Http.expectJson GotApps appListDecoder
|
||||||
|
, headers = [ Http.header "Content-Type" "application/json" ]
|
||||||
|
, body = Http.emptyBody
|
||||||
|
, method = "GET"
|
||||||
|
, timeout = Nothing
|
||||||
|
, tracker = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,15 +70,15 @@ view { page, toMsg } model =
|
|||||||
, body =
|
, body =
|
||||||
[ div [ class "background" ]
|
[ div [ class "background" ]
|
||||||
[ nav
|
[ nav
|
||||||
[ class "navbar navbar-expand-lg sticky-top font-weight-bold" ]
|
[ class "navbar navbar-expand-lg sticky-top" ]
|
||||||
[ a [ class "navbar-text", href (Route.toString Route.Top) ]
|
[ a [ class "font-weight-bold navbar-text", href (Route.toString Route.Top) ]
|
||||||
[ img
|
[ img
|
||||||
[ src "/logo-2.png"
|
[ src "/logo-2.png"
|
||||||
, class "d-inline-block align-top mr-2"
|
, class "d-inline-block align-top mr-2"
|
||||||
, alt ""
|
, alt ""
|
||||||
, width 48
|
, width 48
|
||||||
, height 16 ] []
|
, height 16 ] []
|
||||||
, text "abra recipes"
|
, text "Co-op Cloud Recipes"
|
||||||
]
|
]
|
||||||
, ul [ class "navbar-nav" ]
|
, ul [ class "navbar-nav" ]
|
||||||
[ li [ class "nav-tem" ] [ a [ class "nav-link navbar-text", href (Route.toString Route.About) ] [ text "about" ] ]
|
[ li [ class "nav-tem" ] [ a [ class "nav-link navbar-text", href (Route.toString Route.About) ] [ text "about" ] ]
|
||||||
|
Reference in New Issue
Block a user