Compare commits

..

18 Commits

Author SHA1 Message Date
Hassan Ben Jobrane
ea69d4f0f1 Merge pull request #2341 from nhost/changeset-release/main
chore: update versions
2023-10-25 14:53:21 +01:00
github-actions[bot]
212d58bee5 chore: update versions 2023-10-25 13:22:09 +00:00
Hassan Ben Jobrane
c3d6b7beec Merge pull request #2333 from nhost/feat/vue-sdk/upload-multiple-files
feat(vue-sdk): add support for uploading multiple files
2023-10-25 14:18:36 +01:00
Hassan Ben Jobrane
5d5d8ef4f3 chore: use @nhost/nhost-js from workspace 2023-10-25 13:31:12 +01:00
Hassan Ben Jobrane
deb61fe97c chore: add @nhost/nhost-js to vue-apollo example 2023-10-25 13:21:36 +01:00
Nestor Manrique
04d36154b0 Merge pull request #2334 from nhost/nestor/feat/add-ingress-dashboard
feat(observability): Add ingress metrics dashboard for tenants
2023-10-25 14:21:01 +02:00
Hassan Ben Jobrane
203cfb10b9 chore: fix JSDoc 2023-10-25 12:43:54 +01:00
Hassan Ben Jobrane
9690f871fa chore: fix JSDoc 2023-10-25 11:44:45 +01:00
Hassan Ben Jobrane
74a6b93971 Merge pull request #2335 from nhost/chore/examples/upgrade-to-node18
chore: update toml files to use node 18
2023-10-25 10:36:37 +01:00
Nestor Manrique
dd4c0d2430 wip 2023-10-25 03:30:52 +02:00
Hassan Ben Jobrane
83f2ca5cde chore: update toml files to use node 18 2023-10-24 16:39:09 +01:00
Hassan Ben Jobrane
0c49e757c8 chore: add changeset 2023-10-24 16:25:07 +01:00
Hassan Ben Jobrane
e90a9d7696 feat: add storage page to vue-apollo example 2023-10-24 16:20:02 +01:00
Hassan Ben Jobrane
00a06466f5 fix: return refs from useFileUpload 2023-10-24 16:20:02 +01:00
Hassan Ben Jobrane
8ca9f76cb2 wip: add support for uploading multiple files 2023-10-24 16:20:02 +01:00
Hassan Ben Jobrane
78113dd62a wip: feat: vue-sdk: introduce new composable to upload multiple files 2023-10-24 16:20:02 +01:00
Nestor Manrique
adb0ee82c6 wip 2023-10-24 14:29:25 +02:00
Nestor Manrique
a41bb6cae6 wip 2023-10-24 14:05:42 +02:00
33 changed files with 3382 additions and 212 deletions

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.1'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.21.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -28,7 +28,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -1,5 +1,13 @@
# @nhost-examples/vue-apollo
## 0.0.9
### Patch Changes
- 0c49e757c: feat: add new Storage page to demonstrate how to use the composables `useMultipleFilesUpload` together with `useFileUploadItem`
- Updated dependencies [0c49e757c]
- @nhost/vue@1.14.0
## 0.0.8
### Patch Changes

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/vue-apollo",
"private": true,
"version": "0.0.8",
"version": "0.0.9",
"scripts": {
"dev": "vite",
"build": "vite build",
@@ -17,14 +17,16 @@
"@apollo/client": "^3.7.1",
"@mdi/font": "5.9.55",
"@nhost/apollo": "*",
"@nhost/nhost-js": "*",
"@nhost/vue": "*",
"@vue/apollo-composable": "4.0.0-alpha.18",
"graphql": "15.7.2",
"graphql": "^16.8.1",
"graphql-tag": "^2.12.6",
"roboto-fontface": "*",
"vite-plugin-vuetify": "^1.0.1",
"vue": "^3.2.41",
"vue-router": "^4.1.6",
"vue3-dropzone": "^2.1.2",
"vuetify": "3.0.0-beta.10",
"webfontloader": "^1.0.0"
},
@@ -35,7 +37,7 @@
"@xstate/inspect": "^0.6.2",
"sass": "1.32.0",
"typescript": "4.9.4",
"vite": "^4.0.2",
"vite": "^4.5.0",
"vue-tsc": "^0.38.9"
},
"eslintConfig": {

1657
examples/vue-apollo/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
<script setup lang="ts">
import { ref, toRaw, unref, type Ref } from 'vue'
import { useDropzone, type FileRejectReason } from 'vue3-dropzone'
const { multiple } = defineProps({
multiple: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['onDrop'])
const files: Ref<File[]> = ref([])
const errors: Ref<FileRejectReason[]> = ref([])
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
multiple
})
function onDrop(acceptFiles: File[], rejectReasons: FileRejectReason[]) {
files.value = acceptFiles
errors.value = rejectReasons
emit('onDrop', toRaw(unref(files)))
}
</script>
<template>
<div>
<div class="dropzone" v-bind="getRootProps()">
<div
class="border"
:class="{
isDragActive
}"
>
<input v-bind="getInputProps()" />
<p v-if="isDragActive">Drop here ...</p>
<p v-else>Drag and drop here, or Click to select</p>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.dropzone,
.files {
width: 100%;
margin: 0 auto;
padding: 10px;
border-radius: 8px;
box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
font-size: 12px;
line-height: 1.5;
}
.border {
border: 2px dashed #ccc;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
transition: all 0.3s ease;
background: #fff;
&.isDragActive {
border: 2px dashed #ffb300;
background: rgb(255 167 18 / 20%);
}
}
.file-item {
border-radius: 8px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgb(255 167 18 / 20%);
padding: 7px;
padding-left: 15px;
margin-top: 10px;
&:first-child {
margin-top: 0;
}
.delete-file {
background: red;
color: #fff;
padding: 5px 10px;
border-radius: 8px;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,34 @@
<script lang="ts" setup>
import { FileItemRef } from '@nhost/nhost-js'
import { useFileUploadItem } from '@nhost/vue'
const { file } = defineProps<{ file: FileItemRef }>()
const { name, progress } = useFileUploadItem(file)
</script>
<template>
<div class="container">
<span class="file_name">{{ name }}</span>
<v-progress-linear v-model="progress" color="green">
{{ progress }}
</v-progress-linear>
</div>
</template>
<style lang="css" scoped>
.container {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 1rem;
}
.file_name {
margin-right: 1rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 40%;
}
</style>

View File

@@ -3,8 +3,14 @@
<v-list-item title="Home" to="/" value="home" prepend-icon="mdi-home" />
<v-list-item title="Profile" to="/profile" value="profile" prepend-icon="mdi-account" />
<v-list-item title="Apollo" to="/apollo" value="apollo" prepend-icon="mdi-api" />
<v-list-item title="Storage" to="/storage" value="storage" prepend-icon="mdi-server" />
<v-list-item title="About" to="/about" value="about" prepend-icon="mdi-information" />
<v-list-item v-if="authenticated" title="Sign out" prepend-icon="mdi-exit-to-app" @click="signOutHandler" />
<v-list-item
v-if="authenticated"
title="Sign out"
prepend-icon="mdi-exit-to-app"
@click="signOutHandler"
/>
</v-list>
</template>

View File

@@ -0,0 +1,129 @@
<template>
<div className="d-flex align-center flex-column">
<h1>Storage</h1>
<v-card width="400" class="singleFileUpload" tile>
<v-card-title>Upload a single file</v-card-title>
<v-card-text>
<FileDropZone @on-drop="onDropSingleFile" />
<span v-if="isUploading" class="upload_status">Uploading...</span>
<span v-if="isUploaded" class="upload_status upload_success">Upload Succeeded</span>
<span v-if="isError" class="upload_status upload_error">Upload Failed</span>
<div v-if="fileToUpload" class="file_progress">
<span class="file_progress_name">{{ fileToUpload.name }}</span>
<v-progress-linear v-model="progress" color="green">
{{ progress }}
</v-progress-linear>
<button class="clearButton" @click="clearFile">Clear</button>
</div>
</v-card-text>
</v-card>
<v-card width="400" tile class="relative">
<v-card-title>Upload multiple files</v-card-title>
<v-card-text class="footer">
<FileDropZone :multiple="true" @on-drop="onDropMultipleFiles" />
<span v-if="isUploadingAll" class="upload_status">Uploading...</span>
<span v-if="isUploadedAll" class="upload_status upload_success">Upload Succeeded</span>
<span v-if="isErrorAll" class="upload_status upload_error">Upload Failed</span>
</v-card-text>
<div v-for="(file, index) of files" :key="index">
<FileUploadItem :file="file" />
</div>
<div class="relative buttonsContainer">
<v-btn
class="mb-2 text-white w-100"
:disabled="!files.length"
variant="elevated"
color="green"
@click="uploadAll"
>
Upload
</v-btn>
<v-btn class="w-100" @click="clear"> Clear </v-btn>
</div>
</v-card>
</div>
</template>
<script lang="ts" setup>
import { useFileUpload, useMultipleFilesUpload } from '@nhost/vue'
import { ref } from 'vue'
import FileDropZone from '../components/FileDropZone.vue'
import FileUploadItem from '../components/FileUploadItem.vue'
const fileToUpload = ref<File | null>(null)
const { upload, progress, isUploaded, isUploading, isError } = useFileUpload()
const onDropSingleFile = async ([file]: File[]) => {
fileToUpload.value = file
upload({ file })
}
const clearFile = () => {
fileToUpload.value = null
isUploaded.value = false
}
const {
add,
upload: uploadAll,
isUploaded: isUploadedAll,
isUploading: isUploadingAll,
isError: isErrorAll,
files,
clear
} = useMultipleFilesUpload()
const onDropMultipleFiles = (files: File[]) => {
add({ files })
}
</script>
<style lang="css" scoped>
.buttonsContainer {
padding: 1rem;
}
.upload_success {
color: 'green';
}
.upload_error {
color: 'red';
}
.file_progress {
display: flex;
flex-direction: row;
align-items: center;
}
.file_progress_name {
margin-right: 1rem;
}
.upload_status {
display: block;
margin: 1rem 0;
}
.singleFileUpload {
margin-bottom: 1rem;
}
.clearButton {
margin-left: 1rem;
background: red;
color: #fff;
padding: 5px 10px;
border-radius: 8px;
cursor: pointer;
}
.relative {
position: relative;
}
</style>

View File

@@ -13,6 +13,7 @@ import SignUpEmailPasword from './pages/sign-up/EmailPassword.vue'
import SignUpEmailPaswordless from './pages/sign-up/EmailPasswordless.vue'
import SignUp from './pages/sign-up/IndexPage.vue'
import Signout from './pages/SignoutPage.vue'
import StoragePage from './pages/StoragePage.vue'
export const routes: RouteRecordRaw[] = [
{ path: '/', component: Index, meta: { auth: true } },
@@ -50,5 +51,6 @@ export const routes: RouteRecordRaw[] = [
}
]
},
{ path: '/apollo', component: ApolloPage, meta: { auth: true } }
{ path: '/apollo', component: ApolloPage, meta: { auth: true } },
{ path: '/storage', component: StoragePage, meta: { auth: true } }
]

