Scrierea de aplicații (pluginuri)
Aplicațiile adaugă funcționalități în Antimatter din interior: o parte de server în Go care se integrează în server și o parte de aplicație web în React care adaugă meniuri, panouri, tipuri de mesaje și pagini. Această pagină arată cum este structurată o aplicație, folosind aplicația Polls inclusă ca exemplu, și ce se schimbă în interfața Fusion a Antimatter.
Cum funcționează o aplicație
- Partea de server este un program Go. Serverul îl pornește ca un proces separat și comunică cu el prin RPC: apelează hook-urile aplicației (un mesaj este pe punctul de a fi publicat, o comandă a fost executată…) și aplicația apelează API-ul de plugin al serverului (creare de postări, citire de utilizatori, stocare de date…). Poate, de asemenea, servește rute HTTP sub
/plugins/<app id>/. - Partea de aplicație web este un pachet JavaScript încărcat în browser odată cu aplicația web. Înregistrează componente și callback-uri React în registrul: un buton într-un meniu, un panou, o metodă de a desena un tip de mesaj.
- Orice parte este opțională. O aplicație este instalată ca un pachet
.tar.gz, iar administratorii de sistem o activează, dezactivează și o configurează.
Antimatter rulează aplicațiile scrise pentru Mattermost și invers, atâta timp cât o aplicație folosește doar hook-uri pe care ambele le au. Vezi Hook-uri doar în Antimatter.
Structura pachetului
Nu există un șablon de pornire separat: pornește de la o aplicație mică inclusă, cum ar fi antimatter-plugin-polls sau antimatter-plugin-todo, și modifică ID-ul și codul acesteia. Polls este structurat astfel:
antimatter-plugin-polls/
├── plugin.json the manifest
├── Makefile build, test, bundle, deploy
├── build/ the manifest and pluginctl helper tools (Go), setup.mk
├── assets/icon.svg files shipped with the bundle
├── go.mod
├── server/
│ ├── main.go plugin.ClientMain(&Plugin{})
│ ├── plugin.go the Plugin type, OnActivate, ServeHTTP
│ ├── manifest.go generated from plugin.json
│ ├── api.go, command.go, hooks.go, store.go …
│ └── dist/ compiled binaries, one per platform
└── webapp/
├── package.json, webpack.config.js, tsconfig.json
├── i18n/ en.json and one file per language
├── src/index.tsx registers the app with the web app
├── src/manifest.ts generated from plugin.json
├── src/translations.ts
└── dist/main.js the bundle
Opțional: un folder public/, servit direct la /plugins/<app id>/public/.
plugin.json
{
"id": "com.example.weather",
"name": "Weather",
"description": "Forecasts in the chat.",
"homepage_url": "https://github.com/example/weather",
"support_url": "https://github.com/example/weather/issues",
"icon_path": "assets/icon.svg",
"version": "1.0.0",
"min_server_version": "12.0.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"linux-arm64": "server/dist/plugin-linux-arm64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"darwin-arm64": "server/dist/plugin-darwin-arm64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"webapp": {"bundle_path": "webapp/dist/main.js"},
"settings_schema": {"settings": []}
}
id: unic și permanent; un nume de domeniu invers este uzual. Numește rutele, setările stocate și evenimentele WebSocket.version: versiune semantică. Aplicațiile incluse o lasă dinplugin.json; instrumentul de construcție o preia din cea mai recentă etichetă gitv*.min_server_version: Antimatter raportează12.0.0.settings_schema: pagina de setări a aplicației din Consola de sistem (a se vedea Setări).
make apply copiază manifestul în server/manifest.go și webapp/src/manifest.ts; make manifest-check îl validează.
Partea de server
Partea de server importă același modul Go ca aplicațiile Mattermost, github.com/mattermost/mattermost/server/public, din proxy-ul de module Go. Funcția sa main doar pornește aplicația:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
Tipul Plugin încorporează plugin.MattermostPlugin, ceea ce îi oferă p.API, și implementează hook-urile de care are nevoie:
type Plugin struct {
plugin.MattermostPlugin
router *mux.Router
}
func (p *Plugin) OnActivate() error {
p.router = p.newRouter()
return p.API.RegisterCommand(&model.Command{
Trigger: "weather",
AutoComplete: true,
AutoCompleteDesc: "Show the forecast",
AutoCompleteHint: "[city]",
})
}
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
city := strings.TrimSpace(strings.TrimPrefix(args.Command, "/weather"))
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
Text: "Forecast for " + city + ": sunny",
}, nil
}
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
p.router.ServeHTTP(w, r)
}
Hook-uri comune
| Hook | Când se execută |
|---|---|
OnActivate, OnDeactivate | Când aplicația este activată sau dezactivată, și când serverul pornește și se oprește. Înregistrează comenzile și bot-urile în OnActivate. |
OnConfigurationChange | Când setările se modifică; încarcă-le cu p.API.LoadPluginConfiguration(&cfg). |
ServeHTTP | Cereri către /plugins/<app id>/…. Serverul setează antetul Mattermost-User-Id când cererea vine de la un utilizator autentificat; refuză cererile fără acest antet, cu excepția celor destinate publicului. |
ExecuteCommand | O comandă înregistrată de aplicație a fost executată. |
MessageWillBePosted, MessageWillBeUpdated | Înainte ca un mesaj să fie salvat: returnează un post modificat sau un motiv pentru a-l respinge. |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | A posteriori, pentru efecte secundare. |
ReactionHasBeenAdded, ReactionHasBeenRemoved | Reacții. |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | Modificări de apartenență și de canal. O echipă este o comunitate. |
FileWillBeUploaded | Înainte ca un fișier să fie stocat: modifică-l sau respinge-l. |
OnPluginClusterEvent | Un mesaj de la aceeași aplicație de pe un alt server dintr-un cluster. |
Lista completă, cu semnăturile exacte, se găsește în server/public/plugin/hooks.go; API-ul este în server/public/plugin/api.go. Pachetul pluginapi (server/public/pluginapi) învelește API-ul în helperi mai prietenoși.
Apeluri utile la API
CreatePost,UpdatePost,SendEphemeralPost: postează în numele bot-ului aplicației sau în numele unui utilizator.EnsureBotUser(&model.Bot{Username, DisplayName}): creează bot-ul aplicației o singură dată și obține ID-ul acestuia.PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): trimite un eveniment către aplicația web. Acesta sosește cacustom_<app id>_poll_updated.OpenInteractiveDialog: deschide un dialog.LogInfo,LogErrorși altele: scrie în jurnalul serverului, etichetat cu ID-ul aplicației.GetUser,GetChannel,HasPermissionToChannel: verifică cine poate face ce înainte de a acționa în numele unui utilizator.
O aplicație rulează pe fiecare server din cluster. Păstrați starea în magazinul de chei-valori sau în baza de date, nu doar în memorie.
Serviciul de inteligență artificială
Serverul Antimatter apelează modele de inteligență artificială pentru aplicații, prin conexiunile la modele configurate de un administrator (a se vedea Inteligența artificială în ghidul administratorului). O aplicație nu deține niciodată cheia unui furnizor: indică persoana pentru care acționează și locul, iar serverul verifică dacă această persoană poate utiliza conexiunea acolo, aplică limitele și bugetele, numără tokenii în numele persoanei și al aplicației și auditează apelul fără a include conținutul acestuia.
| Metodă | Ce face |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | O completare de chat: mesaje, instrumente, ieșire structurată (JSONSchema). |
AIChatStream(…) | Același lucru, în flux: citiți Events din flux până când canalul se închide, sau Close pentru a opri apelul. |
AIEmbed(…, *ai.EmbedRequest) | Încorporări (embeddings) de texte. |
AITranscribe(…, *ai.TranscribeRequest) | Vorbire în text. |
AIDecide(…, *ai.DecideRequest) | Întrebări tipizate către un model de decizie (da sau nu, o alegere, un scor) despre un text. |
AIConnectionsForUser(userID, channelID, teamID) | Conexiunile pe care o persoană le poate utiliza acolo, cu modelele și capacitățile lor, niciodată cu cheile lor. |
ConnectionID din fiecare cerere alege conexiunea, iar Model unul dintre modelele acesteia (gol pentru modelul implicit al conexiunii). Operation este o etichetă scurtă pentru înregistrările de utilizare și audit, niciodată conținut. Versiunea minimă a serverului este 11.11.
actingUserID doar dintr-o sursă verificată de server: antetul Mattermost-User-Id al unei cereri către ServeHTTP al dvs., sau utilizatorul din contextul unui hook (autorul unui post, utilizatorul care a rulat o comandă). Nu luați niciodată dintr-un mesaj, dintr-o sarcină de webhook, dintr-o URL sau dintr-o cerere a unei alte aplicații: aplicația dvs. ar consuma accesul cuiva altcineva.Aceste metode și pachetul ai (server/public/ai) există doar în copia Antimatter a modulului, nu în versiunea publicată de Mattermost. Pentru a le utiliza, construiți în raport cu server/public al Antimatter, folosind o linie replace în go.mod, de exemplu replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. O astfel de aplicație rulează doar pe Antimatter.
Stocarea datelor
Fiecare aplicație are propriul magazin de chei-valori în baza de date a serverului:
KVSet(key, value),KVGet(key),KVDelete(key): valorile sunt octeți, de obicei JSON. Cheile au cel mult 150 de caractere.KVSetWithExpiry(key, value, seconds): valoarea dispare după acest timp.KVCompareAndSet(key, old, new)șiKVSetWithOptionscuAtomic: actualizări sigure atunci când mai multe cereri sau servere scriu aceeași cheie. Sondajul salvează fiecare vot în acest mod și reîncearcă în caz de conflict.KVList(page, perPage): cheile, pagină cu pagină.
Partea de aplicație web
Pachetul înregistrează o clasă cu window.registerPlugin. Metoda sa initialize primește registrul și stocul Redux:
import manifest from './manifest';
class Plugin {
initialize(registry, store) {
registry.registerTranslations((locale) => translationsFor(locale));
registry.registerPostTypeComponent('custom_poll', PollPost);
registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
});
registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
}
}
window.registerPlugin(manifest.id, new Plugin());
- Biblioteci partajate. Aplicația web oferă React, ReactDOM, Redux, React Redux și React Intl: enumerați-le ca
externalsîn webpack (React,ReactDOM,Redux,ReactRedux,ReactIntl) în loc să includeți propriile copi. Consultațiwebpack.config.jsal Sondajului. - Apelarea părții de server. Trimiteți cereri către
${window.basename}/plugins/<app id>/…cu opțiunile dinClient4.getOptions()(dinmattermost-redux/client): acestea conțin anteturile de care serverul are nevoie pentru a accepta o cerere din browser. - Starea proprie.
registerReduceradaugă un reducer, stocat substate['plugins-<app id>']. - Tipuri. Metodele suplimentare ale registrului din Antimatter nu se află în tipurile publicate de Mattermost: Sondajul declară registrul pe care îl utilizează în
webapp/src/types/host.ts.
Metodele registrului și parametrii lor sunt documentați în cod, în webapp/fusion/src/plugins/registry.ts (Fusion) și webapp/channels/src/plugins/registry.ts (clasic).
Suportul pentru interfața Fusion
Antimatter are două interfețe web: Fusion, cea implicită, și cea clasică. Ambele încarcă aceleași pachete de aplicații și au același registru, dar Fusion desenează multe elemente de sine și plasează componentele aplicațiilor în propriul mod. Testați aplicația în ambele.
Detectarea interfeței
Înainte de încărcarea oricărei aplicații, ambele interfețe setează window.antimatterWebUI la 'fusion' sau 'classic', și aceeași valoare pe elementul rădăcină al paginii, ca data-am-web-ui. În Mattermost, niciuna nu este setată.
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
Aplicațiile incluse folosesc acest element pentru a fi desenate diferit. Sondajul își desenează cardurile și dialogul de creare în stilul Fusion, iar în Fusion deschide propriul dialog pentru /poll. Apelurile și canalele vocale omit componentele de bară laterală și toast pe care le înlocuiește Fusion cu propriul interfață de apel. Aplicația Agenți își plasează acțiunile AI în meniul mesajului, ca un grup sub Mai multe actiuni, în loc de bara de instrumente la trecerea cu mouse-ul. Clasele CSS proprii ale Fusion (cu prefixul am-) și stratul de dialog (elementul am-layer) sunt ceea ce folosesc aceste aplicații pentru a se potrivi interfeței; ele nu constituie o API documentată și stabilă.
Unde afișează Fusion componentele plugin-urilor
| Metoda din registru | Unde apare în Fusion |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | Calea aplicațiilor, coloana de iconițe de pe marginea dreaptă, între avatarul tău și butonul Despre Antimatter. Butoanele din antetul canalului se plasează și acolo, nu în antetul canalului. Ascunsă când un administrator de sistem activează Dezactivează bara de aplicații (ExperimentalSettings.DisableAppBar, dezactivată implicit). |
registerRightHandSidebarComponent | Panoul din dreapta. Utilizatorii îl pot redimensiona trăgând de margine și îl pot deschide într-o fereastră nouă. Dacă aplicația este și un produs, panoul oferă opțiunea Deschide ca pagină completă. Vezi Panoul într-o fereastră nouă. |
registerChannelHeaderIcon | Iconițe în antetul canalului, după butoanele de apel; pe telefoane, în meniul Mai multe din antet. |
registerCallButtonAction | Butonul de apel din antetul canalului; mai mulți furnizori împărtășesc un meniu Alte moduri de apelare. |
registerPostDropdownMenuAction | Meniul Acțiuni mesaj (⋯) al mesajului, în submeniul său Mai multe actiuni; de asemenea, în meniul Acțiuni pentru firul de discuție al panoului firului de discuție, sub Mai multe actiuni. Fusion desenează iconița aplicației înaintea fiecărei acțiuni, în gri: iconița sa pentru aplicațiile incluse, altfel iconița pe care o afișează aplicația în calea aplicațiilor, altfel una neutru. Un element fără text la începutul textului acțiunii (o iconiță desenată de aplicație) este ascuns. |
registerPostDropdownSubMenuAction | Un grup cu un titlu în același submeniu Mai multe actiuni, după acțiunile individuale. Nicio aplicație nu primește o intrare proprie în meniul mesajului. |
registerPostActionComponent | Bara de instrumente la trecerea cu mouse-ul peste mesaj, înaintea butonului ⋯. |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | Mesajul însuși: corpul său, sub text, după oră și în locul previzualizărilor de link-uri. |
registerChannelHeaderMenuAction | Meniul Opțiuni de canal al canalului (⋯ pe un canal din bara laterală, sau clic dreapta), ca elemente cu o iconiță de priză. |
registerMainMenuAction | Meniul comunității (numele comunității în partea de sus a listei de canale), sub Mai multe actiuni, grupat după aplicație. |
registerSidebarBrowseOrAddChannelMenuAction | Meniul comunității, după Creează un canal și Creează o categorie. |
registerFileUploadMethod | Meniul + al compozitorului (Adaugă la mesaj), după Încarcă un fișier. |
registerPostEditorActionComponent | Bara de formatare a compozitorului, atunci când formatarea este afișată. |
registerAIActionMenuItemComponent | Meniul AI al compozitorului. |
registerFileDropdownMenuAction, registerFilePreviewComponent | Meniul de opțiuni al unui fișier și previzualizarea fișierului. |
registerUserSettings | Profil și setări, cu un tab pentru fiecare aplicație, sub titlul Plugin-uri. |
registerChannelSettingsTab | Dialogul de setări ale canalului, sub titlul Aplicații. |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | Cartea de profil afișată când faceți clic pe un nume sau pe un avatar. |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | Partea de sus a listei de canale a unei comunități, lângă numele canalului, lângă iconița canalului și partea de jos a barei laterale a comunității, din stânga. |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | Începutul istoricului unui canal, partea de sus a listei de mesaje, linia de necitit, deasupra compozitorului și textul de plasă al compozitorului. |
registerChannelTypeOption | Tipurile de canal din dialogul Creează un canal. |
registerSearchComponents | Câmpul de căutare din antetul canalului: ariile de acoperire ale aplicațiilor apar când este gol. |
registerUserGuideDropdownMenuAction | Despre Antimatter (partea de jos a barei laterale a aplicației), sub Ajutor. |
registerProduct | Pagini complete la adresa URL a produsului, listate sub Produse în Despre Antimatter. Paginile de produs păstrează cadrul clasic de pagină, a cărui meniu de produse se afișează doar când o aplicație înregistrează un produs sau adaugă o intrare în acesta. Aplicațiile Kanban și Playbooks incluse nu mai înregistrează produse: paginile lor se deschid în panourile lor laterale. |
registerRootComponent, registerGlobalComponent, hook-uri precum registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | Funcționează ca în interfața clasică. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | Consola de sistem, care este cea clasică în ambele interfețe. |
Ce nu afișează sau nu rulează Fusion
registerPostDropdownMenuComponent(un component în meniul mesajului),registerPostCardTypeComponent(o card a unui mesaj în panoul din dreapta) șiregisterProductSwitcherMenuItemnu sunt afișate în vederea principală a Fusion. FolosițiregisterPostDropdownMenuActionîn locul primului.registerMessageWillBeUpdatedHooknu rulează când un mesaj este editat în Fusion. Verificați editările pe server cu hook-ulMessageWillBeUpdatedîn schimb.registerActionAfterChannelCreationrulează doar din dialogul clasic de creare a canalului, pe care Fusion îl deschide din Mai multe opțiuni… în dialogul său Creează un canal.- Componentele barei de aplicații sunt afișate indiferent de
supportedProductIds. - Rutele din
registerNeedsTeamRouteșiregisterCustomRoutepăstrează cadrul clasic al paginii, la fel ca produsele.
Hook-uri pe care le are doar Antimatter
Aceste metode de registru nu există în Mattermost. Verifică dacă o metodă există înainte de a o apela, astfel încât aplicația să funcționeze și acolo:
if (typeof registry.registerComposerMenuItem === 'function') {
registry.registerComposerMenuItem({
text: 'Create a poll',
icon: <i className='icon icon-chart-bar'/>,
fusionIcon: 'poll',
action: ({channelId, rootId}) => openPollCreation(channelId, rootId),
});
} else {
registry.registerChannelHeaderButtonAction(icon, (channel) => openPollCreation(channel.id), 'Create a poll', 'Create a poll');
}
| Metodă | Ce adaugă |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | Un element în meniul + al casetei de mesaje, în ambele interfețe. action primește {channelId, rootId}. |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | Un tab al selectorului de emoji din caseta de mesaje, cum ar fi GIF-urile sau stickerele. În Fusion, taburile cu un order sub 10 apar înaintea tabului Emoji. Componenta primește channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion și, în Fusion, pickerKit (a se vedea mai jos). |
registerChannelViewPanel({matcher, component}) | Un panou între antetul canalului și mesaje, pentru canalele selectate de matcher(state, channel). Poate ascunde mesajele și compozitorul (setMessagesVisible), la fel cum fac canalele vocale. |
registerSidebarChannelFooterComponent(component) | O linie sub un canal în bara laterală, cum ar fi persoanele din apelul său. Primește channel. |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | Doar în Fusion: o intrare pe paginile de Integrări, lângă emoji-urile personalizate. Aplicația GIF adaugă acolo GIF-urile și stickerele sale personalizate. |
Adaugă și gestionează vizualizări în selectorul de emoji
În Fusion, un tab al selectorului de emoji primește o proprietate pickerKit: componentele pe care le folosește tabul Emoji pentru propriile vizualizări de adăugare și gestionare, astfel încât vizualizările unui tab să arate la fel și să rămână în interiorul selectorului. Aplicațiile GIF și Memes o folosesc pentru a adăuga și gestiona GIF-uri, stickere și șabloane de meme. Conține version (1), hook-urile usePickerView și useOnEscape, și componentele PickerViewButtons (butoanele + și de gestionare lângă caseta de căutare), PickerViewFrame (o vizualizare cu săgeata de retur), PickerNotice, PickerField, PickerSearch, PickerDropZone și PickerManageList. Tasta Escape revine de la o vizualizare la grila tabului. Verifică dacă proprietatea este prezentă: alte gazde o omit, iar tabul își păstrează apoi propriile dialoguri. Tipurile se află în webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) și componentele în webapp/fusion/src/fusion/popovers/picker_views.tsx.
Panoul într-o fereastră nouă
Fusion oferă componentei registerRightHandSidebarComponent încă două proprietăți, astfel încât Deschide într-o fereastră nouă să deschidă panoul acolo unde se afla, în loc de în pagina de pornire a aplicației:
setPanelState(state): apelati-o când panoul își schimbă locul, cu un șir scurt definit de aplicația dvs., de exemplu'note/<id>'sau'board/<id>/card/<id>', saunullîn pagina de pornire. Acesta este inclus în adresa ferestrei, deci nu introduceți niciodată informații secrete în el. O stare mai lungă de 512 de caractere, sau care conține caractere de control, este eliminată.initialPanelState: în fereastra nouă, starea pe care o avea panoul, sauundefined. Tratați-o ca pe o intrare nedește încredere: preluați din nou elementul ca persoană autentificată și afișați pagina de pornire a aplicației sau o vedere de element negăsit dacă nu poate fi deschis.
Starea este uitată când panoul se închide sau când panoul unei alte aplicații îi ia locul. Pentru a deschide singuri fereastra unui panou, popoutRhsPlugin (de asemenea în window.WebappUtils.popouts) acceptă starea ca al cincilea argument opțional. Note, Whiteboard, Todo, Kanban și Playbooks folosesc acest lucru.
fusionIcon numește o icoană din setul de iconițe Fusion (de exemplu poll, sticker, calendar sau image; consultați webapp/fusion/src/fusion/components/icon). Fusion o afișează în locul icon, astfel încât elementul să se potrivească cu celelalte.
Setări
settings_schema din plugin.json descrie pagina aplicației din Consola de sistem › Plugin-uri:
"settings_schema": {
"header": "Forecasts come from the weather service below.",
"settings": [
{"key": "ServiceURL", "display_name": "Service URL", "type": "text", "default": "https://api.example.com"},
{"key": "Units", "display_name": "Units", "type": "dropdown", "default": "metric",
"options": [{"display_name": "Celsius", "value": "metric"}, {"display_name": "Fahrenheit", "value": "imperial"}]},
{"key": "EnableAlerts", "display_name": "Severe weather alerts", "type": "bool", "default": false,
"help_text": "Post alerts in the channels that follow a city."}
]
}
Tipuri de setări: bool, text, longtext, number, dropdown, radio, generated (un secret aleatoriu), username și custom (desenată de o componentă înregistrată de partea de aplicație web cu registerAdminConsoleCustomSetting). Setările pot fi grupate în sections.
Partea de server le citește într-o structură ale cărei câmpuri corespund cheilor:
type configuration struct {
ServiceURL string
Units string
EnableAlerts bool
}
func (p *Plugin) OnConfigurationChange() error {
var cfg configuration
if err := p.API.LoadPluginConfiguration(&cfg); err != nil {
return err
}
p.setConfiguration(&cfg)
return nil
}
Administratorii le pot configura și în configurație, sub PluginSettings.Plugins.<app id>, cu chei în litere mici (consultați ghidul pentru administratori).
Traduceri
Antimatter este tradus în 37 de limbi, iar aplicațiile incluse, de asemenea. Partea de aplicație web face acest lucru astfel:
- Scrieți fiecare șir de caractere cu React Intl, folosind un ID și textul în engleză:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>saudefineMessages. Prefixați ID-urile cu numele aplicației. - Extrageți șirurile în engleză în
webapp/i18n/en.json(aplicațiile incluse folosescformatjs extract). - Adăugați un fișier pentru fiecare limbă lângă acesta, cu aceleași ID-uri:
fr.json,de.json,pt-BR.json,zh-CN.json… - Puneți-le la dispoziția aplicației web, care solicită limba utilizatorului:
import fr from '../i18n/fr.json'; import ptBR from '../i18n/pt-BR.json'; const catalogs = {fr, 'pt-BR': ptBR}; export function translationsFor(locale) { return catalogs[locale] || catalogs[locale.split('-')[0]] || {}; } registry.registerTranslations(translationsFor);
Un șir lipsă revine la defaultMessage în engleză. Pentru șirurile trimise de partea de server (răspunsuri la comenzi, mesaje de bot), pachetul pluginapi/i18n încarcă fișierele de traducere incluse în bundle (de exemplu assets/i18n) și oferă un localizator în limba fiecărui utilizator.
Construiți, instalați și depanați
Aveți nevoie de Go și Node.js (versiunea din .nvmrc al aplicației). În folderul aplicației:
| Comandă | Ce face |
|---|---|
make dist | Construiește partea de server pentru fiecare platformă și partea de aplicație web, și scrie bundle-ul în dist/<app id>-<version>.tar.gz. |
make test | Rulează testele Go și JavaScript. |
make check-style | Analizează codul (golangci-lint, ESLint) și verifică tipurile TypeScript. |
make deploy | Construiește, apoi instalează și activează bundle-ul pe server. |
make watch, make deploy-from-watch | Primul reconstruiește partea de aplicație web de fiecare dată când salvați; al doilea împachetează ce a construit și îl instalează pe server. |
make enable, make disable, make reset | Activează, dezactivează sau repornește aplicația pe server. |
make logs, make logs-watch | Afișează liniile aplicației din jurnalul serverului. |
make attach | Atașează depanatorul Delve la partea de server în funcțiune (construiți mai întâi cu AM_DEBUG=1). |
make deploy și celelalte comenzi de server folosesc un socket local de mod local când găsesc unul (/var/tmp/antimatter_local.socket, sau AM_LOCALSOCKETPATH): rulați-le pe mașina serverului cu mod local activ. În caz contrar, se autentifică pe serverul dat de MM_SERVICESETTINGS_SITEURL cu AM_ADMIN_TOKEN, sau cu AM_ADMIN_USERNAME și AM_ADMIN_PASSWORD. Instrumentele de construcție nu citesc încă un nume AM_ pentru URL-ul site-ului.
Instalați un bundle manual
- Un administrator de sistem permite încărcările cu
AM_PLUGINSETTINGS_ENABLEUPLOADS=true(dezactivat implicit; nu există un comutator în Consola de sistem). - Apoi Consola de sistem › Plugin-uri › Gestionare › Încărcați pluginul, sau
amctl plugin add weather.tar.gzurmat deamctl plugin enable com.example.weather. - Dacă serverul are activată opțiunea Solicitați semnătură de plugin, acceptă doar pachete semnate cu o cheie de încredere; consultați Instalarea altor aplicații.
AM_SERVICESETTINGS_ENABLEDEVELOPER=true pe server: erorile vor include apoi detaliile. Makefile-urile aplicațiilor incluse compilează partea de server pentru platforma dumneavoastră doar dacă MM_SERVICESETTINGS_ENABLEDEVELOPER este setat în shell.