Nextcloud

Accès direct

Création d'une application NextCloud

Les automatisations dans NextCloud

Flux

Les flux NextCloud permettent d'automatiser des actions comme :

Applications

Calendrier, Images Viewer, Mail, Partage plus ouvert, Activité, Outils Css, Racourci bar custom, Notes, Agenda, Taches, Shifts, News, Cartes, Keep : (Glisser pour valider ou non)

Rendez vous sur NextCloud pour plus d'informations sur les différentes application et utilité

Comment créer une application NextCloud ?

  1. Création de l'application via le generateur d'applications squelettes en entrant toute les informations.
  2. Supprimer tout les fichiers qui sont pas nécessaire au type d'application voulu.
  3. Création des fichiers en fonction de l'application a crée.

Exemple d'application (Hello World !)

  1. Création de l'applications squelette.

    Il suffit d'entrer les informations de l'application. Elles seront modifiables dans le futur si besoin.

    Application Squelette Hello World

  2. Une fois téléchargé sous forme *.tar.gz, il faut extraire le fichier.

  3. Suppression des fichiers inutiles :

  4. Le contenu de src, templates, tests, lib/Service, lib/Db, lib/Migration et lib/Controller.

  5. Supprimer babel.config.js et psalm.xml

  6. Modifications / créations des fichiers :

  7. Création du *BackEnd

    • Remplacer le contenu de lib/AppInfo/Application.php par :

    ```php <?php namespace OCA\Emsnchelloworld\AppInfo;

    use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap;

    class Application extends App implements IBootstrap {

    public const APP_ID = 'emsnchelloworld';
    
    public function __construct(array $urlParams = []) {
        parent::__construct(self::APP_ID, $urlParams);
    }
    
    public function register(IRegistrationContext $context): void {
    }
    
    public function boot(IBootContext $context): void {
    }
    

    } `` - Remplacer le contenu deappinfo/routes.php` par :

    ```php <?php

    return [ 'routes' => [ ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], ],

    ]; ```

  8. Créer le fichier lib/Controller/PageController.php avec :

    ```php <?php

    namespace OCA\NoteBook\Controller; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; use OCP\Collaboration\Reference\RenderReferenceEvent; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IRequest; use OCP\AppFramework\Controller;

    use OCA\NoteBook\AppInfo\Application; use OCP\PreConditionNotMetException;

    class PageController extends Controller {

    public function __construct(
        string   $appName,
        IRequest $request,
        private IEventDispatcher $eventDispatcher,
        private IInitialState $initialStateService,
        private IConfig $config,
        private ?string $userId
    ) {
        parent::__construct($appName, $request);
    }
    
    public function index(): TemplateResponse {
        $this->eventDispatcher->dispatchTyped(new RenderReferenceEvent());
        $notes = [];
        $selectedNoteId = (int) $this->config->getUserValue($this->userId, Application::APP_ID, 'selected_note_id', '0');
        $state = [
            'notes' => $notes,
            'selected_note_id' => $selectedNoteId,
        ];
        $this->initialStateService->provideInitialState('notes-initial-state', $state);
        return new TemplateResponse(Application::APP_ID, 'main');
    }
    

    } ```

  9. Maintenant crée le fichier templates/main.php avec :

    php <?php $appId = OCA\Emsnchelloworld\AppInfo\Application::APP_ID; \OCP\Util::addScript($appId, $appId . '-main'); ?>

  10. Création du FrontEnd

  11. Création du fichier src/main.js avec :

    ```js import App from './views/App.vue' import Vue from 'vue' Vue.mixin({ methods: { t, n } })

    const VueApp = Vue.extend(App) new VueApp().$mount('#content') ```

  12. Créer le dossier src/views et le fichier src/views/App.vue avec :

<template>
<NcContent app-name="emsnchelloworld">
    <MyNavigation
        :notes="displayedNotesById"
        :selected-note-id="state.selected_note_id"
        @click-note="onClickNote"
        @export-note="onExportNote"
        @create-note="onCreateNote"
        @delete-note="onDeleteNote" />
    <NcAppContent>
        <MyMainContent v-if="selectedNote"
            :note="selectedNote"
            @edit-note="onEditNote" />
        <NcEmptyContent v-else
            :title="t('tutorial_5', 'Select a note')">
            <template #icon>
                <NoteIcon :size="20" />
            </template>
        </NcEmptyContent>
    </NcAppContent>
</NcContent>
</template>

<script>
import NcContent from '@nextcloud/vue/dist/Components/NcContent.js'
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'

import NoteIcon from '../components/icons/NoteIcon.vue'

import MyNavigation from '../components/MyNavigation.vue'
import MyMainContent from '../components/MyMainContent.vue'

import axios from '@nextcloud/axios'
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
import { showSuccess, showError, showUndo } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'

import { Timer } from '../utils.js'

export default {
name: 'App',

components: {
    NoteIcon,
    NcContent,
    NcAppContent,
    NcEmptyContent,
    MyMainContent,
    MyNavigation,
},

props: {
},

data() {
    return {
        state: loadState('emsnchelloworld', 'notes-initial-state'),
    }
},

computed: {
    allNotes() {
        return this.state.notes
    },
    notesToDisplay() {
        return this.state.notes.filter(n => !n.trash)
    },
    displayedNotesById() {
        const nbi = {}
        this.notesToDisplay.forEach(n => {
            nbi[n.id] = n
        })
        return nbi
    },
    notesById() {
        const nbi = {}
        this.allNotes.forEach(n => {
            nbi[n.id] = n
        })
        return nbi
    },
    selectedNote() {
        return this.displayedNotesById[this.state.selected_note_id]
    },
},

watch: {
},

mounted() {
},

beforeDestroy() {
},

methods: {
    onEditNote(noteId, content) {
        const options = {
            content,
        }
        const url = generateOcsUrl('apps/emsnchelloworld/api/v1/notes/{noteId}', { noteId })
        axios.put(url, options).then(response => {
            this.notesById[noteId].content = content
            this.notesById[noteId].last_modified = response.data.ocs.data.last_modified
        }).catch((error) => {
            showError(t('emsnchelloworld', 'Error saving note content'))
            console.error(error)
        })
    },
    onCreateNote(name) {
        console.debug('create note', name)
        const options = {
            name,
        }
        const url = generateOcsUrl('apps/emsnchelloworld/api/v1/notes')
        axios.post(url, options).then(response => {
            this.state.notes.push(response.data.ocs.data)
            this.onClickNote(response.data.ocs.data.id)
        }).catch((error) => {
            showError(t('emsnchelloworld', 'Error creating note'))
            console.error(error)
        })
    },
    onDeleteNote(noteId) {
        console.debug('delete note', noteId)
        this.$set(this.notesById[noteId], 'trash', true)
        const deletionTimer = new Timer(() => {
            this.deleteNote(noteId)
        }, 10000)
        showUndo(
            t('emsnchelloworld', '{name} deleted', { name: this.notesById[noteId].name }),
            () => {
                deletionTimer.pause()
                this.notesById[noteId].trash = false
            },
            { timeout: 10000 }
        )
    },
    deleteNote(noteId) {
        const url = generateOcsUrl('apps/emsnchelloworld/api/v1/notes/{noteId}', { noteId })
        axios.delete(url).then(response => {
            const indexToDelete = this.state.notes.findIndex(n => n.id === noteId)
            if (indexToDelete !== -1) {
                this.state.notes.splice(indexToDelete, 1)
            }
        }).catch((error) => {
            showError(t('emsnchelloworld', 'Error deleting note'))
            console.error(error)
        })
    },
    onClickNote(noteId) {
        console.debug('click note', noteId)
        this.state.selected_note_id = noteId
        const options = {
            values: {
                selected_note_id: noteId,
            },
        }
        const url = generateUrl('apps/emsnchelloworld/config')
        axios.put(url, options).then(response => {
        }).catch((error) => {
            showError(t('emsnchelloworld', 'Error saving selected note'))
            console.error(error)
        })
    },
    onExportNote(noteId) {
        const url = generateOcsUrl('apps/emsnchelloworld/api/v1/notes/{noteId}/export', { noteId })
        axios.get(url).then(response => {
            showSuccess(t('emsnchelloworld', 'Note exported in {path}', { path: response.data.ocs.data }))
        }).catch((error) => {
            showError(t('emsnchelloworld', 'Error deleting note'))
            console.error(error)
        })
    },
},
}
</script>
<template>
    <NcAppNavigation>
        <template #list>
            <NcAppNavigationNewItem
                :title="t('emsnchelloworld', 'Create note')"
                @new-item="$emit('create-note', $event)">
                <template #icon>
                    <PlusIcon />
                </template>
            </NcAppNavigationNewItem>
            <h2 v-if="loading"
                class="icon-loading-small loading-icon" />
            <NcEmptyContent v-else-if="sortedNotes.length === 0"
                :title="t('emsnchelloworld', 'No notes yet')">
                <template #icon>
                    <NoteIcon :size="20" />
                </template>
            </NcEmptyContent>
            <NcAppNavigationItem v-for="note in sortedNotes"
                :key="note.id"
                :name="note.name"
                :class="{ selectedNote: note.id === selectedNoteId }"
                :force-display-actions="true"
                :force-menu="false"
                @click="$emit('click-note', note.id)">
                <template #icon>
                    <NoteIcon />
                </template>
                <template #actions>
                    <NcActionButton
                        :close-after-click="true"
                        @click="$emit('export-note', note.id)">
                        <template #icon>
                            <FileExportIcon />
                        </template>
                        {{ t('emsnchelloworld', 'Export to file') }}
                    </NcActionButton>
                    <NcActionButton
                        :close-after-click="true"
                        @click="$emit('delete-note', note.id)">
                        <template #icon>
                            <DeleteIcon />
                        </template>
                        {{ t('emsnchelloworld', 'Delete') }}
                    </NcActionButton>
                </template>
            </NcAppNavigationItem>
        </template>
    </NcAppNavigation>
</template>

<script>
import FileExportIcon from 'vue-material-design-icons/FileExport.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import DeleteIcon from 'vue-material-design-icons/Delete.vue'

import NoteIcon from './icons/NoteIcon.vue'

import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcAppNavigationNewItem from '@nextcloud/vue/dist/Components/NcAppNavigationNewItem.js'

import ClickOutside from 'vue-click-outside'

export default {
    name: 'MyNavigation',

    components: {
        NoteIcon,
        NcAppNavigation,
        NcEmptyContent,
        NcAppNavigationItem,
        NcActionButton,
        NcAppNavigationNewItem,
        PlusIcon,
        DeleteIcon,
        FileExportIcon,
    },

    directives: {
        ClickOutside,
    },

    props: {
        notes: {
            type: Object,
            required: true,
        },
        selectedNoteId: {
            type: Number,
            default: 0,
        },
        loading: {
            type: Boolean,
            default: false,
        },
    },

    data() {
        return {
            creating: false,
        }
    },
    computed: {
        sortedNotes() {
            return Object.values(this.notes).sort((a, b) => {
                const { tsA, tsB } = { tsA: a.last_modified, tsB: b.last_modified }
                return tsA > tsB
                    ? -1
                    : tsA < tsB
                        ? 1
                        : 0
            })
        },
    },
    beforeMount() {
    },
    methods: {
        onCreate(value) {
            console.debug('create new note')
        },
    },
}
</script>
<style scoped lang="scss">
.addNoteItem {
    position: sticky;
    top: 0;
    z-index: 1000;
    border-bottom: 1px solid var(--color-border);
    :deep(.app-navigation-entry) {
        background-color: var(--color-main-background-blur, var(--color-main-background));
        backdrop-filter: var(--filter-background-blur, none);
        &:hover {
            background-color: var(--color-background-hover);
        }
    }
}

:deep(.selectedNote) {
    > .app-navigation-entry {
        background: var(--color-primary-light, lightgrey);
    }

    > .app-navigation-entry a {
        font-weight: bold;
    }
}
</style>
<template>
    <span :aria-hidden="!title"
        :aria-label="title"
        class="material-design-icon note-icon"
        role="img"
        v-bind="$attrs"
        @click="$emit('click', $event)">
        <svg
            :fill="fillColor"
            :width="size"
            :height="size"
            enable-background="new 0 0 24 24"
            version="1.1"
            viewBox="0 0 24 24"
            xml:space="preserve"
            xmlns="http://www.w3.org/2000/svg">
            <path d="M18.5 2H5.5C3.6 2 2 3.6 2 5.5V18.5C2 20.4 3.6 22 5.5 22H16L22 16V5.5C22 3.6 20.4 2 18.5 2M20.1 15H18.6C16.7 15 15.1 16.6 15.1 18.5V20H5.8C4.8 20 4 19.2 4 18.2V5.8C4 4.8 4.8 4 5.8 4H18.3C19.3 4 20.1 4.8 20.1 5.8V15M7 7H17V9H7V7M7 11H17V13H7V11M7 15H13V17H7V15Z" />
        </svg>
    </span>
</template>

<script>
export default {
    name: 'NoteIcon',
    props: {
        title: {
            type: String,
            default: '',
        },
        fillColor: {
            type: String,
            default: 'currentColor',
        },
        size: {
            type: Number,
            default: 24,
        },
    },
}
</script>
<template>
    <div class="main-content">
        <h2>
            {{ note.name }}
        </h2>
        <NcRichContenteditable
            class="content-editable"
            :value="note.content"
            :maxlength="10000"
            :multiline="true"
            :placeholder="t('notebook', 'Write a note')"
            @update:value="onUpdateValue" />
    </div>
</template>

<script>
import NcRichContenteditable from '@nextcloud/vue/dist/Components/NcRichContenteditable.js'

import { delay } from '../utils.js'

export default {
    name: 'MyMainContent',

    components: {
        NcRichContenteditable,
    },

    props: {
        note: {
            type: Object,
            required: true,
        },
    },

    data() {
        return {
        }
    },

    computed: {
    },

    watch: {
    },

    mounted() {
    },

    beforeDestroy() {
    },

    methods: {
        onUpdateValue(newValue) {
            delay(() => {
                this.$emit('edit-note', this.note.id, newValue)
            }, 2000)()
        },
    },
}
</script>

<style scoped lang="scss">
.main-content {
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

    .content-editable {
        min-width: 600px;
        min-height: 200px;
    }
}
</style>
let mytimer = 0
export function delay(callback, ms) {
    return function() {
        const context = this
        const args = arguments
        clearTimeout(mytimer)
        mytimer = setTimeout(function() {
            callback.apply(context, args)
        }, ms || 0)
    }
}

export function Timer(callback, mydelay) {
    let timerId
    let start
    let remaining = mydelay

    this.pause = function() {
        window.clearTimeout(timerId)
        remaining -= new Date() - start
    }

    this.resume = function() {
        start = new Date()
        window.clearTimeout(timerId)
        timerId = window.setTimeout(callback, remaining)
    }

    this.resume()
}

export function strcmp(a, b) {
    const la = a.toLowerCase()
    const lb = b.toLowerCase()
    return la > lb
        ? 1
        : la < lb
            ? -1
            : 0
}
const path = require('path')
const webpackConfig = require('@nextcloud/webpack-vue-config')
const ESLintPlugin = require('eslint-webpack-plugin')
const StyleLintPlugin = require('stylelint-webpack-plugin')

const buildMode = process.env.NODE_ENV
const isDev = buildMode === 'development'
webpackConfig.devtool = isDev ? 'cheap-source-map' : 'source-map'

webpackConfig.stats = {
    colors: true,
    modules: false,
}

const appId = 'notebook'
webpackConfig.entry = {
    main: { import: path.join(__dirname, 'src', 'main.js'), filename: appId + '-main.js' },
}

webpackConfig.plugins.push(
    new ESLintPlugin({
        extensions: ['js', 'vue'],
        files: 'src',
        failOnError: !isDev,
    })
)
webpackConfig.plugins.push(
    new StyleLintPlugin({
        files: 'src/**/*.{css,scss,vue}',
        failOnError: !isDev,
    }),
)

module.exports = webpackConfig

Une fois le code modifié, il suffit de déplacer la dossier de l'application dans le dossier de NextCloud, ensuite aller sur la page Application de Nexcloud (--url--/settings/apps/disabled) pour activer l'application (entrer le mot de passe du compte).

Nexcloud - page de l'appllication

Divers

Exemple de résultat

Exemple de résultat de HelloWorld !

Divers

Liens

Documentation

Autres

Opérations de maintenance courante

Accès à la console

Noter l'id du container nextcloud :

>$ docker ps | grep nextcloud  
>docker exec -u www-data -ti ---id container Nextcloud--- bash

Lancement de la console depuis ./occ

Mise à jour

Lancement de la mise à jour à l'aide de la commande :

>./occ upgrade

Lister les applications

>./occ app:list

Listes des commandes les plus utiles

Pour accéder aux autres commendes : ./occ

Options

CommandeRacCommandeDescription
-h--helpAffiche l'aide pour la commande donnée. Lorsqu'aucune commande n'est donnée, affichez l'aide pour la commande list
-q--quietNe plus afficher de message
-V--versionAffiche la version
--ansi, --no-ansiForce (ou désactive --no-ansi) sortie ANSI
-n--no-interactionDésactive les questions d'intéractions
--no-warningsDésactive les Warning, laisse seullement les sortie des commandes
-v, vv, vvv--verboseAugmentez la verbosité des messages : 1 pour une sortie normale, 2 pour une sortie plus détaillée et 3 pour le débogage

Commandes générales

CommandeDescription
checkRegarde les dépendance du serveur
completionVider le script de complétion du scriptTv
helpAfficher l'aide d'une commande
listListe les commandes
statusMontre un statue
upgradeExécuter des routines de mise à niveau après l'installation d'une nouvelle version. La version doit être installée avant

App :

CommandeDescription
app:disableDésactive une application
app:enableActive une application
app:getpathCrée un chemin absolu vers une application
app:installInstalle une application
app:listListe toutes les applications
app:removeSupprime une application
app:updateMet a jour une / des application(s)

Config :

CommandeDescription
config:app:deleteSupprime une configuration
config:app:getRecoit une configuration
config:app:setInstalle une configuration
config:importImporte une configuration
config:listListe toutes les configurations
config:system:deleteSuprimme une configuration systeme
config:system:getRecoit une configuration systeme
config:system:setinstalle une configuration systeme

Update

CommandeDescription
update:checkCherche une mise a jour

User

CommandeDescription
user:addAjoute un utilisateur
user:add-app-passwordAjoute un mot de passe pour un utilisateur
user:deleteSupprime un utilisateur
user:disableDésactive un utilisateur
user:enableActive un utilisateur
user:infoMontre les info des utilisateurs
user:lastseenMontre la derniere connexion des utilisateurs
user:listListe les utilisateur configurée
user:reportMontre les utilisateur ayant accces
user:resetpasswordRéinitialise les mots de passes
user:settingLis et modifi les profile/parametres utilisateurs

Versions

CommandeDescription
versions:cleanupSupprime les versions
versions:expireFait expirer les versions de fichiers des utilisateurs