View File

@@ -0,0 +1,805 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "9.2.0"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 6,
"panels": [],
"title": "General",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Number of requests by method/function",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 1
},
"id": 2,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(method, route) (increase(nginx_ingress_controller_requests{method=~\"$method\",ingress=~\"$ingress\"}[$__rate_interval]))",
"format": "time_series",
"interval": "2m",
"legendFormat": "{{method}} {{route}}",
"range": true,
"refId": "A"
}
],
"title": "Requests",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Number of requests by status response",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 1
},
"id": 21,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(status) (increase(nginx_ingress_controller_requests{method=~\"$method\",ingress=~\"$ingress\"}[$__rate_interval]))",
"format": "time_series",
"interval": "2m",
"legendFormat": "{{status}}",
"range": true,
"refId": "A"
}
],
"title": "Response Status",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"id": 8,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(ingress, method) (increase(nginx_ingress_controller_response_size_sum{ingress=~\"$ingress\",method=~\"$method\"}[$__rate_interval])) / sum by(ingress, method) (increase(nginx_ingress_controller_requests{ingress=~\"$ingress\",method=~\"$method\"}[$__rate_interval]))",
"interval": "2m",
"legendFormat": "{{ method }} - {{ route }}",
"range": true,
"refId": "A"
}
],
"title": "Average Response Size",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.width",
"value": 183
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"id": 4,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Value"
}
]
},
"pluginVersion": "9.2.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"exemplar": false,
"expr": "ceil(sum by(ingress, method) (increase(nginx_ingress_controller_requests{ingress=~\"$ingress\",method=~\"$method\"}[$__range])))",
"format": "table",
"instant": true,
"interval": "2m",
"legendFormat": "{{ingress}} {{method}}",
"range": false,
"refId": "A"
}
],
"title": "Total Requests",
"transformations": [],
"type": "table"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 17
},
"id": 26,
"panels": [],
"title": "Response Times",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 18
},
"id": 11,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(method, ingress) (increase(nginx_ingress_controller_response_duration_seconds_sum{method=~\"$method\", ingress=~\"$ingress\"}[$__rate_interval])) / sum by(method, ingress) (increase(nginx_ingress_controller_response_duration_seconds_count{method=~\"$method\", ingress=~\"$ingress\"}[$__rate_interval]))",
"interval": "2m",
"legendFormat": "{{ ingress }} - {{ method }}",
"range": true,
"refId": "A"
}
],
"title": "Average Response Time",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 26
},
"id": 19,
"panels": [],
"title": "Errors",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Number of requests that failed divided by the total number of requests",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "percentunit"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 27
},
"id": 20,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(ingress,method) (increase(nginx_ingress_controller_requests{ingress=~\"$ingress\",method=~\"$method\",status=~\"^[4-5].*\"}[$__rate_interval])) / sum by(ingress, method) (increase(nginx_ingress_controller_requests[$__rate_interval]))",
"format": "time_series",
"interval": "2m",
"legendFormat": "{{method}} {{ route }}",
"range": true,
"refId": "A"
}
],
"title": "Error Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.width",
"value": 183
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 27
},
"id": 10,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Value"
}
]
},
"pluginVersion": "9.2.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"exemplar": false,
"expr": "ceil(sum by(ingress, method,status) (increase(nginx_ingress_controller_requests{method=~\"$method\",ingress=~\"$ingress\",status=~\"^[4-5].*\"}[$__range])))",
"format": "table",
"instant": true,
"interval": "2m",
"legendFormat": "{{ingress}} {{method}}",
"range": false,
"refId": "A"
}
],
"title": "Total Errors",
"transformations": [],
"type": "table"
}
],
"schemaVersion": 37,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {},
"definition": "label_values(nginx_ingress_controller_requests,ingress)",
"hide": 0,
"includeAll": true,
"multi": false,
"name": "ingress",
"options": [],
"query": {
"query": "label_values(nginx_ingress_controller_requests,ingress)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
},
{
"current": {},
"definition": "label_values(nginx_ingress_controller_requests,method)",
"hide": 0,
"includeAll": true,
"multi": false,
"name": "method",
"options": [],
"query": {
"query": "label_values(nginx_ingress_controller_requests,method)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Ingress Metrics",
"uid": "WOWEHb7Sz",
"version": 16,
"weekStart": ""
}

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.1'

View File

@@ -1,5 +1,11 @@
# @nhost/vue
## 1.14.0
### Minor Changes
- 0c49e757c: feat: add new composable `useMultipleFilesUpload`
## 1.13.39
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/vue",
"version": "1.13.39",
"version": "1.14.0",
"description": "Nhost Vue library",
"license": "MIT",
"keywords": [

View File

@@ -1,8 +1,8 @@
export * from './client'
export * from './useAccessToken'
export * from './useAuthInterpreter'
export * from './useAuthenticated'
export * from './useAuthenticationStatus'
export * from './useAuthInterpreter'
export * from './useChangeEmail'
export * from './useChangePassword'
export * from './useConfigMfa'
@@ -10,6 +10,7 @@ export * from './useDecodedAccessToken'
export * from './useFileUpload'
export * from './useHasuraClaim'
export * from './useHasuraClaims'
export * from './useMultipleFilesUpload'
export * from './useNhostClient'
export * from './useProviderLink'
export * from './useResetPassword'

View File

@@ -8,10 +8,11 @@ import {
uploadFilePromise
} from '@nhost/nhost-js'
import { useInterpret, useSelector } from '@xstate/vue'
import { ToRefs } from 'vue'
import { InterpreterFrom } from 'xstate'
import { useNhostClient } from './useNhostClient'
export interface FileUploadComposableResult extends FileUploadState {
export interface FileUploadComposableResult extends ToRefs<FileUploadState> {
/**
* Add the file without uploading it.
*/
@@ -38,21 +39,41 @@ export type { FileItemRef }
/**
* Use the composable `useFileUploadItem` to control the file upload of a file in a multiple file upload.
*
* It has the same signature as `useFileUpload`.
*
* @example
* ```tsx
* const Item = ({itemRef}) => {
* const { name, progress} = useFileUploadItem(itemRef)
* return <li>{name} {progress}</li>
* }
* ```vue
* <!-- Parent component or page -->
*
* const List = () => {
* const { list } = useMultipleFilesUpload()
* return <ul>
* {list.map((itemRef) => <Item key={item.id} itemRef={item} />)}
* </ul>
* }
* <script lang="ts" setup>
* const { files } = useMultipleFilesUpload()
* <script lang="ts" setup>
*
* <template>
* <div v-for="(file, index) of files" :key="index">
* <FileUploadItem :file="file" />
* </div>
* </template>
*
*
* <!-- FileUploadItem component -->
*
* <script lang="ts" setup>
* import { FileItemRef } from '@nhost/nhost-js'
* import { useFileUploadItem } from '@nhost/vue'
*
* const { file } = defineProps<{ file: FileItemRef }>()
*
* const { name, progress } = useFileUploadItem(file)
* </script>
*
* <template>
* <div>
* <span>{{ name }}</span>
* <v-progress-linear v-model="progress">
* {{ progress }}
* </v-progress-linear>
* </div>
* </template>
*
* ```
*/
@@ -65,7 +86,7 @@ export const useFileUploadItem = (
ref.send({
type: 'ADD',
file: params.file,
bucketId: params.bucketId || bucketId
bucketId: params.bucketId || bucketId.value
})
}
@@ -88,14 +109,14 @@ export const useFileUploadItem = (
ref.send('DESTROY')
}
const isUploading = useSelector(ref, (state) => state.matches('uploading')).value
const isUploaded = useSelector(ref, (state) => state.matches('uploaded')).value
const isError = useSelector(ref, (state) => state.matches('error')).value
const error = useSelector(ref, (state) => state.context.error || null).value
const progress = useSelector(ref, (state) => state.context.progress).value
const id = useSelector(ref, (state) => state.context.id).value
const bucketId = useSelector(ref, (state) => state.context.bucketId).value
const name = useSelector(ref, (state) => state.context.file?.name).value
const isUploading = useSelector(ref, (state) => state.matches('uploading'))
const isUploaded = useSelector(ref, (state) => state.matches('uploaded'))
const isError = useSelector(ref, (state) => state.matches('error'))
const error = useSelector(ref, (state) => state.context.error || null)
const progress = useSelector(ref, (state) => state.context.progress)
const id = useSelector(ref, (state) => state.context.id)
const bucketId = useSelector(ref, (state) => state.context.bucketId)
const name = useSelector(ref, (state) => state.context.file?.name)
return {
add,
@@ -117,7 +138,7 @@ export const useFileUploadItem = (
* Use the composable `useFileUpload` to upload a file.
*
* @example
* ```tsx
* ```ts
* const { add,
* upload,
* cancel,

View File

@@ -0,0 +1,117 @@
import {
createMultipleFilesUploadMachine,
FileItemRef,
MultipleFilesHandlerResult,
MultipleFilesUploadState,
UploadMultipleFilesActionParams,
uploadMultipleFilesPromise
} from '@nhost/nhost-js'
import { useInterpret, useSelector } from '@xstate/vue'
import { Ref, ref, ToRefs } from 'vue'
import { useNhostClient } from './useNhostClient'
export interface MultipleFilesUploadComposableResult extends ToRefs<MultipleFilesUploadState> {
/**
* Add one or multiple files to add to the list of files to upload.
*/
add: (
params: Required<Pick<UploadMultipleFilesActionParams, 'files'>> &
UploadMultipleFilesActionParams
) => void
/**
* Upload the files that has been previously added to the list.
*/
upload: (params?: UploadMultipleFilesActionParams) => Promise<MultipleFilesHandlerResult>
/**
* Cancel the ongoing upload. The files that have been successfully uploaded will not be deleted from the server.
*/
cancel: () => void
/**
* Clear the list of files.
*/
clear: () => void
}
/**
* Use the composable `useMultipleFilesUpload` to upload multiple files.
*
* @example
* ```ts
* const {
* add,
* upload
* } = useMultipleFilesUpload()
*
* const addFiles = async (files) => {
* add({files})
* }
*
* const handleSubmit = async (e) => {
* e.preventDefault()
* await upload()
* }
* ```
*
* @docs https://docs.nhost.io/reference/vue/use-multiple-files-upload
*/
export const useMultipleFilesUpload = (): MultipleFilesUploadComposableResult => {
const { nhost } = useNhostClient()
const errors: Ref<FileItemRef[]> = ref([])
const service = useInterpret(createMultipleFilesUploadMachine, {}, (state) => {
if (state.event.type === 'UPLOAD_ERROR') {
errors.value = state.context.files.filter((ref) => ref.getSnapshot()?.context.error)
} else if (
(state.matches('uploaded') || state.event.type === 'CLEAR') &&
errors.value.length > 0
) {
errors.value = []
}
})
const add = (
params: Required<Pick<UploadMultipleFilesActionParams, 'files'>> &
UploadMultipleFilesActionParams
) => {
service.send({ type: 'ADD', ...params })
}
const upload = (params?: UploadMultipleFilesActionParams) =>
uploadMultipleFilesPromise(
{
url: nhost.storage.url,
accessToken: nhost.auth.getAccessToken(),
adminSecret: nhost.adminSecret,
...params
},
service
)
const cancel = () => {
service.send('CANCEL')
}
const clear = () => {
service.send('CLEAR')
}
const isUploading = useSelector(service, (state) => state.matches('uploading'))
const isUploaded = useSelector(service, (state) => state.matches('uploaded'))
const isError = useSelector(service, (state) => state.matches('error'))
const progress = useSelector(service, (state) => state.context.progress)
const files = useSelector(service, (state) => state.context.files)
return {
upload,
add,
clear,
cancel,
progress,
isUploaded,
isUploading,
files,
isError,
errors
}
}

613
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff