jrosell commited on
Commit
f96ea09
·
1 Parent(s): 12a073f
Files changed (3) hide show
  1. Dockerfile +40 -0
  2. README.md +26 -0
  3. app.R +334 -0
Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM rocker/r2u:22.04
2
+
3
+ WORKDIR /workspace
4
+
5
+ ENV RUSTUP_HOME=/usr/local/rustup \
6
+ CARGO_HOME=/usr/local/cargo \
7
+ PATH=/usr/local/cargo/bin:$PATH
8
+
9
+ RUN set -eux; \
10
+ apt-get update; \
11
+ apt-get install -y --no-install-recommends \
12
+ ca-certificates \
13
+ gcc \
14
+ libc6-dev \
15
+ wget \
16
+ ; \
17
+ \
18
+ url="https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init"; \
19
+ wget "$url"; \
20
+ chmod +x rustup-init; \
21
+ ./rustup-init -y --no-modify-path --default-toolchain beta --default-host x86_64-unknown-linux-gnu; \
22
+ rm rustup-init; \
23
+ chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \
24
+ rustup --version; \
25
+ cargo --version; \
26
+ rustc --version; \
27
+ \
28
+ apt-get remove -y --auto-remove \
29
+ wget \
30
+ ; \
31
+ rm -rf /var/lib/apt/lists/*;
32
+
33
+ RUN R -e "install.packages(c('htmltools', 'tidyverse', 'zeallot', 'rlang', 'glue', 'this.path', 'DBI', 'pool', 'RSQLite', 'remotes'))"
34
+
35
+ RUN R -e "install.packages('b64', repos = c('https://extendr.r-universe.dev', 'https://cloud.r-project.org')); install.packages('uwu', repos = c('https://josiahparry.r-universe.dev', 'https://cloud.r-project.org'))"
36
+
37
+ COPY . .
38
+
39
+ EXPOSE 7860
40
+ CMD R -e "print(Sys.getenv('GITHUB_PAT')); remotes::install_github(c('devOpifex/ambiorix', 'jrosell/ambhtmx', 'devOpifex/scilis', 'devOpifex/signaculum')); options(ambiorix.host='0.0.0.0', 'ambiorix.port'=7860);source('app.R')"
README.md CHANGED
@@ -8,3 +8,29 @@ pinned: false
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
11
+
12
+
13
+ # Docker in local
14
+
15
+ To create the image and run the container in Docker, run:
16
+
17
+ ```
18
+ docker build -f Dockerfile -t ambhtmx . \
19
+ && docker run --env-file=.Renviron -p 7860:7860 --name ambhtmx-container --rm ambhtmx
20
+ ```
21
+
22
+ If you need an output folder:
23
+ ```
24
+ docker build -f Dockerfile -t ambhtmx . \
25
+ && docker run -env-file=.Renviron -p 7860:7860 --name ambhtmx-container --rm -v $(pwd)/output:/workspace/output/:rw ambhtmx
26
+ ```
27
+
28
+
29
+ To stop, remove the container and create the image and run another container:
30
+ ```
31
+ docker container rm -f ambhtmx-container \
32
+ && docker build -f Dockerfile -t ambhtmx . \
33
+ && docker run --env-file=.Renviron -p 7860:7860 --name ambhtmx-container --rm ambhtmx
34
+ ```
35
+
36
+
app.R ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ library(ambhtmx)
2
+ # devtools::load_all()
3
+ library(ambiorix)
4
+ library(tidyverse)
5
+ library(zeallot)
6
+ library(glue)
7
+ library(htmltools)
8
+ library(signaculum)
9
+
10
+ page_title <- "Password protected CRUD (Create, Read, Update, and Delete) example with ambhtmx"
11
+
12
+ live_path <- tryCatch(
13
+ {this.path::this.path()},
14
+ error = function(e) return("")
15
+ )
16
+ print(live_path)
17
+
18
+ render_index <- \() {
19
+ main <- NULL
20
+ tryCatch({
21
+ index <- p("Add your first item.")
22
+ item_rows <- items$read_rows()
23
+ if(nrow(item_rows) > 0) {
24
+ index <- item_rows |>
25
+ rowwise() |>
26
+ group_split() |>
27
+ map(\(item) {
28
+ tags$li(
29
+ tags$a(
30
+ item$name,
31
+ href = glue("/items/{item$id}"),
32
+ `hx-get`= glue("/items/{item$id}"),
33
+ `hx-target` = "#main",
34
+ `hx-swap` = "innerHTML"
35
+ )
36
+ )
37
+ })
38
+ }
39
+ main <- withTags(div(id = "page", style = "margin: 50px",
40
+ div(style ="float:right", id = "logout", button("Logout", onclick = "void(location.href='/logout')")),
41
+ h1(page_title),
42
+ div(id = "main", style = "margin-top: 20px", tagList(
43
+ h2("Index of items"),
44
+ index,
45
+ button(
46
+ "New item",
47
+ style = "margin-top:20px",
48
+ `hx-get` = "/items/new",
49
+ `hx-target` = "#main",
50
+ `hx-swap` = "innerHTML"
51
+ )
52
+ ))
53
+ ))
54
+ },
55
+ error = \(e) print(e)
56
+ )
57
+ return(main)
58
+ }
59
+
60
+
61
+ render_row <- \(item) {
62
+ tags$div(
63
+ tags$strong(item$name),
64
+ tags$br(),
65
+ HTML(item$content)
66
+ )
67
+ }
68
+
69
+ #' Starting the app
70
+ counter <- 0
71
+ c(app, context, items) %<-%
72
+ ambhtmx_app(
73
+ "items.sqlite",
74
+ host = getOption("ambiorix.host"),
75
+ port = getOption("ambiorix.port"),
76
+ value = tibble(
77
+ id = character(1),
78
+ name = character(1),
79
+ content = character(1)
80
+ ),
81
+ # live = live_path,
82
+ render_index = render_index,
83
+ render_row = render_row
84
+ )
85
+
86
+ #' Authentication feature with secret cookies and .Renviron variables
87
+ app$get("/login", \(req, res) {
88
+ process_login_get(req, res)
89
+ })
90
+ app$post("/login", \(req, res) {
91
+ process_login_post(
92
+ req,
93
+ res,
94
+ user = Sys.getenv("AMBHTMX_USER"),
95
+ password = Sys.getenv("AMBHTMX_PASSWORD")
96
+ )
97
+ })
98
+ app$get("/logout", \(req, res) {
99
+ process_logout_get(req, res)
100
+ })
101
+ app$use(\(req, res){
102
+ process_loggedin_middleware(
103
+ req,
104
+ res,
105
+ user = Sys.getenv("AMBHTMX_USER")
106
+ )
107
+ })
108
+
109
+ #' Some CRUD operations examples
110
+ cat("\nBe sure is initially empty:\n")
111
+ walk(items$read_rows()$id, \(x) items$delete_row(id = x))
112
+ items$read_rows() |> print()
113
+
114
+ cat("\nAdd some items:\n")
115
+ tibble(name = "Elis elis", content = "Putxinelis.",) |>
116
+ items$add_row() -> first_id
117
+ tibble(name = "Que bombolles", content = "T\'empatolles.") |>
118
+ items$add_row() -> some_id
119
+ tibble(name = "Holi", content = "Guapi.") |>
120
+ items$add_row() -> last_id
121
+ items$read_rows() |> print()
122
+
123
+ cat("\nDelete last item:\n")
124
+ items$delete_row(id = last_id)
125
+ items$read_rows() |> print()
126
+
127
+ cat("\nUpdate first items:\n")
128
+ tibble(name = "First", content = "Hello in <span style='color:red'>red</span>.") |>
129
+ items$update_row(id = first_id)
130
+ items$read_rows() |> print()
131
+
132
+ cat("\nRender the first item:\n")
133
+ items$read_row(id = first_id) |>
134
+ items$read_row() |>
135
+ as.character() |>
136
+ cat()
137
+
138
+ cat("\nAdd an item with id 1:\n")
139
+ tibble(id = "1", name = "Quines postres", content = "Tant bones.") |>
140
+ items$add_row()
141
+ items$read_rows() |> print()
142
+
143
+
144
+ #' The main page
145
+ app$get("/", \(req, res){
146
+ if (!req$loggedin) {
147
+ return(res$redirect("/login", status = 302L))
148
+ }
149
+ html <- ""
150
+ tryCatch({
151
+ html <- render_page(
152
+ page_title = page_title,
153
+ main = items$render_index()
154
+ )
155
+ },
156
+ error = \(e) print(e)
157
+ )
158
+ res$send(html)
159
+ })
160
+
161
+ #' Read the index of the items
162
+ app$get("/items", \(req, res){
163
+ if (!req$loggedin) {
164
+ return(res$redirect("/login", status = 302L))
165
+ }
166
+ res$send(items$render_index())
167
+ })
168
+
169
+ #' New item form
170
+ app$get("/items/new", \(req, res){
171
+ if (!req$loggedin) {
172
+ return(res$redirect("/login", status = 302L))
173
+ }
174
+ errors <- process_error_get(req, res)
175
+ html <- render_tags(withTags(tagList(
176
+ h2("New item"),
177
+ div(label("Name", p(input(name = "name")))),
178
+ div(label("Content", p(textarea(name = "content")))),
179
+ a(
180
+ "Go back",
181
+ href = "/",
182
+ style = "margin-right:20px",
183
+ `hx-confirm` = "Are you sure you want to go back?",
184
+ `hx-get` = "/items",
185
+ `hx-target` = "#page",
186
+ `hx-swap` = "outerHTML",
187
+ `hx-encoding` = "multipart/form-data"
188
+ ),
189
+ button(
190
+ "Create",
191
+ style = "margin-top:20px",
192
+ `hx-post` = "/items",
193
+ `hx-target` = "#page",
194
+ `hx-swap` = "outerHTML",
195
+ `hx-include` = "[name='name'], [name='content']",
196
+ ),
197
+ errors
198
+ )))
199
+ res$send(html)
200
+ })
201
+
202
+
203
+ #' Show an existing item
204
+ app$get("/items/:id", \(req, res){
205
+ if (!req$loggedin) {
206
+ return(res$redirect("/login", status = 302L))
207
+ }
208
+ item_id <- req$params$id %||% ""
209
+ item <- items$read_row(id = item_id)
210
+ html <- render_tags(withTags(tagList(
211
+ h2("Show item details"),
212
+ items$render_row(item),
213
+ a(
214
+ "Go back",
215
+ href = "/",
216
+ style = "margin-right:20px",
217
+ `hx-get` = "/items",
218
+ `hx-target` = "#page",
219
+ `hx-swap` = "outerHTML",
220
+ ),
221
+ a(
222
+ "Delete",
223
+ href = "/",
224
+ style = "color: red; margin-right:20px",
225
+ `hx-confirm` = "Are you sure you want to delete the item?",
226
+ `hx-delete` = glue("/items/{item$id}"),
227
+ `hx-target` = "#page",
228
+ `hx-swap` = "outerHTML",
229
+ `hx-encoding` = "multipart/form-data"
230
+ ),
231
+ button(
232
+ "Edit",
233
+ style = "margin-top:20px",
234
+ `hx-get` = glue("/items/{item_id}/edit"),
235
+ `hx-target` = "#main",
236
+ `hx-swap` = "innerHTML"
237
+ )
238
+ )))
239
+ res$send(html)
240
+ })
241
+
242
+ #' Edit item form
243
+ app$get("/items/:id/edit", \(req, res){
244
+ if (!req$loggedin) {
245
+ return(res$redirect("/login", status = 302L))
246
+ }
247
+ item_id <- req$params$id %||% ""
248
+ item <- items$read_row(id = item_id)
249
+ html <- render_tags(withTags(tagList(
250
+ h2("Edit item"),
251
+ input(type = "hidden", name = "id", value = item$id),
252
+ div(label("Name", p(input(name = "name", value = item$name)))),
253
+ div(HTML(glue('<textarea rows=5 name="content">{item$content}</textarea>'))),
254
+ a(
255
+ "Go back",
256
+ href = "/",
257
+ style = "margin-right:20px",
258
+ `hx-confirm` = "Are you sure you want to go back?",
259
+ `hx-get` = "/items",
260
+ `hx-target` = "#page",
261
+ `hx-swap` = "outerHTML",
262
+ `hx-encoding` = "multipart/form-data"
263
+ ),
264
+ button(
265
+ "Update",
266
+ style = "margin-top:20px",
267
+ `hx-put` = glue("/items/{item$id}"),
268
+ `hx-target` = "#page",
269
+ `hx-swap` = "outerHTML",
270
+ `hx-include` = "[name='name'], [name='content']",
271
+ )
272
+ )))
273
+ res$send(html)
274
+ })
275
+
276
+ #' Create a new item
277
+ app$post("/items", \(req, res){
278
+ if (!req$loggedin) {
279
+ return(res$redirect("/login", status = 302L))
280
+ }
281
+ params <- parse_multipart(req)
282
+ if (is.null(params[["name"]])) {
283
+ return(process_error_post(
284
+ req,
285
+ res,
286
+ errors = "Name is required",
287
+ error_url = "/items/new"
288
+ ))
289
+ }
290
+ if (is.null(params[["content"]])) {
291
+ params[["content"]] = ""
292
+ }
293
+ tryCatch({
294
+ params |>
295
+ as_tibble() |>
296
+ items$add_row()
297
+ },
298
+ error = \(e) print(e)
299
+ )
300
+ res$send(items$render_index())
301
+ })
302
+
303
+ #' Update an existing item
304
+ app$put("/items/:id", \(req, res){
305
+ if (!req$loggedin) {
306
+ return(res$redirect("/login", status = 302L))
307
+ }
308
+ item_id <- req$params$id %||% ""
309
+ params <- parse_multipart(req) |>
310
+ as_tibble() |>
311
+ mutate(id = item_id)
312
+ item <- items$read_row(id = item_id)
313
+ tryCatch({
314
+ item |>
315
+ dplyr::rows_upsert(params, by = "id") |>
316
+ items$update_row()
317
+ },
318
+ error = \(e) print(e)
319
+ )
320
+ res$send(items$render_index())
321
+ })
322
+
323
+ #' Delete an existing item
324
+ app$delete("/items/:id", \(req, res){
325
+ if (!req$loggedin) {
326
+ return(res$redirect("/login", status = 302L))
327
+ }
328
+ item_id <- req$params$id %||% ""
329
+ items$delete_row(id = item_id)
330
+ res$send(items$render_index())
331
+ })
332
+
333
+ #' Start the app with all the previous defined routes
334
+ app$start(open = FALSE)